@mevdragon/vidfarm-devcli 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.agents/skills/music/SKILL.md +416 -0
  2. package/.agents/skills/music/references/api_reference.md +519 -0
  3. package/.agents/skills/music/references/installation.md +65 -0
  4. package/.agents/skills/text-to-speech/SKILL.md +226 -0
  5. package/.agents/skills/text-to-speech/references/installation.md +90 -0
  6. package/.agents/skills/text-to-speech/references/streaming.md +307 -0
  7. package/.agents/skills/text-to-speech/references/voice-settings.md +115 -0
  8. package/.agents/skills/vidfarm-media/SKILL.md +25 -11
  9. package/.agents/skills/vidfarm-media/references/tts.md +41 -3
  10. package/SKILL.director.md +31 -13
  11. package/SKILL.platform.md +2 -2
  12. package/dist/src/app.js +129 -41
  13. package/dist/src/cli.js +162 -5
  14. package/dist/src/config.js +12 -0
  15. package/dist/src/devcli/clips.js +7 -2
  16. package/dist/src/domain.js +3 -0
  17. package/dist/src/editor-chat.js +4 -0
  18. package/dist/src/primitive-context.js +14 -0
  19. package/dist/src/primitive-registry.js +140 -18
  20. package/dist/src/reskin/chat-page.js +1 -1
  21. package/dist/src/reskin/inpaint-clipper-page.js +446 -205
  22. package/dist/src/reskin/library-page.js +7 -1
  23. package/dist/src/reskin/portfolio-page.js +9 -9
  24. package/dist/src/reskin/settings-page.js +4 -4
  25. package/dist/src/reskin/theme.js +1 -0
  26. package/dist/src/services/billing.js +5 -0
  27. package/dist/src/services/clip-curation/ffmpeg.js +48 -0
  28. package/dist/src/services/clip-curation/hunt.js +2 -0
  29. package/dist/src/services/clip-curation/index.js +1 -1
  30. package/dist/src/services/clip-curation/scan.js +29 -16
  31. package/dist/src/services/elevenlabs.js +222 -0
  32. package/dist/src/services/providers.js +216 -2
  33. package/dist/src/services/swipe-customize.js +5 -2
  34. package/package.json +1 -1
@@ -0,0 +1,115 @@
1
+ # Voice Settings
2
+
3
+ Fine-tune voice characteristics for your use case.
4
+
5
+ ## Parameters
6
+
7
+ | Parameter | Range | Default | Description |
8
+ |-----------|-------|---------|-------------|
9
+ | `stability` | 0.0 - 1.0 | 0.5 | How consistent the voice sounds across the generation. Lower = more emotional variation and expressiveness (but can sound erratic). Higher = steady, predictable tone. |
10
+ | `similarity_boost` | 0.0 - 1.0 | 0.75 | How closely to match the original voice sample. Higher sounds more like the source voice but may amplify audio artifacts or background noise from the original recording. |
11
+ | `style` | 0.0 - 1.0 | 0.0 | Exaggerates the unique characteristics of the voice's speaking style (v2+ and v3 models only). Higher values make the voice more "characterful" but can reduce stability. |
12
+ | `speed` | 0.25 - 4.0 | 1.0 | Speech speed multiplier. 1.0 = normal speed. Range is 0.25-4.0 for the REST API; the Agents Platform restricts to 0.7-1.2. |
13
+ | `use_speaker_boost` | boolean | true | Post-processing that enhances voice clarity and similarity to the original. Generally leave this on unless you're experiencing artifacts. |
14
+
15
+ ## Python Example
16
+
17
+ ```python
18
+ from elevenlabs import ElevenLabs
19
+ from elevenlabs import VoiceSettings
20
+
21
+ client = ElevenLabs()
22
+
23
+ audio = client.text_to_speech.convert(
24
+ text="Testing different voice settings.",
25
+ voice_id="JBFqnCBsd6RMkjVDRZzb",
26
+ model_id="eleven_v3",
27
+ voice_settings=VoiceSettings(
28
+ stability=0.5,
29
+ similarity_boost=0.75,
30
+ style=0.0,
31
+ use_speaker_boost=True
32
+ )
33
+ )
34
+ ```
35
+
36
+ ## JavaScript Example
37
+
38
+ ```javascript
39
+ const audio = await client.textToSpeech.convert("JBFqnCBsd6RMkjVDRZzb", {
40
+ text: "Testing different voice settings.",
41
+ modelId: "eleven_v3",
42
+ voiceSettings: {
43
+ stability: 0.5,
44
+ similarityBoost: 0.75,
45
+ style: 0.0,
46
+ useSpeakerBoost: true,
47
+ },
48
+ });
49
+ ```
50
+
51
+ ## cURL Example
52
+
53
+ ```bash
54
+ curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb" \
55
+ -H "xi-api-key: $ELEVENLABS_API_KEY" \
56
+ -H "Content-Type: application/json" \
57
+ -d '{
58
+ "text": "Testing different voice settings.",
59
+ "model_id": "eleven_v3",
60
+ "voice_settings": {
61
+ "stability": 0.5,
62
+ "similarity_boost": 0.75,
63
+ "style": 0.0,
64
+ "use_speaker_boost": true
65
+ }
66
+ }' \
67
+ --output output.mp3
68
+ ```
69
+
70
+ ## Use Case Recommendations
71
+
72
+ ### Audiobooks / Narration
73
+ ```python
74
+ voice_settings=VoiceSettings(
75
+ stability=0.7, # Consistent tone
76
+ similarity_boost=0.5, # Natural variation
77
+ style=0.0
78
+ )
79
+ ```
80
+
81
+ ### Conversational / Chatbots
82
+ ```python
83
+ voice_settings=VoiceSettings(
84
+ stability=0.4, # More expressive
85
+ similarity_boost=0.75,
86
+ style=0.3 # Slight style emphasis
87
+ )
88
+ ```
89
+
90
+ ### News / Professional
91
+ ```python
92
+ voice_settings=VoiceSettings(
93
+ stability=0.8, # Very consistent
94
+ similarity_boost=0.6,
95
+ style=0.0
96
+ )
97
+ ```
98
+
99
+ ### Character Voices / Drama
100
+ ```python
101
+ voice_settings=VoiceSettings(
102
+ stability=0.3, # Highly expressive
103
+ similarity_boost=0.8,
104
+ style=0.5 # Strong style
105
+ )
106
+ ```
107
+
108
+ ## Tips
109
+
110
+ - **Start with defaults** and adjust incrementally
111
+ - **Lower stability** if voice sounds monotonous
112
+ - **Reduce similarity_boost** if you hear audio artifacts
113
+ - **Style works** with v2+, v3, and multilingual models
114
+ - **Test with representative text** from your actual use case
115
+ - **Flash models** ignore some voice settings for speed
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: vidfarm-media
3
- description: Media resolution for video workflows — narration TTS, voice-matched speech regeneration ("same speaker, new words"), word-level timings, BGM, SFX, and image/clip sourcing through vidfarm. Use when a video needs audio (voiceover/narration, music, sound effects), rewording of existing narration in the original speaker's voice, caption word timings, or media assets. Ships the shared audio engine scripts/audio.mjs (audio_request.json → audio_meta.json) used by faceless-explainer, product-launch-video, and general-video; documents vidfarm tts/stt BYOK primitives, the keyless local Kokoro + whisper.cpp engines, and My Files / raws library / generation sourcing. No third-party media accounts — everything is user-owned or generated on the user's keys.
3
+ description: Media resolution for video workflows — AI music generation, narration TTS, voice-matched speech regeneration ("same speaker, new words"), transcription, word-level timings, BGM, SFX, and image/clip sourcing through vidfarm. Use when a video needs audio (music, voiceover/narration, sound effects), rewording of existing narration in the original speaker's voice, caption word timings, or media assets. ElevenLabs powers music/TTS/STT via the platform key (use_wallet_credits, wallet-billed) or the customer's own ElevenLabs key. Ships the shared audio engine scripts/audio.mjs (audio_request.json → audio_meta.json) used by faceless-explainer, product-launch-video, and general-video; documents the vidfarm music/tts/stt primitives, the keyless local Kokoro + whisper.cpp engines, and My Files / raws library / generation sourcing.
4
4
  ---
