Run ffmpeg and ffprobe commands with nicely parsed output.
Project description
FFmpeg/FFprobe output parser
Overview
Do you already know the ffmpeg command line, and don't want to relearn some syntax of a pythonic wrapper? This is the package for you. Just put in an ffmpeg or ffprobe command and this package structures the output while it's processing or after completion.
Usage
Example: FFmpeg with Progress
The code below converts a video, and prints the percentage completion while it's working. This example includes optional error handling, output shown below.
async def process_video(input_video: Path, output_video: Path):
def handle_status(status: FfmpegStatus):
if status.completion is not None:
print(f"We're: {status.completion * 100:.1f}% there!")
try:
await run_ffmpeg(
["ffmpeg", "-i", input_video, "-c:v", "libx264", output_video],
on_status=handle_status,
overwrite_output=True
)
print("Done!")
except FfmpegError as e:
print(f"ffmpeg failed with error: {e.format_error()}") # Use format_error() for detailed info
Example output: custom status logging
We're: 8.2% there!
We're: 45.5% there!
We're: 100.0% there!
Done!
Error example
ffmpeg failed with error:
User command:
['ffmpeg', '-i', 'nonexistent_input.mp4', '-c:v', 'libx264', 'output.mp4']
Executed command:
ffmpeg -i nonexistent_input.mp4 -c:v libx264 output.mp4 -y -progress pipe:1
Working directory:
C:\Users\your_user\path\to\project
[in#0 @ 0x...] Error opening input: No such file or directory
Error opening input file nonexistent_input.mp4.
Error opening input files: No such file or directory
Example: Ffprobe
Ffprobe output is also supported, use it like this:
async def probe_video(input_video: Path):
try:
result = await run_ffprobe(["ffprobe", input_video])
# You can now access structured data
print(f"Duration: {result.duration_ms} ms")
print(f"Format: {result.format_name}")
print(f"Overall Bitrate: {result.bitrate_kbs} kb/s")
print(f"Found {len(result.streams)} streams:")
for stream in result.streams:
print(f" - Stream {stream.stream_id} ({stream.type}): Codec={stream.codec}")
if isinstance(stream, VideoStream):
print(f" Res: {stream.resolution_w}x{stream.resolution_h}, FPS: {stream.fps}")
elif isinstance(stream, AudioStream):
print(f" Sample Rate: {stream.sample_rate} Hz, Channels: {stream.num_channels}")
except FfmpegError as e:
print(f"ffprobe failed with error: {e.format_error()}")
The ffprobe result object (FfprobeResult) contains structured information. Here's an example representing the parsed data for a multi-stream file:
{
"duration_ms": 14180,
"start_time": 0.0,
"bitrate_kbs": 37744,
"streams": [
{
"stream_id": "0:0",
"codec": "prores",
"type": "video",
"details": "prores (LT) (apcs / 0x73637061), yuv422p10le(bt709, top coded first (swapped)), 1920x1080, 31008 kb/s, SAR 1:1 DAR 16:9, 59.94 fps, 59.94 tbr, 60k tbn (default)",
"bitrate_kbs": 31008,
"resolution_w": 1920,
"resolution_h": 1080,
"fps": 59.94
},
{
"stream_id": "0:1",
"codec": "pcm_s16le",
"type": "audio",
"details": "pcm_s16le (sowt / 0x74776F73), 48000 Hz, 2 channels, s16, 1536 kb/s (default)",
"bitrate_kbs": 1536,
"sample_rate": 48000,
"num_channels": 2,
"channel_layout_str": "2 channels"
},
{
"stream_id": "0:2",
"codec": "pcm_s16le",
"type": "audio",
"details": "pcm_s16le (sowt / 0x74776F73), 48000 Hz, 2 channels, s16, 1536 kb/s (default)",
"bitrate_kbs": 1536,
"sample_rate": 48000,
"num_channels": 2,
"channel_layout_str": "2 channels"
},
{
"stream_id": "0:3",
"codec": "pcm_s16le",
"type": "audio",
"details": "pcm_s16le (sowt / 0x74776F73), 48000 Hz, 2 channels, s16, 1536 kb/s (default)",
"bitrate_kbs": 1536,
"sample_rate": 48000,
"num_channels": 2,
"channel_layout_str": "2 channels"
},
{
"stream_id": "0:4",
"codec": "pcm_s16le",
"type": "audio",
"details": "pcm_s16le (sowt / 0x74776F73), 48000 Hz, 2 channels, s16, 1536 kb/s (default)",
"bitrate_kbs": 1536,
"sample_rate": 48000,
"num_channels": 2,
"channel_layout_str": "2 channels"
},
{
"stream_id": "0:5",
"codec": "none",
"type": "data",
"details": "none (tmcd / 0x64636D74), 0 kb/s (default)",
"bitrate_kbs": 0
}
],
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"metadata": {
"major_brand": "qt",
"minor_version": "537199360",
"compatible_brands": "qt",
"creation_time": "2021-03-04T16:00:20.000000Z"
}
}
Example: run with tqdm to get a progressbar
If you install the tqdm extra dependency (pip install parsed-ffmpeg[tqdm]), you can do the following:
from pathlib import Path
from parsed_ffmpeg import run_ffmpeg
input_video = Path(__file__).parent.parent / "tests/assets/input.mp4" # Adjust path
output_video = Path("output_scaled.mp4")
await run_ffmpeg(
["ffmpeg", "-i", input_video, "-vf", "scale=-1:1440", "-c:v", "libx264", output_video],
print_progress_bar=True,
progress_bar_description=input_video.name,
overwrite_output=True,
)
It'll give output like this:
input.mp4: 73%|███████▎ | 4466/6084 [00:04<00:00, 1620.10ms/s]
Installation
Remember that this package does not come with an ffmpeg/ffprobe binary, you have to have it in your system's PATH or point to it explicitly in your command.
pip install parsed-ffmpeg
or with tqdm support:
pip install parsed-ffmpeg[tqdm]
API
run_ffmpeg
async def run_ffmpeg(
command: list[str | Path] | str, # Command as list or single string
on_status: Callable[[FfmpegStatus], None] | None = None,
on_stdout: Callable[[str], None] | None = None, # stdout lines
on_stderr: Callable[[str], None] | None = None, # stderr lines
on_error: Callable[[list[str]], None] | None = None, # Called with error lines
on_warning: Callable[[str], None] | None = None, # Called with warning lines
overwrite_output: bool = False, # Add -y flag if True
raise_on_error: bool = True, # Raise FfmpegError on failure
print_progress_bar: bool = False, # Use tqdm for progress (requires [tqdm] extra)
progress_bar_description: str | None = None, # Description for tqdm bar
) -> str: # Returns the full stderr output as a single string on success
...
FfmpegStatus
This dataclass holds the parsed status/progress information provided via the on_status callback during run_ffmpeg. Fields are None if not available in a specific progress update.
@dataclass
class FfmpegStatus:
frame: int | None = None
fps: float | None = None
bitrate: str | None = None # e.g., "1536kbits/s"
total_size: int | None = None # In bytes
out_time_ms: float | None = None # Microseconds processed
dup_frames: int | None = None
drop_frames: int | None = None
speed: float | None = None # e.g., 1.5x
progress: str | None = None
# Calculated fields:
duration_ms: int | None = None # Estimated total duration in ms (if known)
completion: float | None = None # Estimated completion ratio (0.0 to 1.0) (if duration known)
run_ffprobe
async def run_ffprobe(
command: list[str | Path] | str, # Command as list or single string
on_error: Callable[[list[str]], None] | None = None, # Called with error lines if raise_on_error=False
on_warning: Callable[[str], None] | None = None, # Called with warning lines
raise_on_error: bool = True, # Raise FfmpegError on failure
) -> FfprobeResult: # Returns the parsed FfprobeResult object
...
ffprobe types
These are the dataclasses used within the FfprobeResult. Fields marked ...|None may be None if the information is not present in the ffprobe output for that specific stream.
class StreamType(StrEnum):
VIDEO = auto()
AUDIO = auto()
DATA = auto()
UNKNOWN = auto() # For unrecognized types
@dataclass
class BaseStream:
stream_id: str # e.g., "0:0"
codec: str # e.g., "h264", "aac", "prores", "pcm_s16le"
type: StreamType # The type of stream
details: str # Raw details string from ffprobe for this stream
bitrate_kbs: int | None = None # Stream bitrate in kb/s
@dataclass
class VideoStream(BaseStream):
type: StreamType = field(default=StreamType.VIDEO, init=False)
resolution_w: int | None = None
resolution_h: int | None = None
fps: float | None = None
@dataclass
class AudioStream(BaseStream):
type: StreamType = field(default=StreamType.AUDIO, init=False)
sample_rate: int | None = None # e.g., 44100, 48000
num_channels: int | None = None # e.g., 1, 2, 6
channel_layout_str: str | None = None # e.g., "mono", "stereo", "5.1(side)"
@dataclass
class DataStream(BaseStream):
type: StreamType = field(default=StreamType.DATA, init=False)
@dataclass
class FfprobeResult:
duration_ms: int | None = None # Total duration in milliseconds
start_time: float | None = None # Start time in seconds
bitrate_kbs: int | None = None # Overall file bitrate in kb/s
streams: list[BaseStream] = field(default_factory=list) # List of detected streams (Video, Audio, Data, etc.)
format_name: str | None = None # e.g., "mov,mp4,m4a,3gp,3g2,mj2", "matroska,webm"
metadata: dict[str, str] = field(default_factory=dict) # Top-level metadata tags
FfmpegError
This exception is raised by default when ffmpeg or ffprobe returns a non-zero exit code.
class FfmpegError(Exception):
def __init__(
self,
err_lines: Sequence[str],
full_command: Sequence[str | Path], # The exact command executed
user_command: str | Sequence[str | Path], # The command user provided
) -> None: ...
def format_error(self) -> str: # Get the formatted error string shown above
...
# Attributes:
# err_lines: Sequence[str] - List of lines from stderr considered errors
# full_command: Sequence[str | Path] - The exact command executed, including added flags like -y, -progress
# user_command: str | Sequence[str | Path] - The command originally passed to run_ffmpeg/run_ffprobe
Changing ffmpeg install location
Just replace the first part of your command (ffmpeg or ffprobe) with the full path to the executable.
Example:
await run_ffmpeg(["C:/apps/ffmpeg/bin/ffmpeg.exe", "-i", "input.mp4", "-c:v", "libx264", "output.mp4"])
# or
result = await run_ffprobe(["/usr/local/bin/ffprobe", "input.mp4"])
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file parsed_ffmpeg-0.4.2.tar.gz.
File metadata
- Download URL: parsed_ffmpeg-0.4.2.tar.gz
- Upload date:
- Size: 21.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73b2b27e4d88592e39403b8e347a2edb5517b15f3b2214b89971bdb56323d042
|
|
| MD5 |
4817d43b90151c1ea53404263f51565c
|
|
| BLAKE2b-256 |
8a6fedd1a64a4e2de36ab2c7bf6a469d33cbf25e713eb14102b62c1d91ba1870
|
Provenance
The following attestation bundles were made for parsed_ffmpeg-0.4.2.tar.gz:
Publisher:
publish-to-pypi.yml on RuurdBijlsma/parsed_ffmpeg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
parsed_ffmpeg-0.4.2.tar.gz -
Subject digest:
73b2b27e4d88592e39403b8e347a2edb5517b15f3b2214b89971bdb56323d042 - Sigstore transparency entry: 203130854
- Sigstore integration time:
-
Permalink:
RuurdBijlsma/parsed_ffmpeg@1b435d70ff45c3ef712a5214cf24579753efb299 -
Branch / Tag:
refs/tags/0.4.2 - Owner: https://github.com/RuurdBijlsma
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@1b435d70ff45c3ef712a5214cf24579753efb299 -
Trigger Event:
push
-
Statement type:
File details
Details for the file parsed_ffmpeg-0.4.2-py3-none-any.whl.
File metadata
- Download URL: parsed_ffmpeg-0.4.2-py3-none-any.whl
- Upload date:
- Size: 12.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bc32b3bf1f65dc7e980a8eabcc23645592f151aa3f73c9d0c5afe3ce37c4ed7
|
|
| MD5 |
9001191dc9f7d822179ec510aaefc21f
|
|
| BLAKE2b-256 |
7a0acca694c7978e23e9712241776183e943ebc989ec31970f606fb8656a4f15
|
Provenance
The following attestation bundles were made for parsed_ffmpeg-0.4.2-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on RuurdBijlsma/parsed_ffmpeg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
parsed_ffmpeg-0.4.2-py3-none-any.whl -
Subject digest:
5bc32b3bf1f65dc7e980a8eabcc23645592f151aa3f73c9d0c5afe3ce37c4ed7 - Sigstore transparency entry: 203130855
- Sigstore integration time:
-
Permalink:
RuurdBijlsma/parsed_ffmpeg@1b435d70ff45c3ef712a5214cf24579753efb299 -
Branch / Tag:
refs/tags/0.4.2 - Owner: https://github.com/RuurdBijlsma
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@1b435d70ff45c3ef712a5214cf24579753efb299 -
Trigger Event:
push
-
Statement type: