Files
8-cut/docs/plans/2026-07-02-audio-tab-design.md

156 lines
6.8 KiB
Markdown

# 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 QSettings `audio_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`) or `ffmpeg 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, and `train_classifier` unchanged.
- **Alternative (separate):** a standalone folder of `.wav` clips + own
`dataset.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's `dataset.json` label via `upsert_clip_annotation`
when 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 existing `audio_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`: update `test_extract_audio_controls_exist` to
locate controls in the Audio tab; add a test that the Audio deck tab exists as
a `QToolBox` with Extract/Classify panes and that scan controls remain
reachable.
- `tests/test_utils.py`: extend the audio-command tests for the new `-af`
filter 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.