Captions: translated subtitles for the training videos, no re-render, no re-voicing

Manson asked whether the training videos could be in other languages. A real dub
means re-rendering every video per language: translated Romance-language speech runs
15-25% longer than English and these are slide videos with fixed beat timings, so
swapped audio drifts off what is on screen. That turns 15 files into 75 and makes
every future lesson edit a five-way job. Captions keep ONE video and add small text
tracks beside it, so editing a lesson re-captions that lesson only.

tools/captions.mjs: ffmpeg pulls the audio, ElevenLabs Scribe transcribes with word
timings (the same STT we already use to verify voiceovers), words are grouped into
SENTENCES, and only then translated. Translating cue-by-cue is why most auto-captions
read badly at cue boundaries. Translation goes through our own /api/public/translate,
so every phrase caches forever in translations.json and costs once across the site.
The STT response is cached on disk because it costs money; never pay for it twice.

Language choice is evidence, not instinct. The translation cache shows real member
demand: Italian and French far ahead, then Spanish, then GERMAN - which beats
Portuguese by more than double, the opposite of what we assumed. Proof of concept is
Italian on the 5-minute overview.

Captions are deliberately NOT on by default. An English reader does not want them
forced over the picture; someone who already switched the site to Italian almost
certainly does. public/vtt-lang.js shows the track matching their 🌐 choice and
leaves the player's CC button to do the rest.

Two bugs this caught in my own code, both found by reading the output:
- the line wrapper truncated each cue to two lines and SILENTLY DELETED the overflow,
  so "the whole plan fits in one sentence" shipped as "fits in" then "sentence". It
  now chunks by the real wrapped line count and never drops a word.
- a one or two word tail ("sentence." alone on screen) folds back into the previous
  cue.
The suite asserts all 780 transcript words survive into the English track.

Also: .vtt had no Content-Type mapping, so it served as octet-stream and browsers
silently ignore such a track. qa/captions-e2e.mjs (12 assertions) reads the parsed
cues back out of the player rather than trusting the markup, which is the only way to
catch that class of failure. profiles-unit 28 and gate-e2e 33 still green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-17 06:06:34 -05:00
parent 8bdc105a7a
commit b3385ca3f8
7 changed files with 3094 additions and 1935 deletions
+2 -2
View File
@@ -8,7 +8,7 @@
<main>
<section class="section"><div class="wrap"><div class="section-head"><div class="eyebrow">Team training</div><h1 style="font-size:clamp(34px,5vw,54px);margin:8px 0 10px">Watch. Learn. <span class="gold">Duplicate.</span></h1><p>Short videos take you from zero to your Premium position — in order. Watch them all before you join, and share this page with your two directs when it's their turn.</p></div>
<div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 1 · 5 min · UPDATED</div><h2 style="margin:6px 0 6px">How the team build works</h2><p style="color:var(--muted);margin:0 0 12px">The full picture in five minutes: the 2-direct strategy, both income streams, the catch-or-pass rule, spillover, the moving link, and how your dashboard coaches you through all of it.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/v/rmc-team-overview-poster.jpg" src="/v/rmc-team-overview-f8856dc743.mp4"></video></div>
<div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 1 · 5 min · UPDATED</div><h2 style="margin:6px 0 6px">How the team build works</h2><p style="color:var(--muted);margin:0 0 12px">The full picture in five minutes: the 2-direct strategy, both income streams, the catch-or-pass rule, spillover, the moving link, and how your dashboard coaches you through all of it.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/v/rmc-team-overview-poster.jpg" src="/v/rmc-team-overview-f8856dc743.mp4"><track kind="captions" srclang="en" label="English" src="/captions/rmc-team-overview-f8856dc743.en.vtt"><track kind="captions" srclang="it" label="Italiano" src="/captions/rmc-team-overview-f8856dc743.it.vtt"></video></div>
<div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 2 · 3 min</div><h2 style="margin:6px 0 6px">Create your MetaMask wallet</h2><p style="color:var(--muted);margin:0 0 12px">Install MetaMask safely and set up your wallet. Never share your Secret Recovery Phrase with anyone.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/create_metamask_wallet_poster.jpg" src="/training/create_metamask_wallet.mp4"></video></div>
@@ -104,4 +104,4 @@
</div></section>
</main>
<footer class="wrap disclaimer">This is an independent RM Circle Team Build training resource. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a><a href="/privacy">Privacy</a><a href="/refunds">Refunds</a></div></footer>
<script src="/track.js"></script><script src="/rmc-wallet.js"></script><script src="/inapp-browser.js"></script><script src="/training.js"></script><script src="/wallet-notice.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script><script src="/nav-dash.js" defer></script></body></html>
<script src="/track.js"></script><script src="/rmc-wallet.js"></script><script src="/inapp-browser.js"></script><script src="/training.js"></script><script src="/wallet-notice.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/vtt-lang.js" defer></script><script src="/tg-app.js" defer></script><script src="/nav-dash.js" defer></script></body></html>