PyAV
PyAV is a Pythonic binding for FFmpeg's libraries, providing direct and precise access to media via containers, streams, packets, codecs, and frames. It aims to expose the full power and control of the underlying FFmpeg library while managing lower-level details where possible. The current version is 17.0.0, and releases generally follow significant FFmpeg updates or major feature additions.
Warnings
- breaking As of v17.0.0, when an FFmpeg C function indicates an error, PyAV now raises `av.ArgumentError` instead of `ValueError`/`av.ValueError`. This helps to more precisely distinguish the source of an exception.
- gotcha Containers (`av.Container`) and streams (`av.Stream`) must be explicitly closed to ensure all data is flushed and resources are properly released, preventing potential data loss or memory leaks. Using them as context managers (`with av.open(...) as container:`) is the recommended practice.
- gotcha PyAV disables FFmpeg's verbose logging by default, which can obscure detailed error messages. While this reduces console noise, it can make debugging challenging when issues arise.
- gotcha When decoding, a single input packet does not always guarantee an output frame, and multiple packets might be required to produce a single frame. Additionally, it's crucial to 'flush' the decoder by sending `None` or empty packets at the end of the input stream to retrieve any remaining buffered frames.
- gotcha Building PyAV from source, especially on Windows or with specific Python versions (e.g., Python 3.10 historically), can lead to `ImportError` or `ModuleNotFoundError` due to issues with dynamic library linking (DLLs on Windows) or internal Python API changes.
Install
-
pip install av -
conda install -c conda-forge pyav
Imports
- av
import av
Quickstart
import av
import os
# Create a dummy video file for demonstration
output_filename = "dummy_video.mp4"
# Encode a simple video (e.g., 1 second of black frames)
# This part requires numpy, but it's a common dependency for video processing
try:
import numpy as np
duration = 1 # seconds
fps = 24 # frames per second
total_frames = duration * fps
width, height = 640, 480
with av.open(output_filename, mode="w") as container:
stream = container.add_stream("mpeg4", rate=fps)
stream.width = width
stream.height = height
stream.pix_fmt = "yuv420p"
for frame_i in range(total_frames):
img = np.zeros((height, width, 3), dtype=np.uint8) # Black frame
frame = av.VideoFrame.from_ndarray(img, format="rgb24")
for packet in stream.encode(frame):
container.mux(packet)
# Flush stream
for packet in stream.encode():
container.mux(packet)
print(f"Successfully created dummy video: {output_filename}")
# --- Decoding and processing part of the quickstart ---
container = av.open(output_filename)
for frame in container.decode(video=0):
print(f"Decoded frame {frame.index} with PTS {frame.pts}")
# Example: Save the first frame
if frame.index == 0:
frame.to_image().save(f"frame-{frame.index:04d}.jpg")
print(f"Saved frame-0000.jpg")
break # Only process the first frame for this quickstart
container.close()
print("Container closed.")
except ImportError:
print("NumPy not found. Skipping video creation. To run the full quickstart, install numpy (pip install numpy).")
print(f"Please ensure '{output_filename}' exists for the decoding example, or create it manually.")
# Attempt to decode if a file exists, otherwise skip
if os.path.exists(output_filename):
container = av.open(output_filename)
for frame in container.decode(video=0):
print(f"Decoded frame {frame.index} with PTS {frame.pts}")
break
container.close()
else:
print("No video file to decode without NumPy.")
finally:
# Clean up the dummy video file
if os.path.exists(output_filename):
os.remove(output_filename)
print(f"Cleaned up {output_filename}")
if os.path.exists("frame-0000.jpg"):
os.remove("frame-0000.jpg")
print(f"Cleaned up frame-0000.jpg")