# Skill: full/dub — "דיבוב לרילס" (Complete video)

A talk becomes a 40-second Reel in another language that looks dubbed, not narrated: Gemini picks the strongest seconds, watches the speaker and writes the dub the way a native speaker would say it — Hebrew in an Israeli voice, English, or any language ElevenLabs speaks — then fits it to his phrases: the voice starts when his mouth starts and stops when he stops. ElevenLabs v3, the best of three takes a sentence, each part cut and fitted to its phrase; captions, a score and sound design. Only for content the customer owns or may use; a narrator voice, never a clone.

**Includes:** the strongest 40 s of a talk · the dub written by Gemini watching the speaker — into Hebrew, English or another language · the dub starts and stops with his phrases · a native voice, best of 3 takes · 9:16 (pushed in on a wide shot) · captions of the dub · a score timed to the story · sound design · hook · kinetic words
**Built on:** hook `punch` ("זום פאנץ׳", `/skills/hook-punch.md`) + captions `hormozi` ("הורמוזי", `/skills/captions-hormozi.md`)
**Price:** 380–460 credits per finished minute · 30 s ≈ 220–310 credits
**Uses:** `/v1/speech/transcripts` · `/v1/ai/text` · `/v1/studio/edit` · `/v1/ai/voice` · `/v1/studio/convert` · `/v1/ai/music` · `/v1/ai/sound-effects`

Base URL: `https://apihub.co.il` · Auth: `Authorization: Bearer <API key>` · Docs: `/docs`
Send the same `X-Tag: <job name>` header on every call of one video; `GET /v1/usage?tag=<job name>` then
returns exactly what it cost. Times in request bodies are **seconds on the edited (output) timeline**.
Unknown fields are rejected with the name of the right field — read the error instead of guessing.
**Files are kept 24 hours** (renders, uploads, generated clips/images/music) — download the finished video.

**Models.** This template runs on the newest models: `quality: "best"` on every AI call — Veo 3.1 for clips,
Nano Banana Pro for images, ElevenLabs v3 for Hebrew voice. Each is priced per asset, so a 30-second video
costs almost as much as a 60-second one. `quality: "balanced"` is the cheaper cut of the same recipe.

---

## 1. Transcribe the whole talk and choose the seconds

```jsonc
POST /v1/studio/uploads { "filename": "talk.mp4", "content_type": "video/mp4", "size_bytes": <bytes> }       // the video — the original file, never a resized copy (and, faster to transcribe, its audio alone)
POST /v1/speech/transcripts { "audio_url": "<audio or video url>", "language_detection": true }             // a 45-min talk: ~20 s, ≈ 48 credits
GET  /v1/speech/transcripts/{id}/sentences                                                                 // every sentence with start/end (ms)
```

**Rights first.** Dub only what the customer owns or is licensed to use, and never clone a real person's voice
without consent — a Hebrew narrator voice re-voices the speech; it does not imitate the speaker.

**From a YouTube link:** take the file with the licence the customer declares — `POST /v1/youtube/videos/{id}/file
{ "kind": "video", "license": "own" | "creative-commons" | "licensed" }` (≈ 8 credits; step 1 of `/skills/full-dub-video.md`,
which also dubs a whole video in its own frame and publishes it to YouTube). The job keeps the source and the licence.

**Length rules.** One render makes up to 30 minutes of video and reads the source up to minute 60; sources up to
4 hours. A moment later than minute 60 → upload only that part.

**Check the words against the speech.** Hebrew now and then loses a phrase in the default model: a second or more of
talking with no words, or words squeezed to zero length. Transcribe the same file again with `"speech_models":
["universal-2"]` (≈ 1 credit a minute) and take its words for that stretch — the dub can only say what the transcript has.
A "sentence" of 10 s or more (fast speech with no full stops): split it at its commas before you send it.

Then let Gemini pick the seconds — text only, the transcript is enough:

```jsonc
POST /v1/ai/text { "quality": "best", "json": true, "max_tokens": 24000,
  "system": "Choose the most powerful ~40 seconds for a Reel: 1–3 spans cut on sentence boundaries, total 36–44 s,
             that play as one mini-story (hook, build, punchline). Return { why, spans: [{start, end}], hook, title }",
  "prompt": "SOURCE SENTENCES: [{\"s\": 0.2, \"e\": 7.1, \"t\": \"…\"}, …]" }                                  // ≈ 43 credits, ~1 min
```

