video-use Best Practices (Hebrew)
Problem
video-use ships with a strong English-first default: the bundled SUB_FORCE_STYLE uses Helvetica (no Hebrew glyphs, renders as boxes), 2-word UPPERCASE chunks (Hebrew has no uppercase), and the filler-removal step assumes an English filler lexicon while Scribe itself does not tag fillers per-word in any language. Hebrew creators using video-use hit the same three walls every time: missing-glyph boxes burned into the final video, captions that look wrong because UPPERCASE doesn't exist in Hebrew, and a "filler removal" step that leaves "אֶה", "כאילו", "יעני", and friends in. On macOS there's also a fourth wall: libass + SRT BiDi reordering is unreliable even with a fully-built libass, so Hebrew sentences render as characters drawn left-to-right in source byte order rather than in proper RTL visual order. This skill is the Hebrew-specific override layer on top of video-use's 12 Hard Rules. None of the upstream rules change, only the style values, font fallback chain, filler lexicon, caption-burn recipe, and self-eval frame checks.
Pricing you should understand before you start
video-use is free; underlying services are paid. Mode determines cost.
| Mode | What | Best for | Cost / 1hr video |
|---|---|---|---|
A. Captions-only (scripts/captions-only.sh) |
Transcribe + burn Hebrew captions on original. No cuts. | Lectures, webinars, podcast videos. | ~$1-3 total |
| B. Full cut (default video-use flow) | Inventory, strategy, cut, render, self-eval. | Teaser from raw footage; multi-take selection. | ~$25-60 (1hr); $120-300 (3hr). Scales super-linearly. |
Scribe v2 is $0.22 per hour of audio on the API rate card. scribe_v1 was removed on 9 July 2026, so scribe_v2 is the only model the scripts may send. Claude tokens dominate Mode A's cost; local FFmpeg rendering is $0.
Two rate cards, do not mix them. $0.22/hr is pay-as-you-go API. On a subscription you spend credits: STT bills ~330 credits/minute, so an hour is 19,800 credits, two thirds of Starter's 30,000 ($6/mo). The free plan's 10,000/month covers ~30 minutes. Warn non-technical users upfront.
Pick the cheap one if unsure. Captions-only is 20-100x cheaper on long videos, produces the same caption quality, and caches the transcript, so cutting later costs little extra.
Pricing trap: no_verbatim=true sounds like a saving but is destructive (the agent loses per-instance keep/cut decisions, usually forcing a re-transcription). Keep no_verbatim=false and run the Hebrew lexicon post-pass instead.
Rate-card breakdown, free-tier arithmetic, validated runs and the offline Whisper fallback: references/pricing.md.
Instructions
This skill is an overlay on top of video-use's upstream SKILL.md. Read the upstream SKILL.md first for the 12 Hard Rules, the EDL JSON schema, takes_packed.md, render.py, and the parallel-animation pattern. Then apply the Hebrew deltas below.
Step 0 (FIRST TURN ONLY): Price disclaimer + mode selection
On the first turn of a new session (not on follow-ups within the same session), state the price disclaimer AND ask which mode. Both in one message. Do NOT re-show this on subsequent turns once the user has picked.
"לפני שמתחילים, חשוב שתדע: השימוש בסקיל הזה צורך מ-ElevenLabs (כ-$0.22 לשעת אודיו) ומ-Anthropic Claude (תלוי במסלול). העלות מצטברת אצלך, לא אצלי. שני מסלולים:
(א) כתוביות בלבד על כל הסרטון, בלי חיתוך. כ-$1 עד $3 לכל סרטון. מתאים להרצאה, וובינר, פודקאסט.
(ב) חיתוך לטיזר/קליפ קצר עם כתוביות. כ-$10 עד $300 לפי אורך המקור.
מה אתה מעדיף?"
English: "Before we start, costs: ElevenLabs (~$0.22/hr) + Claude (depends on mode). Two paths: (A) captions-only ~$1-3, or (B) full cut ~$10-300. Which one?"
Routing: A → Step 8 (skip 1-7). B → Step 1. Unsure → recommend A (Scribe transcript caches, so adding B later only adds ~$0.08).
Step 1: Verify the environment before the first render
Hard Rule 1 (subtitles applied LAST in filter chain) plus libass font resolution means a missing Hebrew font, a missing libass build, or a missing python-bidi install all produce silent failures: the SRT renders as □□□ boxes burned into the final video, or as Hebrew with characters in the wrong positions. Verify all three before you cut.
# 1. ffmpeg has libass + fontconfig + libharfbuzz
ffmpeg -version 2>&1 | grep -oE 'enable-(libass|fontconfig|libharfbuzz|libfreetype)' | sort -u
# Expect: enable-fontconfig, enable-libass, enable-libfreetype, enable-libharfbuzz
# 2. Hebrew fonts are installed (Heebo, Rubik, Assistant, Noto Sans Hebrew)
fc-list :lang=he | head -5
# 3. python-bidi is installed (needed for the macOS BiDi workaround in Step 7)
python3 -c 'import bidi; print(bidi.__version__)'
If any check fails:
- Missing libass in ffmpeg → read
references/macos-ffmpeg-setup.md, install the static evermeet build or use the homebrew-ffmpeg tap. - Missing Hebrew fonts →
bash scripts/install-hebrew-fonts.sh(idempotent installer for the canonical 4 fonts). - Missing python-bidi →
pip3 install python-bidi.
Step 2: Override SUB_FORCE_STYLE for Hebrew
The bundled bold-overlay style in render.py is FontName=Helvetica,FontSize=18,Bold=1,...,Alignment=2,MarginV=90, against libass's default PlayResY: 288, i.e. 31.25% of frame height. (Upstream's own SKILL.md prose says MarginV=35; its shipped code says 90. The code is ground truth.) For Hebrew, override before invoking render.py --build-subtitles:
# Hebrew override for video-use SUB_FORCE_STYLE.
# Applied via render.py monkeypatch OR by setting the env var the helper reads.
SUB_FORCE_STYLE_HE = (
"FontName=Heebo,"
"FontSize=22," # Hebrew x-height runs taller than Helvetica at the same point size
"Bold=1,"
"PrimaryColour=&H00FFFFFF,"
"OutlineColour=&H00000000,"
"BackColour=&H00000000,"
"BorderStyle=1,Outline=2,Shadow=0,"
"Alignment=2,"
"Spacing=2," # default Hebrew tracking is cramped in libass
"MarginV=50," # Hebrew descenders + diacritics need more bottom clearance than Latin
"Encoding=1" # libass rule: MUST always be 1
)
Important: overriding SUB_FORCE_STYLE is necessary but NOT sufficient on macOS. video-use's render.py path produces SRT files, and SRT + libass + BiDi is unreliable on macOS regardless of style settings. You must ALSO follow Step 7 (caption burn-in recipe) to get correct visual output. The override in this step ensures the right font and spacing for when libass eventually gets there.
The full ready-to-use override file is at references/sub-force-style-hebrew.md with three variants: bold-overlay-he (kinetic typography, 4-6 word chunks since UPPERCASE does not exist in Hebrew), natural-sentence-he (documentary/tutorial), and vertical-social-he (1080x1920 with MarginV=120 to clear platform UI).
Step 3: Add a Hebrew filler-word post-pass
Important correction to a common assumption: ElevenLabs Scribe does NOT tag fillers per word in ANY language. The Scribe word object exposes type values of 'word', 'spacing', or 'audio_event' only, there is no 'filler' or 'is_filler' field. The only filler-related control is the request-level boolean no_verbatim (scribe_v2 only), which is destructive: it removes filler words, false starts, and disfluencies from the output entirely instead of marking them for review. tag_audio_events tags non-speech audio like (laughter) and (applause), not verbal hesitations.
What that means in practice: call Scribe with no_verbatim=false (the default, keeps fillers in the word stream verbatim), then run your own lexicon match over words[].text to mark filler candidates. Cutting based on a lexicon you control beats no_verbatim=true because it preserves the editor sub-agent's ability to keep meaning-bearing instances.
Apply the Hebrew filler lexicon at references/hebrew-filler-words.md. The full list there has ~35 entries split into ALWAYS-FILLER (safe to auto-cut) and CONTEXT-DEPENDENT (flag for the editor sub-agent, these words have real meaning in some contexts). The core entries:
ALWAYS-FILLER:
אֶה, אה, אֶמ, אממ, אמממ, אהמ, המ, ממ
CONTEXT-DEPENDENT (flag, don't auto-cut):
כאילו, יעני, אז, אז ככה, בעצם, טוב, טוב נו, אוקיי, סבבה,
נו, האמת, בסדר, וואלה, כזה, ככה, נכון, פשוט, ממש,
סוג של, בקיצור, כנראה, לדעתי,
את יודע, את יודעת, אתה יודע, אתה מבין, את מבינה, הבנת,
תראה, תראי, שמע, שמעי, בוא, בואי
Apply the same rule as upstream: do not strip mid-phrase. Treat detected ALWAYS-FILLER tokens as silence-equivalent cut candidates with the same 30-200ms padding window (Hard Rule 7) and the same word-boundary snap (Hard Rule 6). For CONTEXT-DEPENDENT tokens, surface them to the editor sub-agent with a flag rather than auto-cutting.
Editorial nuance: the CONTEXT-DEPENDENT entries each have a literal sense and a filler sense. "כאילו" is filler in "זה כאילו לא עבד" but a literal "as if" in "התנהגה כאילו לא קרה כלום". "תראה / שמע / בוא" function as turn-starters far more often than as the literal verb in spoken Hebrew, but they ARE sometimes literal. Per upstream's "Unavoidable slips are kept if no better take exists" rule, prefer leaving them in over multiple cuts in tight succession. A small number per minute tends to read as natural Israeli speech.
Step 4: takes_packed.md with Hebrew transcripts
pack_transcripts.py works unchanged on Hebrew Scribe JSON, it is locale-blind, breaks on silence >=0.5s, and produces phrase-level lines. Three Hebrew-specific things to watch:
- Code-switching is common. Tech tutorials say "התקנתי React" (mid-sentence English). The phrase boundary stays on silence, not on script change. Do not try to "fix" this in the packed transcript, let the editor sub-agent see the mixed line as a single phrase. Hard Rule 6 (snap cuts to word boundaries) still applies; the boundary is in the Scribe JSON regardless of script.
- Nikud (vowel marks) is usually absent in Scribe output. Do not add it. The Scribe transcript is for cut decisions, not for end-user reading. Burn-in subtitles use the same un-nikud-ed text.
- Sofit forms (ם ן ץ ף ך) appear correctly in Scribe output when used at end-of-word. If you see middle-of-word sofit forms, that is a Scribe error, flag in pre-scan (upstream step 2) but do not silently rewrite.
Step 5: Animation slots, defer to existing Hebrew skills
video-use's upstream SKILL.md says: "Do not default to Remotion just because the animation is web-adjacent." For Hebrew animation slots, the priority is the same, and we have a dedicated skill per engine:
| Slot type | Engine | Skill to load into the sub-agent prompt |
|---|---|---|
| Kinetic typography, callout cards, product UI motion | HyperFrames | hyperframes-best-practices (covers Heebo via Google Fonts auto-fetch, dir="rtl", <bdi> for mixed scripts) |
| Component-state compositions, existing Remotion brand system | Remotion | remotion-best-practices (covers Hebrew Google Fonts, bidi text animations) |
| Diagrams, equation derivations | Manim | (no Hebrew skill yet, Manim's Hebrew support is limited; pre-render Hebrew labels as PNG and import as ImageMobject) |
| Simple counters, typewriter, single bar reveals | PIL | Use Pillow.ImageFont.truetype("Heebo-Bold.ttf", size, layout_engine=ImageFont.Layout.RAQM) to enable HarfBuzz-based BiDi. Without RAQM, Pillow draws glyphs in input order and Hebrew comes out backwards |
When spawning a parallel sub-agent for a Hebrew animation slot, include in the prompt: "This is a Hebrew animation. Load hyperframes-best-practices (or remotion-best-practices) before writing any composition code. Use Heebo as the default font." The 10-point sub-agent brief from upstream SKILL.md applies unchanged.
Step 6: Frame-sampling self-eval (you must look at actual pixels)
Upstream's self-eval runs timeline_view at every cut boundary and checks four things. For Hebrew you must actually open and look at sampled frames for two more. Do not trust that libass succeeded; the failure modes are silent.
The mandatory protocol:
Do not hand-roll the crop. burn-hebrew-captions.sh already writes verification frames into a verify_*/ directory, cropped to where it actually placed the captions. Open those. A fixed crop like crop=iw:200:0:ih-220 was correct only while the margin was a fixed 80px; now that geometry is a ratio of frame height, that strip holds no captions at all on a 9:16 render, so a self-eval against it certifies an empty band.
To sample a boundary the script missed, derive the window from the values it logged (Font Npx, bottom margin Mpx):
# Read FONTSIZE and MARGINV from the burn script's own log line for this render.
CROP_H=$(( FONTSIZE * 7 )) # anchored on the margin, opening upward
CROP_Y=$(( BASE_H - MARGINV - FONTSIZE * 6 ))
ffmpeg -y -ss <time> -i final.mp4 -frames:v 1 \
-vf "crop=iw:${CROP_H}:0:${CROP_Y}" /tmp/verify/t<time>s.png
Then open each PNG and verify with your own eyes:
Glyph fallback boxes. Scan the subtitle row. If you see
□,?, or visibly different fonts mid-line, libass picked a fallback font. The most common cause:fontsdir=parameter was not passed to thesubtitlesfilter (Step 7 handles this). Re-render withfontsdir=$HOME/Library/Fonts(macOS) or whereverfc-list :family=Heebo filereports.RTL visual order (the most-missed check). For a known caption line, verify the pixel left-to-right order is the REVERSE of source byte order. Example: source SRT line
ספריית הסקילז AI שבניתי.should render with pixel LTR order:ספריית [right] הסקילז AI שבניתי . [left]The first source word (
ספריית) ends up on the visual RIGHT. The period (last source byte) ends up on the visual LEFT.AIis embedded LTR within the RTL flow.If the pixel LTR order matches the source byte order instead of being reversed, BiDi was not applied. Symptoms: words look correctly shaped but appear in "English-style" left-to-right positions, with period on the right. This is the most common Hebrew failure on macOS and the recipe in Step 7 fixes it.
Keep the 3-pass cap from upstream. After 3 failed renders, stop iterating and flag to the user, the problem is environmental (font install, ffmpeg build, missing python-bidi), not editorial.
Honest example: the first validated run (May 2026) passed self-eval because the agent only checked for "no boxes", not pixel-vs-source order. Output had perfect Heebo glyphs but every Hebrew line was reversed (libass skipped BiDi). Step 7 fixes it.
Step 7: Caption burn-in recipe (the one that actually works on macOS)
The bundled scripts/burn-hebrew-captions.sh does this in one command. The recipe is short:
- Sanitize the SRT for Scribe garbage characters (Devanagari
्सetc. that Scribe occasionally drops mid-Hebrew). Auto-fixes known patterns, warns on unknown ones. - Pre-shape the Hebrew with python-bidi (logical to display order), convert to ASS, patch the ASS
Style: Defaultline (FontName=Heebo, size, Bold, Spacing, Encoding=1), and burn via the FFmpegsubtitles=filter with explicitfontsdir=(the style lives in the patched ASS, so noforce_styleis needed). On macOS, libass does NOT reorder SRT BiDi (Hebrew comes out left-to-right in source byte order); pre-shaping withpython-bidiget_displaymakes libass draw what it sees. Do NOT remove the pre-shape step. Thesubtitlesfilter's documentedshaping=complexoption does not replace it: FFmpeg calls complex shaping "required for correct rendering of complex scripts such as Arabic, Hebrew", but shaping is glyph substitution and positioning, not bidirectional reordering. Tested 2026-08-26 on a HarfBuzz-enabled build:shaping=autoandshaping=complexproduce byte-identical output, and both still render an un-pre-shaped Hebrew line in source byte order. - Sample verification frames (1 per minute, capped at 30).
One-line invocation:
bash scripts/burn-hebrew-captions.sh \
--base edit/base.mp4 \
--srt edit/master.srt \
--out edit/final.mp4 \
--ffmpeg /tmp/ffmpeg
Why (macOS): ffmpeg -i master.srt master.ass alone does not fix BiDi; the python-bidi pre-shape converts logical to display order before libass sees it. Always verify rendered frames by comparing pixel order to source byte order, not just "no boxes" (a line can render with perfect glyphs yet be fully reversed).
Caption geometry auto-scales off the probed frame height. The script rewrites PlayResY: 288 to the real height, which changes the unit MarginV, FontSize, Outline and the side margins are expressed in, so the old fixed values meant something different at every resolution. Measured on 1080x1920, they put the baseline 4.6% from the bottom, inside the UI band upstream's MarginV=90 (31.25%) exists to clear. A 9:16 source now uses upstream's ratios; landscape, 4:5 and square keep the previously shipping ones, so the lecture case is unchanged. --font-size and --margin-v still win. See references/sub-force-style-hebrew.md.
Validated on macOS only. On a libass build that DOES reorder, pre-shaped text is reversed a second time and every caption comes out backwards with no error. The script warns when uname -s is not Darwin; elsewhere, check the verification frames first and confirm the FIRST source word lands at the visual RIGHT.
The pre-shape must force RTL base direction. get_display(line) alone infers paragraph direction from the first strong character, so a caption starting with a Latin token ("React הוא מעולה") is treated as an LTR paragraph and renders in reversed word order. The script passes base_dir='R'. Do not remove it, and add it to any pre-shape you write yourself.
Note: since v1.3.0 the script sanitizes into a working copy and no longer rewrites the SRT you pass to --srt (earlier versions overwrote video-use's own master.srt). captions-only.sh copies the sidecar .he.srt before the burn, so it keeps punctuation the burned-in captions do not.
Step 8: Long video, captions-only mode (cheap path for non-editors)
Use this when: the user has a full lecture, webinar, podcast video or talking-head recording and just wants Hebrew captions burned in on the whole thing. No cuts, no take selection. This is the most common non-technical request, and Steps 1-7 are overkill and 20-100x more expensive for it.
The bundled scripts/captions-only.sh collapses the full workflow into one command:
bash scripts/captions-only.sh ~/Movies/my-lecture.mp4 \
[--strip-fillers] # drop אה/אממ from the on-screen text; audio untouched
[--yes] # skip the cost-confirmation prompt (CI / batch)
[--output PATH] [--ffmpeg /tmp/ffmpeg]
What it does, end-to-end:
- Auto-detects
ELEVENLABS_API_KEYfrom env or~/Developer/video-use/.env - Probes the video duration and prints the estimated Scribe cost (unless
--yesis passed) - Transcribes the full video via Scribe v2 with
language_code=hebandtimestamps_granularity=word(scribe_v1was removed on 9 July 2026 and now returns an error) - Auto-recovers Scribe gaps (v1.2.7+): re-transcribes any 30s+ mid-file silence, or a tail ending >10s early, and merges the words back in. Scribe silently drops chunks on long Hebrew files. Each recovery is an extra billed call beyond the quoted estimate.
- Builds an SRT chunking 5-7 words per caption, breaking on silence ≥250ms or sentence-end punctuation
- (Optional) strips ALWAYS-FILLER tokens from the SRT if
--strip-fillersis passed - Invokes
burn-hebrew-captions.shwhich:- Strips sentence-end
./?/!from Hebrew lines by default (BBC/Netflix caption style), because punctuation position under the pre-shape recipe is unpredictable across script mixes. To keep it, comment out theSENTENCE_ENDblock in Step 0 ofburn-hebrew-captions.sh. - Does the python-bidi pre-shape + libass burn with Heebo + verify frames
- Strips sentence-end
Output lands at <input>.captioned.<ext> (or --output). The merged SRT is written alongside as <output>.he.srt for sidecar upload to YouTube/Vimeo. Verify frames land in a verify_*/ directory.
Tuning: thresholds and punctuation-stripping are documented in references/captions-only-tuning.md. Both the main pass and gap recovery use scribe_v2; there is no v1/v2 split to tune any more.
Cost (API rate): a 1-hour Hebrew lecture is $0.22 Scribe + ~$1 Claude = **$1.20**. A 3-hour webinar: $0.66 + ~$2 = **$2.70**. The $1-3 band holds because Claude tokens dominate. Full cut on the same 3-hour source: $120-300.
When NOT to use this: cutting to a teaser, picking the best of several takes, or rearranging beats all need the full Steps 1-7 workflow. Captions-only just captions the original.
Step 9: Sample Hebrew prompts to drive the conversation phase
video-use's "Converse" step (step 3 of "The process") asks questions shaped by the material. Sample Hebrew prompts users send:
| Hebrew user message | What it maps to in the upstream workflow |
|---|---|
| "ערוך את הקבצים האלה לסרטון השקה" | "edit these into a launch video", full inventory then strategy then execute |
| "תנקה את השתיקות ואת המילים המיותרות" | Filler removal + dead-space cuts only |
| "תוסיף כתוביות בעברית במרכז התחתון" | Subtitle generation with Hebrew SUB_FORCE_STYLE override AND burn-hebrew-captions.sh |
| "תוסיף גרידינג חם" | grade.py --filter with warm_cinematic preset (works language-agnostic) |
| "תפצל למקטעים של 30 שניות לאינסטה" | Vertical 1080x1920 reformat with vertical-social-he style |
| "תבדוק שאין שגיאות בכתוביות בעברית" | Trigger Step 6 frame-sampling self-eval (glyph fallback + RTL pixel order) |
Examples
Example 1: Long lecture, captions-only (most common ask)
User says: "תוסיף כתוביות לסרטון הזה." (no editing requested)
Step 0 mode pick: A. Then: bash scripts/captions-only.sh ~/Movies/lecture.mp4. Done. Output lecture.captioned.mp4 + side lecture.he.srt for YouTube. ~$1-3 total.
Example 2: Raw footage to edited cut with captions
User says: "ערוך את הקבצים האלה לסרטון השקה. תוסיף כתוביות בעברית."
Step 0 mode pick: B. Then: env check (Step 1), inventory + transcribe + filler post-pass (Steps 2-3), converse + EDL (Steps 4-5), render.py produces base.mp4, burn-hebrew-captions.sh produces final.mp4, self-eval per Step 6.
Example 3: Mixed Hebrew/English tech tutorial
Source phrases like התקנתי React אבל הוא לא טוען. Both modes work: python-bidi.get_display() preserves "React" as React (not tcaeR) while reversing the surrounding Hebrew. Verify in Step 6 that mixed-script lines render with English LTR inside RTL flow.
Recommended MCP Servers
video-use is a standalone Claude Code skill and does not require any MCP server. If your Hebrew editing workflow needs live data (for example, fetching trending Hebrew tweets to caption over, or pulling Bituach Leumi PSA copy), check the skills-il MCP directory at https://agentskills.co.il/mcp.
Reference Links
| Source | URL | What to Check |
|---|---|---|
| video-use upstream | https://github.com/browser-use/video-use | SKILL.md (12 Hard Rules), install.md, helpers/ |
| libass ASS format guide | https://github.com/libass/libass/wiki/ASS-File-Format-Guide | UTF-8 BOM requirement, Encoding field rules |
| FFmpeg subtitles filter | https://ffmpeg.org/ffmpeg-filters.html | subtitles= filter syntax, libass dependency |
| Heebo on Google Fonts | https://fonts.google.com/specimen/Heebo | License, weights, character coverage |
| Noto Sans Hebrew | https://fonts.google.com/noto/specimen/Noto+Sans+Hebrew | Hebrew script coverage |
| Unicode BiDi algorithm | https://www.unicode.org/reports/tr9/ | UAX #9, directional isolation rules for mixed Hebrew+Latin |
| python-bidi | https://github.com/MeirKriheli/python-bidi | get_display() for the macOS BiDi workaround |
| ElevenLabs Scribe v2 | https://elevenlabs.io/blog/introducing-scribe-v2 | Current Scribe model (v2, January 2026). Multi-language transcription, Hebrew supported. (meet-scribe was the v1 announcement.) |
| ElevenLabs pricing | https://elevenlabs.io/pricing | Scribe per-hour cost and free tier limits |
Bundled Resources
Scripts
scripts/install-hebrew-fonts.sh: Idempotent installer for Heebo, Rubik, Assistant, Noto Sans Hebrew. macOS (Homebrew cask) and Debian/Ubuntu (apt + manual fallback) paths. Re-runsfc-cacheand verifies viafc-list :lang=he.scripts/burn-hebrew-captions.sh: The Step 7 caption burn-in recipe in one command. Pre-shapes SRT with python-bidi, converts to ASS, patches PlayRes/Style/Spacing, burns with explicit fontsdir, samples verification frames. Pre-flight checks that ffmpeg has libass and that Hebrew fonts exist before running. Step 0 of the script also sanitizes Scribe garbage characters (Devanagari etc.) with auto-fixes.scripts/captions-only.sh: The Step 8 cheap path for non-technical users. One command, full video in, captioned full video out. Transcribes via Scribe, builds SRT, optionally strips ALWAYS-FILLER tokens (--strip-fillers), then runs burn-hebrew-captions.sh. ~$1-3 total regardless of video length, vs. ~$25-300 for the full cut workflow.
References
references/sub-force-style-hebrew.md: Three ready-to-useSUB_FORCE_STYLEoverrides for Hebrew (bold-overlay-he,natural-sentence-he,vertical-social-he). Documents why each value differs from the upstream Latin defaults, including PlayResX/Y and Spacing notes.references/hebrew-filler-words.md: Annotated Hebrew filler list with editorial guidance (which are always-fillers vs. sometimes-load-bearing). Drop-in for the Step 3 post-pass.references/macos-ffmpeg-setup.md: Fixes for the common Homebrew ffmpeg-without-libass trap and other macOS-specific gotchas (loudnorm on freeze frames, drawtext fallback to PIL, libass+SRT BiDi failure mode).references/known-limitations.md: Validated-vs-assumed boundaries, unvalidated style variants, and behaviours this skill does not cover.references/captions-only-tuning.md: Allcaptions-only.shtunables in one place , thresholds (MAX_WORD_DUR,GAP_THRESHOLD,TAIL_THRESHOLD,MAX_DISPLAY_SEC), the punctuation-stripping default + how to disable, side-output paths, and the full flags reference. Read this before changing any caption behavior.references/quick-test.md: 10-second synthetic Hebrew test video recipe (uses macOSsay -v Carmit+ ffmpeg). Costs ~$0.001 in Scribe and lets you validate the full pipeline end-to-end without burning your free-tier quota on real footage.
Gotchas
- Scribe occasionally drops non-Hebrew Unicode characters into Hebrew transcripts. Most commonly Devanagari (
्स) at the end of words where the speaker's soft-sor-mending sounded ambiguous. Classic failure: "סקילים" transcribed as "סקיל्स". These chars render as boxes in the burn.burn-hebrew-captions.shStep 0 auto-fixes the known patterns and warns on unknown ones; extendauto_fixeswhen you find more. - Helvetica is the most common mistake. The bundled
SUB_FORCE_STYLEinrender.pyusesFontName=Helvetica. Helvetica's Hebrew glyphs do NOT exist in the macOS or Linux Helvetica builds (Apple's "Helvetica" font is Latin-only; Linux usually maps it to a metric-equivalent). libass silently falls back to a tofu box. Always overrideFontNameto a known Hebrew font before invoking the caption-burn step. - The python-bidi pre-shape is required on macOS, do NOT skip it or feed a raw SRT. macOS libass does not reliably apply BiDi reordering to a raw SRT even when libfribidi is linked, so feeding the source SRT directly to
subtitles=renders Hebrew reversed (characters drawn left-to-right in source byte order, period on the right). Pre-shaping with python-bidi (get_display) before the SRT->ASS burn is what makes the on-screen result RTL-correct. Do NOT remove the python-bidi step or feed a raw SRT, either one silently flips the layout back to reversed/broken. Any older note claiming libass handles SRT BiDi without the pre-shape is wrong for macOS. - Homebrew's default ffmpeg lacks libass and fontconfig. As of 2026-05, a fresh
brew install ffmpegproduces a binary that cannot burn captions at all. Seereferences/macos-ffmpeg-setup.mdfor the static-build or homebrew-tap fixes. - 2-word UPPERCASE chunks do not translate to Hebrew. Hebrew has no uppercase. Do not try to fake it with
\fnHeebo Bold, the result looks the same as regular Heebo. The Hebrew kinetic-typography equivalent is bold weight + larger size + tighter line breaks (4-6 Hebrew words per chunk, since Hebrew words are shorter than English on average). fontsdir=is more reliable than fontconfig. Even whenfc-match Heeboresolves correctly, thesubtitlesfilter sometimes ignores fontconfig and falls back to libass's built-in font, producing the wrong typeface in the burned-in output. Always passsubtitles=foo.ass:fontsdir=$HOME/Library/Fonts(or wherever Heebo lives perfc-list :family=Heebo file).loudnormfails on freeze frames with silent audio. If your edit ends with a freeze frame that has no audio (or has dead air longer than ~1.5s),loudnormreturnsI=-infand aborts. Workarounds: layer 2 seconds of room tone over the freeze before rendering, or skip loudnorm entirely with--no-loudnorm(audio stays at original levels, no normalization applied).drawtextmay be missing in static ffmpeg builds even when other filters work. If you need text overlays outside of subtitles (e.g., a CTA freeze frame caption), test withffmpeg -h filter=drawtextfirst. If unavailable, generate the overlay via PIL (withlayout_engine=ImageFont.Layout.RAQMfor Hebrew) and composite with ffmpeg'soverlayfilter.- Do not pre-translate code-switched English brands. Users typing "תוסיף ראקט" (transliteration of "React") instead of "React" causes Scribe to transcribe phonetically wrong. Tell the user during the conversation phase: code-switched English stays English in the transcript and in the burned-in caption, Hebrew speakers expect this.
yt-dlpHebrew filenames. When pulling Hebrew sources from URLs,yt-dlpwrites filenames with Hebrew chars. Subsequent FFmpeg + libass passes work fine on macOS but can break on Linux filesystems with non-UTF-8 locales (LC_ALL=Cis the common culprit). SetLANG=en_US.UTF-8 LC_ALL=en_US.UTF-8in the install.md.envif you see "No such file" errors on visibly-present files.
Known Limitations
Validated-vs-assumed boundaries, unvalidated variants, and behaviours this skill does not cover: references/known-limitations.md. Read it before promising a result the skill has not measured.
Troubleshooting
One word in the caption renders as a weird non-Hebrew character (Greek/Devanagari/Tamil look)
Cause: Scribe transcribed a Hebrew word with a non-Hebrew Unicode character mixed in. Most common: Devanagari ्स instead of ים at the end of a word with a soft -s sound (e.g., the colloquial pronunciation of "סקילים").
Solution: scan the SRT for non-Hebrew/Latin characters and fix them. burn-hebrew-captions.sh does this in its Step 0 sanitization pass; if you find a new pattern that auto-fix misses, add it to the auto_fixes dict in the script and submit a PR. For ad-hoc one-offs: sed -i '' 's/סקיל्स/סקילים/g' master.srt then re-run the burn script.
Captions render as □□□ boxes
Cause: libass cannot find a Hebrew font, or ffmpeg lacks libass. Fix: ffmpeg -version | grep enable-libass (if empty, see references/macos-ffmpeg-setup.md); fc-list :lang=he (if empty, run install-hebrew-fonts.sh); always pass fontsdir=$HOME/Library/Fonts explicitly.
Captions render reversed (period on the right, words/chars in source byte order)
This means the python-bidi pre-shape did not run. macOS libass does not reorder a raw SRT (see the Gotchas note), so the pre-shape is mandatory, do NOT feed a raw SRT to subtitles=. Re-run burn-hebrew-captions.sh (which performs the pre-shape in Step 1) and confirm python-bidi is installed. Verify by comparing rendered pixel order to source byte order, not just "no boxes".
Captions have no periods or question marks
Default behavior since v1.2.6: burn-hebrew-captions.sh Step 0 strips trailing ./?/! from any caption line containing Hebrew. BBC/Netflix caption style guides recommend this because line breaks and timing already signal end-of-thought. To restore punctuation, comment out the SENTENCE_END block in the Step 0 Python heredoc of burn-hebrew-captions.sh. Full rationale + alternatives in references/captions-only-tuning.md.
Scribe gap-recovery itself returned an HTTP error
Symptom: captions-only.sh logs WARN: gap re-transcribe returned HTTP <code>, skipping and that window stays uncaptioned. Cause: Scribe v2 occasionally rate-limits, times out, or rejects very short or mid-word windows. Fix: (1) re-run the script , cached bulk transcript means only the failed gap retries, no extra base cost; (2) if it fails twice, extract a slightly wider window manually (ffmpeg -ss <start-1> -t <dur+2>), curl Scribe yourself, drop words into the cached scribe.json; (3) lower GAP_THRESHOLD to 20s so recovery splits long failing windows.
Wrong font (not Heebo) despite Heebo installed
Cause: libass fell back to its built-in font because fontconfig integration is unreliable. Fix: pass fontsdir=$HOME/Library/Fonts explicitly to subtitles=.
Captions render at tiny size on HD output
Cause: ffmpeg's SRT->ASS default is PlayResX: 384, PlayResY: 288 and libass scales font size relative to PlayResY. Fix: set PlayResX/Y to match output. burn-hebrew-captions.sh does this automatically.
Filler removal cut English but left Hebrew
Cause: Scribe doesn't tag fillers in any language; you skipped the Hebrew lexicon post-pass. Fix: apply references/hebrew-filler-words.md to Scribe word timestamps before computing cut candidates.
Local renders fine but cloud render shows boxes
Cause: cloud container has no Hebrew fonts. Fix: add install-hebrew-fonts.sh to the container build step, or bundle Heebo into <edit>/fonts/ and pass fontsdir=<edit>/fonts.
loudnorm errors with I=-inf
Cause: video ends with silence (freeze frame with no audio). Fix: layer 2s of room tone over the silent portion, OR pass --no-loudnorm to render.py.