Skip to main content

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

Release files for parsed-ffmpeg 0.4.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for parsed-ffmpeg 0.4.2
File Size Uploaded
parsed_ffmpeg-0.4.2.tar.gz 21.8 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for parsed-ffmpeg 0.4.2
File Interpreter ABI Platform
parsed_ffmpeg-0.4.2-py3-none-any.whl Python 3 none any Details

Total release size: 21.8 MB

Release files / parsed_ffmpeg-0.4.2.tar.gz

Download URL parsed_ffmpeg-0.4.2.tar.gz
Size 21.8 MB
Tags Source
SHA-256 checksum
How to use checksums
73b2b27e4d88592e39403b8e347a2edb5517b15f3b2214b89971bdb56323d042
BLAKE2b-256 checksum
How to use checksums
8a6fedd1a64a4e2de36ab2c7bf6a469d33cbf25e713eb14102b62c1d91ba1870
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 26, 2025.

Transparency log

Release files / parsed_ffmpeg-0.4.2-py3-none-any.whl

Download URL parsed_ffmpeg-0.4.2-py3-none-any.whl
Size 12.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5bc32b3bf1f65dc7e980a8eabcc23645592f151aa3f73c9d0c5afe3ce37c4ed7
BLAKE2b-256 checksum
How to use checksums
7a0acca694c7978e23e9712241776183e943ebc989ec31970f606fb8656a4f15
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 26, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.2 This release

2 release files

0.4.1

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page