Commit Graph
10 Commits
Author SHA1 Message Date
ddidderr 1d03dcd587 feat: transcribe chunks with configurable parallel workers
Process up to eight chunks concurrently by default and add --jobs 1..64,
with --jobs 1 retaining sequential operation. Give each worker separate
encoding and request files, release payloads after completion, and merge
responses by original chunk index regardless of completion order.

Use a bounded worker scheduler that handles jobs finishing before wait is
called. Stop scheduling on an observed worker failure and terminate worker
process groups on failure or interruption before removing temporary files.
Include jobs interrupted between launch and scheduler registration in cleanup.
Preserve all-or-nothing stdout for both text and SRT output.

Document the Bash 5.1 requirement, concurrency option, resource usage, and
limits of cancelling requests already accepted by a provider.

Test Plan:
- python3 -B -m unittest -v: all 52 tests passed.
- Mocked provider tests verified eight concurrent requests by default,
  configurable limits, rolling worker replacement, isolated payloads,
  ordered text, and identical sequential/parallel SRT output.
- Verified immediate encoder failure, concurrent API failure, SIGINT and
  SIGTERM, child-process cancellation, cleanup, and no partial stdout.
- bash -n transcribe, ./transcribe --help, and
  git diff --cached --check: passed.
- No live provider request; credentials are unavailable in this environment.
2026-09-12 09:37:09 +02:00
ddidderr 5dd1b5811b fix: keep chunk details out of speaker headers
Render transcript-wide speaker numbers instead of exposing upload chunks in
reader-facing headers. Reconcile adjacent requests using at least two
unambiguous word-and-time matches in their shared audio, including when
provider speaker IDs change between requests. Reject conflicting mappings
and never treat matching numeric provider IDs as identity evidence.

Assign fresh IDs where continuity cannot be established and report that
uncertainty on stderr. Number only speakers retained after overlap trimming.
Document that overlap matching cannot identify a returning speaker who was
absent from the overlap, and add the reader-facing boundary to AGENTS.md.

Test Plan:
- python3 -B -m unittest -v: all 46 tests passed, including swapped IDs,
  ambiguous evidence, continuity across three requests, and plain headers.
- git diff --cached --check: passed.
- Tests use synthetic speaker labels; live diarization accuracy was not
  verified because provider credentials are unavailable in this environment.
2026-09-12 09:30:46 +02:00
ddidderr 38894c2549 fix: format text transcripts and show speaker markers
Chunked text output previously joined every word into one line and discarded
speaker labels. Add a shared plain-text formatter that wraps lines, breaks
paragraphs at speaker changes and pauses, and splits long turns while
preserving word order. Display provider speaker IDs with chunk scope where
needed, without implying that IDs identify the same person across requests.

Request structured output for short diarized recordings too, and warn when
speaker labels are missing or incomplete. Keep SRT formatting separate and
preserve existing line breaks for short requests without diarization.

Test Plan:
- python3 -B -m unittest -q: all 43 tests passed.
- bash -n transcribe and git diff --cached --check: passed.
- Reflowed the existing transcript locally and verified all 15,888 words
  were preserved; source audio and generated transcripts are not committed.
- A live excerpt check was blocked before upload by unavailable 1Password
  access; actual provider diarization remains unverified.
2026-09-12 09:26:21 +02:00
ddidderr 24b1030776 feat: enable diarization by default with an opt-out
Request speaker diarization through OpenRouter's Azure provider options for
all transcription uploads. Add --no-diarization to explicitly disable it for
both plain text and SRT output; returned speaker labels let existing SRT
cue generation split at speaker changes.

Default to 10-minute chunks when diarization is enabled because Microsoft
reports failures around 15 minutes and longer. Preserve the 30-minute
default with the opt-out and honor explicit --chunk-minutes values in both
modes. Document the options, duration limits, and output behavior.

Test Plan:
- python3 -B -m unittest -v: all 32 tests passed, including request options,
  default upload limits, opt-out combinations, and SRT speaker boundaries.
- bash -n transcribe: passed.
- ./transcribe --help: verified the new option and chunk defaults.
- git diff --cached --check: passed.
- Provider responses were mocked; no live API request was made.
2026-09-12 09:04:29 +02:00
ddidderr a5c535cfb9 feat: transcribe long recordings in overlapping chunks
Sending entire recordings in one request can exceed provider limits.
Decode audio once to a shared PCM sample clock and split uploads at a
configurable maximum of 1 to 30 minutes, defaulting to 30 minutes.
Prefer nearby pauses and include six seconds of overlap to protect words
at audio cuts while keeping each encoded MP3 within the duration cap.

Request word timestamps for chunked text and SRT output. Match overlapping
words and times to retain each passage once, warn when a join falls back
to timestamps, and generate SRT cues on the original recording timeline.
Reuse subtitle timestamp validation without reordering regressing words.
Only emit output after every request and conversion succeeds.

Document the Python dependency, temporary storage, boundary limitations,
and lack of automatic retry or resume. Keep single-request text formatting.

Test Plan:
- python3 -B -m unittest -v: all 29 tests passed, including mocked provider
  failures, native MP3 payload checks, boundary stitching, and SRT timing.
- bash -n transcribe and shellcheck transcribe: passed.
- git diff --cached --check: passed.
- Locally decoded and encoded the supplied 74-minute WebM into three MP3
  chunks; ffprobe confirmed each duration was below 30 minutes.
- Live transcription was not verified: the configured 1Password credential
  was unavailable.
2026-09-12 08:53:31 +02:00
ddidderr f8e50e4a3e fix(subtitles): tolerate regressing word timestamps
SRT conversion previously rejected otherwise valid word timestamps when a
start preceded the previous word's start. Clamp regressing starts to the
preceding nonempty word's start and extend ends only when necessary.
Preserve transcript order and the original response while continuing to
reject malformed timestamps. Document the adjustment and cover regressions,
cue boundaries, blank words, and invalid input.

Test Plan:
- python3 -B -m unittest -v: all eight tests passed.
- CLI conversion of a synthetic regressing response: passed.
- git diff --cached --check: passed.
- Actual recording verification blocked by unavailable 1Password auth.
2026-09-05 16:27:03 +02:00
ddidderr 4d7fcb1c8f feat: support create subtitles (.srt) 2026-09-05 16:14:16 +02:00
ddidderr ca08ad34bc feat: use 1password for API key 2026-09-05 16:14:02 +02:00
ddidderr a2c8502c61 feat: encode audio as 64 kbps MP3 instead of WAV
Convert the normalized audio to mono 16 kHz MP3 at 64 kbps with
FFmpeg's libmp3lame encoder instead of uncompressed WAV. This reduces
request payload size before base64 encoding and submitting to OpenRouter.
Update README documentation accordingly.
2026-09-05 15:16:56 +02:00
ddidderr e6b36c2b48 feat: add simple audio transcription command
Convert audio and video audio tracks to mono 16 kHz WAV with FFmpeg,
then submit them to OpenRouter's MAI-Transcribe-2 endpoint. Read the API
key from the environment and print the transcript to stdout for easy
redirection. Store request payloads in temporary files to avoid shell
argument size limits, and document dependencies and usage.

Test Plan:
- bash -n transcribe and shellcheck transcribe passed.
- All eight documented formats passed local FFmpeg conversion and mocked
  API request/response checks, including WAV rate and channel validation.
- git diff --cached --check passed.
- No live API request was made.
2026-09-05 11:40:26 +02:00