6.8 KiB
Audio workspace tab — Design
Goal: Promote audio from a pair of cramped transport-row controls to a first-class Audio deck tab that unifies everything audio: exact-length extraction, light non-destructive editing of the extracted sound, an audio-dedicated dataset workflow, and the existing scan/classify controls.
Runs in: Python/Qt client (main.py) + core/ helpers. No server changes.
Approach: Replace the current Scan deck tab with an Audio tab.
Because the tab now covers four concerns, its body is a QToolBox accordion
(one pane open at a time) rather than flat stacked group-boxes. Existing
scan/extraction widgets are re-parented (same handlers/signals) — no behavior
change beyond relocation.
Deck order after: Export | Crop & Track | Audio. The ScanResultsPanel
(3rd splitter pane) is untouched.
Delivered in two phases. Phase 1 is self-contained and shippable; Phase 2 adds the dataset workflow.
Deck wiring (both phases)
_build_control_deck (main.py:4661): swap _tab_scan → _tab_audio —
_deck_key="audio", _label="Audio", _pinned=False; replace it in
_deck_panels; one addTab. The pin/split machinery is _deck_panels-driven,
so it picks the new panel up automatically. Old persisted "scan" pin-state is
ignored (harmless). Nothing auto-switches to the old scan tab — verified:
_tab_scan is referenced only in construction + its builder.
_build_scan_tab is renamed _build_audio_tab and rebuilt as a QToolBox
with the panes below.
Phase 1 — Extract, Edit, Classify
Pane 1 — Extract & Edit
Relocated from the transport row (removed there): _spn_audio_len (length)
and _btn_extract_audio (♪ Extract). The teal timeline region band is
unaffected — still driven by _spn_audio_len.
New controls:
_cmb_audio_fmt— output format (WAV / MP3 / FLAC / M4A / OGG / OPUS), persisted to QSettingsaudio_extract_fmt; pre-selects the save-dialog filter so the format isn't re-picked each time.- Waveform strip — a compact widget rendering the current region (or
last-extracted clip). Generated from decoded PCM (reuse
core/audio_scan._load_audio_ffmpeg) orffmpeg showwavespic. Read-only in v1 (no drag handles). - Play / Stop — audition the current region/clip. Playback backend decided
in the plan (likely a short ffmpeg-decoded buffer through a
QMediaPlayer/QSoundEffect, kept separate from the mpv video player).
Editing (non-destructive, ffmpeg filter chain at render time):
| Control | Filter |
|---|---|
| Trim in / out (refine past the length spinbox) | atrim / adjusted -ss / -t |
| Fade in / out (s) | afade=t=in / afade=t=out |
| Normalize (toggle) | loudnorm |
| Gain (dB) | volume |
core/ffmpeg.build_audio_clip_command gains an optional filters param →
appended as -af "<chain>". Foley/existing callers pass none → byte-identical
output. Extract applies the current edit settings; nothing is written until you
save.
Pane 2 — Scan / Classify
Relocated verbatim from the old Scan tab: model combo + _btn_model_history;
_btn_scan, _btn_auto_export, _btn_speech, _btn_scan_mode;
_spn_auto_fuse, _sld_threshold. Same handlers, same ScanResultsPanel
output.
(Train / Hard-negatives / Dataset-stats buttons join Pane 3 in Phase 2; in Phase 1 they stay wherever they are today — the menu bar.)
Phase 2 — Audio dataset
Pane 3 — Dataset (audio-dedicated)
A dedicated audio dataset workflow + view, combining browser-plus-label, add-from-extraction, and in-tab access to the existing tools.
Key data-model finding (the one open decision — confirm before building):
the existing classifier trains on (source_video, time-ranges) per profile —
db.get_training_data(...) returns (source_video_path, positive_times, soft_times, negative_times) per video; standalone clip files are not the
training unit. An extracted audio region always knows its
(source_video, start, end). So the cheapest, most powerful integration is
merged: "Add to dataset" records the region as a labeled positive/negative
time-range in the same per-profile DB the video-export workflow already
feeds — no new trainer, no schema change. The Audio dataset pane is then an
audio-oriented view/filter over that shared dataset.
- Recommended (merged): audio extraction becomes another way to label
training data; reuses
get_training_data,get_training_stats, hard-negatives, andtrain_classifierunchanged. - Alternative (separate): a standalone folder of
.wavclips + owndataset.json+ a new file-based training path. More code, duplicates the trainer. Only needed if the audio set must stay entirely independent of the video profiles.
(Brainstorming default was "separate"; inspecting the DB flips the recommendation to "merged." Flagged for confirmation.)
Pane contents (assuming merged):
- Add to dataset — from the current/last extraction:
[class ▾]+(•positive / ○negative)+[+ Add]. Writes a labeled time-range into the DB for the active profile (and, for standalone-clip parity, writes the clip'sdataset.jsonlabel viaupsert_clip_annotationwhen a file was saved). - Browser — a list of the audio dataset entries (class, pos/neg, source, time-range), filterable by class; select to seek the source video to that region; remove entries.
- Tools — in-tab buttons for the existing dialogs: Dataset Statistics
(
DatasetStatsDialog), Manage Hard Negatives (HardNegativesDialog), Train classifier (existing Train dialog).
Persistence & migration
- QSettings gains
audio_extract_fmt(+ edit defaults: fade/normalize/gain) alongside existingaudio_extract_len,audio_extract_dir. - Deck pin-state key
"scan"→"audio"; absent → default unpinned. - Phase 2 (merged): no schema change — reuses existing training tables /
dataset.json. Phase 2 (separate) would add a folder convention only.
Testing
tests/test_ui_structure.py: updatetest_extract_audio_controls_existto locate controls in the Audio tab; add a test that the Audio deck tab exists as aQToolBoxwith Extract/Classify panes and that scan controls remain reachable.tests/test_utils.py: extend the audio-command tests for the new-affilter chain (trim/fade/normalize/gain), keeping existing codec-by-extension assertions.- Phase 2: a DB test that "add to dataset" produces a training-data row
consumable by
get_training_data.
What this does NOT do
- No full waveform-drag editor in v1 — editing is parametric (trim/fade/normalize/gain).
- No change to the Foley/LTX-2 export pipeline or
ScanResultsPanel. - No new ML model or embedding change.
- Phase 1 makes no DB/schema change; the dataset-model decision is confined to Phase 2.