Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/AvifImagePlugin.py: 43%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import os
4from io import BytesIO
5from typing import IO
7from . import ExifTags, Image, ImageFile, ImageSequence
9try:
10 from . import _avif
12 SUPPORTED = True
13except ImportError:
14 SUPPORTED = False
16# Decoder options as module globals, until there is a way to pass parameters
17# to Image.open (see https://github.com/python-pillow/Pillow/issues/569)
18DECODE_CODEC_CHOICE = "auto"
19DEFAULT_MAX_THREADS = 0
22def get_codec_version(codec_name: str) -> str | None:
23 versions = _avif.codec_versions()
24 for version in versions.split(", "):
25 if version.split(" [")[0] == codec_name:
26 return version.split(":")[-1].split(" ")[0]
27 return None
30def _accept(prefix: bytes) -> bool | str:
31 if prefix[4:8] != b"ftyp":
32 return False
33 major_brand = prefix[8:12]
34 if major_brand in (
35 # coding brands
36 b"avif",
37 b"avis",
38 # We accept files with AVIF container brands; we can't yet know if
39 # the ftyp box has the correct compatible brands, but if it doesn't
40 # then the plugin will raise a SyntaxError which Pillow will catch
41 # before moving on to the next plugin that accepts the file.
42 #
43 # Also, because this file might not actually be an AVIF file, we
44 # don't raise an error if AVIF support isn't properly compiled.
45 b"mif1",
46 b"msf1",
47 ):
48 if not SUPPORTED:
49 return (
50 "image file could not be identified because AVIF support not installed"
51 )
52 return True
53 return False
56def _get_default_max_threads() -> int:
57 if DEFAULT_MAX_THREADS:
58 return DEFAULT_MAX_THREADS
59 if hasattr(os, "sched_getaffinity"):
60 return len(os.sched_getaffinity(0))
61 else:
62 return os.cpu_count() or 1
65class AvifImageFile(ImageFile.ImageFile):
66 format = "AVIF"
67 format_description = "AVIF image"
68 __frame = -1
70 def _open(self) -> None:
71 if not SUPPORTED:
72 msg = "image file could not be opened because AVIF support not installed"
73 raise SyntaxError(msg)
75 if DECODE_CODEC_CHOICE != "auto" and not _avif.decoder_codec_available(
76 DECODE_CODEC_CHOICE
77 ):
78 msg = "Invalid opening codec"
79 raise ValueError(msg)
81 assert self.fp is not None
82 self._decoder = _avif.AvifDecoder(
83 self.fp.read(),
84 DECODE_CODEC_CHOICE,
85 _get_default_max_threads(),
86 )
88 # Get info from decoder
89 self._size, self.n_frames, self._mode, icc, exif, exif_orientation, xmp = (
90 self._decoder.get_info()
91 )
92 self.is_animated = self.n_frames > 1
94 if icc:
95 self.info["icc_profile"] = icc
96 if xmp:
97 self.info["xmp"] = xmp
99 if exif_orientation != 1 or exif:
100 exif_data = Image.Exif()
101 if exif:
102 exif_data.load(exif)
103 original_orientation = exif_data.get(ExifTags.Base.Orientation, 1)
104 else:
105 original_orientation = 1
106 if exif_orientation != original_orientation:
107 exif_data[ExifTags.Base.Orientation] = exif_orientation
108 exif = exif_data.tobytes()
109 if exif:
110 self.info["exif"] = exif
111 self.seek(0)
113 def seek(self, frame: int) -> None:
114 if not self._seek_check(frame):
115 return
117 # Set tile
118 self.__frame = frame
119 self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.mode)]
121 def load(self) -> Image.core.PixelAccess | None:
122 if self.tile:
123 # We need to load the image data for this frame
124 data, timescale, pts_in_timescales, duration_in_timescales = (
125 self._decoder.get_frame(self.__frame)
126 )
127 self.info["timestamp"] = round(1000 * (pts_in_timescales / timescale))
128 self.info["duration"] = round(1000 * (duration_in_timescales / timescale))
130 if self.fp and self._exclusive_fp:
131 self.fp.close()
132 self.fp = BytesIO(data)
134 return super().load()
136 def load_seek(self, pos: int) -> None:
137 pass
139 def tell(self) -> int:
140 return self.__frame
143def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
144 _save(im, fp, filename, save_all=True)
147def _save(
148 im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
149) -> None:
150 info = im.encoderinfo.copy()
151 if save_all:
152 append_images = list(info.get("append_images", []))
153 else:
154 append_images = []
156 grayscale_modes = {"1", "L", "I", "I;16", "I;16L", "I;16B", "I;16N", "F"}
157 grayscale = all(
158 frame.mode in grayscale_modes
159 for ims in [im] + append_images
160 for frame in ImageSequence.Iterator(ims)
161 )
163 quality = info.get("quality", 75)
164 if not isinstance(quality, int) or quality < 0 or quality > 100:
165 msg = "Invalid quality setting"
166 raise ValueError(msg)
168 duration = info.get("duration", 0)
169 subsampling = info.get("subsampling", "4:0:0" if grayscale else "4:2:0")
170 speed = info.get("speed", 6)
171 max_threads = info.get("max_threads", _get_default_max_threads())
172 codec = info.get("codec", "auto")
173 if codec != "auto" and not _avif.encoder_codec_available(codec):
174 msg = "Invalid saving codec"
175 raise ValueError(msg)
176 range_ = info.get("range", "full")
177 tile_rows_log2 = info.get("tile_rows", 0)
178 tile_cols_log2 = info.get("tile_cols", 0)
179 alpha_premultiplied = bool(info.get("alpha_premultiplied", False))
180 autotiling = bool(info.get("autotiling", tile_rows_log2 == tile_cols_log2 == 0))
182 icc_profile = info.get("icc_profile", im.info.get("icc_profile"))
183 exif_orientation = 1
184 if exif := info.get("exif"):
185 if isinstance(exif, Image.Exif):
186 exif_data = exif
187 else:
188 exif_data = Image.Exif()
189 exif_data.load(exif)
190 if ExifTags.Base.Orientation in exif_data:
191 exif_orientation = exif_data.pop(ExifTags.Base.Orientation)
192 exif = exif_data.tobytes() if exif_data else b""
193 elif isinstance(exif, Image.Exif):
194 exif = exif_data.tobytes()
196 xmp = info.get("xmp")
198 if isinstance(xmp, str):
199 xmp = xmp.encode("utf-8")
201 advanced = info.get("advanced")
202 if advanced is not None:
203 if isinstance(advanced, dict):
204 advanced = advanced.items()
205 try:
206 advanced = tuple(advanced)
207 except TypeError:
208 invalid = True
209 else:
210 invalid = any(not isinstance(v, tuple) or len(v) != 2 for v in advanced)
211 if invalid:
212 msg = (
213 "advanced codec options must be a dict of key-value string "
214 "pairs or a series of key-value two-tuples"
215 )
216 raise ValueError(msg)
218 # Setup the AVIF encoder
219 enc = _avif.AvifEncoder(
220 im.size,
221 subsampling,
222 quality,
223 speed,
224 max_threads,
225 codec,
226 range_,
227 tile_rows_log2,
228 tile_cols_log2,
229 alpha_premultiplied,
230 autotiling,
231 icc_profile or b"",
232 exif or b"",
233 exif_orientation,
234 xmp or b"",
235 advanced,
236 )
238 # Add each frame
239 frame_idx = 0
240 frame_duration = 0
241 cur_idx = im.tell()
242 is_single_frame = not append_images and not getattr(im, "is_animated", False)
243 try:
244 for ims in [im] + append_images:
245 for frame in ImageSequence.Iterator(ims):
246 # Make sure image mode is supported
247 rawmode = frame.mode
248 if ims.mode not in {"L", "RGB", "RGBA"}:
249 if ims.has_transparency_data:
250 rawmode = "RGBA"
251 elif ims.mode in grayscale_modes:
252 rawmode = "L"
253 else:
254 rawmode = "RGB"
255 frame = frame.convert(rawmode)
257 # Update frame duration
258 if isinstance(duration, (list, tuple)):
259 frame_duration = duration[frame_idx]
260 else:
261 frame_duration = duration
263 # Append the frame to the animation encoder
264 enc.add(
265 frame.tobytes("raw", rawmode),
266 frame_duration,
267 frame.size,
268 rawmode,
269 is_single_frame,
270 )
272 # Update frame index
273 frame_idx += 1
275 if not save_all:
276 break
278 finally:
279 im.seek(cur_idx)
281 # Get the final output from the encoder
282 data = enc.finish()
283 if data is None:
284 msg = "cannot write file as AVIF (encoder returned None)"
285 raise OSError(msg)
287 fp.write(data)
290Image.register_open(AvifImageFile.format, AvifImageFile, _accept)
291if SUPPORTED:
292 Image.register_save(AvifImageFile.format, _save)
293 Image.register_save_all(AvifImageFile.format, _save_all)
294 Image.register_extensions(AvifImageFile.format, [".avif", ".avifs"])
295 Image.register_mime(AvifImageFile.format, "image/avif")