**His phrases** (no call — from the transcript's word timings inside the spans, moved onto the Reel's clock, the
spans joined): the Reel's sentences, and inside each one a new phrase wherever he pauses **0.5 s or more** between
two words. A 42-second Reel had 7 sentences and 15 phrases, e.g. `[[0.0, 2.47, "But when you start doubting
yourself"], [3.85, 5.03, "that's very dangerous"], [6.43, 8.36, "because now what you're basically saying is,"]]`.
The dub is built on these: the Hebrew starts when his mouth starts and stops when he stops — that is what makes it
look dubbed rather than narrated.

**Everything below is timed on the cut video's clock.**

## 2. The Reel cut (9:16, the speaker in frame)

A stage or interview is wide; a centre crop lands between the people. Put the whole frame over a blurred copy and
push in on the speaker — one render, no new footage:

```jsonc
POST /v1/studio/edit { "video_url": "<talk url>", "keep": [ {"start": 1764, "end": 1780.7}, {"start": 1782.21, "end": 1792.16}, {"start": 1829.4, "end": 1845.3} ],
  "zoom": [ { "start": 0, "end": <reel length>, "from": 1.78, "to": 1.78, "x": 0.28, "y": 0.5, "ease": "linear" } ],   // x = where the speaker stands (0 left … 1 right)
  "output": { "aspect_ratio": "9:16", "resolution": "1080", "fit": "blur", "quality": "high" } }              // 42 s from a 45-min talk: ~2.3 min, ≈ 43 credits
```

The picture is now a sharp band in the middle; titles go in the blurred top, captions at the bottom of the band.

**A vertical phone video is already a Reel:** send only `keep` and `"fit": "crop"` — no zoom, no blur (the example on
/skills: 42 s from a 62-second selfie video, ≈ 43 credits).

## 3. Spoken Hebrew, then fitted to his phrases

Gemini watches and hears the cut and writes the Hebrew the way an Israeli would say it on that stage — not a
translation:

```jsonc
POST /v1/ai/text { "quality": "best", "json": true, "temperature": 0.9, "max_tokens": 8000,
  "media": [ { "url": "<reel cut url>", "type": "video", "fps": 1 } ],
  "system": "You are the best Israeli dubbing writer. You WATCH and HEAR the attached Reel and write his Hebrew dub,
             sentence by sentence, the way an Israeli would actually SAY it on stage: spoken, direct, punchy, with
             energy — not a translation, not a narrator, no formal Hebrew. Short clauses; commas, ? and ! for rhythm.
             For every sentence: he (10–13 Hebrew letters per second of the sentence) and delivery (English, one line:
             energy 1–10, where the pitch goes up or down, the word to hit). Return { sentences: [{ i, he, delivery }] }",
  "prompt": "SENTENCES (Reel seconds): [{\"i\": 0, \"start\": 0, \"end\": 8.36, \"en\": \"But when you start doubting yourself…\"}, …]" }   // ≈ 20 credits
```

**Into another language** — the example on /skills is English: the same call with the language swapped — *"You are the best
American dubbing writer … write his English dub … the way an American creator would actually SAY it to camera … Keep
product names as they are. For every sentence: text (14–16 characters per second of the sentence, spaces included — it must
fit his time) and delivery"* — and each sentence as he said it (`"he": "…"` for a Hebrew source).

Then split every sentence into one part per phrase of his (Hebrew ≈ 12 letters, English ≈ 15 characters a second of the
phrase). **One phrase a sentence** — a speaker who never pauses 0.5 s inside a sentence — needs no call: each sentence is
its own part.

```jsonc
POST /v1/ai/text { "quality": "best", "json": true, "temperature": 0.6, "max_tokens": 6000,
  "system": "You split a Hebrew dub into parts that match the speaker's phrases, so the Hebrew starts and stops with
             his mouth. For every sentence you get his phrases (English + seconds) and a spoken Hebrew draft. Write one
             Hebrew part per phrase, in order — the Hebrew said while he says that phrase: spoken Israeli Hebrew,
             energetic, natural, keeping the draft's words where possible. Each part ≈ 12 Hebrew letters per second of
             its phrase. Return { sentences: [{ i, parts: [\"…\"] }] } — exactly as many parts as his phrases.",
  "prompt": "[{\"i\": 0, \"hebrew_draft\": \"…\", \"delivery\": \"…\", \"his_phrases\": [{\"seconds\": 2.47, \"en\": \"…\"}, …]}, …]" }   // ≈ 16 credits
```

## 4. The voice: three takes a sentence, with word timings

One call per sentence — its parts joined by `"... "`, so each part ends in a pause the voice actually makes:

