Imported from rsevero/resolve_media_converter (
AGENTS.md). Install upstream withnpx skills add rsevero/resolve_media_converter. Copyright stays with the author.
CLAUDE.md
Project overview, commands, architecture, and repository-specific development rules.
Project Overview
Resolve Media Converter is a Flutter desktop application that converts media readable by FFmpeg into formats that are easier to edit in DaVinci Resolve. It supports Linux, macOS, and Windows.
The application accepts a single file or the top level of a directory, probes each candidate with ffprobe, skips files already considered Resolve-friendly, builds an ffmpeg conversion command, runs jobs sequentially, reports progress, and writes a persistent log for every converted or skipped item.
Tech stack: Flutter/Dart, ChangeNotifier controllers, FFmpeg/ffprobe subprocesses, shared_preferences, file_picker/file_selector, and window_manager.
Commands
flutter run -d linux # Run the Linux desktop app
flutter test # Run all tests
flutter test test/services_test.dart # Run one test file
flutter analyze # Run static analysis
flutter build linux --release # Build Linux release bundle
flutter build windows --release # Build Windows release bundle (on Windows)
flutter build macos --release # Build macOS release bundle (on macOS)
dart run scripts/update_flutter_version.dart --check
# Verify release workflows use one Flutter version
dart run scripts/update_releases_summary.dart
# Rewrite releases/releases_summary.json from git history
dart run scripts/prepare_tagged_release.dart
# Run every pre-tag generator (Flutter version + releases summary)
The pre-commit hook runs dart format on staged Dart files and re-stages them automatically. Do not run dart format manually unless explicitly requested or diagnosing hook behavior.
The tracked hook source is scripts/git-hooks/pre-commit. Install it in a fresh clone with:
cp scripts/git-hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Architecture Summary
The code under lib/ is split into:
app/— top-levelMaterialAppand theme.features/— presentation and application controllers grouped by feature.models/— immutable request, result, progress, enum, and settings value objects.services/— probing, command construction, process execution, paths, persistence, and logging.
There is no dependency-injection framework. Controllers accept optional service instances through constructors and default to concrete services. Preserve this pattern so tests can provide lightweight fakes.
State Management
Controllers extend ChangeNotifier and expose read-only getters over private mutable state. State-changing methods update all related fields before calling notifyListeners().
Key controllers:
ConversionSetupController— source/output choices, rotation, trim parsing, validation, and request construction.ConversionRunController— sequential probing and conversion orchestration, progress, results, and skip behavior.ToolPathsController— FFmpeg/ffprobe detection, validation, manual overrides, and persistence.
ConversionShellPage owns the controllers used by the UI. Keep conversion logic in controllers/services rather than embedding it in widgets.
Conversion Pipeline
The main flow is:
SourceResolutionServiceresolves a file or scans only the selected directory's top level.MediaProbeServicerunsffprobeand classifies streams, accepted formats, duration, bit depth, frame rate, and rotation metadata.ConversionRunControllerskips unsupported or already accepted sources.OutputPathServicecreates a unique.wavor.mxfdestination.FfmpegCommandServicebuilds the media-specific FFmpeg arguments.ConversionExecutionServiceadds progress arguments, runs FFmpeg, parses progress, and captures stdout/stderr.ConversionLogServicewrites a per-item diagnostic log.
Keep these responsibilities separate. In particular, probing discovers source properties, while command construction decides how those properties affect the output.
Update Check
On startup main() fires a non-awaited background check (UpdateCheckService) that, at most once per 24h, fetches releases/releases_summary.json from the repo's GitHub raw URL and compares the running package_info_plus version against it. If newer vX.Y.Z releases exist, showUpdateAvailableDialog presents a dialog via appNavigatorKey naming the latest tag, the number of newer releases, and their combined commit count. Every failure (offline, non-200, unparseable, throttled) resolves to null silently.
releases/releases_summary.jsonis generated data, not a bundled Flutter asset. It is regenerated byscripts/update_releases_summary.dartfrom local git tags (vX.Y.Zonly;name,datetime,commitsper entry, newest first) and served only over HTTP.ReleaseSummaryServiceholds all pure parsing/comparison logic and is unit-tested with literal JSON.UpdateCheckServiceadds throttling (viaAppSettingsService) and the HTTP fetch; inject anhttp.Clientfactory and a clock in tests.- The 24h throttle timestamp and last-seen newer-release count persist through
AppSettingsService(shared_preferences).
Output Invariants
- Audio output: 48 kHz, 24-bit PCM WAV (
pcm_s24le). - Video output: MXF containing DNxHR HQ for 8-bit sources or DNxHR HQX for sources above 8-bit.
- Video pixel formats:
yuv422pfor HQ andyuv422p10lefor HQX. - Video audio: 48 kHz, 24-bit PCM (
pcm_s24le). - Variable or nonstandard video rates are converted to constant frame rate. Select the smallest supported MXF rate equal to or greater than the source rate.
- FFmpeg autorotation stays disabled. Source rotation metadata and the user's requested rotation are combined exactly once by the app.
- Start seeking uses one input-side
-ss. When an end is supplied, use-twith the computed duration; do not add a second-ssor replace this with output-relative-towithout accounting for the reset timeline. - Output path generation avoids existing files by appending a numeric suffix. FFmpeg is invoked with
-y, so this is not an atomic overwrite guarantee if a file appears at the selected destination after path generation.
FFmpeg option placement matters: input options belong before -i, while encoding and output options belong after it. Any command change should be asserted in test/services_test.dart.
Accepted Input Formats
Already Resolve-friendly sources are skipped rather than reconverted. Detection currently recognizes:
- Constant-frame-rate H.264 MP4.
- Apple ProRes in MOV.
- Avid DNxHR in MOV or MXF.
- BRAW and CinemaDNG.
- Audio-only 48 kHz / 24-bit WAV/BWF.
When changing accepted-format detection, add probe JSON coverage for both accepted and rejected boundary cases.
UI Rules
- This is a desktop-only application; supported targets are Linux, macOS, and Windows.
- Material widgets come from
package:material_ui/material_ui.dart, notpackage:flutter/material.dart. - Keep filesystem and process operations out of widget build methods.
- Preserve Linux's
file_selectorpath and its manual-entry fallback when changing picker behavior. - User-visible conversion failures should remain available through the result row and persistent log.
Testing
Tests are organized by scope:
test/services_test.dart— probe classification, output paths, FFmpeg arguments, logs, and run orchestration.test/edge_cases_test.dart— trim parsing, setup state, settings precedence, and output collisions.test/widget_test.dart— application shell and workflow behavior.
Prefer testing services with deterministic inputs. Use parsed ffprobe JSON fixtures for media metadata cases and injected fakes for controller orchestration. Do not require real media files or installed FFmpeg in the automated test suite.
For every code change:
- Run the most relevant focused test while iterating.
- Run
flutter testbefore completion. - Run
flutter analyze. - Run
git diff --checkand summarize the changed files.
Documentation-only changes do not require the Flutter test suite unless they alter executable examples or release instructions.
Coding Conventions
- Follow
analysis_options.yamland the existing Dart style. - Prefer
constconstructors andfinalvalues where possible. - Keep models immutable.
- Keep platform branching explicit with
Platform.isLinux,Platform.isMacOS, andPlatform.isWindows. - Use
package:pathfor path construction; do not concatenate platform paths manually. - Catch
ProcessExceptionat subprocess boundaries and return domain results with actionable error messages. - Preserve complete FFmpeg stdout/stderr in conversion logs even when the UI shows only a concise error.
- Do not edit generated plugin registrants or generated CMake plugin files manually.
- Update
CHANGELOG.mdunder the current unreleased version for user-visible fixes and behavior changes. - Keep
scripts/git-hooks/pre-commitsynchronized with the active local.git/hooks/pre-commithook.
Release Targets
Release automation is split across three pipelines:
- Linux tarball and AppImage:
.github/workflows/linux-release.yml. - Windows Inno Setup installer:
.github/workflows/windows-release.yml. - macOS DMG:
codemagic.yaml.
Tags matching vX.Y.Z trigger release builds. See packaging/README.md for the complete release checklist and artifact names.
Before creating a vX.Y.Z tag, run every pre-tag generator at once:
dart run scripts/prepare_tagged_release.dart
This runs scripts/update_flutter_version.dart (forwarding any arguments) and then scripts/update_releases_summary.dart. Pass --check for a dry run. Never update pinned Flutter versions in the three pipeline files independently, and never hand-edit releases/releases_summary.json.
Prompt Abbreviations
- cc: Update CHANGELOG.md + prepare commit. Always asks for confirmation of commit message before actually commiting.
- Commit messages should include Assisted_By/Signed-off-by (first Assisted_By: and then finish with Signed-off-by:) when appliable.