vocatio is a CLI event-media workflow with two parallel per-day pipelines:
- an audio-assisted pipeline that uses synced video and transcription to build performance boundaries
- an image-only pipeline that works from photos alone when usable audio is unavailable or unreliable
Both pipelines write artifacts into DAY/_workspace and feed the same review GUI.
scripts/pipeline/- operational pipeline scriptsconf/- export profiles for reviewed set deliverydocs/PROJECT_INTENT.md- product direction
| Mode | Use When | Main Inputs | Main Review Index |
|---|---|---|---|
| Audio-assisted | you have usable video audio and want the strongest boundary detection | v-* streams, optional p-* streams |
performance_proxy_index.json |
| Image-only | you only have photos, or audio/video timing is unusable | p-* streams |
performance_proxy_index.json |
Common requirements:
- Python 3.10+
ffmpegandffprobeexiftoolPyYAML,rich,PySide6- ImageMagick (
magick) for JPG proxy and embedded JPG extraction paths
Audio-assisted extras:
whisperx
Image-only extras:
numpy- local DINOv2 runtime for preview embeddings
Optional image-only VLM extras:
- a local VLM backend reachable through the shared transport layer in
build_photo_pre_model_annotations.pyandprobe_vlm_photo_boundaries.py - supported providers for both scripts:
ollama,llamacpp, andvllm - backend capability support still applies, for example
keep_aliveand reasoning controls remain Ollama-specific
Install Python dependencies in your preferred environment, then run scripts directly with python3.
The project uses one pyproject.toml with two mutually exclusive dependency groups:
gpu(default):torch==2.11.0+cu128,torchvision==0.26.0+cu128,xformersautogluon:torch==2.9.0+cu128,torchvision==0.24.0+cu128,autogluon==1.5.0
Sync commands:
uv lock
uv sync
uv sync --no-default-groups --group autogluon
uv sync --no-default-groups --group gpuRun Python with explicit group selection:
uv run python
uv run scripts/pipeline/export_media.py DAY
uv run --no-default-groups --group autogluon python
uv run --no-default-groups --group autogluon scripts/pipeline/export_media.py DAYImportant:
- avoid
uv sync --only-group ...for normal project work --only-groupperforms an exact sync for that group only and can remove most installed packages, including project dependencies outside that group
The pipeline runs per day_dir, for example:
/data/20260323If you process multiple days, run the same sequence for each directory.
By default, generated artifacts are written to:
DAY/_workspaceWorkspace resolution precedence is:
--workspace-dirDAY/.vocatiowithWORKSPACE_DIR=...DAY/_workspace
Example DAY/.vocatio:
WORKSPACE_DIR=/fast/local/vocatio/20250324Use export_media.py to build the canonical single-manifest export for a day:
python3 scripts/pipeline/export_media.py DAYBy default this writes:
DAY/_workspace/media_manifest.csvThe exporter uses a resumable checkpoint file during interrupted or in-progress work:
DAY/_workspace/media_manifest.csv.partialBehavior:
- resume is the default
- if
.partialexists, the exporter resumes from it - if
.partialdoes not exist butmedia_manifest.csvexists, the exporter reconstructs.partialfrom the final manifest and resumes --restartignores both files and rebuilds from scratch- after each completed metadata batch, the exporter updates both
.partialandmedia_manifest.csvusing atomic replace - after a clean successful finish,
.partialis removed
Operational notes:
Ctrl+Cis safe- the first interrupt stops scheduling new work, lets active batch(es) finish, writes the latest snapshot, and exits
- press
Ctrl+Cagain only if you want to abort immediately --jobsremains safe with resume enabled because state is only promoted from the main thread after completed batches
Useful options:
--list-targetsto inspect detectedp-*andv-*streams without writing output--media-types photoand--media-types videofor staged debugging and migration checks--restartto discard existing export state and rebuild from zero
Example under /data/DAY/:
/data/20260323/p-a7r5//data/20260323/v-a7r5//data/20260323/v-gh7//data/20260323/v-pocket3//data/20260323/_workspace/
Prefix meaning:
v-...= video streamp-...= photo stream_workspace/= generated pipeline artifacts (CSV, JSON, transcripts, proxies, review state)
The pipeline discovers stream directories using these prefixes.
This path uses video sync, transcription, and announcement extraction to build performance boundaries, then assigns photos to those intervals.
merged_video.csvsync_map.csvsync_diagnostics.csvmerged_video_synced.csvtranscripts_manifest.csvannouncement_candidates.csvorannouncement_candidates_semantic.csvperformance_timeline.csvphoto_assignments.csvphoto_review.csvphoto_unassigned.csvphoto_assignment_summary.csvphoto_proxy_manifest.csvperformance_proxy_index.jsonreview_state.jsonselected_photos_*.jsonoptional GUI photo selection exports
Directories:
transcripts/proxy_jpg/
Replace DAY with your day directory path.
python3 scripts/pipeline/export_media.py DAYThis writes DAY/_workspace/media_manifest.csv and replaces the old stream-specific media export step.
python3 scripts/pipeline/estimate_video_sync_map.py DAYpython3 scripts/pipeline/apply_video_sync_map.py DAYpython3 scripts/pipeline/transcribe_video_batch.py DAY --all-streamspython3 scripts/pipeline/extract_announcement_candidates.py DAY --all-streamspython3 scripts/pipeline/extract_announcement_candidates_semantic.py DAY --all-streamsIf you use semantic output, pass it explicitly in the next step:
python3 scripts/pipeline/build_performance_timeline.py DAY --candidates-csv DAY/_workspace/announcement_candidates_semantic.csvpython3 scripts/pipeline/build_performance_timeline.py DAYpython3 scripts/pipeline/assign_photos_to_timeline.py DAYpython3 scripts/pipeline/generate_photo_proxy_jpg.py DAY --all-streamspython3 scripts/pipeline/build_performance_proxy_index.py DAYpython3 scripts/pipeline/review_performance_proxy_gui.py DAYThis step creates or updates review_state.json.
The GUI also supports exporting an ad-hoc photo selection:
- select one or more photo rows inside a set
- press
Ctrl+E - enter a JSON filename such as
selected_photos_a.json - the file is written to
DAY/_workspaceunless you enter an absolute path
Example GUI views:
python3 scripts/pipeline/copy_reviewed_set_assets.py DAY out 158 --config conf/copy_reviewed_set_assets.default.yamlWhere:
outis the target root directory158is the final set name (number or text label)
You can also export only explicitly selected photos from a GUI selection JSON:
python3 scripts/pipeline/copy_reviewed_set_assets.py DAY out selected_photos_a.json --streams photoIn this mode:
- the third argument is a selection JSON saved from the GUI
- the export goes to
out/selected_photos_a/ - only the listed photos are exported
--index-jsonstill works as usual when you need a non-default index
This path builds photo ordering, quality signals, DINOv2 embeddings, and photo boundary candidates directly from images. It has two review flows:
- a deterministic heuristic flow
- an optional VLM-assisted flow
media_manifest.csvmedia_manifest.csvphoto_embedded_manifest.csvphoto_quality.csvphoto_boundary_features.csvphoto_boundary_scores.csvphoto_segments.csvperformance_proxy_index.json
Directories:
embedded_jpg/thumb/embedded_jpg/preview/features/
python3 scripts/pipeline/export_media.py DAYThis writes DAY/_workspace/media_manifest.csv and is the canonical exporter for image-only work as well.
python3 scripts/pipeline/extract_embedded_photo_jpg.py DAYThis reads photo rows from DAY/_workspace/media_manifest.csv and creates photo_embedded_manifest.csv with thumb_path and preview_path.
python3 scripts/pipeline/build_photo_quality_annotations.py DAYpython3 scripts/pipeline/embed_photo_previews_dinov2.py DAYThis creates:
features/dinov2_embeddings.npyfeatures/dinov2_index.csv
python3 scripts/pipeline/build_photo_boundary_features.py DAYpython3 scripts/pipeline/bootstrap_photo_boundaries.py DAYpython3 scripts/pipeline/build_photo_segments.py DAYpython3 scripts/pipeline/build_photo_review_index.py DAYpython3 scripts/pipeline/review_performance_proxy_gui.py DAY --index performance_proxy_index.json --state review_state.jsonUse a dedicated --state file for image-only review so you do not mix it with the audio-assisted review state.
The VLM flow starts from the same image-only artifacts as the deterministic flow. It optionally adds a lightweight per-photo pre-model pass, then probes only candidate time gaps with a local VLM.
build_photo_pre_model_annotations.py and probe_vlm_photo_boundaries.py both run through the shared VLM transport layer and resolve model transport settings from shared conf/vlm_models.yaml presets plus exact-name .vocatio inheritance. Grouped prompt templates are registered separately in conf/vlm_prompt_templates.yaml. You can select ollama, llamacpp, or vllm, subject to backend capability support. When you explicitly provide a CLI model flag, that CLI value wins over .vocatio; workflow-only fields remain local to the day config.
python3 scripts/pipeline/build_photo_pre_model_annotations.py DAY --limit 1000 --workers 4Default behavior:
- exact preset selection comes from
.vocatioPREMODEL_NAME, which must match aPREMODEL_NAMEentry inconf/vlm_models.yaml .vocatiomay override only these preset fields locally:PREMODEL_PROVIDER,PREMODEL_BASE_URL,PREMODEL_MODEL,PREMODEL_MAX_OUTPUT_TOKENS,PREMODEL_TEMPERATURE,PREMODEL_TIMEOUT_SECONDS- explicitly provided CLI model flags override the inherited preset values for that run
- input index:
photo_embedded_manifest.csv - image column:
preview_path - output directory:
DAY/_workspace/photo_pre_model_annotations - resume behavior: existing per-photo JSON files are skipped unless you pass
--overwrite - default
--limitis20, so use a larger explicit value for real runs and rerun the same command until it finishes the remaining photos
python3 scripts/pipeline/probe_vlm_photo_boundaries.py DAY \
--photo-manifest-csv DAY/_workspace/media_manifest.csv \
--image-variant thumb \
--window-radius 2 \
--boundary-gap-seconds 10 \
--max-batches 100 \
--response-schema-mode on \
--json-validation-mode strict \
--photo-pre-model-dir photo_pre_model_annotations \
--new-runImportant behavior:
- exact preset selection comes from
.vocatioVLM_NAME, which must match aVLM_NAMEentry inconf/vlm_models.yaml .vocatiomay override these shared preset fields locally:VLM_PROVIDER,VLM_BASE_URL,VLM_MODEL,VLM_MAX_OUTPUT_TOKENS,VLM_TIMEOUT_SECONDS,VLM_TEMPERATURE,VLM_RESPONSE_SCHEMA_MODE,VLM_JSON_VALIDATION_MODE.vocatiomay overrideVLM_CONTEXT_TOKENS,VLM_KEEP_ALIVE, andVLM_REASONING_LEVELonly when the resolved VLM provider isollama;llamacppandvllmpresets must omit those fields- explicitly provided CLI model flags override the inherited preset values for that run
- symmetric VLM context sample count is configured only through
--window-radiusor.vocatioVLM_WINDOW_RADIUS - VLM frame selection inside each side segment is configured through
--window-schemaor.vocatioVLM_WINDOW_SCHEMA - deterministic schema sampling is configured through
--window-schema-seedor.vocatioVLM_WINDOW_SCHEMA_SEED - grouped prompt template selection is configured through
--prompt-template-idor.vocatioVLM_PROMPT_TEMPLATE_ID; the built-in default isgroup_compare_long - grouped prompt templates are loaded from
conf/vlm_prompt_templates.yaml --prompt-template-fileor.vocatioVLM_PROMPT_TEMPLATE_FILEoptionally overrides the registry entry with a repo-relative or absolute template file; any non-empty file override wins over the selected template id- supported window schemas are
consecutive,random,index_quantile,time_quantile,time_max_min, andtime_boundary_spread - grouped prompt runs use the
grouped_v1response contract vlm_boundary_results.csvpersistswindow_radiusandresponse_contract_id; VLM run metadata persistswindow_schema,window_schema_seed,prompt_template_id, and optionalprompt_template_file; downstream GUI artifact payloads persistprompt_template_id, optionalprompt_template_file, andresponse_contract_id- only gaps larger than
--boundary-gap-secondsare probed - default
--max-batchesis10, so use a larger explicit value for real runs and rerun the same command or continue with--run-id --new-runstarts a fresh VLM run--run-id ...resumes a specific existing VLM run--photo-pre-model-dir ...is optional; if per-photo annotations exist, they are appended to the prompt frame-by-frame
This creates:
vlm_boundary_results.csvvlm_runs/
Optional debugging:
python3 scripts/pipeline/probe_vlm_photo_boundaries.py DAY \
--photo-manifest-csv DAY/_workspace/media_manifest.csv \
--image-variant thumb \
--window-radius 2 \
--boundary-gap-seconds 10 \
--max-batches 100 \
--dump-debug-dir /tmp \
--new-runpython3 scripts/pipeline/build_vlm_photo_boundary_gui_index.py DAY --run-id YOUR_RUN_IDThis creates:
performance_proxy_index.json
python3 scripts/pipeline/review_performance_proxy_gui.py DAY \
--index performance_proxy_index.json \
--state review_state.image.vlm.jsonUse a dedicated VLM review-state file so you do not mix VLM review decisions with the heuristic image-only or audio-assisted states.
The GUI Manual VLM analyze action loads model presets from conf/vlm_models.yaml and grouped prompt templates from conf/vlm_prompt_templates.yaml. The section exposes three runtime dropdowns: model preset, window schema, and prompt template. The prompt-template dropdown uses registered template ids, and its startup default comes from day-level .vocatio VLM_PROMPT_TEMPLATE_ID when that id is present in the registry; otherwise the first available template is selected. The window-schema dropdown still defaults from .vocatio VLM_WINDOW_SCHEMA, or falls back to consecutive.
This shared preset file contains one models: list and may mix VLM_* and PREMODEL_* entries. Day-level .vocatio must select an existing preset by exact VLM_NAME or PREMODEL_NAME; it does not define full models from scratch. Workflow fields such as VLM_WINDOW_RADIUS, VLM_WINDOW_SCHEMA, VLM_WINDOW_SCHEMA_SEED, VLM_PROMPT_TEMPLATE_ID, VLM_PROMPT_TEMPLATE_FILE, VLM_IMAGE_VARIANT, VLM_ML_MODEL_RUN_ID, and VLM_PHOTO_PRE_MODEL_DIR remain local to .vocatio or explicit CLI flags rather than the shared preset file.
Each VLM preset must define these shared fields:
VLM_NAMEVLM_PROVIDERVLM_BASE_URLVLM_MODELVLM_MAX_OUTPUT_TOKENSVLM_TIMEOUT_SECONDSVLM_TEMPERATUREVLM_RESPONSE_SCHEMA_MODEVLM_JSON_VALIDATION_MODE
Ollama presets may additionally define these optional fields:
VLM_CONTEXT_TOKENSVLM_KEEP_ALIVEVLM_REASONING_LEVEL
llamacpp and vllm presets must omit those Ollama-only fields.
GUI behavior:
- the GUI loads
conf/vlm_models.yamlandconf/vlm_prompt_templates.yamlat startup - if the current selection is not preserved, the first preset becomes the default selection
- if the current prompt template selection is not preserved, the first template becomes the default selection
- when you click
AnalyzeinManual VLM analyze, the GUI recomputes both config-file MD5 values - if either MD5 changed, that click reloads the changed config and refreshes the affected dropdown, then stops without starting analysis; click
Analyzeagain to run with the refreshed selection - manual result and error diagnostics include
VLM_PROMPT_TEMPLATE_ID, optionalVLM_PROMPT_TEMPLATE_FILE, andresponse_contract_id
The ML verifier flow starts from reviewed image-only segmentation truth and trains two independent predictors (segment_type, boundary) on fixed 5-photo candidate windows.
Single-day run with explicit split config:
python3 scripts/pipeline/run_ml_boundary_pipeline.py /data/20260323 \
--mode tabular_only \
--split-strategy global_stratified \
--train-fraction 0.70 \
--validation-fraction 0.15 \
--test-fraction 0.15 \
--split-seed 42 \
--model-run-id run-001Multi-day run with global_stratified:
python3 scripts/pipeline/run_ml_boundary_pipeline.py /data/20260323 /data/20260324 /data/20260325 \
--mode tabular_only \
--split-strategy global_stratified \
--model-run-id run-001Multi-day prepare-only run:
python3 scripts/pipeline/run_ml_boundary_pipeline.py /data/20260323 /data/20260324 /data/20260325 \
--mode tabular_only \
--prepare-only \
--model-run-id run-001By default this uses each day's .vocatio WORKSPACE_DIR (or DAY/_workspace), then:
- exports reviewed truth (
ml_boundary_reviewed_truth.csv) from GUI index + state - builds and validates per-day candidate datasets
- merges day candidates into a corpus dataset
- builds day metadata and a merged-corpus split manifest
- validates corpus + split policy
- trains model artifacts under
ml_boundary_models/RUN_ID - evaluates on the
testsplit underml_boundary_eval/RUN_ID
Main corpus outputs are written to:
FIRST_DAY_WORKSPACE/ml_boundary_corpus/(or--corpus-workspace)
Use --prepare-only to stop before train/evaluate.
Global corpus split rule:
- all input days are merged into one candidate corpus first
- train/validation/test split is applied to merged candidate rows
- input days are data sources, not split units
- one day is enough as long as the merged corpus contains at least 3 candidate rows
- supported strategies are
global_randomandglobal_stratified global_stratifiedis the requested strategy by default; the pipeline may fall back toglobal_randomwhen stratified allocation or held-out coverage cannot be satisfied- the corpus split surface is
--split-strategy,--train-fraction,--validation-fraction,--test-fraction,--split-seed,--required-heldout-classes
Exact defaults:
split_strategy = global_stratifiedtrain_fraction = 0.70validation_fraction = 0.15test_fraction = 0.15split_seed = 42
.vocatio support:
run_ml_boundary_pipeline.pyreads these keys from the first day directory's.vocatio- CLI flags override
.vocatiowhen both are provided
ML_SPLIT_STRATEGY=global_stratified
ML_SPLIT_TRAIN_FRACTION=0.70
ML_SPLIT_VALIDATION_FRACTION=0.15
ML_SPLIT_TEST_FRACTION=0.15
ML_SPLIT_SEED=42The default resolution path is:
- CLI flags if provided
- otherwise the first day directory's
.vocatio - otherwise built-in defaults shown above
Verification:
- inspect
FIRST_DAY_WORKSPACE/ml_boundary_corpus/ml_boundary_pipeline_summary.json - check
window_radiusandcandidate_rule_params_jsonto confirm the merged corpus and downstream training/eval artifacts stayed on the radius contract - check
requested_split_strategyandeffective_split_strategyto confirm whether the run stayed onglobal_stratifiedor fell back toglobal_random - inspect
required_heldout_classesin the same summary to confirm which held-out coverage classes were enforced for the run
- Export reviewed truth per day:
python3 scripts/pipeline/export_ml_boundary_reviewed_truth.py DAY- Build and validate per-day candidates:
python3 scripts/pipeline/build_ml_boundary_candidate_dataset.py DAY
python3 scripts/pipeline/validate_ml_boundary_dataset.py DAY- Train with AutoGluon (isolated dependency group):
uv run --no-default-groups --group autogluon \
python3 scripts/pipeline/train_ml_boundary_verifier.py \
CORPUS/ml_boundary_candidates.corpus.csv \
--split-manifest-csv CORPUS/ml_boundary_splits.csv \
--mode tabular_only \
--output-dir CORPUS/ml_boundary_models/run-001- Evaluate trained predictors on the
testsplit:
uv run --no-default-groups --group autogluon \
python3 scripts/pipeline/evaluate_ml_boundary_verifier.py \
CORPUS/ml_boundary_candidates.corpus.csv \
--model-dir CORPUS/ml_boundary_models/run-001 \
--split-manifest-csv CORPUS/ml_boundary_splits.csv \
--output-dir CORPUS/ml_boundary_eval/run-001Notes:
tabular_plus_thumbnailpreserves ordered image inputs through:frame_01_thumb_path...frame_05_thumb_path
- the merged corpus split requires at least 3 candidate rows total
- evaluation is real model inference on the explicit
testsplit (not truth replay)
Available profiles:
conf/copy_reviewed_set_assets.default.yaml- photo: converted JPG (max edge 3200, quality 90)
- video: converted MP4 (H.264/AAC), default end padding 10s
conf/copy_reviewed_set_assets.raw.yaml- photo/video copied as raw sources
Use with:
python3 scripts/pipeline/copy_reviewed_set_assets.py DAY out SET_NAME --config conf/copy_reviewed_set_assets.raw.yamlAdditional semantic and benchmark scripts:
build_semantic_announcement_demo.pydemo_semantic_announcement_classifier.pybenchmark_semantic_announcement_models.py
Use these for model experiments and benchmark runs, not for the minimal production flow.
Inspect per-script options:
python3 scripts/pipeline/export_media.py --help
python3 scripts/pipeline/build_photo_pre_model_annotations.py --help
python3 scripts/pipeline/probe_vlm_photo_boundaries.py --help
python3 scripts/pipeline/review_performance_proxy_gui.py --help
python3 scripts/pipeline/copy_reviewed_set_assets.py --help