5
5
 
6
6
  # vidfarm-media
7
7
 
8
- One engine and one rule. The engine: `scripts/audio.mjs` turns a neutral `audio_request.json` into narration files, word timings, a music bed, and SFX — written into the project and described by one meta JSON. The rule: **no third-party media accounts, ever** — audio and images are the user's own files, found in the user's My Files / raws library, generated on the user's provider keys, or produced by keyless local models. If a source needs a sign-up, it is not a source.
8
+ One engine and one rule. The engine: `scripts/audio.mjs` turns a neutral `audio_request.json` into narration files, word timings, a music bed, and SFX — written into the project and described by one meta JSON. The rule: **the user never signs up for a third-party media account.** Audio and images are the user's own files (My Files / raws library), generated on the user's provider keys, produced by keyless local models, OR generated through vidfarm's own ElevenLabs integration — music/TTS/STT on the platform key billed to the wallet (`use_wallet_credits`, the default) or on the customer's own ElevenLabs key. If a source would need the user to sign up somewhere else, it is not a source.
9
9
 
10
10
  ## Preflight (run once, before the brief)
11
11
 
@@ -56,27 +56,41 @@ Contract points:
56
56
  - BGM is **not** loop-extended by the engine — the workflow assemblers loop-extend short beds to the video length at mount time. `bgm_pending` is never set; `scripts/wait-bgm.mjs` exists as an exit-0 shim.
57
57
  - Mount the results per `hyperframes-core`: each `voices[].path` and (`bgm.path`, `sfx[]`) as `<audio>` tracks; `voices[].words` drive captions.
58
58
  - **Multi-track mixing is native — keep voice, music, and SFX on SEPARATE `<audio>` layers, each at its own `data-volume`** (do NOT pre-mix them into one file). The runtime and both render paths mix all audio layers together with per-track volume honored (a real ffmpeg `amix` at render). Standard balance: **narration/voice ~1.0, BGM ~0.1–0.2** under the voice, SFX per-item (the engine already emits `sfx[].volume`, e.g. `0.35`). This is the whole reason each stem is a distinct track — the user (or a later edit) can re-level any track independently.
59
- - **De-combining an original on recreate.** When you're recreating a video whose original had music + narration baked into ONE track, don't try to reproduce that single bed — you cannot stem-separate baked audio. Instead build the mix fresh: a new narration track (`tts` / `regenerate-speech`) at ~1.0 **plus** a real music file (owned / user-provided / found via `vidfarm directory search "<query> music"`) at ~0.1–0.2, as two separate `<audio>` layers, and drop the original combined track. Never fake a music layer or duplicate the voice track to stand in for music.
59
+ - **De-combining an original on recreate.** When you're recreating a video whose original had music + narration baked into ONE track, don't try to reproduce that single bed — you cannot stem-separate baked audio. Instead build the mix fresh: a new narration track (`tts` / `regenerate-speech`) at ~1.0 **plus** a real music file (owned / user-provided / found via `vidfarm directory search "<query> music"` / generated with `vidfarm music "<vibe>"`) at ~0.1–0.2, as two separate `<audio>` layers, and drop the original combined track. Never fake a music layer or duplicate the voice track to stand in for music.
60
60
 
