Skip to content

feat(prebuilt): ffmpeg and OCR stacks for dk.nikse.subtitleedit - #7

Merged
jing2uo merged 6 commits into
mainfrom
add/ffmpeg-and-ocr-stacks
Aug 20, 2026
Merged

feat(prebuilt): ffmpeg and OCR stacks for dk.nikse.subtitleedit#7
jing2uo merged 6 commits into
mainfrom
add/ffmpeg-and-ocr-stacks

Conversation

@jing2uo

@jing2uo jing2uo commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Eleven releases so dk.nikse.subtitleedit can stop building its dependencies into FlatPark's OSTree repository (flatpark/flatpark#238). That app's ref goes from 12.2 MB to 1.6 MB.

Cut individually rather than as one bundle, so an app can pin just the encoder it needs and a version bump touches one stack:

stack packed contents
ffmpeg-full 11.0 MB ffmpeg n9.0 + ffprobe + libav*
tesseract 1.6 MB engine + CLI + share/tessdata presets
leptonica 1.1 MB libleptonica.so.6
x265 1.0 MB libx265.so.216, 8-bit
x264 0.5 MB libx264.so.165
sevenzip 0.5 MB 7zr, one dependency-free binary
rubberband 0.2 MB librubberband.so.3
lame 0.1 MB libmp3lame.so.0
libass 0.1 MB libass.so.9 (0.17.4)
uchardet 0.1 MB libuchardet.so.0

All dev-complete so ffmpeg-full and tesseract can build against them, and both clean their inputs out of their own archive — the consuming app pins those releases too and puts each lib/ on LD_LIBRARY_PATH, the split wemeet-screenshare-hook already uses for opencv-imgproc.

libass is deliberately its own release. mpv-stack builds its own copy, so an app shipping both ends up with two libass.so.9 contending for one soname and the winner decided by module order — the problem flatpark/flatpark#238 had to work around with post-install: rm -f libass.so.9.3.1. One release consumed from both ends settles it by construction. Follow-up: mpv-stack should consume this rather than build its own.

