Skip to content

Transcribe long recordings in parts, and say why a transcription failed - #64

Merged
davior merged 1 commit into
mainfrom
claude/beautiful-wright-og4xur
Nov 5, 2026
Merged

davior merged 1 commit into
mainfrom
claude/beautiful-wright-og4xur

Conversation

@davior

@davior davior commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes two problems with transcribing a long YouTube import: it failed with HTTP 413, and the panel showed only "error" without the reason.

Long recordings. A transcription used to send the whole recording's audio to Gecko Suite in one request. At over two hours that reaches the 300 MB cap of both the suite and its nginx. Nginx answers with an HTML 413, and the client can only report that as "HTTP 413". Raising the caps would only move the limit, because one call that long also runs into the 1900 s read timeouts and Deepgram's own processing time. So GAM now sends the audio in parts:

  • audio.split_audio cuts the extracted FLAC into 30-minute pieces (transcribe.PART_SECONDS). Each piece is cut on its own with -ss before -i and re-encoded, so the cut lands on an exact sample and the file records its length. The segment muxer was tried first, but its FLAC pieces have no length in their header.
  • Audio shorter than one part is returned as it is, so short files take exactly the same path as before.
  • transcribe.run transcribes each part, through the suite or the local Deepgram key. Between parts it reports "part n of m", and that report is also where a cancel takes effect.
  • join_parts moves every segment's and word's times by the start of its part, then stores the joined transcript through the existing store_transcript.
  • Deepgram identifies speakers separately in each part, so speaker numbers can restart from one part to the next. They are kept as Deepgram gave them rather than guessed across parts.
  • On the suite, each part's seconds are recorded there. In local mode GAM still writes one usage row for the whole recording.

The missing reason. TranscriptPanel only followed the list of active jobs. A failed job drops out of that list, so its error_message never showed. Now:

  • When the job it was watching ends, the panel reads that job by id (activityApi.get).
  • When you open an asset whose transcript is in error, it finds the last transcription job (from all jobs, not just active ones) and shows its message.

Only GAM changes. The suite, Caddy and nginx are untouched.

Test plan

  • Backend: pytest -q passes (1547), including the new tests:
    • split_audio splits a 5 s tone into 3 parts with offsets 0, 2 and 4, and returns short audio as it is.
    • A recording longer than one part makes one suite request per part.
    • Segment and word times come back offset by where their part starts, and the job reports "part 3 of 3".
  • Frontend: npm run lint, npm test (487) and npm run build pass, including the new tests:
    • A job that fails while being watched shows its reason.
    • Opening an asset after its transcription failed shows the last transcription's reason.

Deployment

Rebuild both the backend and the frontend. There is no migration and no new setting.

git pull origin main
docker compose up --build -d
docker compose ps
curl -f http://localhost:${APP_PORT}/api/health

To check it:

  • Open the over-2-hour YouTube asset and press Re-transcribe. The background processes show "Transcribing · part n of m", and the transcript finishes.
  • Click a timestamp near the end: the player jumps to where those words are spoken.
  • If a transcription fails for any other reason, the transcript panel now shows the reason in red.

Generated by Claude Code

A two-hour YouTube import failed its transcription with HTTP 413: the whole
recording's audio went to Gecko Suite in one request, at the 300 MB cap of the
suite and its nginx. The audio now goes in half-hour parts, cut sample-accurately
with ffmpeg, and the parts' transcripts are joined with each word moved to where
it was said. Audio shorter than one part takes exactly the path it took before.

The transcript panel followed only the active job list, so a job that failed
dropped out of it and the panel showed "error" without the reason. It now reads
the job it was watching by id once it leaves that list, and on opening an asset
whose transcript is in error it finds the last transcription's message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1geD2ELWgL2sDpxYbx3Aw
@davior
davior marked this pull request as ready for review November 5, 2026 08:14
@davior
davior merged commit 3f03eac into main Nov 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants