Presets: import and export, and three filename bugs (#81) - #96
Conversation
Issue #81's fourth ask was for "JSON profiles as implemented in Massie VFX Open Media Encoder". The format already existed and is richer than what was asked for — a preset is JSON carrying the whole pipeline *and* the encoding settings, in plain files under ~/.vapourbox/presets. What was missing is everything around it, so this is an import/export and error-surfacing change wearing a format request's clothes. Import is two-phase, and that is the substance of the change rather than a structural nicety. A preset carries `customVapoursynth` — Python the worker executes — and `customFfmpegArgs`, both verified to survive a round trip. Importing someone else's preset is therefore closer to running their script than to loading their settings, and nothing else in the app would reveal it: those fields only render in advanced mode. So `inspectPresetFile` validates without committing, the dialog shows any custom code verbatim, and `commitImport` takes `stripCustomCode` — the middle option between trusting everything and importing nothing. Import deliberately does not apply the preset. "Add this to my presets" should not replace the user's current pipeline as a side effect, and there is no undo. Three bugs found while writing it, each with a regression test named after the behaviour. All are pre-existing and all become far easier to hit with import, because an imported preset is very likely to be named like one the user already has: - The on-disk filename came from the preset's *name*, and the sanitizer lowercases and collapses whitespace and separators — so "VHS Cleanup", "vhs cleanup", "VHS/Cleanup" and "VHS Cleanup" all produced vhs_cleanup.json. Saving one destroyed another. Filenames are now the preset id; savePreset sweeps any other file carrying the same id, which migrates legacy files as they are touched. - Renaming a preset wrote a second file and left the original, so it appeared twice after a reload. - deletePreset removed the name-derived file *before* looking by id, so deleting one preset took a similarly-named one's file with it. `isBuiltIn` is never taken from a file. The flag survives fromJson, and a built-in cannot be deleted or overwritten, so a preset claiming to be one would be stuck in the menu permanently. Load failures are collected instead of printed. A `print()` per bad file means that in a release build the preset simply never appears, which is indistinguishable from one that was never saved — tolerable while the app wrote every preset itself, wrong once files arrive from elsewhere. They now surface as a red row in the preset menu with the filename, the reason in plain words, and the full path. Also fixes `saveAsPreset` never calling notifyListeners(), so a newly-saved preset only appeared once something unrelated rebuilt the toolbar. app/test/preset_service_test.dart is the first test over this service at all, which is how the three bugs survived; `directoryOverride` exists so it can run against a temp directory rather than the real $HOME. Not done: encoder-only profiles. A preset always carries the whole pipeline, so "just my ProRes output settings" cannot be expressed without also pinning someone's deinterlacer. That needs an optional-pipeline model change and is its own piece of work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Push gate: green on all four platforms — run 34682762362
Dispatched with All 25 new The red check above is not this branch. Every job dies at "Download dependencies" with |
Presets: import and export, and three filename bugs (#81)
Closes issue #81's fourth ask — "consider JSON profiles as implemented in Massie VFX Open Media Encoder".
The format already existed and is richer than what was asked for: a preset is JSON carrying the whole pipeline and the encoding settings, in plain files under
~/.vapourbox/presets. What was missing is everything around it. So this is an import/export and error-surfacing change wearing a format request's clothes, which is what makes it small.Import is two-phase, and that is the substance
Verified by running it: a preset carries
customVapoursynth— Python the worker executes — andcustomFfmpegArgs, both surviving a round trip intact. Importing someone else's preset is therefore closer to running their script than to loading their settings, and nothing else in the app would reveal it: those fields only render in advanced mode.So
inspectPresetFilevalidates without committing, the confirmation dialog shows any custom code verbatim, andcommitImporttakesstripCustomCode— surfaced as an "Import without the code" button, the middle option between trusting everything and importing nothing.Import deliberately does not apply the preset. "Add this to my presets" should not replace the user's current pipeline as a side effect, and there is no undo.
Three bugs found while writing it
All pre-existing; all become far easier to hit with import, because an imported preset is very likely to be named like one the user already has. Each has a regression test named after the behaviour, and each was reproduced before being changed.
VHS Cleanup,vhs cleanup,VHS/CleanupandVHS Cleanupall producedvhs_cleanup.json— saving one destroyed another.deletePresetremoved the name-derived file before looking by id, so deleting one preset took a similarly-named one's file with it.Filenames are the preset id now;
savePresetsweeps any other file carrying the same id, which migrates legacy name-based files as they are touched.Also
isBuiltInis never taken from a file. The flag survivesfromJson, and a built-in cannot be deleted or overwritten — a preset claiming to be one would be stuck in the menu permanently.print()per bad file means that in a release build the preset simply never appears, indistinguishable from one that was never saved. They now surface as a red row in the preset menu with the filename, the reason in plain words, and the full path.saveAsPresetnever callednotifyListeners(), so a newly-saved preset only appeared once something unrelated rebuilt the toolbar.Tests
app/test/preset_service_test.dartis the first test over this service at all, which is how the three bugs survived. 25 tests;directoryOverrideexists so they run against a temp directory rather than the real$HOME. Push gate 877 locally (was 852).Verified by hand in a packaged build against five sample files: ordinary, carries-custom-code, malformed, valid-JSON-but-not-a-preset, and one claiming to be built-in.
Not done
Encoder-only profiles. A preset always carries the whole pipeline, so "just my ProRes output settings" cannot be expressed without also pinning someone's deinterlacer. That needs an optional-pipeline model change and is its own piece of work. Noted in CLAUDE.md.
Note on CI
The PR's own checks will fail at "Download dependencies" on all four platforms —
deps-version.jsonnamesdeps-v1.10.0, which is still a draft, and CI's read-only token cannot see draft assets. That affects every PR in the repo andmainitself, not this branch. A gate run dispatched withdeps_run_idis the real signal; see the comment below.🤖 Generated with Claude Code