CI took about 5 min 10 s per PR on vm-201: 1.5 min of `pacman -Syu` and 3.5 min of build, half of it qmlcachegen compiling QML the tests never load. Now it takes **1 min 49 s with a cold ccache and 26 s with a warm one**. - CI runs in a prebuilt Arch image with the build deps (`git.trivk.dev/tri/qtpotato-ci:latest`). `ci-image.yml` rebuilds it weekly, when its Dockerfile changes, or on demand. It builds on the runner's own Docker daemon, so vm-201 has the new image right away, since the runner does not re-pull tags it already has. - CI configures with `QT_QML_NO_CACHEGEN=ON` and no debug info. Release and AUR builds keep both. - ccache, kept between runs with `actions/cache`. - Docs-only changes (`README.md`, `LICENSES/`) skip CI. Pushing the image to the registry needed two workarounds. Gitea's Actions token cannot push container images, so it uses the `PACKAGE_TOKEN` secret from #21. Cloudflare rejects request bodies over 100 MB and `docker push` sends each layer in one request, so `regctl` (pinned by sha256) pushes the layers in 50 MB chunks. Giving vm-201 more vCPUs is left out; it is an infra change. Refs #20 Proof: on this PR, CI run 328 (cold ccache) took 1 min 49 s and run 332 (warm, 41/44 hits) took 26 s, both 8/8 tests. Image run 333 pushed the image, and the registry digest matches the runner's local image. A local build with the CI flags takes 74 steps instead of 154. Reviewed with sol-loop: 3 rounds with gpt-6.1-sol. Sol verdict: approved Created with Claude Opus 5.5 in Claude Code (T3 Code). Reviewed-on: #22 Co-authored-by: Tri Vo Khac <git@trivk.dev>
QtPotato
A KDE video player built on mpv that translates subtitles with an LLM.
QtPotato is a Qt 6 / Kirigami video player for Linux. Playback is handled by libmpv, and the interface is modelled after Haruna. It can also translate a whole subtitle track with any OpenAI-compatible API and show the result in the player.
Features
- Subtitle translation: sends a text subtitle track (embedded or external) to an
OpenAI-compatible chat completions API in batches, with nearby lines as context. The result is
cached as an
.srtfile, so the same video does not have to be translated twice. - mpv playback: hardware decoding and everything else libmpv supports. Your
mpv.confis loaded at startup (can be turned off in the settings). - Subtitles: track selection, a secondary subtitle, delay, font size and outline settings.
- Playlist: other videos in the same folder are added automatically, with repeat and stop-after modes.
- Recent files, MPRIS (media keys and Plasma's media controls), and full screen with auto-hiding controls.
- Follows your Plasma style, colours and icons.
Installing on Arch Linux
From the pacman repository
Every release is built and published to this Gitea's package registry. Import the signing key once:
curl -O https://git.trivk.dev/api/packages/tri/arch/repository.key
sudo pacman-key --add repository.key
sudo pacman-key --lsign-key 9C48725295A5F54A6A491CF86B273DD49890E8A7
Then add the repository to /etc/pacman.conf and install:
[qtpotato]
SigLevel = Required
Server = https://git.trivk.dev/api/packages/tri/arch/$repo/$arch
sudo pacman -Syu qtpotato
The package is built against Arch's current Qt and KDE Frameworks. If it breaks after a system update before a new release is out, build it from source as below.
From the PKGBUILD
git clone https://git.trivk.dev/tri/QtPotato.git
cd QtPotato/packaging/aur/qtpotato # or qtpotato-git for the latest main
makepkg -si
Building
Dependencies
- CMake ≥ 3.25 and a C++20 compiler
- Qt ≥ 6.8 (Core, Gui, Qml, Quick, QuickControls2, Widgets, Network, Concurrent, DBus)
- KDE Frameworks ≥ 6.10: Extra CMake Modules, Kirigami, KI18n, KCoreAddons, KCrash, KConfig
- MpvQt ≥ 1.0
- QCoro ≥ 0.10 (Qt 6)
- QtKeychain ≥ 0.14 (Qt 6)
- FFmpeg (libavformat, libavcodec, libavutil)
On Arch Linux:
sudo pacman -S cmake ninja extra-cmake-modules qt6-base qt6-declarative \
kirigami ki18n kcoreaddons kcrash kconfig mpvqt qcoro qtkeychain-qt6 ffmpeg
Build and run
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/bin/qtpotato [file]
To install system-wide:
sudo cmake --install build
Tests
cmake -B build -DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build
AppImage
packaging/appimage/build-appimage.sh [build-dir]
The script downloads linuxdeploy and bundles the Qt, KF6 and mpv libraries from the host. The AppImage only runs on distros whose glibc is at least as new as the build machine's.
Translating subtitles
- Open Settings → Translation (
Ctrl+Shift+,) and fill in:- API base URL: the OpenAI-compatible endpoint including the version path, for example
https://api.openai.com/v1orhttp://localhost:11434/v1for Ollama. - API key: stored in your system wallet (KWallet / Secret Service), not in the config file.
- Model: type a name or click Load Models to fetch the list from the API.
- Translate into: the target language, written the way you would tell the model (default: Vietnamese).
- API base URL: the OpenAI-compatible endpoint including the version path, for example
- Play a video and select the subtitle track you want to translate.
- Press
Ctrl+T(Subtitles → Translate Subtitle). Progress is shown in the player; when it finishes, the translated track replaces the original.
Translations are cached in ~/.cache/qtpotato/translations/ as <video>.<key>.<lang>.srt.
Only text subtitles can be translated; image-based ones (PGS, VobSub) are not supported.
Use Subtitles → Edit Translation Glossary… to back up or transfer terms:
- Export… saves the full current list and episode groups to a JSON file, including edits you have made in the dialog. The search filter does not limit the export.
- Import… merges a JSON glossary into the dialog. Existing Original terms take precedence (duplicates are compared without case differences), and episode groups are preserved. Review the result and click Save to apply it, or Cancel to discard it.
Importing and exporting glossary files do not call the translation API.
Terms extracted while translating an episode are stored under that episode. Later episodes reuse all existing terms and keep their original ownership; only newly extracted terms are added to the current episode. Old or imported terms without provenance are assigned to the first episode in which the glossary is opened, because their original episode cannot be reconstructed. Your edited translations and existing episode assignments are preserved. Groups sort by episode number, and changing group assignments does not invalidate the translation cache.
Keyboard shortcuts
The defaults follow Haruna.
Playback
| Action | Shortcut |
|---|---|
| Play / pause | Space |
| Seek backward / forward 5 s | Left / Right |
| Seek backward / forward 15 s | Shift+Left / Shift+Right |
| Seek backward / forward 30 s | Down / Up |
| Previous / next chapter | PgDown / PgUp |
| Previous / next frame | , / . |
| Slower / faster | [ / ] |
| Reset speed | Backspace |
| Previous / next file | Shift+, / Shift+. |
| Volume down / up | 0 / 9 |
| Mute | M |
Subtitles
| Action | Shortcut |
|---|---|
| Translate subtitle | Ctrl+T |
| Toggle subtitles | Ctrl+S |
| Previous / next subtitle line | Ctrl+Left / Ctrl+Right |
| Next / previous subtitle track | J / Shift+J |
| Show earlier / later | Z / Shift+Z |
| Larger / smaller font | Ctrl+Z / Ctrl+X |
| Add external subtitle | Alt+S |
Window
| Action | Shortcut |
|---|---|
| Open file | Ctrl+O |
| Open URL | Ctrl+Shift+O |
| Full screen | F |
| Playlist | P |
| Screenshot | S |
| Settings | Ctrl+Shift+, |
Seek step sizes and the volume step can be changed under Settings → Playback.
Configuration
- Settings:
~/.config/qtpotatorc - mpv config:
/etc/mpv/mpv.confand~/.config/mpv/mpv.conf - Translation cache:
~/.cache/qtpotato/translations/
License
QtPotato is licensed under the GNU General Public License v3.0 or later.