Skip to content

Fix DVD import: worker entitlements, flat rips, folder drops - #69

Merged
StuartCameronCode merged 5 commits into
mainfrom
fix/macos-worker-library-validation
Aug 13, 2026
Merged

Fix DVD import: worker entitlements, flat rips, folder drops#69
StuartCameronCode merged 5 commits into
mainfrom
fix/macos-worker-library-validation

Conversation

@StuartCameronCode

Copy link
Copy Markdown
Owner

Five changes, all reached from the same starting point: DVD import was broken in the shipped app.

fix(macos): sign the worker with the app's entitlements

Entitlements are per-Mach-O and the app bundle's set does not reach a child process, so vapourbox-worker was signed hardened with none of its own. That leaves library validation enabled, and the worker dlopens the downloaded, ad-hoc-signed libdvdread.dylib from deps/<arch>/lib/ — which fails with "mapping process and mapped file (non-platform) have different Team IDs".

DVD extraction has therefore never worked in a signed release build: Developer ID signing landed in 8181288 (Feb), DVD import in c4a5ec8 (Mar).

Nothing caught it. Debug builds are ad-hoc signed, so they have no Team ID to mismatch and extraction works locally; codesign --verify --deep --strict and notarization pass either way; and title enumeration parses the IFOs in pure Rust, so a disc lists its titles correctly right up to the point of extracting one.

package-macos.sh now signs the worker with distribution.entitlements and asserts after signing that both vapourbox and vapourbox-worker carry disable-library-validation, so the property is checked where its absence is visible.

fix(app): open a flat DVD rip as a DVD

A rip holding the VIDEO_TS contents directly (VIDEO_TS.IFO + VTS_01_*.VOB in a folder named after the disc) fell through to the generic folder scan, queueing every VOB fragment as a separate video. Measured across the five folder shapes a user can drop, that was the only one wrong.

findVideoTsParentfindDvdRoot, plus the matching change in the worker's find_video_ts_dir. Detection is now case-insensitive throughout (the old File('$path/VIDEO_TS.IFO').exists() pair only worked because macOS and Windows fold case).

The flat case requires an IFO and a VOB, where the other shapes need only an IFO — a stray VIDEO_TS.IFO must not divert a folder of ordinary videos into the title picker.

feat(app): accept folders dropped on the queue

The two drop targets had separate handlers, and the queue panel's filtered directories out — so dropping a folder there did nothing, silently. One shared implementation in views/dropped_paths.dart now backs both.

fix(app): match the About screen attributions to README

Missing: VIVTC, nnedi3, akarin, zsmooth, whisper.cpp, libdvdread. Listed but no longer shipped: ffms2.

chore: bump to 0.9.13

Tests

  • 15 Dart cases over every folder shape (dvd_folder_detection_test.dart)
  • 4 over isVideoFile, now the single definition behind both drop targets
  • Rust: the directory lookup plus a full title enumeration from a flat rip

Not covered: the drag-and-drop gesture itself (a desktop_drop platform-channel event, not simulatable headlessly), and the signing fix, which by nature only manifests in a Developer-ID-signed bundle.

Entitlements are per-Mach-O and the app bundle's set does not reach a
child process, so vapourbox-worker was signed hardened with none of its
own. That leaves library validation enabled, and the worker dlopens the
downloaded, ad-hoc-signed libdvdread.dylib from deps/<arch>/lib/
(dvd_reader.rs) — which fails with "mapping process and mapped file
(non-platform) have different Team IDs". DVD extraction has therefore
never worked in a signed release build: Developer ID signing landed in
8181288 (Feb) and DVD import in c4a5ec8 (Mar).

Nothing caught it. Debug builds are ad-hoc signed, so they have no Team
ID to mismatch and extraction works locally; codesign --verify --deep
--strict passes and notarization passes either way; and title
enumeration parses the IFO files in pure Rust, so a disc lists its
titles correctly right up to the point of extracting one.