Decided by building, not by assumption

  • rubberband picks its built-in FFT and resampler even though fftw3, libsamplerate and speexdsp are all in the SDK. Pinned explicitly with -Dfft=builtin -Dresampler=builtin so a change in upstream's defaults cannot silently add a DT_NEEDED. The result links nothing beyond libstdc++/libm/libgcc/libc.

  • leptonica's dependency closure resolves completely inside org.gnome.Platform//50 — libtiff, libgif, libopenjp2, libwebp, libzstd all present.

  • tesseract verified against leptonica-v1: the archive carries no libleptonica, and with only that release's lib/ added, bin/tesseract has zero unresolved sonames.

  • QSV and AMF are not enabled in ffmpeg-full, and libvpl is deleted. Both are two-layer: ffmpeg links a dispatcher or dlopens a vendor library, and the layer that encodes ships with Intel's media driver or AMD's proprietary stack, neither of which any Flatpak extension provides. Measured in a sandbox on an Intel Arc 140V with --device=dri:

    h264_vaapi   encode succeeded, exit 0
    h264_qsv     Error creating a MFX session: -9
    h264_amf     DLL libamfrt64.so.1 failed to open
    

    VAAPI encoded through the same /dev/dri/renderD128, so the GPU was never the issue. libvpl was cut for QSV and then deleted, manifest and release both: no app in this ecosystem can consume it usefully, and keeping it with a warning would be a trap with a sign on it.

  • --enable-libharfbuzz and --enable-libfontconfig: freetype alone stopped being enough for drawtext in FFmpeg 7+, and without fontconfig it only takes fontfile=/abs/path. Verified: drawtext with font=sans renders and tesseract reads the text back exactly.

  • ffplay is disabled, and the cp of each input archive skips its manifest.json — the first cut shipped ffplay (putting an unwanted command on the consuming app's PATH) and libvpl's build record renamed to a misleading manifest-base-1.json.

Also here

wemeet-screenshare-hook now pins opencv-imgproc-v2 for its build — v1 is the unstripped 140 MB tree. The released hook artifact is not re-cut: re-pinning it would change com.tencent.wemeet's commit and make every user re-download a 197 MB payload for no functional gain. opencv-imgproc-v1 stays until the hook is next cut for a reason of its own.

README: the ten new stacks in the table, the extra-data list brought up to date, and the strip rule recorded.

🤖 Generated with Claude Code

jing2uo and others added 6 commits August 20, 2026 22:41
Groundwork for moving dk.nikse.subtitleedit's build dependencies out of
FlatPark's OSTree repository (flatpark/flatpark#238). Each is its own release
rather than one bundle, so an app can pin exactly what it needs and a version
bump touches one stack instead of a monolith:

  x264       libx264.so.165        H.264 encoder
  x265       libx265.so.216        H.265 encoder, 8-bit only
  lame       libmp3lame.so.0       MP3 encoder
  rubberband librubberband.so.3    time-stretch / pitch-shift
  libass     libass.so.9  (0.17.4) ASS/SSA subtitle rendering
  libvpl     libvpl.so.2           Intel QSV dispatcher
  uchardet   libuchardet.so.0      charset detection
  leptonica  libleptonica.so.6     image processing, Tesseract's base
  sevenzip   bin/7zr               .7z extractor, one dependency-free binary

All dev-complete (library + headers + pkg-config, and CMake config where
upstream installs one) so ffmpeg-full.yml and tesseract.yml can build against
them, which is what they are for in the first place.

libass is deliberately its own release. mpv-stack builds its own copy today, so
an app shipping both mpv-stack and an ffmpeg built against a different libass
gets two libass.so.9 files contending for one soname in /app/lib, decided by
module order. One release consumed from both ends settles that by construction
rather than per app.

Two things settled by building rather than by assumption:

  * rubberband's meson picks its BUILT-IN FFT and resampler even though fftw3,
    libsamplerate and speexdsp are all in the GNOME SDK. Pinned explicitly with
    -Dfft=builtin -Dresampler=builtin so a change in upstream's defaults cannot
    silently add a DT_NEEDED. The result links nothing beyond libstdc++/libm/
    libgcc/libc. Its LADSPA plugin is dropped: unpacked under an app's
    /app/extra no LADSPA host can ever find it.
  * leptonica's dependency closure resolves completely inside
    org.gnome.Platform//50 — libtiff, libgif, libopenjp2, libwebp, libzstd and
    the rest are all there, nothing has to travel with the archive.

Every stack drops *.a and *.la; a .la records absolute build paths and
misleads a downstream libtool link.

Each built locally against org.gnome.Sdk//50 and its contents checked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two stacks that build against the nine leaf releases from the previous
commit, completing the set dk.nikse.subtitleedit needs
(flatpark/flatpark#238).

ffmpeg-full: ffmpeg n9.0 + ffprobe, shared. The runtimes do ship an ffmpeg, but
without libass — so the `ass` and `subtitles` filters do not exist and burning
subtitles into a video fails at filter-graph parse time. The encoder set covers
what a media application's export dialog offers end to end: libx264, libx265,
libvpx-vp9 and prores_ks in software, NVENC / AMF / QSV / VAAPI where the
hardware provides them, aac / ac3 / libmp3lame / libopus / libvorbis for audio.

tesseract: the OCR engine and its command-line driver.

Both consume their dependencies from the leaf releases at build time and then
clean every trace of them out of the archive — x264, x265, lame, rubberband,
libass and libvpl for ffmpeg-full, leptonica for tesseract. The consuming app
pins those releases itself and puts each lib/ on LD_LIBRARY_PATH, the same
split wemeet-screenshare-hook already uses for opencv-imgproc. That is what
makes the granularity worth having: an app keeps ONE libass even when it also
ships mpv, and x265 can move to a new version without re-cutting ffmpeg for
apps that only ever wanted the encoder.

tesseract ships no language data — a .traineddata file is ~20 MB, versioned
independently of the engine, and every app wants a different set. It does ship
share/tessdata/{configs,tessconfigs}, the output-format presets read from
TESSDATA_PREFIX, so a consuming app stages its .traineddata into that same
directory and points TESSDATA_PREFIX at it.

tesseract verified against leptonica-v1: the archive carries no libleptonica,
and inside org.gnome.Platform//50 with only the leptonica release's lib/ added
to LD_LIBRARY_PATH, bin/tesseract resolves with zero unresolved sonames.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things the first cut of ffmpeg-full-v1 and tesseract-v1 got wrong, both
found by unpacking the released artifacts.

ffplay was being built and shipped. It is an SDL2 debug player nothing here
drives, and shipping it puts an `ffplay` command on the consuming app's PATH —
a behaviour surface this stack has no business adding, since an application
that probes for ffplay would find one. --disable-ffplay.

Each `cp -a ./. /app/` also copied the source archive's own manifest.json.
flatpak-builder then renamed it to manifest-base-1.json and wrote its own, so
ffmpeg-full shipped libvpl's build record under a name suggesting it described
ffmpeg. It cannot be cleaned away — flatpak-builder writes those files after
cleanup runs — so the copy skips manifest.json instead.

Both tags are re-cut rather than superseded by a -v2: nothing consumes them yet,
and a v2 differing only in this would be noise in the release list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
QSV and AMF cannot work in a Flatpak, and enabling them is worse than leaving
them out.

Both are two-layer. ffmpeg links a dispatcher (libvpl for QSV) or dlopens a
vendor library (libamfrt64.so.1 for AMF); the layer that actually encodes ships
with the vendor's userspace — libmfx-gen.so from intel-media-driver, or AMD's
proprietary driver. Flatpak has no extension for either, so neither can ever be
present in the sandbox. Measured inside one on an Intel Arc 140V, the hardware
QSV exists for, with --device=dri:

  h264_vaapi  encode succeeded, exit 0
  h264_qsv    Error creating a MFX session: -9    (dispatcher found no runtime)
  h264_amf    DLL libamfrt64.so.1 failed to open

The GPU and the render node were never the problem: VAAPI encoded through the
same /dev/dri/renderD128, because org.freedesktop.Platform.VAAPI.Intel and the
GL.* extensions do put a driver in the sandbox. NVENC stays for that same
reason — org.freedesktop.Platform.GL.nvidia-* ships NVIDIA's userspace (463 such
extensions on Flathub), so on an NVIDIA host the library is there. Not verified
here; no NVIDIA GPU.

Leaving them enabled would actively recreate a bug upstream just fixed.
Subtitle Edit 5.2 probes `ffmpeg -encoders` and hides what is missing
(SubtitleEdit/subtitleedit#13915), but that probe sees "the encoder is compiled
in" and cannot see "the vendor runtime is absent" — so h264_qsv and h264_amf
would stay in the user's dropdown and fail at encode time, which is exactly what
the probe was added to prevent. Upstream reached the same conclusion in that
PR's follow-up note.

Also --enable-libharfbuzz: freetype alone stopped being enough for the drawtext
filter in FFmpeg 7+, so drawtext was silently absent from the first cut. Both
the SDK (harfbuzz 11.4.5) and the Platform (libharfbuzz.so.0) have it.

libvpl.yml keeps its release but leads with the caveat, so the next person to
reach for it learns why ffmpeg-full does not.

ffmpeg-full-v1 is re-cut rather than superseded: nothing consumes it yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…to opencv v2

libvpl is deleted, manifest and release both. It was cut for ffmpeg-full's QSV
support, which the previous commit removed for good: the dispatcher alone cannot
encode, the oneVPL runtime that can ships with Intel's media driver, and no
Flatpak extension provides it — so no app in this ecosystem can ever consume
this stack usefully. Keeping it with a warning in the header would just be a
trap with a sign on it.

wemeet-screenshare-hook now pins opencv-imgproc-v2 for its build. v1 is the
unstripped 140 MB tree; v2 is the same sources stripped and without the
unusable objdetect cascade data. The released hook-v1 artifact is unaffected and
is not re-cut: re-pinning it would change com.tencent.wemeet's commit and make
every user re-download the 197 MB payload for no functional gain. opencv-v1 is
kept until the hook is next cut for a reason of its own, which is the point at
which it can be deleted.

README: the ten new stacks in the table, and the extra-data list brought up to
date.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
freetype and harfbuzz get the drawtext filter compiled in, but without
fontconfig it only accepts fontfile=/abs/path — font=Sans fails with "No font
filename provided". Verified in a sandbox: drawtext rendered correctly with an
explicit fontfile and the text came back through tesseract exactly, so the
filter itself was fine; only the lookup was missing.

fontconfig is in org.gnome.Sdk//50 and the Platform, and libass in this same
stack already resolves fonts through it, so nothing new travels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jing2uo
jing2uo merged commit 3066fa0 into main Aug 20, 2026
1 check passed
@jing2uo
jing2uo deleted the add/ffmpeg-and-ocr-stacks branch August 20, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant