Better FFmpeg Progress
Runs an FFmpeg command and shows a progress bar with percentage progress, elapsed time and ETA.
FFmpeg outputs something like:
frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
Better FFmpeg Progress outputs something like:
⠏ Processing abc.webm (5.50 MB / ~23.91 MB) ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
Where:
Processing abc.webmis the description of the progresss bar. Filenames longer than 60 characters are truncated.(5.50 MB / ~23.91 MB)is the current output size and the estimated final output size.23%is the percentage progress.0:00:04is the time elapsed.00:15is the estimated time until the FFmpeg process completes.
Installation
pip install better-ffmpeg-progress --upgrade
Usage
Create an instance of the FfmpegProcess class and supply a list of arguments like you would when using subprocess.run() or subprocess.Popen(). Example:
from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
command = [
"ffmpeg",
"-i",
"https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
"-map",
"0:V",
"-c:V",
"libx264",
"-preset",
"ultrafast",
"-f",
"null",
"-",
]
try:
# Pass use_tqdm=True to use tqdm instead of Rich.
process = FfmpegProcess(command)
process.run()
except FfmpegProcessError as e:
print(
"An error occurred when running Better FFmpeg Progress:\n"
f"{e}"
)
Trimming
If your command trims the input with -ss, -sseof, -t or -to (as input or output options), the expected duration of the output is calculated automatically, so the progress bar and ETA reflect the trimmed output rather than the whole input file. This adjustment is skipped when -copyts is used.
Duration Override
By default, Better FFmpeg Progress detects the duration of the input file and uses it to calculate percentage progress and ETA.
For partial, damaged, streamed or synthetic inputs, FFmpeg may be unable to detect the duration correctly. In these cases, you can provide the duration manually using duration_override.
The value should be the duration of the input file, in seconds:
from better_ffmpeg_progress import FfmpegProcess
command = [
"ffmpeg",
"-i",
"partial-input.flv",
"-c",
"copy",
"recovered-output.mkv",
]
process = FfmpegProcess(
command,
duration_override=1222.64,
print_detected_duration=True,
)
process.run()
Generated (lavfi) Inputs
Inputs generated by FFmpeg's lavfi device, such as test patterns, are supported. FFprobe cannot detect the duration of these inputs, so provide it with duration_override:
from better_ffmpeg_progress import FfmpegProcess
command = [
"ffmpeg",
"-f",
"lavfi",
"-i",
"testsrc2=duration=30:size=1920x1080:rate=30",
"-c:v",
"libx264",
"output.mp4",
]
process = FfmpegProcess(command, duration_override=30)
process.run()
By default, the FFmpeg log file is named after the source, e.g. lavfi_testsrc2_ffmpeg_log.txt.
Optional Arguments
An instance of FfmpegProcess accepts the following optional arguments:
ffmpeg_log_level— Set a value for FFmpeg's-logleveloption. Default:"verbose".ffmpeg_log_file— Filepath or file-like object to which FFmpeg stderr will be written. Default:<input filename>_ffmpeg_log.txt.print_detected_duration— Print the detected duration. Default:False.duration_override— Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. Default:None.use_tqdm— Use tqdm instead of Rich for the progress bar. Default:False.overwrite— What to do if the output file already exists:Trueoverwrites it,FalseraisesFfmpegCommandError, andNoneasks whether to overwrite it. Ignored if the command contains-yor-n. Default:None.quiet— Do not show a progress bar or status messages. Default:False.progress_callback— A function to call each time FFmpeg reports its progress. See Progress callback. Default:None.delete_log_on_success— Delete the FFmpeg log file if FFmpeg succeeds. Default:False.
Creating an FfmpegProcess only validates the command. FFprobe is run, and you are asked whether to overwrite an existing output file, when run() is called.
The run method accepts the following optional argument:
print_command— Print the FFmpeg command being executed. Default:False.
Using tqdm
By default, the Rich library is used to display a progress bar. tqdm can be used instead by passing use_tqdm=True. tqdm is an optional dependency, installed with:
pip install "better-ffmpeg-progress[tqdm]" --upgrade
process = FfmpegProcess(command, use_tqdm=True)
process.run()
Progress callback
To show progress in your own UI (e.g. a GUI or a web app), pass a function as progress_callback. It is called with the number of seconds processed, the expected output duration in seconds (None if it is unknown) and the current output size in MB. Combine it with quiet=True to hide the progress bar:
def on_progress(secs: float, duration: float | None, size_mb: float) -> None:
if duration:
print(f"{secs / duration:.0%}")
FfmpegProcess(command, quiet=True, progress_callback=on_progress).run()
If the callback raises an exception, FFmpeg is stopped and the exception is propagated by run().
Errors
All exceptions raised by the package are subclasses of FfmpegProcessError, which has these attributes:
returncode— FFmpeg's exit code, orNoneif FFmpeg was not started or had not exited.log_file— The path or file object that FFmpeg's log was written to.
Pressing Ctrl+C raises FfmpegProcessInterruptedError. FFmpeg is first asked to stop the way pressing q does, so it can finish writing the output file, e.g. an MP4 file stays playable. If it does not stop within 5 seconds, it is terminated.
Logging
Warnings, such as when the input duration cannot be detected, are logged with the logging module using the better_ffmpeg_progress logger. Without any logging configuration, Python prints them to standard error. To hide them:
import logging
logging.getLogger("better_ffmpeg_progress").setLevel(logging.ERROR)
Writing to standard output
On Linux and macOS, the output can be written to standard output, e.g. -f mpegts -, to pipe it to another program. The progress bar is then shown on standard error. This is not supported on Windows, where FFmpeg's progress is read from its standard output.
Duration detection
FFprobe is looked for next to the FFmpeg executable first (e.g. /opt/ffmpeg/bin/ffprobe for /opt/ffmpeg/bin/ffmpeg), then in PATH. If it does not finish within 30 seconds, e.g. for an unresponsive URL, the duration is treated as unknown.
Limitations
- Progress is calculated from the duration of the first input (the first
-i). - The last argument of the command is treated as the output file, e.g. when checking whether it already exists. With multiple outputs, only the last one is checked.
Development
Install the package in editable mode with the development dependencies, then run the tests (FFmpeg and FFprobe must be installed):
pip install -e ".[dev]"
pytest
Lint, format and type-check the code like CI does:
ruff check .
ruff format --check better_ffmpeg_progress tests test.py
mypy
test.py is a standalone example that encodes a test pattern and shows the progress bar.
Publishing
Update __version__ in better_ffmpeg_progress/__init__.py and add an entry to CHANGELOG.md.
One-time setup:
python -m pip install --upgrade build twine pkginfo
Then, for each release:
rm -rf dist
python -m build
python -m twine check dist/*
python -m twine upload dist/*
Metadata
Release files for better-ffmpeg-progress 7.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| better_ffmpeg_progress-7.0.0.tar.gz | 24.8 kB | Without provenance | Details for better_ffmpeg_progress-7.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| better_ffmpeg_progress-7.0.0-py3-none-any.whl | Python 3 | none | any | Details for better_ffmpeg_progress-7.0.0-py3-none-any.whl |
Total release size: 45.1 kB
Release files / better_ffmpeg_progress-7.0.0.tar.gz
| Download URL | better_ffmpeg_progress-7.0.0.tar.gz |
|---|---|
| Size | 24.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
053957aae8cf2af0a66de4f9f120729292ea5c4382e5a8c4f25374a87503732b
|
|
BLAKE2b-256 checksum How to use checksums |
1818575a619a31c1288cf8b531eefc8cbfe240ce7302226d76ecc70eba07d9a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.6
|
Release files / better_ffmpeg_progress-7.0.0-py3-none-any.whl
| Download URL | better_ffmpeg_progress-7.0.0-py3-none-any.whl |
|---|---|
| Size | 20.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d99c9185fe775116243de7482ddcfb30093bf50de0b13b1e8fa9d7bcdf2d73b
|
|
BLAKE2b-256 checksum How to use checksums |
dc6b9a2833a0a6c7bb3f39a5c218d9dd143e81e6c33d85e38f284b3351ff54ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.6
|
Security for better-ffmpeg-progress 7.0.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.