imageio-ffmpeg 0.8.0__py3-none-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ """imageio_ffmpeg, FFMPEG wrapper for Python."""
2
+
3
+ # flake8: noqa
4
+
5
+ from ._definitions import __version__
6
+ from ._io import count_frames_and_secs, read_frames, write_frames
7
+ from ._utils import get_ffmpeg_exe, get_ffmpeg_version
@@ -0,0 +1,69 @@
1
+ import sys
2
+ import platform
3
+
4
+ __version__ = "0.8.0"
5
+
6
+
7
+ def get_platform():
8
+ # get_os_string and get_arch are taken from wgpu-py
9
+ return _get_os_string() + "-" + _get_arch()
10
+
11
+
12
+ def _get_os_string():
13
+ if sys.platform.startswith("win"):
14
+ return "windows"
15
+ elif sys.platform.startswith("darwin"):
16
+ return "macos"
17
+ elif sys.platform.startswith("linux"):
18
+ return "linux"
19
+ else:
20
+ return sys.platform
21
+
22
+
23
+ def _get_arch():
24
+ # See e.g.: https://stackoverflow.com/questions/45124888
25
+ is_64_bit = sys.maxsize > 2**32
26
+ machine = platform.machine().lower() # e.g. "ARM64" on Windows
27
+
28
+ if machine == "armv7l":
29
+ # Raspberry pi
30
+ detected_arch = "armv7"
31
+ elif is_64_bit and machine.startswith(("arm", "aarch64")):
32
+ # Includes MacOS M1, arm linux, ...
33
+ detected_arch = "aarch64"
34
+ elif is_64_bit:
35
+ detected_arch = "x86_64"
36
+ else:
37
+ detected_arch = "i686"
38
+ return detected_arch
39
+
40
+
41
+ # The binaries are downloaded from the sources pinned in
42
+ # tools/ffmpeg-sources.json (see tools/download_ffmpeg.py, which also updates
43
+ # the file names below). The wheel tags must match the requirements of the
44
+ # binaries, which `invoke check-binaries` verifies:
45
+ # * Linux (BtbN): glibc 2.28+ (manylinux_2_28), kernel 4.18+.
46
+ # * macOS x86_64 (evermeet.cx): macOS 10.13+; aarch64 (osxexperts): 12.0+.
47
+
48
+
49
+ # Platform string -> ffmpeg filename
50
+ FNAME_PER_PLATFORM = {
51
+ "macos-aarch64": "ffmpeg-macos-aarch64-v9.0",
52
+ "macos-x86_64": "ffmpeg-macos-x86_64-v9.0.2",
53
+ "windows-x86_64": "ffmpeg-win-x86_64-v9.0.2.exe",
54
+ "windows-aarch64": "ffmpeg-win-aarch64-v9.0.1.exe",
55
+ "windows-i686": "ffmpeg-win32-v4.2.2.exe", # Windows 7+
56
+ "linux-aarch64": "ffmpeg-linux-aarch64-v9.0.2",
57
+ "linux-x86_64": "ffmpeg-linux-x86_64-v9.0.2",
58
+ }
59
+
60
+ # Wheel tag -> platform string
61
+ WHEEL_BUILDS = {
62
+ "py3-none-manylinux_2_28_x86_64": "linux-x86_64",
63
+ "py3-none-manylinux_2_28_aarch64": "linux-aarch64",
64
+ "py3-none-macosx_10_13_x86_64": "macos-x86_64",
65
+ "py3-none-macosx_12_0_arm64": "macos-aarch64",
66
+ "py3-none-win32": "windows-i686",
67
+ "py3-none-win_amd64": "windows-x86_64",
68
+ "py3-none-win_arm64": "windows-aarch64",
69
+ }
imageio_ffmpeg/_io.py ADDED
@@ -0,0 +1,698 @@
1
+ import pathlib
2
+ import subprocess
3
+ import sys
4
+ import time
5
+ from collections import defaultdict
6
+ from fractions import Fraction
7
+ from functools import lru_cache
8
+
9
+ from ._parsing import LogCatcher, cvsecs, parse_ffmpeg_header
10
+ from ._utils import _popen_kwargs, get_ffmpeg_exe, logger
11
+
12
+ ISWIN = sys.platform.startswith("win")
13
+
14
+ h264_encoder_preference = defaultdict(lambda: -1)
15
+ # The libx264 was the default encoder for a longe time with imageio
16
+ h264_encoder_preference["libx264"] = 100
17
+
18
+ # Encoder with the nvidia graphics card dedicated hardware
19
+ h264_encoder_preference["h264_nvenc"] = 90
20
+ # Deprecated names for the same encoder
21
+ h264_encoder_preference["nvenc_h264"] = 90
22
+ h264_encoder_preference["nvenc"] = 90
23
+
24
+ # vaapi provides hardware encoding with intel integrated graphics chipsets
25
+ h264_encoder_preference["h264_vaapi"] = 80
26
+
27
+ # openh264 is cisco's open source encoder
28
+ h264_encoder_preference["libopenh264"] = 70
29
+
30
+ h264_encoder_preference["libx264rgb"] = 50
31
+
32
+
33
+ def ffmpeg_test_encoder(encoder):
34
+ # Use the null streams to validate if we can encode anything
35
+ # https://trac.ffmpeg.org/wiki/Null
36
+ cmd = [
37
+ get_ffmpeg_exe(),
38
+ "-hide_banner",
39
+ "-f",
40
+ "lavfi",
41
+ "-i",
42
+ "nullsrc=s=256x256:d=8",
43
+ "-vcodec",
44
+ encoder,
45
+ "-f",
46
+ "null",
47
+ "-",
48
+ ]
49
+ p = subprocess.run(
50
+ cmd,
51
+ stdin=subprocess.PIPE,
52
+ stdout=subprocess.PIPE,
53
+ stderr=subprocess.PIPE,
54
+ )
55
+ return p.returncode == 0
56
+
57
+
58
+ def get_compiled_h264_encoders():
59
+ cmd = [get_ffmpeg_exe(), "-hide_banner", "-encoders"]
60
+ p = subprocess.run(
61
+ cmd,
62
+ stdin=subprocess.PIPE,
63
+ stdout=subprocess.PIPE,
64
+ stderr=subprocess.PIPE,
65
+ )
66
+ stdout = p.stdout.decode().replace("\r", "")
67
+ # 2022/04/08: hmaarrfk
68
+ # I couldn't find a good way to get the list of available encoders from
69
+ # the ffmpeg command
70
+ # The ffmpeg command return a table that looks like
71
+ # Notice the leading space at the very beginning
72
+ # On ubuntu with libffmpeg-nvenc-dev we get
73
+ # $ ffmpeg -hide_banner -encoders | grep -i h.264
74
+ #
75
+ # Encoders:
76
+ # V..... = Video
77
+ # A..... = Audio
78
+ # S..... = Subtitle
79
+ # .F.... = Frame-level multithreading
80
+ # ..S... = Slice-level multithreading
81
+ # ...X.. = Codec is experimental
82
+ # ....B. = Supports draw_horiz_band
83
+ # .....D = Supports direct rendering method 1
84
+ # ------
85
+ # V..... libx264 libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264)
86
+ # V..... libx264rgb libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 RGB (codec h264)
87
+ # V....D h264_nvenc NVIDIA NVENC H.264 encoder (codec h264)
88
+ # V..... h264_omx OpenMAX IL H.264 video encoder (codec h264)
89
+ # V..... h264_qsv H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (Intel Quick Sync Video acceleration) (codec h264)
90
+ # V..... h264_v4l2m2m V4L2 mem2mem H.264 encoder wrapper (codec h264)
91
+ # V....D h264_vaapi H.264/AVC (VAAPI) (codec h264)
92
+ # V..... nvenc NVIDIA NVENC H.264 encoder (codec h264)
93
+ # V..... nvenc_h264 NVIDIA NVENC H.264 encoder (codec h264)
94
+ #
95
+ # However, just because ffmpeg was compiled with the options enabled
96
+ # it doesn't mean that it will be successful
97
+ header_footer = stdout.split("------")
98
+ footer = header_footer[1].strip("\n")
99
+ encoders = []
100
+ for line in footer.split("\n"):
101
+ # Strip to remove any leading spaces
102
+ line = line.strip()
103
+ encoder = line.split(" ")[1]
104
+
105
+ if encoder in h264_encoder_preference:
106
+ # These encoders are known to support H.264
107
+ # We forcibly include them in case their description changes to
108
+ # not include the string "H.264"
109
+ encoders.append(encoder)
110
+ elif (line[0] == "V") and ("H.264" in line):
111
+ encoders.append(encoder)
112
+
113
+ encoders.sort(reverse=True, key=lambda x: h264_encoder_preference[x])
114
+ if "h264_nvenc" in encoders:
115
+ # Remove deprecated names for the same encoder
116
+ for encoder in ["nvenc", "nvenc_h264"]:
117
+ if encoder in encoders:
118
+ encoders.remove(encoder)
119
+ # Return an immutable tuple to avoid users corrupting the lru_cache
120
+ return tuple(encoders)
121
+
122
+
123
+ @lru_cache()
124
+ def get_first_available_h264_encoder():
125
+ compiled_encoders = get_compiled_h264_encoders()
126
+ for encoder in compiled_encoders:
127
+ if ffmpeg_test_encoder(encoder):
128
+ return encoder
129
+ else:
130
+ raise RuntimeError(
131
+ "No valid H.264 encoder was found with the ffmpeg installation"
132
+ )
133
+
134
+
135
+ def count_frames_and_secs(path):
136
+ """
137
+ Get the number of frames and number of seconds for the given video
138
+ file. Note that this operation can be quite slow for large files.
139
+
140
+ Disclaimer: I've seen this produce different results from actually reading
141
+ the frames with older versions of ffmpeg (2.x). Therefore I cannot say
142
+ with 100% certainty that the returned values are always exact.
143
+ """
144
+ # https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg
145
+
146
+ if isinstance(path, pathlib.PurePath):
147
+ path = str(path)
148
+ if not isinstance(path, str):
149
+ raise TypeError("Video path must be a string or pathlib.Path.")
150
+
151
+ cmd = [
152
+ get_ffmpeg_exe(),
153
+ "-i",
154
+ path,
155
+ "-map",
156
+ "0:v:0",
157
+ "-vf",
158
+ "null",
159
+ "-f",
160
+ "null",
161
+ "-",
162
+ ]
163
+ try:
164
+ out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, **_popen_kwargs())
165
+ except subprocess.CalledProcessError as err:
166
+ out = err.output.decode(errors="ignore")
167
+ raise RuntimeError(
168
+ "FFMPEG call failed with {}:\n{}".format(err.returncode, out)
169
+ )
170
+
171
+ # Note that other than with the subprocess calls below, ffmpeg wont hang here.
172
+ # Worst case Python will stop/crash and ffmpeg will continue running until done.
173
+
174
+ nframes = nsecs = None
175
+ for line in reversed(out.splitlines()):
176
+ if line.startswith(b"frame="):
177
+ line = line.decode(errors="ignore")
178
+ i = line.find("frame=")
179
+ if i >= 0:
180
+ s = line[i:].split("=", 1)[-1].lstrip().split(" ", 1)[0].strip()
181
+ nframes = int(s)
182
+ i = line.find("time=")
183
+ if i >= 0:
184
+ s = line[i:].split("=", 1)[-1].lstrip().split(" ", 1)[0].strip()
185
+ nsecs = cvsecs(*s.split(":"))
186
+ return nframes, nsecs
187
+
188
+ raise RuntimeError("Could not get number of frames") # pragma: no cover
189
+
190
+
191
+ def read_frames(
192
+ path,
193
+ pix_fmt="rgb24",
194
+ bpp=None,
195
+ input_params=None,
196
+ output_params=None,
197
+ bits_per_pixel=None,
198
+ ):
199
+ """
200
+ Create a generator to iterate over the frames in a video file.
201
+
202
+ It first yields a small metadata dictionary that contains:
203
+
204
+ * ffmpeg_version: the ffmpeg version in use (as a string).
205
+ * codec: a hint about the codec used to encode the video, e.g. "h264".
206
+ * source_size: the width and height of the encoded video frames.
207
+ * size: the width and height of the frames that will be produced.
208
+ * fps: the frames per second. Can be zero if it could not be detected.
209
+ * duration: duration in seconds. Can be zero if it could not be detected.
210
+
211
+ After that, it yields frames until the end of the video is reached. Each
212
+ frame is a bytes object.
213
+
214
+ This function makes no assumptions about the number of frames in
215
+ the data. For one because this is hard to predict exactly, but also
216
+ because it may depend on the provided output_params. If you want
217
+ to know the number of frames in a video file, use count_frames_and_secs().
218
+ It is also possible to estimate the number of frames from the fps and
219
+ duration, but note that even if both numbers are present, the resulting
220
+ value is not always correct.
221
+
222
+ Example:
223
+
224
+ gen = read_frames(path)
225
+ meta = gen.__next__()
226
+ for frame in gen:
227
+ print(len(frame))
228
+
229
+ Parameters:
230
+ path (str): the filename of the file to read from.
231
+ pix_fmt (str): the pixel format of the frames to be read.
232
+ The default is "rgb24" (frames are uint8 RGB images).
233
+ input_params (list): Additional ffmpeg input command line parameters.
234
+ output_params (list): Additional ffmpeg output command line parameters.
235
+ bits_per_pixel (int): The number of bits per pixel in the output frames.
236
+ This depends on the given pix_fmt. Default is 24 (RGB)
237
+ bpp (int): DEPRECATED, USE bits_per_pixel INSTEAD. The number of bytes per pixel in the output frames.
238
+ This depends on the given pix_fmt. Some pixel formats like yuv420p have 12 bits per pixel
239
+ and cannot be set in bytes as integer. For this reason the bpp argument is deprecated.
240
+ """
241
+
242
+ # ----- Input args
243
+
244
+ if isinstance(path, pathlib.PurePath):
245
+ path = str(path)
246
+ if not isinstance(path, str):
247
+ raise TypeError("Video path must be a string or pathlib.Path.")
248
+ # Note: Dont check whether it exists. The source could be e.g. a camera.
249
+
250
+ pix_fmt = pix_fmt or "rgb24"
251
+ bpp = bpp or 3
252
+ bits_per_pixel = bits_per_pixel or bpp * 8
253
+ input_params = input_params or []
254
+ output_params = output_params or []
255
+
256
+ assert isinstance(pix_fmt, str), "pix_fmt must be a string"
257
+ assert isinstance(bits_per_pixel, int), "bpp and bits_per_pixel must be an int"
258
+ assert isinstance(input_params, list), "input_params must be a list"
259
+ assert isinstance(output_params, list), "output_params must be a list"
260
+
261
+ # ----- Prepare
262
+
263
+ pre_output_params = ["-pix_fmt", pix_fmt, "-vcodec", "rawvideo", "-f", "image2pipe"]
264
+
265
+ cmd = [get_ffmpeg_exe()]
266
+ cmd += input_params + ["-i", path]
267
+ cmd += pre_output_params + output_params + ["-"]
268
+
269
+ process = subprocess.Popen(
270
+ cmd,
271
+ stdin=subprocess.PIPE,
272
+ stdout=subprocess.PIPE,
273
+ stderr=subprocess.PIPE,
274
+ **_popen_kwargs(prevent_sigint=True)
275
+ )
276
+
277
+ log_catcher = LogCatcher(process.stderr)
278
+
279
+ # Init policy by which to terminate ffmpeg. May be set to "kill" later.
280
+ stop_policy = "timeout" # not wait; ffmpeg should be able to quit quickly
281
+
282
+ # Enter try block directly after opening the process.
283
+ # We terminate ffmpeg in the finally clause.
284
+ # Generators are automatically closed when they get deleted,
285
+ # so the finally block is guaranteed to run.
286
+ try:
287
+ # ----- Load meta data
288
+
289
+ # Wait for the log catcher to get the meta information
290
+ etime = time.time() + 10.0
291
+ while log_catcher.is_alive() and not log_catcher.header and time.time() < etime:
292
+ time.sleep(0.01)
293
+
294
+ # Check whether we have the information
295
+ if not log_catcher.header:
296
+ err2 = log_catcher.get_text(0.2)
297
+ fmt = "Could not load meta information\n=== stderr ===\n{}"
298
+ raise IOError(fmt.format(err2))
299
+ elif "No such file or directory" in log_catcher.header:
300
+ raise IOError("{} not found! Wrong path?".format(path))
301
+
302
+ meta = parse_ffmpeg_header(log_catcher.header)
303
+ yield meta
304
+
305
+ # ----- Read frames
306
+
307
+ width, height = meta["size"]
308
+ framesize_bits = width * height * bits_per_pixel
309
+ framesize_bytes = framesize_bits / 8
310
+ assert (
311
+ framesize_bytes.is_integer()
312
+ ), "incorrect bits_per_pixel, framesize in bytes must be an int"
313
+ framesize_bytes = int(framesize_bytes)
314
+ framenr = 0
315
+
316
+ while True:
317
+ framenr += 1
318
+ try:
319
+ bb = bytes()
320
+ while len(bb) < framesize_bytes:
321
+ extra_bytes = process.stdout.read(framesize_bytes - len(bb))
322
+ if not extra_bytes:
323
+ if len(bb) == 0:
324
+ return
325
+ else:
326
+ raise RuntimeError(
327
+ "End of file reached before full frame could be read."
328
+ )
329
+ bb += extra_bytes
330
+ yield bb
331
+ except Exception as err:
332
+ err1 = str(err)
333
+ err2 = log_catcher.get_text(0.4)
334
+ fmt = "Could not read frame {}:\n{}\n=== stderr ===\n{}"
335
+ raise RuntimeError(fmt.format(framenr, err1, err2))
336
+
337
+ except GeneratorExit:
338
+ # Note that GeneratorExit does not inherit from Exception but BaseException
339
+ pass
340
+
341
+ except Exception:
342
+ # Normal exceptions fall through
343
+ raise
344
+
345
+ except BaseException:
346
+ # Detect KeyboardInterrupt / SystemExit: don't wait for ffmpeg to quit
347
+ stop_policy = "kill"
348
+ raise
349
+
350
+ finally:
351
+ # Stop the LogCatcher thread, which reads from stderr.
352
+ log_catcher.stop_me()
353
+
354
+ # Make sure that ffmpeg is terminated.
355
+ if process.poll() is None:
356
+ # Ask ffmpeg to quit
357
+ try:
358
+ # I read somewhere that modern ffmpeg on Linux prefers a
359
+ # "ctrl-c", but tests so far suggests sending q is more robust.
360
+ # > p.send_signal(signal.SIGINT)
361
+ # Sending q via communicate works, but can hang (see #17)
362
+ # > p.communicate(b"q")
363
+ # So let's do similar to what communicate does, but without
364
+ # reading stdout (which may block). It looks like only closing
365
+ # stdout is enough (tried Windows+Linux), but let's play safe.
366
+ # Found that writing to stdin can cause "Invalid argument" on
367
+ # Windows # and "Broken Pipe" on Unix.
368
+ # p.stdin.write(b"q") # commented out in v0.4.1
369
+ process.stdout.close()
370
+ process.stdin.close()
371
+ # p.stderr.close() -> not here, the log_catcher closes it
372
+ except Exception as err: # pragma: no cover
373
+ logger.warning("Error while attempting stop ffmpeg (r): " + str(err))
374
+
375
+ if stop_policy == "timeout":
376
+ # Wait until timeout, produce a warning and kill if it still exists
377
+ try:
378
+ etime = time.time() + 1.5
379
+ while time.time() < etime and process.poll() is None:
380
+ time.sleep(0.01)
381
+ finally:
382
+ if process.poll() is None: # pragma: no cover
383
+ logger.warning("We had to kill ffmpeg to stop it.")
384
+ process.kill()
385
+
386
+ else: # stop_policy == "kill"
387
+ # Just kill it
388
+ process.kill()
389
+
390
+
391
+ def write_frames(
392
+ path,
393
+ size,
394
+ pix_fmt_in="rgb24",
395
+ pix_fmt_out="yuv420p",
396
+ fps=16,
397
+ quality=5,
398
+ bitrate=None,
399
+ codec=None,
400
+ macro_block_size=16,
401
+ ffmpeg_log_level="warning",
402
+ ffmpeg_timeout=None,
403
+ input_params=None,
404
+ output_params=None,
405
+ audio_path=None,
406
+ audio_codec=None,
407
+ ):
408
+ """
409
+ Create a generator to write frames (bytes objects) into a video file.
410
+
411
+ The frames are written by using the generator's `send()` method. Frames
412
+ can be anything that can be written to a file. Typically these are
413
+ bytes objects, but c-contiguous Numpy arrays also work.
414
+
415
+ Example:
416
+
417
+ gen = write_frames(path, size)
418
+ gen.send(None) # seed the generator
419
+ for frame in frames:
420
+ gen.send(frame)
421
+ gen.close() # don't forget this
422
+
423
+ Parameters:
424
+ path (str): the filename to write to.
425
+ size (tuple): the width and height of the frames.
426
+ pix_fmt_in (str): the pixel format of incoming frames.
427
+ E.g. "gray", "gray8a", "rgb24", or "rgba". Default "rgb24".
428
+ pix_fmt_out (str): the pixel format to store frames. Default yuv420p".
429
+ fps (float or Fraction): The frames per second. Default 16.
430
+ quality (float): A measure for quality between 0 and 10. Default 5.
431
+ Ignored if bitrate is given.
432
+ bitrate (str): The bitrate, e.g. "192k". The defaults are pretty good.
433
+ codec (str): The codec. Default "libx264" for .mp4 (if available from
434
+ the ffmpeg executable) or "msmpeg4" for .wmv.
435
+ macro_block_size (int): You probably want to align the size of frames
436
+ to this value to avoid image resizing. Default 16. Can be set
437
+ to 1 to avoid block alignment, though this is not recommended.
438
+ ffmpeg_log_level (str): The ffmpeg logging level. Default "warning".
439
+ ffmpeg_timeout (float): Timeout in seconds to wait for ffmpeg process
440
+ to finish. Value of 0 or None will wait forever (default). The time that
441
+ ffmpeg needs depends on CPU speed, compression, and frame size.
442
+ input_params (list): Additional ffmpeg input command line parameters.
443
+ output_params (list): Additional ffmpeg output command line parameters.
444
+ audio_path (str): A input file path for encoding with an audio stream.
445
+ Default None, no audio.
446
+ audio_codec (str): The audio codec to use if audio_path is provided.
447
+ "copy" will try to use audio_path's audio codec without re-encoding.
448
+ Default None, but some formats must have certain codecs specified.
449
+ """
450
+
451
+ # ----- Input args
452
+
453
+ if isinstance(path, pathlib.PurePath):
454
+ path = str(path)
455
+ if not isinstance(path, str):
456
+ raise TypeError("Video path must be a string or pathlib.Path.")
457
+
458
+ # The pix_fmt_out yuv420p is the best for the outpur to work in
459
+ # QuickTime and most other players. These players only support
460
+ # the YUV planar color space with 4:2:0 chroma subsampling for
461
+ # H.264 video. Otherwise, depending on the source, ffmpeg may
462
+ # output to a pixel format that may be incompatible with these
463
+ # players. See https://trac.ffmpeg.org/wiki/Encode/H.264#Encodingfordumbplayers
464
+
465
+ pix_fmt_in = pix_fmt_in or "rgb24"
466
+ pix_fmt_out = pix_fmt_out or "yuv420p"
467
+ fps = fps or 16
468
+ # bitrate, codec, macro_block_size can all be None or ...
469
+ macro_block_size = macro_block_size or 16
470
+ ffmpeg_log_level = ffmpeg_log_level or "warning"
471
+ input_params = input_params or []
472
+ output_params = output_params or []
473
+ ffmpeg_timeout = ffmpeg_timeout or 0
474
+
475
+ floatish = float, int
476
+ if isinstance(size, (tuple, list)):
477
+ assert len(size) == 2, "size must be a 2-tuple"
478
+ assert isinstance(size[0], int) and isinstance(
479
+ size[1], int
480
+ ), "size must be ints"
481
+ sizestr = "{:d}x{:d}".format(*size)
482
+ # elif isinstance(size, str):
483
+ # assert "x" in size, "size as string must have format NxM"
484
+ # sizestr = size
485
+ else:
486
+ assert False, "size must be str or tuple"
487
+ assert isinstance(pix_fmt_in, str), "pix_fmt_in must be str"
488
+ assert isinstance(pix_fmt_out, str), "pix_fmt_out must be str"
489
+ assert isinstance(fps, (*floatish, Fraction)), "fps must be float or Fraction"
490
+ if quality is not None:
491
+ assert isinstance(quality, floatish), "quality must be float"
492
+ assert 1 <= quality <= 10, "quality must be between 1 and 10 inclusive"
493
+ assert isinstance(macro_block_size, int), "macro_block_size must be int"
494
+ assert isinstance(ffmpeg_log_level, str), "ffmpeg_log_level must be str"
495
+ assert isinstance(ffmpeg_timeout, floatish), "ffmpeg_timeout must be float"
496
+ assert isinstance(input_params, list), "input_params must be a list"
497
+ assert isinstance(output_params, list), "output_params must be a list"
498
+
499
+ # ----- Prepare
500
+
501
+ # Get parameters
502
+ if not codec:
503
+ if path.lower().endswith(".wmv"):
504
+ # This is a safer default codec on windows to get videos that
505
+ # will play in powerpoint and other apps. H264 is not always
506
+ # available on windows.
507
+ codec = "msmpeg4"
508
+ else:
509
+ codec = get_first_available_h264_encoder()
510
+
511
+ audio_params = ["-an"]
512
+ if audio_path is not None and not path.lower().endswith(".gif"):
513
+ audio_params = ["-i", audio_path]
514
+ if audio_codec is not None:
515
+ output_params += ["-acodec", audio_codec]
516
+ output_params += ["-map", "0:v:0", "-map", "1:a:0"]
517
+
518
+ # Get command
519
+ cmd = [
520
+ get_ffmpeg_exe(),
521
+ "-y",
522
+ "-f",
523
+ "rawvideo",
524
+ "-vcodec",
525
+ "rawvideo",
526
+ "-s",
527
+ sizestr,
528
+ ]
529
+ # Pass fps as a rational so values like 24000/1001 are not rounded (#116).
530
+ if not isinstance(fps, Fraction):
531
+ fps = Fraction(fps).limit_denominator()
532
+ fps_arg = str(fps)
533
+ cmd += ["-pix_fmt", pix_fmt_in, "-r", fps_arg] + input_params
534
+ cmd += ["-i", "-"] + audio_params
535
+ cmd += ["-vcodec", codec, "-pix_fmt", pix_fmt_out]
536
+
537
+ # Add fixed bitrate or variable bitrate compression flags
538
+ if bitrate is not None:
539
+ cmd += ["-b:v", str(bitrate)]
540
+ elif quality is not None: # If None, then we don't add anything
541
+ quality = 1 - quality / 10.0
542
+ if codec == "libx264":
543
+ # crf ranges 0 to 51, 51 being worst.
544
+ quality = int(quality * 51)
545
+ cmd += ["-crf", str(quality)] # for h264
546
+ else: # Many codecs accept q:v
547
+ # q:v range can vary, 1-31, 31 being worst
548
+ # But q:v does not always have the same range.
549
+ # May need a way to find range for any codec.
550
+ quality = int(quality * 30) + 1
551
+ cmd += ["-qscale:v", str(quality)] # for others
552
+
553
+ # Note, for most codecs, the image dimensions must be divisible by
554
+ # 16 the default for the macro_block_size is 16. Check if image is
555
+ # divisible, if not have ffmpeg upsize to nearest size and warn
556
+ # user they should correct input image if this is not desired.
557
+ if macro_block_size > 1:
558
+ if size[0] % macro_block_size > 0 or size[1] % macro_block_size > 0:
559
+ out_w = size[0]
560
+ out_h = size[1]
561
+ if size[0] % macro_block_size > 0:
562
+ out_w += macro_block_size - (size[0] % macro_block_size)
563
+ if size[1] % macro_block_size > 0:
564
+ out_h += macro_block_size - (size[1] % macro_block_size)
565
+ cmd += ["-vf", "scale={}:{}".format(out_w, out_h)]
566
+ logger.warning(
567
+ "IMAGEIO FFMPEG_WRITER WARNING: input image is not"
568
+ " divisible by macro_block_size={}, resizing from {} "
569
+ "to {} to ensure video compatibility with most codecs "
570
+ "and players. To prevent resizing, make your input "
571
+ "image divisible by the macro_block_size or set the "
572
+ "macro_block_size to 1 (risking incompatibility).".format(
573
+ macro_block_size, size[:2], (out_w, out_h)
574
+ )
575
+ )
576
+
577
+ # Rather than redirect stderr to a pipe, just set minimal
578
+ # output from ffmpeg by default. That way if there are warnings
579
+ # the user will see them.
580
+ cmd += ["-v", ffmpeg_log_level]
581
+ cmd += output_params
582
+ cmd.append(path)
583
+ cmd_str = " ".join(cmd)
584
+ if any(
585
+ [level in ffmpeg_log_level for level in ("info", "verbose", "debug", "trace")]
586
+ ):
587
+ logger.info("RUNNING FFMPEG COMMAND: " + cmd_str)
588
+
589
+ # Launch process
590
+ p = subprocess.Popen(
591
+ cmd,
592
+ stdin=subprocess.PIPE,
593
+ stdout=subprocess.PIPE,
594
+ stderr=None,
595
+ **_popen_kwargs(prevent_sigint=True)
596
+ )
597
+
598
+ # Note that directing stderr to a pipe on windows will cause ffmpeg
599
+ # to hang if the buffer is not periodically cleared using
600
+ # StreamCatcher or other means.
601
+ # Setting bufsize to 0 or a small value does not seem to have much effect
602
+ # (tried on Windows and Linux). I suspect that ffmpeg buffers
603
+ # multiple frames (before encoding in a batch).
604
+
605
+ # Init policy by which to terminate ffmpeg. May be set to "kill" later.
606
+ stop_policy = "timeout"
607
+ if not ffmpeg_timeout:
608
+ stop_policy = "wait"
609
+
610
+ # ----- Write frames
611
+
612
+ # Enter try block directly after opening the process.
613
+ # We terminate ffmpeg in the finally clause.
614
+ # Generators are automatically closed when they get deleted,
615
+ # so the finally block is guaranteed to run.
616
+ try:
617
+ # Just keep going until the generator.close() is called (raises GeneratorExit).
618
+ # This could also happen when the generator is deleted somehow.
619
+ nframes = 0
620
+ while True:
621
+ # Get frame
622
+ bb = yield
623
+
624
+ # framesize = size[0] * size[1] * depth * bpp
625
+ # assert isinstance(bb, bytes), "Frame must be send as bytes"
626
+ # assert len(bb) == framesize, "Frame must have width*height*depth*bpp bytes"
627
+ # Actually, we accept anything that can be written to file.
628
+ # This e.g. allows writing numpy arrays without having to make a copy ...
629
+
630
+ # Write
631
+ try:
632
+ p.stdin.write(bb)
633
+ except Exception as err:
634
+ # Show the command and stderr from pipe
635
+ msg = (
636
+ "{0:}\n\nFFMPEG COMMAND:\n{1:}\n\nFFMPEG STDERR "
637
+ "OUTPUT:\n".format(err, cmd_str)
638
+ )
639
+ raise IOError(msg)
640
+
641
+ nframes += 1
642
+
643
+ except GeneratorExit:
644
+ # Note that GeneratorExit does not inherit from Exception but BaseException
645
+ # Detect premature closing
646
+ if nframes == 0:
647
+ logger.warning("No frames have been written; the written video is invalid.")
648
+
649
+ except Exception:
650
+ # Normal exceptions fall through
651
+ raise
652
+
653
+ except BaseException:
654
+ # Detect KeyboardInterrupt / SystemExit: don't wait for ffmpeg to quit
655
+ stop_policy = "kill"
656
+ raise
657
+
658
+ finally:
659
+ # Make sure that ffmpeg is terminated.
660
+ if p.poll() is None:
661
+ # Tell ffmpeg that we're done
662
+ try:
663
+ p.stdin.close()
664
+ except Exception as err: # pragma: no cover
665
+ logger.warning("Error while attempting stop ffmpeg (w): " + str(err))
666
+
667
+ if stop_policy == "timeout":
668
+ # Wait until timeout, produce a warning and kill if it still exists
669
+ try:
670
+ etime = time.time() + ffmpeg_timeout
671
+ while (time.time() < etime) and p.poll() is None:
672
+ time.sleep(0.01)
673
+ finally:
674
+ if p.poll() is None: # pragma: no cover
675
+ logger.warning(
676
+ "We had to kill ffmpeg to stop it. "
677
+ + "Consider increasing ffmpeg_timeout, "
678
+ + "or setting it to zero (no timeout)."
679
+ )
680
+ p.kill()
681
+
682
+ elif stop_policy == "wait":
683
+ # Wait forever, kill if it if we're interrupted
684
+ try:
685
+ while p.poll() is None:
686
+ time.sleep(0.01)
687
+ finally: # the above can raise e.g. by ctrl-c or systemexit
688
+ if p.poll() is None: # pragma: no cover
689
+ p.kill()
690
+
691
+ else: # stop_policy == "kill":
692
+ # Just kill it
693
+ p.kill()
694
+ # Just to be safe, wrap in try/except
695
+ try:
696
+ p.stdout.close()
697
+ except Exception:
698
+ pass
@@ -0,0 +1,208 @@
1
+ import re
2
+ import threading
3
+ import time
4
+
5
+ from ._utils import logger
6
+
7
+
8
+ class LogCatcher(threading.Thread):
9
+ """Thread to keep reading from stderr so that the buffer does not
10
+ fill up and stalls the ffmpeg process. On stderr a message is send
11
+ on every few frames with some meta information. We only keep the
12
+ last ones.
13
+ """
14
+
15
+ def __init__(self, file):
16
+ self._file = file
17
+ self._header = ""
18
+ self._lines = []
19
+ self._remainder = b""
20
+ threading.Thread.__init__(self)
21
+ self.daemon = True # do not let this thread hold up Python shutdown
22
+ self._should_stop = False
23
+ self.start()
24
+
25
+ def stop_me(self):
26
+ self._should_stop = True
27
+
28
+ @property
29
+ def header(self):
30
+ """Get header text. Empty string if the header is not yet parsed."""
31
+ return self._header
32
+
33
+ def get_text(self, timeout=0):
34
+ """Get the whole text written to stderr so far. To preserve
35
+ memory, only the last 50 to 100 frames are kept.
36
+
37
+ If a timeout is given, wait for this thread to finish. When
38
+ something goes wrong, we stop ffmpeg and want a full report of
39
+ stderr, but this thread might need a tiny bit more time.
40
+ """
41
+
42
+ # Wait?
43
+ if timeout > 0:
44
+ etime = time.time() + timeout
45
+ while self.is_alive() and time.time() < etime: # pragma: no cover
46
+ time.sleep(0.01)
47
+ # Return str
48
+ lines = b"\n".join(self._lines)
49
+ return self._header + "\n" + lines.decode("utf-8", "ignore")
50
+
51
+ def run(self):
52
+ # Create ref here so it still exists even if Py is shutting down
53
+ limit_lines_local = limit_lines
54
+
55
+ while not self._should_stop:
56
+ time.sleep(0)
57
+ # Read one line. Detect when closed, and exit
58
+ try:
59
+ line = self._file.read(20)
60
+ except ValueError: # pragma: no cover
61
+ break
62
+ if not line:
63
+ break
64
+ # Process to divide in lines
65
+ line = line.replace(b"\r", b"\n").replace(b"\n\n", b"\n")
66
+ lines = line.split(b"\n")
67
+ lines[0] = self._remainder + lines[0]
68
+ self._remainder = lines.pop(-1)
69
+ # Process each line
70
+ self._lines.extend(lines)
71
+ if not self._header:
72
+ if get_output_video_line(self._lines):
73
+ header = b"\n".join(self._lines)
74
+ self._header += header.decode("utf-8", "ignore")
75
+ elif self._lines:
76
+ self._lines = limit_lines_local(self._lines)
77
+
78
+ # Close the file when we're done
79
+ # See #61 and #69
80
+ try:
81
+ self._file.close()
82
+ except Exception:
83
+ pass
84
+
85
+
86
+ def get_output_video_line(lines):
87
+ """Get the line that defines the video stream that ffmpeg outputs,
88
+ and which we read.
89
+ """
90
+ in_output = False
91
+ for line in lines:
92
+ sline = line.lstrip()
93
+ if sline.startswith(b"Output "):
94
+ in_output = True
95
+ elif in_output:
96
+ if sline.startswith(b"Stream ") and b" Video:" in sline:
97
+ return line
98
+
99
+
100
+ def limit_lines(lines, N=32):
101
+ """When number of lines > 2*N, reduce to N."""
102
+ if len(lines) > 2 * N:
103
+ lines = [b"... showing only last few lines ..."] + lines[-N:]
104
+ return lines
105
+
106
+
107
+ def cvsecs(*args):
108
+ """converts a time to second. Either cvsecs(min, secs) or
109
+ cvsecs(hours, mins, secs).
110
+ """
111
+ if len(args) == 1:
112
+ return float(args[0])
113
+ elif len(args) == 2:
114
+ return 60 * float(args[0]) + float(args[1])
115
+ elif len(args) == 3:
116
+ return 3600 * float(args[0]) + 60 * float(args[1]) + float(args[2])
117
+
118
+
119
+ def parse_ffmpeg_header(text):
120
+ lines = text.splitlines()
121
+ meta = {}
122
+
123
+ # meta["header"] = text # Can enable this for debugging
124
+
125
+ # Get version
126
+ ver = lines[0].split("version", 1)[-1].split("Copyright")[0]
127
+ meta["ffmpeg_version"] = ver.strip() + " " + lines[1].strip()
128
+
129
+ # get the output line that speaks about video
130
+ videolines = [
131
+ l for l in lines if l.lstrip().startswith("Stream ") and " Video: " in l
132
+ ]
133
+
134
+ # Codec and pix_fmt hint
135
+ line = videolines[0]
136
+ meta["codec"] = line.split("Video: ", 1)[-1].lstrip().split(" ", 1)[0].strip()
137
+ meta["pix_fmt"] = re.split(
138
+ # use a negative lookahead regexp to ignore commas that are contained
139
+ # within a parenthesis
140
+ # this helps consider a pix_fmt of the kind
141
+ # yuv420p(tv, progressive)
142
+ # as what it is, instead of erroneously reporting as
143
+ # yuv420p(tv
144
+ r",\s*(?![^()]*\))",
145
+ line.split("Video: ", 1)[-1],
146
+ )[1].strip()
147
+
148
+ # get the output line that speaks about audio
149
+ audiolines = [
150
+ l for l in lines if l.lstrip().startswith("Stream ") and " Audio: " in l
151
+ ]
152
+
153
+ if len(audiolines) > 0:
154
+ audio_line = audiolines[0]
155
+ meta["audio_codec"] = (
156
+ audio_line.split("Audio: ", 1)[-1].lstrip().split(" ", 1)[0].strip()
157
+ )
158
+
159
+ # get the frame rate.
160
+ # matches can be empty, see #171, assume nframes = inf
161
+ # the regexp omits values of "1k tbr" which seems a specific edge-case #262
162
+ # it seems that tbr is generally to be preferred #262
163
+ fps = 0
164
+ for line in [videolines[0]]:
165
+ matches = re.findall(r" ([0-9]+\.?[0-9]*) (fps)", line)
166
+ if matches:
167
+ fps = float(matches[0][0].strip())
168
+ meta["fps"] = fps
169
+
170
+ # get the size of the original stream, of the form 460x320 (w x h)
171
+ line = videolines[0]
172
+ match = re.search(" [0-9]*x[0-9]*(,| )", line)
173
+ parts = line[match.start() : match.end() - 1].split("x")
174
+ meta["source_size"] = tuple(map(int, parts))
175
+
176
+ # get the size of what we receive, of the form 460x320 (w x h)
177
+ line = videolines[-1] # Pipe output
178
+ match = re.search(" [0-9]*x[0-9]*(,| )", line)
179
+ parts = line[match.start() : match.end() - 1].split("x")
180
+ meta["size"] = tuple(map(int, parts))
181
+
182
+ # Check the two sizes
183
+ if meta["source_size"] != meta["size"]:
184
+ logger.warning(
185
+ "The frame size for reading {} is "
186
+ "different from the source frame size {}.".format(
187
+ meta["size"], meta["source_size"]
188
+ )
189
+ )
190
+
191
+ # get the rotate metadata
192
+ reo_rotate = re.compile(r"rotate\s+:\s([0-9]+)")
193
+ match = reo_rotate.search(text)
194
+ rotate = 0
195
+ if match is not None:
196
+ rotate = match.groups()[0]
197
+ meta["rotate"] = int(rotate)
198
+
199
+ # get duration (in seconds)
200
+ line = [l for l in lines if "Duration: " in l][0]
201
+ match = re.search(" [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9]", line)
202
+ duration = 0
203
+ if match is not None:
204
+ hms = line[match.start() + 1 : match.end()].split(":")
205
+ duration = cvsecs(*hms)
206
+ meta["duration"] = duration
207
+
208
+ return meta
@@ -0,0 +1,127 @@
1
+ import logging
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ from functools import lru_cache
6
+ import importlib.resources
7
+
8
+ from ._definitions import FNAME_PER_PLATFORM, get_platform
9
+
10
+ logger = logging.getLogger("imageio_ffmpeg")
11
+
12
+
13
+ def get_ffmpeg_exe():
14
+ """
15
+ Get the ffmpeg executable file. This can be the binary defined by
16
+ the IMAGEIO_FFMPEG_EXE environment variable, the binary distributed
17
+ with imageio-ffmpeg, an ffmpeg binary installed with conda, or the
18
+ system ffmpeg (in that order). A RuntimeError is raised if no valid
19
+ ffmpeg could be found.
20
+ """
21
+
22
+ # 1. Try environment variable. - Dont test it: the user is explicit here!
23
+ exe = os.getenv("IMAGEIO_FFMPEG_EXE", None)
24
+ if exe:
25
+ return exe
26
+
27
+ # Auto-detect
28
+ exe = _get_ffmpeg_exe()
29
+ if exe:
30
+ return exe
31
+
32
+ # Nothing was found
33
+ raise RuntimeError(
34
+ "No ffmpeg exe could be found. Install ffmpeg on your system, "
35
+ "or set the IMAGEIO_FFMPEG_EXE environment variable."
36
+ )
37
+
38
+
39
+ @lru_cache()
40
+ def _get_ffmpeg_exe():
41
+ plat = get_platform()
42
+
43
+ # 2. Try from here
44
+ exe = os.path.join(_get_bin_dir(), FNAME_PER_PLATFORM.get(plat, ""))
45
+ if exe and os.path.isfile(exe) and _is_valid_exe(exe):
46
+ return exe
47
+
48
+ # 3. Try binary from conda package
49
+ # (installed e.g. via `conda install ffmpeg -c conda-forge`)
50
+ if plat.startswith("win"):
51
+ exe = os.path.join(sys.prefix, "Library", "bin", "ffmpeg.exe")
52
+ else:
53
+ exe = os.path.join(sys.prefix, "bin", "ffmpeg")
54
+ if exe and os.path.isfile(exe) and _is_valid_exe(exe):
55
+ return exe
56
+
57
+ # 4. Try system ffmpeg command
58
+ exe = "ffmpeg"
59
+ if _is_valid_exe(exe):
60
+ return exe
61
+
62
+ return None
63
+
64
+
65
+ def _get_bin_dir():
66
+ if sys.version_info < (3, 9):
67
+ context = importlib.resources.path("imageio_ffmpeg.binaries", "__init__.py")
68
+ else:
69
+ ref = importlib.resources.files("imageio_ffmpeg.binaries") / "__init__.py"
70
+ context = importlib.resources.as_file(ref)
71
+ with context as path:
72
+ pass
73
+ # Return the dir. We assume that the data files are on a normal dir on the fs.
74
+ return str(path.parent)
75
+
76
+
77
+ def _popen_kwargs(prevent_sigint=False):
78
+ startupinfo = None
79
+ preexec_fn = None
80
+ creationflags = 0
81
+ if sys.platform.startswith("win"):
82
+ # Stops executable from flashing on Windows (see #22)
83
+ startupinfo = subprocess.STARTUPINFO()
84
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
85
+ if prevent_sigint:
86
+ # Prevent propagation of sigint (see #4)
87
+ # https://stackoverflow.com/questions/5045771
88
+ if sys.platform.startswith("win"):
89
+ creationflags = 0x00000200
90
+ else:
91
+ preexec_fn = os.setpgrp # the _pre_exec does not seem to work
92
+
93
+ falsy = ("", "0", "false", "no")
94
+ if os.getenv("IMAGEIO_FFMPEG_NO_PREVENT_SIGINT", "").lower() not in falsy:
95
+ # Unset preexec_fn to work around a strange hang on fork() (see #58)
96
+ preexec_fn = None
97
+
98
+ return {
99
+ "startupinfo": startupinfo,
100
+ "creationflags": creationflags,
101
+ "preexec_fn": preexec_fn,
102
+ }
103
+
104
+
105
+ def _is_valid_exe(exe):
106
+ cmd = [exe, "-version"]
107
+ try:
108
+ with open(os.devnull, "w") as null:
109
+ subprocess.check_call(
110
+ cmd, stdout=null, stderr=subprocess.STDOUT, **_popen_kwargs()
111
+ )
112
+ return True
113
+ except (OSError, ValueError, subprocess.CalledProcessError):
114
+ return False
115
+
116
+
117
+ def get_ffmpeg_version():
118
+ """
119
+ Get the version of the used ffmpeg executable (as a string).
120
+ """
121
+ exe = get_ffmpeg_exe()
122
+ line = subprocess.check_output([exe, "-version"], **_popen_kwargs()).split(
123
+ b"\n", 1
124
+ )[0]
125
+ line = line.decode(errors="ignore").strip()
126
+ version = line.split("version", 1)[-1].lstrip().split(" ", 1)[0].strip()
127
+ return version
@@ -0,0 +1,2 @@
1
+ Exes are dropped here by `invoke get-ffmpeg-binary` and the release script
2
+ (see `tools/download_ffmpeg.py`).
@@ -0,0 +1 @@
1
+ # Just here to make importlib.resources work
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: imageio_ffmpeg/binaries/ffmpeg-win-aarch64-v9.0.1.exe
3
+ size: 78507008 bytes
4
+ sha256: a169b9d26b2380be66211022525c9ac16affc923ba05b561024e57c5ed4281f9
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: imageio-ffmpeg
3
+ Version: 0.8.0
4
+ Summary: FFMPEG wrapper for Python
5
+ Home-page: https://github.com/imageio/imageio-ffmpeg
6
+ Download-URL: http://pypi.python.org/pypi/imageio-ffmpeg
7
+ Author: imageio contributors
8
+ Author-email: almar.klein@gmail.com
9
+ License: BSD-2-Clause
10
+ Keywords: video ffmpeg
11
+ Platform: any
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: BSD License
17
+ Classifier: Operating System :: MacOS :: MacOS X
18
+ Classifier: Operating System :: Microsoft :: Windows
19
+ Classifier: Operating System :: POSIX
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
22
+ Provides: imageio_ffmpeg
23
+ Requires-Python: >=3.10
24
+ License-File: LICENSE
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: download-url
30
+ Dynamic: home-page
31
+ Dynamic: keywords
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: platform
35
+ Dynamic: provides
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ FFMPEG wrapper for Python.
40
+
41
+ Note that the platform-specific wheels contain the binary executable
42
+ of ffmpeg, which makes this package around 60 MiB in size.
43
+ I guess that's the cost for being able to read/write video files.
44
+
45
+ For Linux users: the above is not the case when installing via your
46
+ Linux package manager (if that is possible), because this package would
47
+ simply depend on ffmpeg in that case.
@@ -0,0 +1,13 @@
1
+ imageio_ffmpeg/__init__.py,sha256=up19VQbBji1XMFotEd96oazT3u17Dq-GMzHYHbFqOFI,225
2
+ imageio_ffmpeg/_definitions.py,sha256=4DADH1EdLc9AA98RYf0dWxfIEMxtFl80Q21J4sEXJ2s,2192
3
+ imageio_ffmpeg/_io.py,sha256=XdqDqEjnMT-kJijRt1dZ5-TkFFS_uh2eD9zsE6238-M,27290
4
+ imageio_ffmpeg/_parsing.py,sha256=UUtYPybzv9vQ4sKPwYlRTyaP_k9koKXfl-zI6Rm4Qpo,6840
5
+ imageio_ffmpeg/_utils.py,sha256=eVF4ny9zP3jPfeYIwl6VfV9T1v34S6yURWC_RiGUNF8,3833
6
+ imageio_ffmpeg/binaries/README.md,sha256=nwMe1zGsWei5WruCZKFAeUuUj4Ud9Air6uNVObFdlt0,109
7
+ imageio_ffmpeg/binaries/__init__.py,sha256=zjnRZoM6bQUBYOI30HpzIp4ck07bOy4oxXB-bwN3NW8,45
8
+ imageio_ffmpeg/binaries/ffmpeg-win-aarch64-v9.0.1.exe,sha256=oWm50msjgL5mIRAiUlyawWr_ySO6BbVhAk5Xxe1Cgfk,78507008
9
+ imageio_ffmpeg-0.8.0.dist-info/licenses/LICENSE,sha256=7Kc0IdDJBakulSpVPU1YkZxaUHwsLHbjwM-bpAjCmeQ,1317
10
+ imageio_ffmpeg-0.8.0.dist-info/METADATA,sha256=x4s48LRsmfavQIkgJFk3z-kZ2BazBDmhqScuhdC-YQo,1530
11
+ imageio_ffmpeg-0.8.0.dist-info/WHEEL,sha256=lTfaOYr-DH5rklomOvgFCC9CuI2mD5X71wfmwV8y5wQ,96
12
+ imageio_ffmpeg-0.8.0.dist-info/top_level.txt,sha256=ODQYUYYbtj9I1SjASEMY7h8Q8haLrmMNURSCn0FEB18,15
13
+ imageio_ffmpeg-0.8.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-win_arm64
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2019-2025, imageio
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ imageio_ffmpeg