Sign the worker with distribution.entitlements, and assert after signing
that both vapourbox and vapourbox-worker carry
disable-library-validation so the property is checked where its absence
is visible rather than relying on someone testing DVD extraction from a
notarized DMG.
The About dialog had drifted from README's Acknowledgments in both
directions. Missing entirely: VIVTC, nnedi3, akarin, zsmooth,
whisper.cpp and libdvdread — all bundled, all GPL/LGPL/MIT code we are
obliged to attribute. Listed but no longer shipped: ffms2, which is
absent from deps-expected-plugins.json on every platform (FFMS2 gave way
to BestSource, and BestSource to the pipe source, which was removed
2026-08-07).

Licence and copyright for each addition come from licenses/NOTICES.txt,
except two that were checked upstream: VIVTC is LGPL 2.1 (from its own
LICENSE file, not GPL as the sibling plugins are), and libdvdread is
GPL 2.0-or-later (COPYING and the source headers in the 6.1.3 tarball
the deps scripts build from).

The list stays a superset of README's, which defers the complete
inventory to NOTICES.txt — EEDI3, DFTTest, neo-f3kdb, CAS, Flutter and
Python are bundled and keep their attribution. QTGMC and Hybrid get a
plain-text credit rather than tiles: one is an algorithm shipped inside
havsfunc, the other is inspiration, so neither has a licence of its own
to badge.

Also repoints fmtconv at gitlab.com, since the GitHub repo is the
abandoned mirror the deps scripts no longer use.
A rip that keeps the wrapping VIDEO_TS directory was already detected, but
one holding the VIDEO_TS *contents* directly — VIDEO_TS.IFO and
VTS_01_*.VOB in a folder named after the disc, which is what several
rippers produce and what you get by copying the files out by hand — fell
through to the generic folder scan. Every VOB fragment was then queued as
a separate video, with no titles, no chapters and no audio track choice.

Measured across the five shapes a user can drop, only that one was wrong:
plain folders, folder-containing-VIDEO_TS, and the VIDEO_TS directory
itself all routed correctly before and after.

findVideoTsParent becomes findDvdRoot and gains the flat case. It now also
matches VIDEO_TS, the IFOs and the VOBs case-insensitively by listing the
directory; the old File('$path/VIDEO_TS.IFO').exists() pair only worked
because macOS and Windows fold case, so a mixed-case rip on Linux was
already missed.

The flat case demands an IFO *and* at least one VOB where the other two
need only an IFO. A folder with a VIDEO_TS subdirectory is unambiguous,
but a flat folder is ordinary until proven otherwise, and one stray
VIDEO_TS.IFO must not divert a folder of holiday videos into the title
picker. An IFO with no VOB is not extractable anyway.

The worker needs the same shape or the app would route a flat rip to a
process that rejects it: find_video_ts_dir now treats a folder holding a
DVD IFO as the VIDEO_TS directory itself.

Tests: 15 Dart cases over every folder shape, and on the Rust side both
the directory lookup and a full title enumeration from a flat rip, which
proves the reading path works end to end for the new shape rather than
just the lookup.
The app has two drop targets — the drop zone for the empty state, the
queue panel once something is queued — and each had its own handler. The
queue panel filtered dropped paths through a private _isVideoFile copy,
which a directory fails, so dropping a folder there did nothing at all and
said nothing about why. Only the empty state ever handled folders.

Rather than copy the folder and DVD-picker logic into the second widget,
move the one implementation into views/dropped_paths.dart and have both
call it: handleDroppedPaths, showDvdPicker, isVideoFile, showDropError.
A ripped DVD dropped on the queue now opens the title picker, a folder of
videos is scanned and queued, and failures surface identically either way.
drop_zone's Open Folder and Open DVD buttons go through the same picker,
so the disc-drive path cannot drift from the drop path either.

Net effect is 140 lines out of drop_zone.dart and the duplicate extension
list out of queue_panel.dart.

The "Please drop video files or folders" message keeps the drop zone's
behaviour of staying quiet when a directory was involved, since folders
report their own outcome — queued, title picker, or "No video files found
in folder" in the log — and would otherwise produce two contradictory
messages for one drop.

The drag-and-drop gesture itself arrives over a desktop_drop platform
channel and cannot be simulated headlessly, so the tests cover
isVideoFile, now the single definition behind both targets, and the
routing is exercised through addFolder's own tests.
@StuartCameronCode
StuartCameronCode merged commit 7cb2519 into main Aug 13, 2026
4 checks passed
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