Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Media Conversion

Status: Current Last updated: 2026-09-10 01:43 EDT

Overview

Batchalign commands that process audio (align, transcribe, opensmile, avqi, benchmark) must resolve a media file for each input. Depending on the command, Rust then either prepares typed PCM artifacts for worker-protocol V2 execution or passes through a normalized media path to a provider-specific engine. Container formats that downstream audio libraries cannot read, primarily MP4: must first be converted to WAV via ffmpeg.

This conversion is automatic, cached, and transparent to the user.

Formats

ExtensionCan soundfile read?Conversion needed?
.wavYesNo
.mp3YesNo
.flacYesNo
.oggYesNo
.mp4NoYes
.m4aNoYes
.webmNoYes
.wmaNoYes

The canonical list of forced-conversion extensions is defined in crates/batchalign/src/ensure_wav.rs::FORCED_CONVERSION.

Align Pipeline End-to-End

The align command has the most complex media handling. Here is the complete pipeline, from CLI invocation to output CHAT, showing where media resolution and conversion fit in.

batchalign3 [--server http://<your-server>:8001] align input/ output/ --lang eng
  │
  ├─ CLI: discover .cha files in input/ (sorted largest-first)
  ├─ CLI: detect dispatch mode
  │     paths_mode / execution-host local: audio sits alongside .cha files
  │     content mode: .cha text POSTed, server resolves media from its own view
  │
  ├─ Server: POST /jobs/submit → create job (Queued → Running)
  │
  │  ┌──── For each .cha file (sequential, each has its own audio) ────┐
  │  │                                                                   │
  │  │  1. PARSE                                                         │
  │  │     parse_lenient() → ChatFile AST                                │
  │  │     pre-validate (MainTierValid)                                  │
  │  │                                                                   │
  │  │  2. MEDIA RESOLUTION                                              │
  │  │     paths_mode:                                                   │
  │  │       look alongside .cha for matching stem with known extensions │
  │  │     content mode / shared-fs remap:                               │
  │  │       trust server-visible local paths only                       │
  │  │       source_dir when the server shares that filesystem           │
  │  │       or local media_mappings / explicit --media-dir             │
  │  │                                                                   │
  │  │  3. MEDIA CONVERSION (ensure_wav)                ◄── THIS STEP    │
  │  │     .wav/.mp3/.flac/.ogg → pass through unchanged                │
  │  │     .mp4/.m4a/.webm/.wma → ffmpeg convert to WAV, cache result   │
  │  │       fingerprint: full-file BLAKE3 + versioned recipe    │
  │  │       cache dir: platform data_dir/batchalign3/media_cache/      │
  │  │       file lock: per-fingerprint .lock prevents concurrent ffmpeg │
  │  │       output: 16kHz mono PCM_S16LE WAV                           │
  │  │                                                                   │
  │  │  4. AUDIO IDENTITY                                                │
  │  │     compute_audio_identity(path, mtime, size)                     │
  │  │     used as cache key component for FA results                    │
  │  │                                                                   │
  │  │  5. AUDIO DURATION PROBE (optional)                               │
  │  │     ffprobe → total_audio_ms                                      │
  │  │     used for proportional estimation of untimed utterances        │
  │  │                                                                   │
  │  │  6. GROUP UTTERANCES                                              │
  │  │     split into ~20s time windows (Whisper) or ~15s (Wave2Vec)    │
  │  │                                                                   │
  │  │  7. CACHE LOOKUP                                                  │
  │  │     BLAKE3(words + audio_identity + time_window + engine)         │
  │  │     hits → skip worker IPC                                        │
  │  │                                                                   │
  │  │  8. FA INFERENCE (cache misses only)                              │
  │  │     checkout worker from pool                                     │
  │  │     execute_v2(task="fa", prepared_audio + prepared_text)         │
  │  │     Python reads prepared artifacts → model inference             │
  │  │     returns raw timings                                           │
  │  │                                                                   │
  │  │  9. DP ALIGNMENT                                                  │
  │  │     Hirschberg align model tokens → transcript words              │
  │  │     convert chunk-relative → file-absolute milliseconds           │
  │  │                                                                   │
  │  │  10. POST-PROCESSING                                              │
  │  │      inject timings → chain word ends → update bullets            │
  │  │      generate %wor tier → monotonicity check (E362)               │
  │  │      same-speaker overlap enforcement (E704)                      │
  │  │                                                                   │
  │  │  11. SERIALIZE                                                    │
  │  │      validate → to_chat_string() → write output .cha             │
  │  │                                                                   │
  │  └───────────────────────────────────────────────────────────────────┘
  │
  └─ CLI: poll /jobs/{id}/results → write output files

ensure_wav: Conversion Cache

Module: crates/batchalign/src/ensure_wav.rs

Implements content-fingerprinted WAV conversion with file-locking and atomic writes.

Algorithm

  1. Check extension: if .wav/.mp3/.flac/.ogg, return unchanged.
  2. Check ffmpeg: if not on PATH, return a clear error with install hint.
  3. Fingerprint: stream every source byte through BLAKE3 and retain its full digest in the whole-pcm16-mono16k-strict-v3 recipe namespace. This reads the complete file with bounded memory; same-size edits in the middle of a recording cannot reuse the old key. Segment keys additionally bind the requested time window in their own strict-recipe namespace.
  4. Cache lookup: check the media cache directory for {fingerprint}.wav. If it exists, return immediately (cache hit).
  5. Lock: acquire exclusive fs2 file lock on {fingerprint}.wav.lock to prevent concurrent ffmpeg invocations for the same source file. This is important for parallel FA processing where multiple groups reference the same audio.
  6. Re-check: another task may have completed conversion while we waited.
  7. Convert: ffmpeg -y -nostdin -v error -xerror -i source -acodec pcm_s16le -ar 16000 -ac 1 tmp.wav. The locked slot becomes a produced slot only after its own temporary path passes strict conversion.
  8. Atomic rename: publish the produced slot while retaining its lock; release the lock only after the rename succeeds.

ffmpeg Arguments

FlagPurpose
-yOverwrite output without asking
-nostdinPrevent an unattended conversion from consuming terminal input
-v errorEmit error diagnostics, which prevent output admission
-xerrorStop decoding at the first error
-i sourceInput file (mp4, m4a, etc.)
-acodec pcm_s16le16-bit signed PCM (what soundfile reads natively)
-ar 1600016 kHz sample rate (FA/ASR model input rate)
-ac 1Mono (models expect single channel)

Cache Management

# Default cache location
ls ~/Library/Application\\ Support/batchalign3/media_cache/

# Or relocate it for isolated runs
export BATCHALIGN_MEDIA_CACHE_DIR=/tmp/ba-media-cache

# Inspect or clear both analysis + media caches
batchalign3 cache stats
batchalign3 cache clear --yes

Where ensure_wav Is Called

ensure_wav is called in four dispatch paths, always after media resolution and before the audio path is passed to Python workers:

Dispatch PathFilePurpose
FA (align)runner/dispatch/fa_pipeline.rsBefore audio identity + FA inference
Transcriberunner/dispatch/transcribe_pipeline.rsBefore ASR inference
Benchmarkrunner/dispatch/benchmark_pipeline.rs:process_one_benchmark_fileBefore Rust benchmark orchestration dispatches ASR
Media analysisrunner/dispatch/media_analysis_v2.rsBefore openSMILE/AVQI prepared-audio execution

Error Handling

Whole-file and segment conversions use strict ffmpeg decoding (-xerror), with error-level diagnostics. A zero exit status accompanied by decoding errors is also rejected. Partial output is removed before returning the conversion error; it cannot be published as a successful new conversion. The strict conversion recipe uses a new cache namespace, preserving old entries without accepting them as strict-recipe hits. This does not certify preexisting cache bytes or media formats passed through without conversion.

If conversion fails, the file is marked with a clear error:

Media conversion failed for ACWT01a.cha: ffmpeg not found in PATH.
Hint: install ffmpeg (https://ffmpeg.org/download.html) or convert
your input audio to .wav beforehand.

or:

Media conversion failed for example.cha: ffmpeg conversion failed
for /path/to/media/example.mp4: [stderr]

The job continues processing remaining files, one conversion failure does not abort the entire job.

Media Resolution

Before conversion can happen, the server must find the audio file. Resolution depends on the dispatch mode:

paths_mode / execution-host local

Audio files sit alongside the .cha files in the input directory. The server looks for a file with the same stem and a known media extension:

input/ACWT01a.cha  →  input/ACWT01a.mp4  (or .wav, .mp3, etc.)

shared-filesystem server mode (--server for audio commands)

The CLI no longer asks the server to infer remote media from client-specific path mappings. For audio commands, explicit --server submits filesystem paths via paths_mode:

  • source_paths: absolute input paths the server must be able to read
  • output_paths: absolute output paths the server must be able to write

This means the clean operational model is:

  • run the CLI on the execution host itself, or
  • use a standardized shared mount layout so the server sees the same paths

For direct HTTP content-mode submissions, Batchalign only trusts server-visible local paths such as source_dir, local media_mappings, or an explicit --media-dir. The important rule is that the mapping is local to the execution host, not a way to dereference an arbitrary remote client’s private directory layout.

MP4 Media on Network Volumes

Total: 16,739 MP4 files across all volumes.

VolumeMP4MP3WAV
CHILDES7,98820,92411,042
aphasia2,9733,140601
ca1,8014,6964,139
phon1,4379,3129,018
fluency1,2171,12458
class4382619
tbi262145149
rhd1984251
asd1014737
slabank835,4783,649
open8200
homebank652,32022,455
psychosis36979479
samtale207372
dementia156,1172,456
psyling1300
biling0315228
motor000

Benchmarking Considerations

  • First run on MP4 files: includes WAV conversion time (~seconds per file depending on duration)
  • Subsequent runs: WAV is cached, no conversion overhead
  • For fair benchmarks: either use --override-media-cache or ensure both old/new runs have the same cache state (warm or cold)
  • For %wor-only fixes: conversion cache is irrelevant since the audio doesn’t change. FA cache keys include audio identity, so same audio = same cached alignment.
  • Re-alignment scenario: if re-aligning files that already had alignment, both the FA cache and the media conversion cache will be warm. Use --override-media-cache for cold-start numbers.

Dependencies

  • ffmpeg must be on PATH for mp4/m4a/webm/wma conversion. Without it, those formats fail with a clear error. WAV/MP3/FLAC/OGG work without ffmpeg.
  • ffprobe (bundled with ffmpeg) is used for audio duration probing in the FA pipeline. Optional, if unavailable, proportional estimation uses a fallback.
  • blake3 crate for content fingerprinting.
  • fs2 crate for cross-platform file locking.

This page last changed: 2026-09-10 (commit 423245db). The whole book last changed: 2026-09-16 (commit 34d249d8).