61
- ## TTS / STT providers
61
+ ## Music, TTS & STT (ElevenLabs + BYOK)
62
62
 
63
- | Need | Command | Notes |
63
+ **Music and speech are core primitives — use them freely.** ElevenLabs powers high-quality
64
+ music, narration, and transcription. The `use_wallet_credits` flag (DEFAULT **true** on the
65
+ music / TTS / STT primitives) runs on vidfarm's platform ElevenLabs key and bills the customer's
66
+ wallet. **Keep it true** for the best voices/music out of the box; only set it **false** to save
67
+ wallet credits or to use the customer's OWN saved ElevenLabs key (Settings → AI keys → ElevenLabs
68
+ is an audio-only key that does NOT count as a qualified text/image/video key). With
69
+ `use_wallet_credits: false` and no ElevenLabs key, TTS/STT fall back to a BYOK openai/gemini/
70
+ openrouter key; music always needs ElevenLabs (own key or platform).
71
+
72
+ | Need | Command / route | Notes |
64
73
  | --- | --- | --- |
65
- | Narration, promptable voice (BYOK) | `vidfarm tts "" --style "calm, warm narrator"` | Local-first on your env key; `--voice alloy`/`Kore`…; `--cloud` = saved-key platform job `POST /api/v1/primitives/audio/speech` |
74
+ | **Music** (bed, beat, jingle, song, score) | `vidfarm music "upbeat lo-fi beat" --length 30` · `POST /api/v1/primitives/music/generate` | ElevenLabs. `use_wallet_credits` default true (platform key + wallet); `--own-key` = your ElevenLabs key. `music_length_ms` 300000 (5 min). Place as its own `<audio>` layer ~0.1–0.2 under narration. |
75
+ | **Narration** (default) | `vidfarm tts "…" --cloud` · `POST /api/v1/primitives/audio/speech` | Default = ElevenLabs on the platform key (wallet-billed). Pick a voice with `--voice <voice_id>` (browse below). `--own-key` for your ElevenLabs/BYOK key. Local-first `vidfarm tts` (no `--cloud`) still runs on your env openai/gemini key. |
76
+ | **List voices** | `vidfarm voices` · `GET /api/v1/primitives/audio/voices` | ElevenLabs voice catalog (voice_id, name, labels, preview_url). Default = platform account; `--own-key` / `?use_wallet_credits=false` = the customer's ElevenLabs account. **Default a sensible voice AND tell the user they can pick from many.** |
66
77
  | Narration, zero keys | `npx hyperframes tts "…" -v af_heart --json` | Kokoro-82M, local, WAV + duration in JSON |
67
- | Transcript + SRT (BYOK) | `vidfarm stt <file\|url>` | Gemini key labels speakers; OpenAI whisper-1 gives real word timestamps |
68
- | Reword existing narration in the (approximate) original voice | `POST /api/v1/primitives/audio/regenerate-speech` (platform job on saved keys; no devcli command yet) | One job: listens to a video/audio source, profiles the speaker (needs a Gemini key), rewords the transcript per `rewrite_instruction` (or takes exact `text`), regenerates with the closest preset voice + matched style. Always an approximation — OpenAI/Gemini cannot clone voices. Details: `references/tts.md` |
78
+ | **Transcript + SRT** | `vidfarm stt <file\|url> --cloud` · `POST /api/v1/primitives/audio/transcribe` | Default = ElevenLabs Scribe (native diarization + real word timestamps), wallet-billed. `--own-key`/BYOK: gemini labels speakers, openai/whisper-1 gives real word timings. |
79
+ | Reword existing narration in the (approximate) original voice | `POST /api/v1/primitives/audio/regenerate-speech` | Listens, profiles the speaker (needs a Gemini key), rewords, regenerates with the closest preset voice + matched style. Approximation, never a clone. Details: `references/tts.md` |
69
80
  | Word timings, zero keys | `npx hyperframes transcribe <audio> --json` | whisper.cpp; writes word-level `transcript.json` |
70
81
  | Captions straight into a composition | `vidfarm captions generate <dir>` | whisper-1 real timings when an OpenAI key exists; estimates otherwise |
71
82
 
72
- Voices, styles, formats, and the exact word-timestamp paths: `references/tts.md`.
83
+ Voices, styles, formats, the ElevenLabs SDK details, and the exact word-timestamp paths:
84
+ `references/tts.md`, plus the loadable `music` and `text-to-speech` skill packs for the raw
85
+ ElevenLabs API.
73
86
 
74
87
  ## BGM and SFX resolution order
75
88
 
76
89
  1. **User file / URL** — `bgm.file` / `bgm.url` in the request; copied or downloaded into `assets/bgm/`.
77
90
  2. **File directory** — `vidfarm directory search "<query> music"` or the legacy `vidfarm files --search` (needs `$VIDFARM_API_KEY`); the user's own library, searchable by meaning when notes are annotated.
78
- 3. **Local dir** — `$MUSIC_DIR` / `$SFX_DIR` (or request `bgm.dir` / `sfx_dir`), picked by filename keyword match against the query.
79
- 4. **Skip, with a note** — a silent bed or a missing whoosh is fine; a stock-audio account is not. The meta's `notes[]` says exactly what was skipped and why.
91
+ 3. **Generate it** — `vidfarm music "<query>" --length <sec>` (ElevenLabs; `use_wallet_credits` default) writes an mp3 you can pass back as `bgm.file`. Great when the user has no fitting track. The engine script itself has no generate mode — run `vidfarm music` yourself and feed the result in.
92
+ 4. **Local dir** — `$MUSIC_DIR` / `$SFX_DIR` (or request `bgm.dir` / `sfx_dir`), picked by filename keyword match against the query.
93
+ 5. **Skip, with a note** — a silent bed or a missing whoosh is fine. The meta's `notes[]` says exactly what was skipped and why.
80
94
 