```jsonc
POST /v1/ai/voice { "text": "אבל כשאתה מתחיל לפקפק בעצמך... זה נהיה מאוד מסוכן... כי מה שאתה בעצם אומר עכשיו זה.",
  "voice_id": "SxE1yZJthRl35h6VxNqR", "quality": "best", "language_code": "he", "stability": 0, "timestamps": true }
// → output.url + output.words [{ text, start, end }] (seconds) · ≈ 2–3 credits · 3 takes: stability 0, 0, 0.5 · all in parallel
```

- **The voice:** "API Hub · Israeli speaker A" (`SxE1yZJthRl35h6VxNqR`) — a designed native Israeli voice; "Israeli
  speaker B" (`qV7tO2vkoFcwWJtQ22Zi`) is the second one (`GET /v1/ai/voices`). The library's stock voices speak
  Hebrew with an accent. The default model (eleven_v4, the newest) ignores `speed`.
- **Hebrew is directed for you.** `POST /v1/ai/voice` runs the voice director on Hebrew text (`prepare`, on by
  default): niqqud on the words a reader could misread, an English delivery tag per sentence, a stability that fits.
  Send plain text and the delivery in `mood` ("the speaker doubts himself, then warns"); do not add tags or niqqud
  yourself, and leave `stability` out (an explicit one overrides the director's). `output.words` and `output.text` come back plain.
- **Another language:** a stock voice of that language from `GET /v1/ai/voices` with `"language_code"` (the English
  example: "Chris", `iP95p4xoKVk53GoZ742B`, `"language_code": "en"`), the same default model (eleven_v4) and the same
  three takes — never a clone of the speaker.
- **Misreadings:** "תוכנית א'" is read wrong — write "תוכנית אָלֶף" / "תוכנית בֵּית" in the voice text (captions keep
  the short form).
- **Pick the take:** split each take's `words` into parts at the words ending in "..." (a take that does not split into
  exactly as many parts as his phrases is out). For each part: its length = last word's end − first word's start; the
  take's score = the largest |ln(part length ÷ his phrase length)|. Lowest score wins.

## 5. Cut and fit every part, placed where he starts the phrase

```jsonc
POST /v1/studio/convert { "video_url": "<take url>", "trim": { "start": <first word start − 0.03>, "end": <last word end + 0.1> },
  "speed": 1.12, "output": { "format": "mp3" } }                                                  // ≈ 1 credit a part, all in parallel, pitch kept
```

- **speed** = part length ÷ min(his phrase length, room), where room = until his next phrase starts − 0.08 s. Between
  0.92 and 1.06 → leave it at 1; otherwise clamp to 0.9–1.18, and only if the part would still run into the next
  phrase allow up to 1.25.
- **start** of the part in the Reel = his phrase's start − 0.03 s.
- **Caption timing** comes free: every word of the part lands at phrase start + (word start − part start) ÷ speed.
  A caption line every ≤ 5 words or at punctuation.

## 6. Gemini watches the cut and plans the drama

```jsonc
POST /v1/ai/text { "quality": "best", "json": true, "media": [ { "url": "<reel cut url>", "type": "video", "fps": 2 } ],
  "system": "You WATCH a Reel whose voice will be replaced by the Hebrew dub below. Return { hook {text, style, duration},
             captions_style, grade, texts [{start, duration, text, effect, preset, y 8–18}], zooms, flashes (≤ 2),
             sounds (≤ 8), music {style, sections covering 0 → L on the story's turns} }",
  "prompt": "HEBREW DUB (reel seconds): [{start, end, he}, …]" }                                              // ≈ 18 credits
POST /v1/ai/music { "prompt": "Epic cinematic trailer drama: deep drums, low strings, brass swells; instrumental.", "sections": [ … ], "instrumental": true }   // 42 s ≈ 32 credits
POST /v1/ai/sound-effects { "text": "heavy cinematic bass drop", "duration": 2.5 }                            // ≈ 2 credits each
```

A kinetic word goes where the dub says it: look the word up in the parts' timed words and use the nearest one to
the planned second.

## 7. One render

