Fix DVD import: worker entitlements, flat rips, folder drops - #69
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 entitlementsEntitlements are per-Mach-O and the app bundle's set does not reach a child process, so
vapourbox-workerwas signed hardened with none of its own. That leaves library validation enabled, and the workerdlopens the downloaded, ad-hoc-signedlibdvdread.dylibfromdeps/<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 inc4a5ec8(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 --strictand 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.shnow signs the worker withdistribution.entitlementsand asserts after signing that bothvapourboxandvapourbox-workercarrydisable-library-validation, so the property is checked where its absence is visible.fix(app): open a flat DVD rip as a DVDA rip holding the VIDEO_TS contents directly (
VIDEO_TS.IFO+VTS_01_*.VOBin 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.findVideoTsParent→findDvdRoot, plus the matching change in the worker'sfind_video_ts_dir. Detection is now case-insensitive throughout (the oldFile('$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.IFOmust not divert a folder of ordinary videos into the title picker.feat(app): accept folders dropped on the queueThe 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.dartnow backs both.fix(app): match the About screen attributions to READMEMissing: VIVTC, nnedi3, akarin, zsmooth, whisper.cpp, libdvdread. Listed but no longer shipped: ffms2.
chore: bump to 0.9.13Tests
dvd_folder_detection_test.dart)isVideoFile, now the single definition behind both drop targetsNot covered: the drag-and-drop gesture itself (a
desktop_dropplatform-channel event, not simulatable headlessly), and the signing fix, which by nature only manifests in a Developer-ID-signed bundle.