The project is now public domain under the Unlicense (LICENSE, README). The packaged archives bundle GPL ffmpeg/ffprobe builds but did not include their license. fetch_ffmpeg.py now extracts the build's LICENSE.txt into ffmpeg/LICENSE.txt, and build.py --ffmpeg-dir copies it into the archive as ffmpeg-LICENSE.txt, failing before the PyInstaller run if it can't find one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
6.5 KiB
Markdown
121 lines
6.5 KiB
Markdown
# FFmpeg Wrapper
|
|
|
|
A desktop GUI for common ffmpeg jobs: remuxing MKV files to Jellyfin-friendly MP4s, choosing which
|
|
audio tracks to keep, stripping audio, re-encoding, and scaling. Works on single files or whole batches.
|
|
It's a single executable for Windows and Linux, built with Python, Qt (PySide6) and PyInstaller.
|
|
|
|

|
|
|
|
## Features
|
|
|
|
- **Single file or batch.** Use *Add Files*, *Add Folder* (recursive), or drag and drop. Files are
|
|
probed in the background and shown with duration, video info, and audio and subtitle counts.
|
|
- **Target container.** MP4, MKV, MOV, M4V, WebM, AVI or MPEG-TS. The encoder lists only show codecs
|
|
the chosen container can hold.
|
|
- **Audio track picker.** Every audio track in the selected file is listed with its language, codec,
|
|
channels and title. Tick the tracks you want and drag them into order. The first ticked track
|
|
becomes the default. **Apply to All Files** copies your choice to the rest of the queue,
|
|
matching tracks by language, codec and layout, so a season of episodes takes one click.
|
|
- **Strip audio.** Set Audio mode to *Strip audio*.
|
|
- **Keep streams unaltered.** *Copy* mode remuxes video and/or audio without re-encoding. *Auto*
|
|
copies whatever the container supports and re-encodes only what it can't hold (for example
|
|
DTS/TrueHD to AAC for MP4).
|
|
- **Up/downscale video.** Pick 360p up to 8K, or a custom WxH, with aspect ratio kept and Lanczos
|
|
scaling. Portrait and rotated phone videos are handled. You can also change the frame rate.
|
|
Encoders: H.264, HEVC, AV1, VP9, MPEG-4, plus NVENC, QuickSync and AMF if your ffmpeg has them.
|
|
There are CRF quality and speed preset controls.
|
|
- **Up/downmix audio.** Change channels (mono, stereo, 5.1, 7.1), sample rate, bitrate, and codec
|
|
(AAC, AC-3, E-AC-3, Opus, MP3, Vorbis, FLAC, ALAC, PCM).
|
|
- **Output folder.** Save next to the source or in a chosen folder. You can add a filename suffix
|
|
and choose what happens when the output file already exists (keep both, overwrite, or skip).
|
|
The source file is never overwritten.
|
|
- **Subtitles** are kept where possible. MP4 and MOV turn text subs into `mov_text`, and image subs
|
|
(PGS) are dropped with a note in the log. MKV keeps everything, including font attachments.
|
|
- MP4/MOV outputs get `+faststart`, and HEVC is tagged `hvc1` for Apple and TV compatibility.
|
|
- The window shows per-file and overall progress, has a **Stop** button, and keeps a full log.
|
|
**Show Command** prints the exact ffmpeg command so you can copy it.
|
|
- ffmpeg writes to `name.part.ext` and the file is renamed only on success, so a failed or
|
|
cancelled job never leaves a half-written file.
|
|
|
|
## Getting ffmpeg
|
|
|
|
The app looks for `ffmpeg` and `ffprobe` in this order:
|
|
|
|
1. The folder set in **Settings > Set FFmpeg Location...**
|
|
2. Next to the executable (or in `ffmpeg/`, `ffmpeg/bin/` or `bin/` beside it)
|
|
3. Your `PATH`
|
|
|
|
The release archives (`FFmpegWrapper-<version>-windows-x64.zip` / `-linux-x64.tar.gz`) already
|
|
include ffmpeg and ffprobe next to the executable, with ffmpeg's license as `ffmpeg-LICENSE.txt`.
|
|
If you use the bare executable, install ffmpeg (`sudo apt install ffmpeg`, `winget install ffmpeg`,
|
|
...) or point the app at it.
|
|
|
|
## Running from source
|
|
|
|
```bash
|
|
python -m venv .venv
|
|
.venv/bin/pip install -r requirements-dev.txt # Windows: .venv\Scripts\pip ...
|
|
python scripts/fetch_ffmpeg.py # optional: downloads ffmpeg into ./ffmpeg/bin
|
|
python run.py
|
|
```
|
|
|
|
Tests: `python -m pytest`. The integration tests run real conversions when ffmpeg is found, either
|
|
on `PATH` or through `FFWRAP_FFMPEG_DIR=ffmpeg/bin`.
|
|
|
|
## Building the executables
|
|
|
|
PyInstaller does not cross-compile. Build the Windows `.exe` on Windows and the Linux binary on Linux.
|
|
|
|
```bash
|
|
pip install -r requirements-dev.txt
|
|
python build.py # -> dist/FFmpegWrapper or dist/FFmpegWrapper.exe
|
|
python build.py --ffmpeg-dir ffmpeg/bin # also bundles ffmpeg + ffprobe + their license into a .zip / .tar.gz
|
|
python scripts/smoke_test.py dist/FFmpegWrapper # optional: headless start-up check
|
|
```
|
|
|
|
`--onedir` builds a folder instead of a single file. It starts faster because nothing has to be
|
|
unpacked on launch.
|
|
|
|
**CI:** [.github/workflows/build.yml](.github/workflows/build.yml) builds, tests and smoke-tests both
|
|
platforms on every `v*` tag (or a manual run) and attaches the archives to the release. Gitea and
|
|
Forgejo Actions can run it too, but they need a Windows runner registered.
|
|
|
|
### Notes
|
|
|
|
- **Linux:** the binary needs glibc 2.35 or newer (Ubuntu 22.04+, Debian 12+, Fedora 36+,
|
|
Mint 21+). It bundles the Qt XCB libraries, but uses the system's OpenGL/EGL driver libraries.
|
|
To get a menu entry, copy `assets/icon.png` and write a `.desktop` file that points at the binary.
|
|
- **PySide6 is pinned below 6.12.** PySide6 6.12.0 mis-counts references to Python's `None`
|
|
(in `QComboBox.clear()`/`currentData()`), which crashes the app on Python 3.11. Python 3.12+ is
|
|
not affected, but keep the pin until a fixed PySide6 ships.
|
|
- **ffmpeg licensing:** the bundled ffmpeg builds come from
|
|
[BtbN/FFmpeg-Builds](https://github.com/BtbN/FFmpeg-Builds) and are GPLv3, so their license has to
|
|
ship with them. `scripts/fetch_ffmpeg.py` saves it as `ffmpeg/LICENSE.txt`, and
|
|
`build.py --ffmpeg-dir` copies it into the archive as `ffmpeg-LICENSE.txt`. It looks in the ffmpeg
|
|
folder and the folder above it, and stops with an error if the license isn't in either.
|
|
- Settings (last options, window size, ffmpeg location) are saved per user through Qt's QSettings:
|
|
the registry on Windows, `~/.config/ffwrap/FFmpegWrapper.conf` on Linux.
|
|
|
|
## Project layout
|
|
|
|
```
|
|
ffwrap/
|
|
formats.py container/encoder tables and compatibility rules (no Qt)
|
|
probe.py ffprobe -> MediaInfo/Stream dataclasses
|
|
command.py Options -> ffmpeg arguments, output naming, audio-track matching (no Qt)
|
|
tools.py locating ffmpeg/ffprobe, encoder detection
|
|
gui/ main window, queue model, QProcess batch runner
|
|
build.py PyInstaller build + packaging
|
|
scripts/ fetch_ffmpeg.py, smoke_test.py, make_icon.py
|
|
tests/ unit tests + real-ffmpeg integration tests
|
|
```
|
|
|
|
## License
|
|
|
|
FFmpeg Wrapper is public domain, released under [the Unlicense](LICENSE). Kopimi: copy it, change
|
|
it, share it, sell it. No permission needed.
|
|
|
|
That covers this project's own code. The ffmpeg and ffprobe binaries in the release archives are
|
|
GPLv3 (license in `ffmpeg-LICENSE.txt`, source at <https://ffmpeg.org/download.html>), and the Qt
|
|
libraries built into the executable are LGPLv3.
|