vfo
April 26, 2026 · View on GitHub
vfo
vfo is an autonomous media optimization engine for video libraries.
Instead of applying one preset to every file, vfo reads a configuration file, inspects each input video, and chooses an FFmpeg command based on matching profile rules (configured with PROFILE= entries). The project is aimed at media-library and streaming workflows where source files vary widely in codec, color space, bit depth, and resolution.
At a system level, vfo implements a repeatable loop:
- detect media properties
- decide the profile + scenario path
- act by executing deterministic profile actions
- verify outcomes through status observability and optional quality scoring
Why use vfo?
- Turn heterogeneous libraries into consistent, target-shaped outputs.
- Model output intent as rules (profiles + scenarios), not one-off encode scripts.
- Keep playback-driven output families separated with explicit profile targets.
- Preserve folder structure while generating variants across one or many storage locations.
- Keep full FFmpeg flexibility through explicit profile action commands.
- Validate readiness and outcomes with
doctor,status,status-json,visualize, and optional PSNR/SSIM/VMAF gates.
Outcome framing
Today, vfo is strongest when you need:
- a deterministic, auditable decision path from mezzanine to delivery profile
- repeatable profile behavior across large libraries
- a balance between quality, compatibility, and throughput
- observable pipeline state for local runs and CI lanes
vfo does not yet claim:
- real-device playback certification for every target
- universal automatic tuning without operator profile choices
- complete quality guarantees unless thresholds/gates are explicitly configured
Current project status
- Actively implemented today: macOS support is the clearest documented path in the repository.
- Planned / incomplete: Linux and Windows support are referenced in project docs, but are still marked as work in progress.
- Configuration-first workflow:
vfodoes not do much without avfo_config.conffile.
The repository also includes an in-progress sample config at services/vfo/src/vfo_config.conf, which is the best reference for how profiles and scenarios are currently expressed.
AI workflow memory
This repository keeps AI routing context in markdown files that live with the code:
AGENTS.mdobjectives.mdarchitecture.mdsubsystems.mdtesting.mdescalation.md
These files define the cheap-first routing policy, subsystem map, verification bar, and Codex escalation rules for issue-driven automation.
For day-to-day Codex work, the repository also keeps a small packet format in task-packets.md and issue templates under .github/ISSUE_TEMPLATE/ so requests stay scoped and context-light.
Terminology
Mezzanine= high-quality working library inputSource= normalized intermediate layerProfile= delivery target definition
Recommended pipeline modes:
- Default:
mezzanine -> source -> profile - Optional:
mezzanine -> profile(skip source if your library is already normalized)
How vfo works
At a high level, vfo works like this:
- Read
vfo_config.conf. - Discover your mezzanine/source locations and profile definitions.
- For each candidate input file, inspect its media properties.
- Match the file against profile criteria and scenario conditions.
- Run the FFmpeg command attached to the first matching scenario.
- Write output into the profile destination while keeping the source folder layout.
- Optionally score outputs against a reference layer (PSNR/SSIM and optional VMAF) and apply strict gates.
- Surface run/stage outcomes through status observability for operator and CI feedback.
Optional hygiene stage (configurable):
- Run
vfo mezzanine-cleanto audit or normalize mezzanine folder/file naming before encode stages.
Optional quality scoring stage (configurable):
- Run post-profile
PSNR/SSIMchecks (and optionalVMAF) against the selected reference layer (source,mezzanine, orauto). - Surface per-profile scoring status in run observability output.
Core concepts
Mezzanine and source folders
The sample configuration distinguishes between a MEZZANINE_LOCATION and a SOURCE_LOCATION. The code also supports a KEEP_SOURCE switch, which changes whether profile generation runs from the mezzanine set or from the source set.
Profiles
A profile represents a target output collection, such as a compatibility tier for a specific playback device or streaming profile.
Each profile can define criteria such as:
- codec
- bit depth
- color space
- minimum resolution
- maximum resolution
- destination folder
Scenarios
Each profile can contain one or more scenarios. A scenario is effectively a condition-expression plus an FFmpeg command.
Examples of scenario conditions visible in the sample config include:
CODEC_JUST_RIGHTRES_TOO_HIGHFULL_HD_OR_HIGHERNO_VALID_COLOR_SPACE_DETECTEDELSE
When a scenario matches, vfo runs the FFmpeg command tied to that scenario.
Installation
Option 1: install from a release
The repository's release notes describe two macOS-oriented installation paths:
- Install the
.pkgrelease asset. - Download the static build zip and copy the bundled
bin/commands plus config file.
Release notes: RELEASE.txt
Release channels
vfo uses three delivery lanes:
- Main snapshot artifacts (on
mainpushes) for internal/beta testing. - Beta prereleases from tags like
v1.2.3-beta.1. - Stable releases from tags like
v1.2.3. - Successful
mainpush validations auto-create the next stable tag (vX.Y.Z) for that commit, which triggers the stable release workflow automatically.
CI/CD test integration:
- Validation workflow (
PR + main) runsmake ciwith hosted synthetic e2e (VFO_E2E_ASSET_MODE=synthetic,VFO_E2E_MAX_SEEDS=1). - Tag release workflows (beta + stable) also run
make cion hosted runners before packaging/release. - Full media-backed e2e runs on the self-hosted runner workflow
.github/workflows/on-self-hosted-e2e.yml. - The self-hosted full-media workflow runs automatically on same-repo PRs to
mainand onmainpushes, and can also be triggered manually with customassets_dir,clip_duration,max_seeds, and optional DV fixture inputs. - Use local run mode (
VFO_E2E_ASSET_MODE=local) when you want to validate against your own mounted media library. - Every e2e run captures
vfo+ dependency versions undertests/e2e/.reports/latest/and uploads them as workflow artifacts. - AI routing + premium escalation lanes are available via:
.github/workflows/ci-issue-routing.yml(issue open/label routing withGPT-5.4 mini firstorGPT-5.4 high nowrecommendation).github/workflows/ci-codex-autonomous-loop.yml(premium Codex sweep +agent-readyissue trigger, opt-in only).github/workflows/ci-codex-pr-autofix.yml(bounded premium CI autofix for Codex-owned PRs only, opt-in only)- premium lane guardrails doc:
infra/docs/codex-autonomous-loop.md - task packet guide:
task-packets.md
- Docs site build + Pages deploy is available via:
.github/workflows/ci-docs-pages.yml(PR build validation +mainPages deploy)
- GHCR package lane is available via:
.github/workflows/ci-package-ghcr.yml(publishesghcr.io/<owner>/vfoonmainand tag pushes)
- Release publication jobs use explicit deployment environments:
- stable:
release-stable - beta:
release-beta
- stable:
- Delivery model overview:
infra/docs/github-delivery-lanes.md
- Deployment/runtime strategy:
infra/docs/deployment-runtime-strategy.md
Package quickstart:
docker pull ghcr.io/craigwatt/vfo:main
docker run --rm ghcr.io/craigwatt/vfo:main --version
Toolchain mode (CLI)
vfo now supports runtime toolchain mode selection via environment variable:
TOOLCHAIN_MODE=system(default): use host PATH tools.TOOLCHAIN_MODE=auto: currently falls back to host PATH tools (managed lane reserved).TOOLCHAIN_MODE=managed: currently hard-fails with a clear message (managed CLI lane not active yet).
Examples:
TOOLCHAIN_MODE=system vfo doctor
TOOLCHAIN_MODE=auto vfo status-json
Option 2: build from source
Prerequisites:
gccmake- a reasonably recent
ffmpeg zshavailable as/bin/zshorzshin your shell environment, since theMakefilesetsSHELL := zsh
Build the binary:
make all
The compiled executable is written to:
services/vfo/bin/vfo
Install target
The repository includes an install target:
make install
By default this copies the binary under /usr/local/bin and copies services/vfo/src/vfo_config.conf into /usr/local/bin/vfo_conf_folder.
It also copies stock profile-action scripts (*.sh, for example transcode_*_profile.sh, profile_guardrail_skip.sh) into /usr/local/bin.
Quick start
- Install FFmpeg and MKVToolNix (
mkvmerge). - Build or download
vfo. - Ensure
vfo_config.confexists at/usr/local/bin/vfo_conf_folder/vfo_config.conf. - Edit the config file so the folder paths match your machine.
- Define at least one profile (
PROFILE=) and one scenario. - Run
vfo doctorto validate environment + config. - Run
vfo statusfor a high-level readiness snapshot. - Run
vfo visualizefor a local workflow view (HTML + JSON + Mermaid). - Run
vfo mezzanine-cleanto preview mezzanine hygiene changes (or apply if enabled). - Run
vfo runfor the default end-to-end pipeline. - Run
vfowith individual stage commands or a specific profile name when needed.
Example starter flow:
sudo mkdir -p /usr/local/bin/vfo_conf_folder
sudo cp services/vfo/src/vfo_config.conf /usr/local/bin/vfo_conf_folder/vfo_config.conf
$EDITOR /usr/local/bin/vfo_conf_folder/vfo_config.conf
vfo --help
vfo doctor
vfo status
vfo status-json
vfo visualize
vfo mezzanine-clean
vfo run
vfo profiles
Command-line usage
The help text in the repository currently documents this shape:
vfo [argument] || [options]
Options
-h,--help— print help text-v,--version— print version information--no-color— disable colored output
Common arguments
mezzaninesourceshowwizardconfigdoctorstatusstatus-jsonvisualizerunmezzanine-cleanprofileswipe- any profile name defined in
vfo_config.conf(viaPROFILE=)
A useful mental model is:
- use
profilesto run every configured profile - use a specific profile name to run only that profile
- use
wipetogether with profile-oriented commands when you want profile outputs removed - use
doctorbefore first run (or after machine/config changes) - use
statusto view a component-level readiness summary before execution - use
status-jsonin automation/tests when you need machine-readable status - use
visualizefor local workflow artifacts (status.json, Mermaid, HTML) - use
mezzanine-cleanfor optional mezzanine filename/folder hygiene and recommendations - use
runfor the default pipeline (mezzanine -> source if enabled -> profiles -> optional quality scoring) - use
wizardas onboarding: choosequickstartfor recommended defaults oradvancedfor full tuning - use
configto open the active config invim, then fall back to$EDITOR
Wizard behavior highlights:
- preflight checks for required tools and config-path write permissions
- step-based onboarding flow with explicit review before write
- safe config writes (temporary file + atomic replace + timestamped backup)
- stock preset multi-select across the current shipped packs, including:
balanced_open_audioroku_family_all_sub_convert_audio_conformfire_tv_family_all_sub_convert_audio_conformchromecast_google_tv_family_all_sub_convert_audio_conformapple_tv_family_all_sub_convert_audio_conformfire_tv_stick_4k_dv_all_sub_convert_audio_conformdevice_targets_open_audio(legacy compatibility)craigstreamy_hevc_selected_english_subtitle_preservecraigstreamy_hevc_all_sub_preservecraigstreamy_hevc_smart_eng_sub_audio_conformcraigstreamy_hevc_all_sub_audio_conformcraigstreamy_hevc_smart_eng_sub_subtitle_convertcraigstreamy_hevc_smart_eng_sub_subtitle_convert_audio_conformcraigstreamy_hevc_smart_eng_sub_aggressive_vmafcraigstreamy_hevc_smart_eng_sub_audio_conform_aggressive_vmaf
Configuration guide
The sample services/vfo/src/vfo_config.conf is long, but the structure is consistent.
For hardware-aware profile actions (for example Apple Silicon encode with CPU fallback), see:
services/vfo/actions/services/vfo/docs/profile-action-schema.mdservices/vfo/examples/vfo_config.profile_actions.conf
For stock shipping profile packs, see:
services/vfo/presets/
For device compatibility conformance checks, see:
services/vfo/docs/device-conformance.mdtests/e2e/validate_device_conformance.shservices/vfo/docs/mezzanine-clean.mdservices/vfo/docs/quality-scoring.md
For engine readiness and observability status output, see:
services/vfo/docs/status-observability.mdservices/vfo/docs/workflow-visualization.mdservices/vfo/docs/workflow-engine.bpmnservices/vfo/docs/workflow-decisions.dmn
1. Set required paths
Start by updating the top-level locations:
MEZZANINE_LOCATIONMEZZANINE_LOCATIONS(semicolon-separated, optional but recommended for multi-drive)MEZZANINE_LOCATION_MAX_USAGE_PCT(semicolon-separated caps aligned toMEZZANINE_LOCATIONS)SOURCE_LOCATIONSOURCE_LOCATIONS(semicolon-separated, optional but recommended for multi-drive)SOURCE_LOCATION_MAX_USAGE_PCT(semicolon-separated caps aligned toSOURCE_LOCATIONS)SOURCE_AS_LOCATION
For profile outputs, you can now also define:
<PROFILE>_LOCATION<PROFILE>_LOCATIONS<PROFILE>_LOCATION_MAX_USAGE_PCT
When multiple locations are configured, vfo selects destinations by projected post-write utilization and available free space, honoring each location cap and keeping a reserve so near-full volumes are avoided.
2. Decide global behavior
Examples from the sample file include:
KEEP_SOURCESOURCE_TEST_ACTIVESOURCE_TEST_TRIM_STARTSOURCE_TEST_TRIM_DURATIONSOURCE_AS_ACTIVATEMEZZANINE_CLEAN_ENABLEDMEZZANINE_CLEAN_APPLY_CHANGESMEZZANINE_CLEAN_APPEND_MEDIA_TAGSMEZZANINE_CLEAN_STRICT_QUALITY_GATEQUALITY_CHECK_ENABLEDQUALITY_CHECK_INCLUDE_VMAFQUALITY_CHECK_STRICT_GATEQUALITY_CHECK_REFERENCE_LAYERQUALITY_CHECK_MIN_PSNRQUALITY_CHECK_MIN_SSIMQUALITY_CHECK_MIN_VMAFQUALITY_CHECK_MAX_FILES_PER_PROFILE
3. Define custom folders
The sample uses repeated CUSTOM_FOLDER="name,type" entries, where the type is one of the approved values used in code today:
filmstv
4. Define a profile
A minimal profile pattern looks like this:
PROFILE="queen"
QUEEN_LOCATION="/path/to/output"
QUEEN_LOCATIONS="/path/to/output;/Volumes/Media-2/path/to/output"
QUEEN_LOCATION_MAX_USAGE_PCT="90;95"
QUEEN_CRITERIA_CODEC_NAME="h264"
QUEEN_CRITERIA_COLOR_SPACE="bt709"
QUEEN_CRITERIA_RESOLUTION_MIN_WIDTH="352"
QUEEN_CRITERIA_RESOLUTION_MIN_HEIGHT="240"
QUEEN_CRITERIA_RESOLUTION_MAX_WIDTH="1920"
QUEEN_CRITERIA_RESOLUTION_MAX_HEIGHT="1080"
5. Add scenarios and FFmpeg commands
Each scenario is paired with one FFmpeg command:
QUEEN_SCENARIO="RES_TOO_HIGH"
QUEEN_FFMPEG_COMMAND="ffmpeg ... $vfo_input ... $vfo_output"
Use these placeholders in your command strings:
$vfo_input$vfo_output
That lets vfo substitute the actual source and destination file paths at runtime.
Example use cases
Build a 1080p H.264 compatibility tier
Create a profile for older TVs, low-end streamers, or browser playback. Set the max resolution to 1920x1080, prefer h264, and add scenarios that:
- remux compliant files
- downscale larger files
- reject files that are too small
Build multiple output targets from one library
Define separate profiles for:
- a high-quality archive copy
- a 1080p SDR streaming tier
- a smaller mobile-friendly tier
Then run:
vfo profiles
Test your workflow on short clips first
The sample config includes source test settings so you can generate trimmed outputs while checking that your profile and FFmpeg logic behaves as expected.
Development
Repository layout
infra/
packaging/
macos/
platform/
docs-site/
vfo-desktop/
src-tauri/
web/
contracts/
vfo-contracts/
services/
vfo/
src/
...
test/
tests/
e2e/
Testing convention:
- Service tests live beside each service in
services/<service-name>/test/. - Platform component tests live beside each component in
platform/<component>/test/. - Infra validation tests live in
infra/test/. - Cross-service end-to-end tests live in
tests/e2e/.
Naming conventions and terminology policy:
- canonical policy:
AGENTS.md - latest audit snapshot:
infra/docs/naming-conventions-audit.md
Build commands
make all
make tests
make e2e
make ci
make clean
make clean_tests
Note that make tests links against cmocka, so you may need to install that dependency first.
For full local-media e2e:
VFO_E2E_ASSET_MODE=local VFO_E2E_ASSETS_DIR="/absolute/path/to/open-source-media" VFO_E2E_MAX_SEEDS=4 make e2e
Docs site
The monorepo docs site source is in platform/docs-site/.
Key visual docs include:
platform/docs-site/docs/pipeline-end-to-end.mdplatform/docs-site/docs/flow-levels.md(interactive React Flow canvases)platform/docs-site/docs/profile-visual-standard.mdplatform/docs-site/docs/e2e-toolchain-latest.md- generated stock profile sheets under
platform/docs-site/docs/profiles/generated/
Desktop scaffold
The monorepo now includes a desktop-app scaffold intended for:
services/vfo/as the standalone processing engineplatform/vfo-desktop/as Tauri runtime + React UI shell (with React Flow web scaffold)platform/vfo-contracts/as machine-readable schema contracts shared across UI/automation integrations
See:
platform/vfo-desktop/README.mdplatform/vfo-desktop/web/README.mdplatform/vfo-desktop/contracts/allowed-cli-commands.jsonplatform/vfo-contracts/status-json.schema.json
Local docs workflow:
make docs-generate
make docs-build
Optional local preview:
make docs-serve
Published docs (when GitHub Pages is enabled for the repository):
https://craigwatt.github.io/vfo/
Known rough edges
- Some documentation in the repository is still marked as work in progress.
- The install path for
vfo_config.confappears inconsistent between theMakefileand the runtime error/help text. - The sample configuration contains roadmap comments and experimental notes alongside active settings.
Contributing
Contributions that improve documentation, example configs, portability, and installation behavior would all make the project easier to adopt.
- Ask broader questions in GitHub Discussions
- Report bugs or feature ideas in GitHub Issues
Support
If the project is useful to you, consider starring the repository.