Skill Tracer
Skills › HyperFrames › media-use › transcript-handling.md

transcript-handling.md

97 lines · skills/media-use/audio/references/captions/transcript-handling.md at a595755

Open on GitHub
1# Transcript Guide
2
3For the `transcribe` CLI invocation, the `.en`-translates-non-English rule, and whisper model selection, see [`../transcribe.md`](../transcribe.md). This file covers what to do with the resulting transcript when authoring captions: input formats, mandatory quality checks, cleaning code, external-API fallbacks.
4

Transcript Guide

For the transcribe CLI invocation, the .en-translates-non-English rule, and whisper model selection, see [../transcribe.md](../transcribe.md). This file covers what to do with the resulting transcript when authoring captions: input formats, mandatory quality checks, cleaning code, external-API fallbacks.

Raw lines 1 to 4
5## Supported Input Formats
6
7The CLI auto-detects and normalizes these formats:
8
9| Format | Extension | Source | Word-level? |
10| --------------------- | --------- | --------------------------------------------------------------------------- | ----------------- |
11| whisper.cpp JSON | `.json` | `hyperframes init --video`, `hyperframes transcribe` | Yes |
12| OpenAI Whisper API | `.json` | `openai.audio.transcriptions.create({ timestamp_granularities: ["word"] })` | Yes |
13| SRT subtitles | `.srt` | Video editors, subtitle tools, YouTube | No (phrase-level) |
14| VTT subtitles | `.vtt` | Web players, YouTube, transcription services | No (phrase-level) |
15| Normalized word array | `.json` | Pre-processed by any tool | Yes |
17**Word-level timestamps produce better captions.** SRT/VTT give phrase-level timing, which works but can't do per-word animation effects.

Supported Input Formats

The CLI auto-detects and normalizes these formats:

FormatExtensionSourceWord-level?
whisper.cpp JSON.jsonhyperframes init --video, hyperframes transcribeYes
OpenAI Whisper API.jsonopenai.audio.transcriptions.create({ timestamp_granularities: ["word"] })Yes
SRT subtitles.srtVideo editors, subtitle tools, YouTubeNo (phrase-level)
VTT subtitles.vttWeb players, YouTube, transcription servicesNo (phrase-level)
Normalized word array.jsonPre-processed by any toolYes

Word-level timestamps produce better captions. SRT/VTT give phrase-level timing, which works but can't do per-word animation effects.

Raw lines 5 to 18
19## Transcript Quality Check (Mandatory)
21After every transcription, **read the transcript and check for quality issues before proceeding.** Bad transcripts produce nonsensical captions. Never skip this step.

Transcript Quality Check (Mandatory)

After every transcription, read the transcript and check for quality issues before proceeding. Bad transcripts produce nonsensical captions. Never skip this step.

Raw lines 19 to 22
23### What to look for
25| Signal | Example | Cause |
26| ---------------------------- | -------------------------------------- | ---------------------------------------------------------------------------- |
27| Music note tokens (`♪`, `�`) | `{ "text": "♪" }` or `{ "text": "�" }` | Whisper detected music, not speech |
28| Garbled / nonsense words | "Do a chin", "Get so gay", "huh" | Model misheard lyrics or background noise |
29| Long gaps with no words | 20+ seconds of only `♪` tokens | Instrumental section — expected, but high ratio means speech is being missed |
30| Repeated filler | Many "huh", "uh", "oh" entries | Model is hallucinating on music |
31| Very short word spans | Words with `end - start < 0.05` | Unreliable timestamp alignment |

What to look for

SignalExampleCause
Music note tokens (♪, �){ "text": "♪" } or { "text": "�" }Whisper detected music, not speech
Garbled / nonsense words"Do a chin", "Get so gay", "huh"Model misheard lyrics or background noise
Long gaps with no words20+ seconds of only ♪ tokensInstrumental section — expected, but high ratio means speech is being missed
Repeated fillerMany "huh", "uh", "oh" entriesModel is hallucinating on music
Very short word spansWords with end - start < 0.05Unreliable timestamp alignment
Raw lines 23 to 32
33### Automatic retry rules
35**If more than 20% of entries are `♪`/`�` tokens, or the transcript contains obvious nonsense words, the transcription failed.** Do not proceed with the bad transcript. Instead:
371. **Retry with `medium.en`** if the original used `small.en` or smaller:
38 ```bash
39 npx hyperframes transcribe audio.mp3 --model medium.en
40 ```
412. **If `medium.en` also fails** (still >20% music tokens or garbled), tell the user the audio is too noisy for local transcription and suggest:
42 - Providing lyrics manually as an SRT/VTT file
43 - Using an external API (OpenAI or Groq Whisper — see below)
443. **Always clean the transcript** before building captions — filter out `♪`/`�` tokens and entries where `text` is a single non-word character. Only real words should reach the caption composition.

