Skip to content

transcribe phase F: export — formats, merge, and a hard-coded cue policy #24

Description

@fyang0507

Context

Decomposes #12, phase F of
TRANSCRIBE_IMPLEMENTATION_PLAN.md.

export is deterministic post-processing: no stack, no packages, no plan/run split. It is the
command that turns a result document into something a person or a video editor reads, and the
command where a missing capability has to fail closed rather than invent cue bounds.

Cue segmentation policy — parameters, break-priority order, millisecond quantization — stays
#10. This issue consumes it with the
policy hard-coded for v1, which CONTRACT §4 explicitly permits.

Scope

src/audio_cli/export/ — the command, writers.py for the five formats, cues.py for
segmentation.

  • srt, vtt require word timing and refuse without it: timing_required_for_format at exit 2,
    with a fix naming the transcribe run line that would produce it. Cue bounds come from the first
    and last word of a segment, never from a segment extent — on qwen-1.7b there is no segment extent
    to misuse, which is exactly why word_timestamps is requested up front.
  • vtt carries speaker labels as voice tags when diarization is present, which is a commitment
    to VTT as a real format rather than SRT with dots.
  • md, txt are for people.
  • jsonl is one segment object per line ordered by start time — the same segment objects the JSON
    result carries, without the envelope or the provenance. It has no timing requirement, and because it
    drops the provenance it is an export for reading rather than an artifact to audit against.
  • Several --input documents merge in timeline order with ids re-numbered as it goes, which is
    what makes a partial run plus its resumed remainder usable without anyone hand-editing JSON. Ids are
    document-scoped and explicitly not stable across runs, so re-numbering is the mechanism rather than a
    workaround.

Two things the recorded artifacts impose on the splitter, and neither is a convention question:

  • Breaking at punctuation means locating a mark in the sentence text and mapping it to a word
    index
    , which is sound only under the punctuation floor's invariant and only case-insensitively,
    because FireRedPunc lowercases and then re-capitalizes.
  • A segment may carry text with no word stream at all — VibeVoice's non-speech event tags — so the
    splitter needs a rule for those rather than assuming every segment yields cues. Whether an event tag
    should render as an SDH cue is a subtitle-convention question and belongs to export: cue segmentation for --format srt|vtt #10.

Timing quality is not validated: boundary MAE/P95 is unmeasured for both FireRed's native times and
the aligner, so these files are producible and are not claimed broadcast-acceptable. The
cue_timing_unvalidated warning says so from the payload.

Out of scope

Cue-policy tuning options (#10). Any change to the transcription path.

Acceptance

  • --format srt on a transcript with no words exits 2 with timing_required_for_format, and
    the fix is a runnable transcribe run line with word_timestamps added.
  • Cue bounds equal the first and last word bounds of their segment; no cue bound is ever read from
    a segment extent.
  • A segment with text and no word stream produces no cue, and does not raise.
  • Merging a partial result and its resumed remainder yields contiguous ids, no duplicated span,
    and bounds already on the original timeline — no consumer arithmetic.
  • vtt renders a voice tag per speaker when diarization is present and none when it is absent.
  • jsonl carries no envelope and no provenance, one segment per line, ordered by start.
  • uv run --extra dev pytest green.

Depends on

  • #19export reads the result schema.
  • #21 — a real transcript with word timing to export, and the partial/resumed pair the merge exists for.
  • #10 — the cue policy this consumes, hard-coded in v1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions