@mevdragon/vidfarm-devcli 0.19.1 → 0.20.1
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.
- package/.agents/skills/music/SKILL.md +416 -0
- package/.agents/skills/music/references/api_reference.md +519 -0
- package/.agents/skills/music/references/installation.md +65 -0
- package/.agents/skills/text-to-speech/SKILL.md +226 -0
- package/.agents/skills/text-to-speech/references/installation.md +90 -0
- package/.agents/skills/text-to-speech/references/streaming.md +307 -0
- package/.agents/skills/text-to-speech/references/voice-settings.md +115 -0
- package/.agents/skills/vidfarm-media/SKILL.md +25 -11
- package/.agents/skills/vidfarm-media/references/tts.md +41 -3
- package/SKILL.director.md +31 -13
- package/SKILL.platform.md +2 -2
- package/demo/dist/app.css +1 -1
- package/demo/dist/app.js +75 -75
- package/dist/src/app.js +129 -41
- package/dist/src/cli.js +162 -5
- package/dist/src/config.js +12 -0
- package/dist/src/devcli/clips.js +7 -2
- package/dist/src/domain.js +3 -0
- package/dist/src/editor-chat.js +5 -1
- package/dist/src/frontend/file-directory.js +41 -18
- package/dist/src/primitive-context.js +14 -0
- package/dist/src/primitive-registry.js +140 -18
- package/dist/src/reskin/chat-page.js +1 -1
- package/dist/src/reskin/inpaint-clipper-page.js +446 -205
- package/dist/src/reskin/library-page.js +7 -1
- package/dist/src/reskin/portfolio-page.js +9 -9
- package/dist/src/reskin/settings-page.js +4 -4
- package/dist/src/reskin/theme.js +8 -4
- package/dist/src/services/billing.js +5 -0
- package/dist/src/services/clip-curation/ffmpeg.js +48 -0
- package/dist/src/services/clip-curation/hunt.js +2 -0
- package/dist/src/services/clip-curation/index.js +1 -1
- package/dist/src/services/clip-curation/scan.js +29 -16
- package/dist/src/services/elevenlabs.js +222 -0
- package/dist/src/services/providers.js +216 -2
- package/dist/src/services/swipe-customize.js +5 -2
- package/package.json +1 -1
- package/public/assets/file-directory-app.js +3 -2
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: music
|
|
3
|
+
description: Generate music using ElevenLabs Music API. Use when creating instrumental tracks, songs with lyrics, background music, jingles, or any AI-generated music composition. Supports prompt-based generation, composition plans for granular control, and detailed output with metadata.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Requires internet access and an ElevenLabs API key (ELEVENLABS_API_KEY).
|
|
6
|
+
metadata: {"openclaw": {"requires": {"env": ["ELEVENLABS_API_KEY"]}, "primaryEnv": "ELEVENLABS_API_KEY"}}
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ElevenLabs Music Generation
|
|
10
|
+
|
|
11
|
+
Generate music from text prompts - supports instrumental tracks, songs with lyrics, and fine-grained control via composition plans.
|
|
12
|
+
|
|
13
|
+
> **Setup:** See [Installation Guide](references/installation.md). For JavaScript, use `@elevenlabs/*` packages only.
|
|
14
|
+
|
|
15
|
+
All examples below default to `music_v2`, the current generation model. Pass `model_id="music_v1"` only when explicitly requested to.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Python
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from elevenlabs import ElevenLabs
|
|
23
|
+
|
|
24
|
+
client = ElevenLabs()
|
|
25
|
+
|
|
26
|
+
audio = client.music.compose(
|
|
27
|
+
prompt="A chill lo-fi hip hop beat with jazzy piano chords",
|
|
28
|
+
music_length_ms=30000,
|
|
29
|
+
model_id="music_v2",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
with open("output.mp3", "wb") as f:
|
|
33
|
+
for chunk in audio:
|
|
34
|
+
f.write(chunk)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### TypeScript
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
|
|
41
|
+
import { createWriteStream } from "fs";
|
|
42
|
+
|
|
43
|
+
const client = new ElevenLabsClient();
|
|
44
|
+
const audio = await client.music.compose({
|
|
45
|
+
prompt: "A chill lo-fi hip hop beat with jazzy piano chords",
|
|
46
|
+
musicLengthMs: 30000,
|
|
47
|
+
modelId: "music_v2",
|
|
48
|
+
});
|
|
49
|
+
audio.pipe(createWriteStream("output.mp3"));
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### cURL
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -X POST "https://api.elevenlabs.io/v1/music" \
|
|
56
|
+
-H "xi-api-key: $ELEVENLABS_API_KEY" -H "Content-Type: application/json" \
|
|
57
|
+
-d '{"prompt": "A chill lo-fi beat", "music_length_ms": 30000, "model_id": "music_v2"}' \
|
|
58
|
+
--output output.mp3
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Methods
|
|
62
|
+
|
|
63
|
+
| Method | Description |
|
|
64
|
+
|--------|-------------|
|
|
65
|
+
| `music.compose` | Generate audio from a prompt or composition plan |
|
|
66
|
+
| `music.stream` | Stream audio chunks as they are generated (paid plans) |
|
|
67
|
+
| `music.composition_plan.create` | Generate a structured plan for fine-grained control |
|
|
68
|
+
| `music.compose_detailed` | Generate audio + composition plan + metadata; pass `store_for_inpainting=True` to enable inpainting |
|
|
69
|
+
| `music.compose_detailed_stream` | Stream audio plus composition plan, metadata, and optional word timestamps as Server-Sent Events |
|
|
70
|
+
| `music.video_to_music` | Generate background music from one or more uploaded video files |
|
|
71
|
+
| `music.upload` | Upload an audio file for later inpainting workflows, optionally extracting its composition plan or word-level timestamps |
|
|
72
|
+
|
|
73
|
+
See [API Reference](references/api_reference.md) for full parameter details.
|
|
74
|
+
|
|
75
|
+
`music.upload` is available to enterprise clients with access to the inpainting feature.
|
|
76
|
+
|
|
77
|
+
## Video to Music
|
|
78
|
+
|
|
79
|
+
Generate background music from uploaded video clips via
|
|
80
|
+
[`POST /v1/music/video-to-music`](https://elevenlabs.io/docs/api-reference/music/video-to-music)
|
|
81
|
+
(`client.music.video_to_music`). This is separate from prompt-based
|
|
82
|
+
[`music.compose`](https://elevenlabs.io/docs/api-reference/music/compose) (`POST /v1/music`).
|
|
83
|
+
|
|
84
|
+
The API combines videos in order, accepts an optional natural-language description, and lets you
|
|
85
|
+
steer style with up to 10 tags such as `upbeat` or `cinematic`. This endpoint still defaults to
|
|
86
|
+
`music_v1`; pass `model_id="music_v2"` to use the newer model.
|
|
87
|
+
|
|
88
|
+
### Python
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from elevenlabs import ElevenLabs
|
|
92
|
+
|
|
93
|
+
client = ElevenLabs()
|
|
94
|
+
|
|
95
|
+
audio = client.music.video_to_music(
|
|
96
|
+
videos=["trailer.mp4"],
|
|
97
|
+
description="Build suspense, then resolve with a warm cinematic finish.",
|
|
98
|
+
tags=["cinematic", "suspenseful", "uplifting"],
|
|
99
|
+
model_id="music_v2",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
with open("video-score.mp3", "wb") as f:
|
|
103
|
+
for chunk in audio:
|
|
104
|
+
f.write(chunk)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### TypeScript
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
|
|
111
|
+
import { createReadStream, createWriteStream } from "fs";
|
|
112
|
+
|
|
113
|
+
const client = new ElevenLabsClient();
|
|
114
|
+
|
|
115
|
+
const audio = await client.music.videoToMusic({
|
|
116
|
+
videos: [createReadStream("trailer.mp4")],
|
|
117
|
+
description: "Build suspense, then resolve with a warm cinematic finish.",
|
|
118
|
+
tags: ["cinematic", "suspenseful", "uplifting"],
|
|
119
|
+
modelId: "music_v2",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
audio.pipe(createWriteStream("video-score.mp3"));
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### cURL
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
curl -X POST "https://api.elevenlabs.io/v1/music/video-to-music" \
|
|
129
|
+
-H "xi-api-key: $ELEVENLABS_API_KEY" \
|
|
130
|
+
-F "videos=@trailer.mp4" \
|
|
131
|
+
-F "description=Build suspense, then resolve with a warm cinematic finish." \
|
|
132
|
+
-F "tags=cinematic" \
|
|
133
|
+
-F "tags=suspenseful" \
|
|
134
|
+
-F "tags=uplifting" \
|
|
135
|
+
-F "model_id=music_v2" \
|
|
136
|
+
--output video-score.mp3
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Constraints from the current API schema:
|
|
140
|
+
|
|
141
|
+
- Upload 1-10 video files per request
|
|
142
|
+
- Keep total combined upload size at or below 200 MB
|
|
143
|
+
- Keep total combined video duration at or below 600 seconds
|
|
144
|
+
- Use `description` for high-level musical direction and `tags` for concise style cues
|
|
145
|
+
|
|
146
|
+
## Composition Plans
|
|
147
|
+
|
|
148
|
+
`music_v2` composition plans are an ordered list of `chunks`. Each chunk specifies its own
|
|
149
|
+
`text` (section label, lyrics, inline cues), `duration_ms`, `positive_styles`, `negative_styles`,
|
|
150
|
+
and `context_adherence` (`low`, `medium`, or `high`, default `high`). Up to 30 chunks per plan,
|
|
151
|
+
each 3,000–120,000 ms, total length 3 s to 10 minutes.
|
|
152
|
+
|
|
153
|
+
Generate a plan first, edit it, then compose:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
plan = client.music.composition_plan.create(
|
|
157
|
+
prompt="An epic orchestral piece building to a climax",
|
|
158
|
+
music_length_ms=60000,
|
|
159
|
+
model_id="music_v2",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Edit chunks in place
|
|
163
|
+
plan["chunks"][0]["text"] = "[Intro]\nQuiet strings rising"
|
|
164
|
+
|
|
165
|
+
audio = client.music.compose(
|
|
166
|
+
composition_plan=plan,
|
|
167
|
+
model_id="music_v2",
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const plan = await client.music.compositionPlan.create({
|
|
173
|
+
prompt: "An epic orchestral piece building to a climax",
|
|
174
|
+
musicLengthMs: 60000,
|
|
175
|
+
modelId: "music_v2",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
plan.chunks[0].text = "[Intro]\nQuiet strings rising";
|
|
179
|
+
|
|
180
|
+
const audio = await client.music.compose({
|
|
181
|
+
compositionPlan: plan,
|
|
182
|
+
modelId: "music_v2",
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Or hand-build a plan to control lyrics and style per section:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
composition_plan = {
|
|
190
|
+
"chunks": [
|
|
191
|
+
{
|
|
192
|
+
"text": "[Verse]\nWalking down an empty street",
|
|
193
|
+
"duration_ms": 15000,
|
|
194
|
+
"positive_styles": ["pop", "upbeat", "female vocals", "acoustic guitar"],
|
|
195
|
+
"negative_styles": ["dark", "slow"],
|
|
196
|
+
"context_adherence": "high",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"text": "[Chorus]\nThis is my moment",
|
|
200
|
+
"duration_ms": 15000,
|
|
201
|
+
"positive_styles": ["powerful vocals", "full band"],
|
|
202
|
+
"negative_styles": [],
|
|
203
|
+
"context_adherence": "high",
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
audio = client.music.compose(composition_plan=composition_plan, model_id="music_v2")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const compositionPlan = {
|
|
213
|
+
chunks: [
|
|
214
|
+
{
|
|
215
|
+
text: "[Verse]\nWalking down an empty street",
|
|
216
|
+
durationMs: 15000,
|
|
217
|
+
positiveStyles: ["pop", "upbeat", "female vocals", "acoustic guitar"],
|
|
218
|
+
negativeStyles: ["dark", "slow"],
|
|
219
|
+
contextAdherence: "high",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
text: "[Chorus]\nThis is my moment",
|
|
223
|
+
durationMs: 15000,
|
|
224
|
+
positiveStyles: ["powerful vocals", "full band"],
|
|
225
|
+
negativeStyles: [],
|
|
226
|
+
contextAdherence: "high",
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const audio = await client.music.compose({
|
|
232
|
+
compositionPlan,
|
|
233
|
+
modelId: "music_v2",
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Put broader characteristics (genre, instrumentation, vocal style) in `positive_styles`, not in
|
|
238
|
+
`text`. The first chunk's styles set the overall tone — include 6–7 styles there.
|
|
239
|
+
|
|
240
|
+
## Output Formats
|
|
241
|
+
|
|
242
|
+
Use the `output_format` query parameter on compose, detailed compose, or stream requests to select
|
|
243
|
+
the generated audio format. `auto` chooses a model-appropriate MP3 format; for `music_v2`, it
|
|
244
|
+
selects `mp3_48000_192`. Higher-bitrate MP3 options include `mp3_48000_240` and `mp3_48000_320`.
|
|
245
|
+
|
|
246
|
+
## Streaming
|
|
247
|
+
|
|
248
|
+
For paid plans, stream audio chunks as they are generated instead of waiting for the full file:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from io import BytesIO
|
|
252
|
+
|
|
253
|
+
stream = client.music.stream(
|
|
254
|
+
prompt="A driving synthwave track with arpeggiated leads",
|
|
255
|
+
music_length_ms=30000,
|
|
256
|
+
model_id="music_v2",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
buffer = BytesIO()
|
|
260
|
+
for chunk in stream:
|
|
261
|
+
if chunk:
|
|
262
|
+
buffer.write(chunk)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const stream = await client.music.stream({
|
|
267
|
+
prompt: "A driving synthwave track with arpeggiated leads",
|
|
268
|
+
musicLengthMs: 30000,
|
|
269
|
+
modelId: "music_v2",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const chunks: Buffer[] = [];
|
|
273
|
+
for await (const chunk of stream) {
|
|
274
|
+
chunks.push(chunk);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Detailed streaming
|
|
279
|
+
|
|
280
|
+
Use detailed streaming when the application needs generated music metadata while audio is still
|
|
281
|
+
arriving. `POST /v1/music/detailed/stream` accepts the same prompt or composition-plan body as
|
|
282
|
+
detailed compose, streams `text/event-stream`, and can include word timestamps with
|
|
283
|
+
`with_timestamps`.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
curl -N -X POST "https://api.elevenlabs.io/v1/music/detailed/stream?output_format=auto" \
|
|
287
|
+
-H "xi-api-key: $ELEVENLABS_API_KEY" \
|
|
288
|
+
-H "Content-Type: application/json" \
|
|
289
|
+
-d '{"prompt": "A bright indie pop hook with warm guitars", "music_length_ms": 30000, "model_id": "music_v2", "with_timestamps": true}'
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Inpainting
|
|
293
|
+
|
|
294
|
+
Inpainting edits or extends a stored song by mixing **audio reference chunks** (unchanged slices
|
|
295
|
+
of a stored song) with new **generation chunks** in a single composition plan.
|
|
296
|
+
|
|
297
|
+
Step 1 — get a `song_id`, either by storing a fresh generation or uploading existing audio:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
# Option A: keep a generation for later editing
|
|
301
|
+
result = client.music.compose_detailed(
|
|
302
|
+
prompt="An upbeat pop song with verse and chorus",
|
|
303
|
+
music_length_ms=60000,
|
|
304
|
+
model_id="music_v2",
|
|
305
|
+
store_for_inpainting=True,
|
|
306
|
+
)
|
|
307
|
+
song_id = result.song_id
|
|
308
|
+
|
|
309
|
+
# Option B: upload an existing track and extract its plan
|
|
310
|
+
uploaded = client.music.upload(
|
|
311
|
+
file=open("my-song.mp3", "rb"),
|
|
312
|
+
extract_composition_plan="music_v2",
|
|
313
|
+
)
|
|
314
|
+
song_id = uploaded.song_id
|
|
315
|
+
composition_plan = uploaded.composition_plan
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { createReadStream } from "fs";
|
|
320
|
+
|
|
321
|
+
// Option A: keep a generation for later editing
|
|
322
|
+
const result = await client.music.composeDetailed({
|
|
323
|
+
prompt: "An upbeat pop song with verse and chorus",
|
|
324
|
+
musicLengthMs: 60000,
|
|
325
|
+
modelId: "music_v2",
|
|
326
|
+
storeForInpainting: true,
|
|
327
|
+
});
|
|
328
|
+
let songId = result.songId;
|
|
329
|
+
|
|
330
|
+
// Option B: upload an existing track and extract its plan
|
|
331
|
+
const uploaded = await client.music.upload({
|
|
332
|
+
file: createReadStream("my-song.mp3"),
|
|
333
|
+
extractCompositionPlan: "music_v2",
|
|
334
|
+
});
|
|
335
|
+
songId = uploaded.songId;
|
|
336
|
+
const compositionPlan = uploaded.compositionPlan;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Step 2 — compose a plan that references the stored audio and regenerates the part you want to
|
|
340
|
+
change:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
plan = {
|
|
344
|
+
"chunks": [
|
|
345
|
+
{"song_id": song_id, "range": {"start_ms": 0, "end_ms": 30000}},
|
|
346
|
+
{
|
|
347
|
+
"text": "[Chorus]\nWe're rising up tonight",
|
|
348
|
+
"duration_ms": 30000,
|
|
349
|
+
"positive_styles": ["bigger drums", "layered vocals", "anthemic"],
|
|
350
|
+
"negative_styles": ["sparse"],
|
|
351
|
+
"context_adherence": "high",
|
|
352
|
+
},
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
audio = client.music.compose(composition_plan=plan, model_id="music_v2")
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const plan = {
|
|
361
|
+
chunks: [
|
|
362
|
+
{ songId, range: { startMs: 0, endMs: 30000 } },
|
|
363
|
+
{
|
|
364
|
+
text: "[Chorus]\nWe're rising up tonight",
|
|
365
|
+
durationMs: 30000,
|
|
366
|
+
positiveStyles: ["bigger drums", "layered vocals", "anthemic"],
|
|
367
|
+
negativeStyles: ["sparse"],
|
|
368
|
+
contextAdherence: "high",
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const audio = await client.music.compose({
|
|
374
|
+
compositionPlan: plan,
|
|
375
|
+
modelId: "music_v2",
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
To match the feel of a stored slice without copying it, attach a `conditioning_ref` (up to
|
|
380
|
+
30,000 ms) plus a `condition_strength` of `low`, `medium`, `high`, or `xhigh` to a generation
|
|
381
|
+
chunk. Conditioning placed on the first chunk influences every later chunk.
|
|
382
|
+
|
|
383
|
+
See [API Reference](references/api_reference.md) for the full inpainting parameter list.
|
|
384
|
+
|
|
385
|
+
## Content Restrictions
|
|
386
|
+
|
|
387
|
+
- Cannot reference specific artists, bands, or copyrighted lyrics
|
|
388
|
+
- `bad_prompt` errors include a `prompt_suggestion` with alternative phrasing
|
|
389
|
+
- `bad_composition_plan` errors include a `composition_plan_suggestion`
|
|
390
|
+
|
|
391
|
+
## Error Handling
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
try:
|
|
395
|
+
audio = client.music.compose(prompt="...", music_length_ms=30000)
|
|
396
|
+
except Exception as e:
|
|
397
|
+
print(f"API error: {e}")
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
try {
|
|
402
|
+
const audio = await client.music.compose({
|
|
403
|
+
prompt: "...",
|
|
404
|
+
musicLengthMs: 30000,
|
|
405
|
+
});
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.error("API error:", err);
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Common errors: 401 (invalid key), 422 (invalid params), 429 (rate limit).
|
|
412
|
+
|
|
413
|
+
## References
|
|
414
|
+
|
|
415
|
+
- [Installation Guide](references/installation.md)
|
|
416
|
+
- [API Reference](references/api_reference.md)
|