Parse FFmpeg Progress Using Python Subprocess
Tracking the progress of an FFmpeg operation in Python requires
launching the process via subprocess.Popen and reading its
output stream in real time. Rather than attempting to parse the erratic,
carriage-return-delimited output FFmpeg prints to stderr by
default, the most reliable approach is to use FFmpeg's
-progress pipe:1 flag. This directs FFmpeg to write
machine-readable, key-value pairs directly to standard output
(stdout), allowing your Python script to easily capture
frames, execution time, and completion status.
The Strategy: Using Machine-Readable Progress
FFmpeg includes a dedicated -progress flag that outputs
periodic key-value metrics:
frame: Current frame processed.fps: Current processing speed in frames per second.total_size: Size of the output file so far in bytes.out_time_us: Processing position in microseconds.progress: Set tocontinueduring processing, andendwhen finished.
Directing this data to pipe:1 routes it to
stdout, keeping it separate from standard log messages
(which go to stderr).
Python Implementation
The following script executes FFmpeg to convert a video and reads the structured output line-by-line:
import subprocess
import sys
def run_ffmpeg_with_progress(input_path: str, output_path: str):
# Construct the command with the -progress flag sending data to stdout (pipe:1)
cmd = [
"ffmpeg",
"-y",
"-i", input_path,
"-progress", "pipe:1",
"-nostats",
output_path
]
# Launch FFmpeg
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
progress_data = {}
# Read stdout line by line as it is generated
for line in process.stdout:
line = line.strip()
if not line:
continue
# FFmpeg outputs lines in key=value format
if "=" in line:
key, value = line.split("=", 1)
progress_data[key.strip()] = value.strip()
# The 'progress' key indicates the end of a single progress update block
if line.startswith("progress="):
frame = progress_data.get("frame", "N/A")
fps = progress_data.get("fps", "N/A")
out_time = progress_data.get("out_time", "N/A")
status = progress_data.get("progress")
sys.stdout.write(f"\rFrame: {frame} | FPS: {fps} | Time: {out_time}")
sys.stdout.flush()
if status == "end":
print("\nTranscoding complete.")
break
# Wait for the process to exit cleanly
process.wait()
if process.returncode != 0:
error_output = process.stderr.read()
raise RuntimeError(f"FFmpeg error:\n{error_output}")
if __name__ == "__main__":
run_ffmpeg_with_progress("input.mp4", "output.mkv")Calculating Completion Percentage
Because FFmpeg does not automatically report progress as a percentage, you must provide the source file's total duration to calculate it.
- Retrieve Total Duration: Use
ffprobebefore transcoding to obtain the file's duration in seconds:
def get_duration(input_path: str) -> float:
cmd = [
"ffprobe",
"-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
input_path
]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
return float(result.stdout.strip())- Compute Percentage: Inside the progress loop, read
out_time_us(microseconds), convert it to seconds, and divide by the total duration:
out_time_us = int(progress_data.get("out_time_us", 0))
current_seconds = out_time_us / 1_000_000
percent = min(100.0, (current_seconds / total_duration) * 100)
sys.stdout.write(f"\rProgress: {percent:.2f}%")Key Considerations
- Buffering: Setting
bufsize=1ensures line-buffering is enabled, preventing delays between FFmpeg generating an update and Python processing it. - Deadlocks: When reading
stdout, ensurestderrdoes not fill the operating system's pipe buffer. If you do not need FFmpeg's logs, routestderr=subprocess.DEVNULL. If you need error logs, consume them asynchronously or only read them afterstdoutcloses if the process fails.