Automatic retry rules

**If more than 20% of entries are ♪/� tokens, or the transcript contains obvious nonsense words, the transcription failed.** Do not proceed with the bad transcript. Instead:

  1. **Retry with medium.en** if the original used small.en or smaller: ``bash npx hyperframes transcribe audio.mp3 --model medium.en
  2. **If medium.en also fails** (still >20% music tokens or garbled), tell the user the audio is too noisy for local transcription and suggest:
  3. Providing lyrics manually as an SRT/VTT file
  4. Using an external API (OpenAI or Groq Whisper — see below)
  5. Always clean the transcript before building captions — filter out ♪/� tokens and entries where text is a single non-word character. Only real words should reach the caption composition.
Raw lines 33 to 45
46### Cleaning a transcript
48After transcription (even with a good model), strip non-word entries:
50```js
51var raw = JSON.parse(transcriptJson);
52var words = raw.filter(function (w) {
53 if (!w.text || w.text.trim().length === 0) return false;
54 if (/^[♪�\u266a\u266b\u266c\u266d\u266e\u266f]+$/.test(w.text)) return false;
55 if (/^(huh|uh|um|ah|oh)$/i.test(w.text) && w.end - w.start < 0.1) return false;
56 return true;
57});
58```
60For model-selection guidance by content type, see [`../transcribe.md`](../transcribe.md) → "Picking a model by content type".

Cleaning a transcript

After transcription (even with a good model), strip non-word entries:

var raw = JSON.parse(transcriptJson);
var words = raw.filter(function (w) {
  if (!w.text || w.text.trim().length === 0) return false;
  if (/^[♪�\u266a\u266b\u266c\u266d\u266e\u266f]+$/.test(w.text)) return false;
  if (/^(huh|uh|um|ah|oh)$/i.test(w.text) && w.end - w.start < 0.1) return false;
  return true;
});

For model-selection guidance by content type, see [../transcribe.md](../transcribe.md) → "Picking a model by content type".

Raw lines 46 to 61
62## Using External Transcription APIs
64For the best accuracy, use an external API and import the result:
66**OpenAI Whisper API** (recommended for quality):
68```bash
69# Generate with word timestamps, then import
70curl https://api.openai.com/v1/audio/transcriptions \
71 -H "Authorization: Bearer $OPENAI_API_KEY" \
72 -F file=@audio.mp3 -F model=whisper-1 \
73 -F response_format=verbose_json \
74 -F "timestamp_granularities[]=word" \
75 -o transcript-openai.json
77npx hyperframes transcribe transcript-openai.json
78```
80**Groq Whisper API** (fast, free tier available):
82```bash
83curl https://api.groq.com/openai/v1/audio/transcriptions \
84 -H "Authorization: Bearer $GROQ_API_KEY" \
85 -F file=@audio.mp3 -F model=whisper-large-v3 \
86 -F response_format=verbose_json \
87 -F "timestamp_granularities[]=word" \
88 -o transcript-groq.json
90npx hyperframes transcribe transcript-groq.json
91```

Using External Transcription APIs

For the best accuracy, use an external API and import the result:

OpenAI Whisper API (recommended for quality):

# Generate with word timestamps, then import
curl https://api.openai.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -F file=@audio.mp3 -F model=whisper-1 \
  -F response_format=verbose_json \
  -F "timestamp_granularities[]=word" \
  -o transcript-openai.json

npx hyperframes transcribe transcript-openai.json

Groq Whisper API (fast, free tier available):

curl https://api.groq.com/openai/v1/audio/transcriptions \
  -H "Authorization: Bearer $GROQ_API_KEY" \
  -F file=@audio.mp3 -F model=whisper-large-v3 \
  -F response_format=verbose_json \
  -F "timestamp_granularities[]=word" \
  -o transcript-groq.json

npx hyperframes transcribe transcript-groq.json
Raw lines 62 to 92
93## If No Transcript Exists
951. Check the project root for `transcript.json`, `.srt`, or `.vtt` files.
962. If none found, run [`../transcribe.md`](../transcribe.md) — pick the starting model from "Picking a model by content type" there.
973. Run the quality check above. If it fails, retry with a larger model or fall back to manual lyrics / external API.

If No Transcript Exists

  1. Check the project root for transcript.json, .srt, or .vtt files.
  2. If none found, run [../transcribe.md](../transcribe.md) — pick the starting model from "Picking a model by content type" there.
  3. Run the quality check above. If it fails, retry with a larger model or fall back to manual lyrics / external API.
Raw lines 93 to 97