```jsonc
POST /v1/studio/edit { "video_url": "<reel cut url>",
  "audio": { "mute": true, "normalize": false },                                        // the original voice is gone — see below
  "voiceover": [ { "url": "<part 0.0>", "start": 0.0, "volume": 1.1 }, { "url": "<part 0.1>", "start": 3.82, "volume": 1.1 }, … ],   // every part at his phrase
  "sounds": [ { "url": "<sfx>", "start": 13.84, "volume": 0.7 } ],                      // parts + sounds ≤ 30 per render
  "music": { "url": "<score>", "volume": 0.3, "duck": false, "loop": false, "fade_in_ms": 300, "fade_out_ms": 400 },
  "hook": { "text": "למה אסור שתהיה לכם תוכנית *ב'*", "start": 0, "duration": 2.6, "style": "punch" },
  "captions": { "style": "hormozi", "captions": [ { "start": 120, "end": 2380, "text": "אבל כשאתה מתחיל לפקפק בעצמך", "words": [ … ] }, … ] },   // the written Hebrew, timed by the parts (ms)
  "texts": [ { "text": "תוכנית ב'", "start": 15, "duration": 1.5, "effect": "punch", "preset": "hero", "style": { "y": 12 } } ],
  "zoom": [ { "start": 13.84, "end": 16.59, "to": 1.2, "ease": "punch", "x": 0.4, "y": 0.45 } ],
  "panels": [ { "start": 13.84, "duration": 0.12, "color": "#FFFFFF", "fade_ms": 30 } ],
  "color": { "preset": "cinematic" },
  "output": { "aspect_ratio": "9:16", "resolution": "1080", "fit": "crop", "quality": "high" } }         // 42 s ≈ 43 credits, ~2.5 min
```

**The level, measured (27.9.2026).** `normalize` levels the video's own sound — with the voice muted that is only the
score, which it lifts to −16 LUFS, over the dub (the lines from `/v1/ai/voice` are ≈ −18 LUFS and are mixed as they are):
with `"normalize": true` and the score at 0.24 the example came out at −14.3 LUFS with the music louder than the voice.
Leave `normalize` off: the lines carry the level and a score at 0.3 sits ≈ 11 dB under them (−17.8 LUFS, true peak −1.6 dB).

## What the run cost and how long it took

A 45-minute talk → a 42.5-second Hebrew Reel (7 sentences, 15 parts, a 3-section score, 7 sounds):

| Step | Credits | Time |
|---|---|---|
| Uploads, style lists, reads | 6 | ~1 min (upload) |
| Transcribe the whole talk (45 min) | 48 | 20 s |
| Gemini: choose the seconds | 43 | ~1 min |
| Reel cut (9:16, pushed in on the speaker) | 43 | ~2.3 min |
| Gemini: spoken Hebrew, then fitted to his 15 phrases | 36 | ~1.5 min |
| 21 takes (3 a sentence) with word timings | 54 | ~40 s (parallel) |
| Cut + fit the 15 parts | 15 | ~10 s (parallel) |
| Gemini watches the cut and plans | 18 | 30 s |
| Score (3 sections) + 7 sounds | 45 | ~10 s (parallel) |
| Final render | 43 | ~2.5 min |
| **Total** | **351** | **~10 min** |

A shorter source costs less (the transcript is ≈ 1 credit a minute of talk); the Reel itself is ≈ 303 credits.

**The example on /skills** — the owner's 62-second Hebrew car video → a 42-second **English** Reel (7 sentences, one phrase
each, 21 takes with "Chris", a 3-section score, 4 sounds): **270 credits**, about 12 minutes — two transcripts and their sentences 5, Gemini
choosing 17, writing 26 and planning 20, the Reel cut 43, the takes 69, fitting 11, the score 32, sounds 4, the render 43.

Send `x-wait-ms: 110000` to wait for the render in the same call; if the response is still
`processing`, poll `GET /v1/executions/{id}` until `status` is `completed` and read `output.url`.

## Check the bill

`GET /v1/usage?tag=<job name>` — per call, per operation and the total, in credits. Quote that to the
customer, not the estimate above.

## Rules that keep captions readable on every platform

- **Safe zone.** Keep text inside the middle 80% of the frame: `side_margin` ≥ 6 handles the sides; `margin`
  ≥ 12 keeps the bottom clear of the platform UI (username, buttons, progress bar).
- **A line never leaves the frame** — a line too wide for the side margins is set smaller automatically. Do
  not shrink the size yourself, wrap instead (`max_chars_per_line`).
- **Hebrew has no capitals** — keep `uppercase: false`; it only breaks Latin words mixed into a sentence.
- **Fonts.** `GET /v1/studio/fonts` lists the bundled families and which scripts they draw. A font that
  cannot draw the caption's language is swapped and reported in `warnings`.
- The customer's transcript wins: to fix a word, `GET /v1/speech/transcripts/{id}/captions`, edit the text,
  and send the list back as `captions.captions` — timing is kept when the word count is unchanged.