81
95
  ## The file directory (My Files + raws, unified)
82
96
 
@@ -1,6 +1,44 @@
1
- # TTS / STT providers — voices, timings, formats
1
+ # Music / TTS / STT — voices, timings, formats
2
2
 
3
- Two tiers, one preflight. **BYOK cloud** rides the user's own provider key through vidfarm; **keyless local** runs Kokoro-82M and whisper.cpp on the user's machine with no account and no key. There is never a sign-in step.
3
+ Three tiers. **ElevenLabs (default)** powers music, high-quality narration, and Scribe
4
+ transcription — on vidfarm's platform key billed to the wallet (`use_wallet_credits: true`, the
5
+ default) or on the customer's OWN saved ElevenLabs key (`use_wallet_credits: false`). **BYOK
6
+ cloud** rides the user's own openai/gemini/openrouter key through vidfarm (TTS/STT fallback when
7
+ `use_wallet_credits` is false and no ElevenLabs key is present). **Keyless local** runs Kokoro-82M
8
+ and whisper.cpp on the user's machine with no account. There is never a sign-in step for the user.
9
+
10
+ ## use_wallet_credits (default true — keep it on)
11
+
12
+ `music`, `tts` (`--cloud`), and `stt` (`--cloud`) default to ElevenLabs on the platform key,
13
+ billed to the customer's wallet. Recommend keeping it on for the best voices/music with zero
14
+ setup. Turn it OFF only to (a) save wallet credits or (b) use the customer's own ElevenLabs key.
15
+ With it off and no ElevenLabs key, TTS/STT fall back to a BYOK openai/gemini/openrouter key; music
16
+ always needs ElevenLabs. ElevenLabs in Settings → AI keys is **audio-only** — it does NOT count as
17
+ a qualified text/image/video key, so the user still needs an openai/gemini/openrouter key for the
18
+ core generation features.
19
+
20
+ ## Music — `vidfarm music`
21
+
22
+ ```bash
23
+ vidfarm music "chill lo-fi hip hop beat with jazzy piano" --length 30 --json
24
+ ```
25
+
26
+ - ElevenLabs music generation (`POST /api/v1/primitives/music/generate`). `--length <sec>` or
27
+ `--length-ms`, default 30s, max 5 min. `--own-key` uses the customer's ElevenLabs key instead of
28
+ the wallet. Returns a durable mp3; mount it as its own `<audio>` layer ~0.1–0.2 under narration.
29
+ - For fine control, the primitive also accepts an ElevenLabs `composition_plan` (section-by-section
30
+ lyrics/style) — see the loadable `music` skill pack for the plan schema.
31
+
32
+ ## Voices — `vidfarm voices`
33
+
34
+ ```bash
35
+ vidfarm voices # platform-account voices (voice_id, name, labels, preview_url)
36
+ vidfarm voices --own-key # the customer's own ElevenLabs account voices
37
+ ```
38
+
39
+ - `GET /api/v1/primitives/audio/voices` (`?use_wallet_credits=false` for the user's own key).
40
+ - **Default a sensible voice and TELL the user they can pick from many** — surface a few names +
41
+ the returned `voice_library_url`, then pass the chosen `voice_id` to `tts --voice <voice_id>`.
4
42
 
5
43
  ## Preflight
6
44
 
@@ -37,7 +75,7 @@ POST /api/v1/primitives/audio/regenerate-speech # alias: /api/v1/primitives/re
37
75
 
38
76
  One job: listens to the source, profiles the dominant speaker with an audio-native Gemini call (verbatim transcript + gender/age/accent/pace/pitch/energy profile + a dense style direction + the closest preset in both voice families), rewords the transcript per `rewrite_instruction` (keeping length and rhythm) or takes the exact `text`, then regenerates via TTS with the matched preset + profiled style instructions. Result: durable `audioUrl`, `script`, `original_transcript`, `voice_profile`, `voice_match`, plus a `regenerate-speech.json` artifact.
39
77
 
40
- - **Honesty rule (non-negotiable): this is an approximation, never a clone.** Neither OpenAI nor Gemini exposes voice cloning through their public APIs — both offer preset voices with style steering only (actual cloning is sales-allowlisted at both, e.g. OpenAI "Custom Voices", Google Chirp 3 Instant Custom Voice). `voice_match` says `"approximate"` or `"none"`; always present it that way to the user. True self-serve cloning would need an ElevenLabs/Cartesia-class provider or a local OSS model (F5-TTS, CosyVoice, XTTS-v2, Chatterbox) none are wired into vidfarm today.
78
+ - **Honesty rule (non-negotiable): regenerate-speech is an approximation, never a clone.** It runs on OpenAI/Gemini, which expose preset voices with style steering only (no self-serve cloning of an arbitrary speaker). `voice_match` says `"approximate"` or `"none"`; always present it that way. (ElevenLabs IS wired into vidfarm for music, preset-voice TTS, and Scribe STT but regenerate-speech does not use it for speaker cloning; treat any voice-match as an approximation.)
41
79
  - **Voice profiling needs a saved Gemini key** (it is the audio-native LLM). With only openai/openrouter keys the job degrades gracefully: STT transcript + reword still work, but the voice is a default or explicitly passed preset and `voice_match` is `"none"`.
42
80
  - Sources over ~40 minutes of speech must be trimmed first (same cap as `/audio/transcribe`).
43
81
  - To swap the result into a composition: mute the original span (`/videos/mute` or the layer's volume), then place `audioUrl` as a new audio layer.
package/SKILL.director.md CHANGED
@@ -147,7 +147,7 @@ Name the plan back in these terms ("I'll SWAP the captions and REPLACE the scene
147
147
 
148
148
  **Fuel a scene REPLACE with raw clips, not expensive AI video.** A heavy scenes-axis replace needs footage; sources in cost order: (1) the director's own library — search `/raws` and `/files` (`vidfarm raws search …`, `vidfarm files --search …` / `browse_files`); (2) **HUNT new raws** out of a long-form source (podcast/VOD/webinar or any YouTube/TikTok/IG/X URL) — `vidfarm raws scan <src> --prompt "<what the new scenes need>" --aspect <canvas> [--duration N --no-text --range …]` (local-first, free compute) or the async `POST /clips/scan` / `/raws/scan`; then reuse the picks (`set_layer_media` / `vidfarm set-media` to swap in place, `add_layer`/`vidfarm place` for net-new scenes); (3) `generate_layer` / `vidfarm generate` AI generation — the **expensive last resort**, only for scenes no real clip can cover. When a big scene re-work is asked for but no footage is given, **ask for a source to hunt (or point at the raws library) before AI-generating** — see [Raws](#raws-long-form--short-form-raws) and [Generate AI media …](#generate-ai-media-and-drop-it-on-the-timeline).
149
149
 
150
- **Audio is natively multi-track — overlay narration + music + SFX, each at its own volume.** A composition mixes UNLIMITED simultaneous `<audio>` layers; each sits on its own `data-track-index` and carries its own `data-volume` (0–2, default 1), and the runtime mixes them with per-track volume honored identically in the preview and the exported MP4 (a real ffmpeg `amix` of every audio layer at render). So you never need a pre-mixed file — lay **narration/voiceover at ~1.0 on one track, a music bed at ~0.1–0.2 on a separate track, and SFX on their own tracks**, each via `add_layer kind=audio` (web) / `vidfarm place --kind audio --volume …` (devcli), tuning levels later with the Inspector's Volume slider or `set_layer_media` (`volume`, `muted`). **The key move when recreating a template whose original baked music + narration into ONE audio track: rebuild it as TWO independent tracks** — a fresh narration track (`/audio/speech`, or same-voice reword via `/audio/regenerate-speech` / `vidfarm speech regenerate`) at ~1.0 and a separate real music track at ~0.1–0.2 — then mute/remove the original combined source-audio layer. This gives the director independent voice and music volume, and works around AI TTS being unable to emit narration+music in one file: you compose the mix on the timeline. Honesty: you can't un-mix / stem-separate the original's baked audio — the two tracks are a fresh narration track **plus** a real music file (owned / user-provided / `browse_files`), never a faked or duplicated voice layer.
150
+ **Audio is natively multi-track — overlay narration + music + SFX, each at its own volume.** A composition mixes UNLIMITED simultaneous `<audio>` layers; each sits on its own `data-track-index` and carries its own `data-volume` (0–2, default 1), and the runtime mixes them with per-track volume honored identically in the preview and the exported MP4 (a real ffmpeg `amix` of every audio layer at render). So you never need a pre-mixed file — lay **narration/voiceover at ~1.0 on one track, a music bed at ~0.1–0.2 on a separate track, and SFX on their own tracks**, each via `add_layer kind=audio` (web) / `vidfarm place --kind audio --volume …` (devcli), tuning levels later with the Inspector's Volume slider or `set_layer_media` (`volume`, `muted`). **The key move when recreating a template whose original baked music + narration into ONE audio track: rebuild it as TWO independent tracks** — a fresh narration track (`/audio/speech`, or same-voice reword via `/audio/regenerate-speech` / `vidfarm speech regenerate`) at ~1.0 and a separate real music track at ~0.1–0.2 — then mute/remove the original combined source-audio layer. This gives the director independent voice and music volume, and works around AI TTS being unable to emit narration+music in one file: you compose the mix on the timeline. Honesty: you can't un-mix / stem-separate the original's baked audio — the two tracks are a fresh narration track **plus** a real music file (owned / user-provided / `browse_files` / generated with `/api/v1/primitives/music/generate` (`vidfarm music`)), never a faked or duplicated voice layer.
151
151
 
152
152
  ## Edit in the Trackpad Editor
153
153
 
@@ -623,8 +623,10 @@ Send a stable `tracer` on export so retries are traceable and filterable in job
623
623
  | `vidfarm inpaint <image> --mask <png> --prompt "…" [--region "label=…"] [--ref …] [--out <f>]` | `POST /api/v1/primitives/images/inpaint` (polls job) | masked image EDIT — replace ONLY the transparent-mask region, keep everything else (devcli twin of the /inpaint page) |
624
624
  | `vidfarm create-overlay "<subject>" [--key-color #00FF00] [--aspect-ratio 1:1] [--place <dir>] [--out <f>]` | `POST /api/v1/primitives/images/create-overlay` (polls job) | **Vox-style** transparent OVERLAY — AI image on a forced key-color background, chroma-keyed out in one job → ready-to-composite transparent PNG |
625
625
  | `vidfarm remove-background-greenscreen <image> [--key-color #00FF00] [--tolerance 0.3] [--out <f>]` | `POST /api/v1/primitives/images/remove-background-greenscreen` (polls job) | cheap chroma-key removal of a FLAT solid background → transparent PNG (cloud primitive) |
626
- | `vidfarm tts "…" [--style "…"] [--voice <v>] [--out <file>]` | (LOCAL-FIRST: your own OPENAI/GEMINI/OPENROUTER_API_KEY → audio file on disk; `--cloud` = `POST /api/v1/primitives/audio/speech` + poll) | text → narration audio with a promptable voice style |
627
- | `vidfarm stt <file\|url> [--out <base>] [--no-diarize]` (alias: `transcribe`) | (LOCAL-FIRST: local ffmpeg demux + your own key; `--cloud` = `POST /api/v1/primitives/audio/transcribe` + poll) | video/audiotranscript in BOTH formats: simple subtitles (txt + SRT) and multi-speaker segments (json) |
626
+ | `vidfarm tts "…" [--style "…"] [--voice <v>] [--out <file>]` | (LOCAL-FIRST: your own OPENAI/GEMINI/OPENROUTER_API_KEY → audio file on disk; `--cloud` = `POST /api/v1/primitives/audio/speech` + poll, ElevenLabs on the platform key by default, `--own-key` for yours) | text → narration audio; `--cloud --voice <voice_id>` picks an ElevenLabs voice |
627
+ | `vidfarm music "<prompt>" [--length <sec>] [--out <f>] [--own-key]` | `POST /api/v1/primitives/music/generate` (polls job) | promptmusic track (ElevenLabs; platform key + wallet by default, `--own-key` for yours) |
628
+ | `vidfarm voices [--own-key] [--limit N]` | `GET /api/v1/primitives/audio/voices` | list ElevenLabs voices (voice_id/name/labels) for `tts --voice`; default a voice + tell the user they can choose |
629
+ | `vidfarm stt <file\|url> [--out <base>] [--no-diarize]` (alias: `transcribe`) | (LOCAL-FIRST: local ffmpeg demux + your own key; `--cloud` = `POST /api/v1/primitives/audio/transcribe` + poll, ElevenLabs Scribe on the platform key by default, `--own-key` for yours) | video/audio → transcript in BOTH formats: simple subtitles (txt + SRT) and multi-speaker segments (json) |
628
630
  | `vidfarm place <dir> --src <url\|file> [--at\|--replace]` | (edits local composition.html; local files → serve disk store or temp upload) | drop media (URL **or local file**) into a gap / over a scene |
629
631
  | `vidfarm captions generate <dir> [--style <preset>] [--audio <f>\|--srt <f>\|--text "…"]` | (LOCAL-FIRST: STT on your own key — OpenAI = real word timestamps — then edits local composition.html) | transcribe narration → animated word-by-word caption cues |
630
632
  | `vidfarm captions style <dir> --style <preset>` / `captions list` / `captions clear` | (edits local composition.html) | restyle / inspect / remove animated captions |
@@ -978,19 +980,34 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
978
980
  }'
979
981
  ```
980
982
 
983
+ ## Primitive: music (text → music)
984
+
985
+ Generate music (instrumental, songs with lyrics, background beds, jingles, scores) via ElevenLabs. **Default this on freely — music is a core primitive.** `use_wallet_credits` defaults **true**: it runs on vidfarm's platform ElevenLabs key and bills the customer's wallet. Recommend keeping it on; set it false only to save wallet credits or to use the customer's OWN saved ElevenLabs key.
986
+
987
+ - `POST /api/v1/primitives/music/generate` (alias: `POST /api/v1/primitives/music`)
988
+ - Body: `{ "tracer": "...", "payload": { "prompt": "...", "music_length_ms"?: 30000, "composition_plan"?: {...}, "model"?: "...", "use_wallet_credits"?: true }, "webhook_url"?: "..." }`
989
+ - `prompt` — describe the vibe/genre/instrumentation. `music_length_ms` ≤ 300000 (5 min), default 30000.
990
+ - `composition_plan` (optional) — ElevenLabs section-by-section plan for granular lyric/style control (see the loadable `music` skill pack).
991
+ - Result: durable mp3 (`primary_file_url` / `audio.file_url`). Mount as its own `<audio>` layer ~0.1–0.2 under narration.
992
+ - devcli: `vidfarm music "<prompt>" --length 30` (`--own-key` to use your ElevenLabs key).
993
+
981
994
  ## Primitive: tts (text → speech)
982
995
 
983
- Generate spoken narration audio from text on the caller's saved AI provider keys (OpenAI, Gemini, or OpenRouter BYOK, never wallet-billed). The **voice style is promptable**: `instructions` is a free-form direction for tone, pacing, accent, emotion, and persona, on top of the provider's `voice` preset. Result is a durable Vidfarm audio URL (mp3/wav) ready to drop into a composition as an audio layer.
996
+ Generate spoken narration audio from text. **`use_wallet_credits` defaults true** → high-quality ElevenLabs narration on vidfarm's platform key, billed to the wallet — the recommended default. Set it false to use the customer's OWN ElevenLabs key or a BYOK OpenAI/Gemini/OpenRouter key (BYOK is never wallet-billed). The **voice style is promptable** via `instructions` (tone/pacing/accent/emotion/persona). Result is a durable Vidfarm audio URL (mp3/wav) ready to drop into a composition as an audio layer.
984
997
 
985
998
  - `POST /api/v1/primitives/audio/speech` (alias: `POST /api/v1/primitives/tts`)
986
- - Body: `{ "tracer": "...", "payload": { "text": "...", "voice"?: "...", "instructions"?: "...", "provider"?: "openai" | "gemini" | "openrouter", "model"?: "...", "output_format"?: "mp3" | "wav" }, "webhook_url"?: "..." }`
999
+ - Body: `{ "tracer": "...", "payload": { "text": "...", "voice"?: "...", "instructions"?: "...", "use_wallet_credits"?: true, "provider"?: "openai" | "gemini" | "openrouter", "model"?: "...", "output_format"?: "mp3" | "wav" }, "webhook_url"?: "..." }`
987
1000
  - `text` (required, ≤8000 chars) — the words to speak, verbatim. Aliases: `input`, `script`.
988
- - `voice` (optional) — provider voice preset (OpenAI: `alloy`, `ash`, `coral`, …; Gemini: `Kore`, `Puck`, `Charon`, ).
989
- - `instructions` (optional) — the voice-STYLE prompt, e.g. `"calm, warm bedtime narrator with a slight British accent"` or `"excited sports announcer, fast paced"`. Aliases: `style`, `style_instructions`, `voice_style`.
990
- - `output_format` defaults to `mp3` (note: Gemini answers in wav regardless — read `audio.content_type`).
1001
+ - `voice` (optional) — on the ElevenLabs path, an ElevenLabs **voice_id** (or friendly name george/sarah/…); on the BYOK path a provider preset (OpenAI `alloy`/…; Gemini `Kore`/…). **Default a sensible voice and tell the user they can pick from many** — list them with `GET /api/v1/primitives/audio/voices` (or `vidfarm voices`).
1002
+ - `instructions` (optional) — the voice-STYLE prompt. Aliases: `style`, `style_instructions`, `voice_style`.
1003
+ - `output_format` defaults to `mp3` (Gemini/ElevenLabs may answer in wav — read `audio.content_type`).
991
1004
  - Response: standard primitive job. Poll to completion, then read `primary_file_url` / `audio.file_url`.
992
- - Billing: BYOK the speech call runs on the customer's own provider key and is never wallet-billed.
993
- - devcli: `vidfarm tts "…" --style "…"` runs LOCAL-FIRST on your own env key (no job, no cloud); add `--cloud` to run this route instead.
1005
+ - Billing: wallet-billed on the platform ElevenLabs key (default); free/BYOK when `use_wallet_credits: false` and the caller's own key serves it.
1006
+ - devcli: `vidfarm tts "…" --style "…"` runs LOCAL-FIRST on your own env key (no job); add `--cloud` for the ElevenLabs platform job (`--own-key` = your key). `vidfarm voices` lists voices.
1007
+
1008
+ ## Primitive: audio/voices (list ElevenLabs voices)
1009
+
1010
+ `GET /api/v1/primitives/audio/voices` returns the ElevenLabs voice catalog — `{ scope, default_voice_id, voice_library_url, voices: [{ voice_id, name, category, labels, description, preview_url }] }`. Default scope is vidfarm's platform account; add `?use_wallet_credits=false` to list the voices on the customer's OWN saved ElevenLabs key. Pick a `voice_id` and pass it as `voice` to `/audio/speech`. devcli: `vidfarm voices` (`--own-key` for the user's account).
994
1011
 
995
1012
  Example:
996
1013
 
@@ -1016,13 +1033,14 @@ Transcribe speech from **any video or audio URL** on the caller's saved AI provi
1016
1033
  2. **Advanced multi-speaker version** — speaker-attributed, timestamped segments (`output.segments`, each `{ speaker, start_sec, end_sec, text }`, with `output.speakers` listing the cast) for multi-narration sources like podcasts, interviews, and skits — stored durably as `transcript.json`.
1017
1034
 
1018
1035
  - `POST /api/v1/primitives/audio/transcribe` (alias: `POST /api/v1/primitives/stt`)
1019
- - Body: `{ "tracer": "...", "payload": { "source_url": "https://...", "diarize"?: true, "language"?: "en", "prompt"?: "...", "provider"?: "gemini" | "openai" | "openrouter", "model"?: "...", "media_type"?: "auto" | "video" | "audio" }, "webhook_url"?: "..." }`
1036
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://...", "diarize"?: true, "word_timestamps"?: false, "use_wallet_credits"?: true, "language"?: "en", "prompt"?: "...", "provider"?: "gemini" | "openai" | "openrouter", "model"?: "...", "media_type"?: "auto" | "video" | "audio" }, "webhook_url"?: "..." }`
1020
1037
  - `source_url` (required) — video OR audio URL. Aliases: `source_video_url`, `source_audio_url`, `video_url`, `audio_url`, `url`. For a fork's composition, pick the right source via `GET /api/v1/compositions/:forkId/remove-video-captions` first.
1021
- - `diarize` (default `true`) — attribute segments to speakers. **Speaker labels need a Gemini key** (audio-native model); OpenAI/OpenRouter keys degrade to a plain single-speaker transcript (`diarization: "none"`).
1038
+ - `use_wallet_credits` (default `true`) — ElevenLabs Scribe on the platform key (native diarization + real word timestamps), billed to the wallet. Set false to use the customer's OWN ElevenLabs key or a BYOK gemini/openai/openrouter key.
1039
+ - `diarize` (default `true`) — attribute segments to speakers. On the BYOK path speaker labels need a **Gemini key**; OpenAI/OpenRouter degrade to a single-speaker transcript. ElevenLabs Scribe (the default) diarizes natively.
1022
1040
  - `prompt` (optional) — domain/vocabulary hint; `language` (optional) — expected language code.
1023
1041
  - Response: standard primitive job. Poll to completion; `output` carries both formats inline (very long transcripts set `text_truncated: true` and keep the full copy in the `transcript.json` artifact), and the artifacts `transcript.json` / `transcript.srt` / `transcript.txt` are durable URLs.
1024
1042
  - Limits: ~40 minutes of speech per job (20MB extracted audio) — trim longer sources with `/videos/trim` or `/audio/trim` and transcribe in parts.
1025
- - Billing: the AI transcription is BYOK (never wallet-billed); only the tiny audio-demux compute step bills (`primitive_media_lambda`), like every media primitive.
1043
+ - Billing: wallet-billed on the platform ElevenLabs key (default); free/BYOK when `use_wallet_credits: false`. The tiny audio-demux compute step always bills (`primitive_media_lambda`), like every media primitive.
1026
1044
  - For the CURRENT fork, the decompose-time transcript may already exist via `video-context.json` — prefer it before paying for a new transcription; use this primitive for other URLs, multi-speaker attribution, or SRT output.
1027
1045
  - devcli: `vidfarm stt <file|url>` runs LOCAL-FIRST (local ffmpeg + your own key); add `--cloud` to run this route instead.
1028
1046
 
package/SKILL.platform.md CHANGED
@@ -281,9 +281,9 @@ Primitives are hosted operations that are NOT templates — they're reusable bui
281
281
  - `download_media_video` — social/media URL → durable MP4
282
282
  - `hyperframes_render` — composition HTML → MP4 (the render primitive publishing uses)
283
283
  - `brainstorm` — LLM brainstorm route
284
- - `tts` / `stt` — speech, BYOK on the caller's saved provider keys. `POST /api/v1/primitives/audio/speech` (`primitive:tts`) turns text into narration audio with a promptable voice style (`instructions`); `POST /api/v1/primitives/audio/transcribe` (`primitive:stt`) transcribes any video or audio URL (video demuxes through the primitive-media ffmpeg seam first, 64kbps mono, ~40min/20MB cap) and returns **two formats**: a simple subtitle transcript (plain text + SRT cues) and an advanced multi-speaker version (speaker-attributed timed segments; diarization is Gemini-only JSON-mode audio transcription in `src/services/speech.ts`). The raw provider speech calls live in `src/services/speech.ts`, shared by `ProviderService` (leased BYOK keys, usage metering) and the devcli `vidfarm tts`/`vidfarm stt` commands, which run LOCAL-FIRST on the user's own env key (local ffmpeg demux, no job, no wallet) with `--cloud` as the explicit backup route.
284
+ - `music` / `tts` / `stt` — ElevenLabs audio (music, narration, transcription) with a `use_wallet_credits` flag that **defaults true**. `POST /api/v1/primitives/music/generate` (`primitive:music`) generates music from a prompt or composition plan. `POST /api/v1/primitives/audio/speech` (`primitive:tts`) turns text into narration with a promptable style (`instructions`) and an ElevenLabs `voice_id` (browse via `GET /api/v1/primitives/audio/voices` → `providers.listElevenLabsVoices`). `POST /api/v1/primitives/audio/transcribe` (`primitive:stt`) transcribes any video/audio URL (video demuxes through the primitive-media ffmpeg seam first, 64kbps mono, ~40min/20MB cap) into **two formats** simple subtitles (text + SRT) and speaker-attributed segments. **Routing (`src/services/providers.ts` `resolveAudioAuthPlan`):** `use_wallet_credits: true` (default) the platform `ELEVENLABS_API_KEY`, billed to the wallet (`elevenlabs_music`/`elevenlabs_tts`/`elevenlabs_stt` cost centers); `false` the customer's own saved `elevenlabs` key (no charge), else a BYOK gemini/openai/openrouter key (TTS/STT only), else the platform key + wallet. The ElevenLabs calls live in `src/services/elevenlabs.ts`; the openai/gemini/openrouter speech calls in `src/services/speech.ts`. The `elevenlabs` provider key is **audio-only** it never counts as a qualified text/image/video key. devcli: `vidfarm music`, `vidfarm voices`, and `vidfarm tts`/`vidfarm stt` (LOCAL-FIRST on the user's env key; `--cloud` hits ElevenLabs, `--own-key` uses the user's own key).
285
285
 
286
- Directors call primitives when they want a one-off asset before feeding it into a template. Templates call primitives internally as workflow steps. Each primitive declares one of three billing modes: **WALLET** — a metered platform charge (e.g. `remove_background` / `image_remove_background` RapidAPI pass-through, `video_remove_captions` GhostCut, cloud `hyperframes_render`, the local `image_remove_background_greenscreen` chroma key, and `media_overlay` which layers a small platform fee on top of its BYOK image-gen leg); **BYOK** — the AI spend rides the caller's own provider key and is never wallet-billed (tts/stt, smart decompose, clip tagging/embedding); or **COMPUTE-ONLY** — only the AWS Lambda / Step-Functions compute bills (clip hunts, the stt audio-demux step). So "all primitives are metered against the caller's wallet" holds only for the compute/platform legs — every BYOK AI call is excepted.
286
+ Directors call primitives when they want a one-off asset before feeding it into a template. Templates call primitives internally as workflow steps. Each primitive declares one of three billing modes: **WALLET** — a metered platform charge (e.g. `remove_background` / `image_remove_background` RapidAPI pass-through, `video_remove_captions` GhostCut, cloud `hyperframes_render`, the local `image_remove_background_greenscreen` chroma key, `media_overlay`, and **platform ElevenLabs music/TTS/STT when `use_wallet_credits` is on** the default); **BYOK** — the AI spend rides the caller's own provider key and is never wallet-billed (smart decompose, clip tagging/embedding, and music/TTS/STT run with `use_wallet_credits: false` on the user's own ElevenLabs/openai/gemini/openrouter key); or **COMPUTE-ONLY** — only the AWS Lambda / Step-Functions compute bills (clip hunts, the stt audio-demux step). So "all primitives are metered against the caller's wallet" holds only for the compute/platform legs — every BYOK AI call is excepted.
287
287
 
288
288
  ## Billing
289
289