@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.
Files changed (38) 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/demo/dist/app.css +1 -1
  13. package/demo/dist/app.js +75 -75
  14. package/dist/src/app.js +129 -41
  15. package/dist/src/cli.js +162 -5
  16. package/dist/src/config.js +12 -0
  17. package/dist/src/devcli/clips.js +7 -2
  18. package/dist/src/domain.js +3 -0
  19. package/dist/src/editor-chat.js +5 -1
  20. package/dist/src/frontend/file-directory.js +41 -18
  21. package/dist/src/primitive-context.js +14 -0
  22. package/dist/src/primitive-registry.js +140 -18
  23. package/dist/src/reskin/chat-page.js +1 -1
  24. package/dist/src/reskin/inpaint-clipper-page.js +446 -205
  25. package/dist/src/reskin/library-page.js +7 -1
  26. package/dist/src/reskin/portfolio-page.js +9 -9
  27. package/dist/src/reskin/settings-page.js +4 -4
  28. package/dist/src/reskin/theme.js +8 -4
  29. package/dist/src/services/billing.js +5 -0
  30. package/dist/src/services/clip-curation/ffmpeg.js +48 -0
  31. package/dist/src/services/clip-curation/hunt.js +2 -0
  32. package/dist/src/services/clip-curation/index.js +1 -1
  33. package/dist/src/services/clip-curation/scan.js +29 -16
  34. package/dist/src/services/elevenlabs.js +222 -0
  35. package/dist/src/services/providers.js +216 -2
  36. package/dist/src/services/swipe-customize.js +5 -2
  37. package/package.json +1 -1
  38. package/public/assets/file-directory-app.js +3 -2
@@ -429,7 +429,11 @@ const ttsPayloadSchema = z.preprocess((raw) => {
429
429
  text: z.string().min(1).max(8000),
430
430
  voice: z.string().min(1).max(120).optional(),
431
431
  instructions: z.string().min(1).max(4000).optional(),
432
- output_format: z.enum(["mp3", "wav"]).default("mp3")
432
+ output_format: z.enum(["mp3", "wav"]).default("mp3"),
433
+ // Default TRUE: great ElevenLabs narration out of the box on the platform key,
434
+ // billed to the wallet. Set false to use your own ElevenLabs key or a BYOK
435
+ // openai/gemini/openrouter key instead. See voice options via /audio/voices.
436
+ use_wallet_credits: z.boolean().default(true)
433
437
  }).superRefine((value, ctx) => {
434
438
  // Reject an explicit provider/voice contradiction at enqueue (400) instead
435
439
  // of queueing a job that can only fail at the provider.
@@ -465,7 +469,36 @@ const sttPayloadSchema = z.preprocess((raw) => {
465
469
  // output and downstream callers estimate word windows.
466
470
  word_timestamps: z.boolean().default(false),
467
471
  language: z.string().min(2).max(16).optional(),
468
- prompt: z.string().min(1).max(2000).optional()
472
+ prompt: z.string().min(1).max(2000).optional(),
473
+ // Default TRUE: platform ElevenLabs Scribe (native diarization + real word
474
+ // timestamps), billed to the wallet. Set false to use your own ElevenLabs
475
+ // key or a BYOK gemini/openai/openrouter key.
476
+ use_wallet_credits: z.boolean().default(true)
477
+ }));
478
+ // Music generation (ElevenLabs). Prompt-based, or a hand-built composition plan.
479
+ const musicPayloadSchema = z.preprocess((raw) => {
480
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
481
+ return raw;
482
+ const payload = raw;
483
+ return {
484
+ ...payload,
485
+ prompt: payload.prompt ?? payload.text ?? payload.description,
486
+ music_length_ms: payload.music_length_ms ?? payload.length_ms ?? payload.duration_ms
487
+ };
488
+ }, z.object({
489
+ prompt: z.string().min(1).max(2000).optional(),
490
+ // ElevenLabs music caps at 5 minutes per generation.
491
+ music_length_ms: z.number().int().min(3000).max(300000).default(30000),
492
+ // Optional ElevenLabs composition plan for granular section-by-section control.
493
+ composition_plan: z.record(z.string(), z.any()).optional(),
494
+ model: z.string().min(1).optional(),
495
+ output_format: z.string().min(1).optional(),
496
+ // Default TRUE: platform ElevenLabs key, billed to the wallet. Set false to
497
+ // use your own saved ElevenLabs key (no wallet charge).
498
+ use_wallet_credits: z.boolean().default(true)
499
+ }).refine((value) => Boolean(value.prompt) || Boolean(value.composition_plan), {
500
+ message: "Provide either a prompt or a composition_plan.",
501
+ path: ["prompt"]
469
502
  }));
470
503
  // Animated captions from speech: STT with word-level timings, paged into
471
504
  // ready-to-apply cue layers. Either transcribe a source_url or page plain text.
@@ -714,6 +747,7 @@ const PRIMITIVE_BRAINSTORM_PRODUCT_PLACEMENT_ID = "primitive:brainstorm_product_
714
747
  const PRIMITIVE_MEDIA_DEDUPE_ID = "primitive:media_dedupe";
715
748
  const PRIMITIVE_TTS_ID = "primitive:tts";
716
749
  const PRIMITIVE_STT_ID = "primitive:stt";
750
+ const PRIMITIVE_MUSIC_ID = "primitive:music";
717
751
  const PRIMITIVE_CAPTIONS_ID = "primitive:captions";
718
752
  const PRIMITIVE_SPEECH_REGENERATE_ID = "primitive:speech_regenerate";
719
753
  // Gemini inline audio caps out around 20MB; at the 64kbps mono mp3 we extract,
@@ -2018,20 +2052,34 @@ const ttsPrimitive = definePrimitive({
2018
2052
  text: payload.text,
2019
2053
  voice: payload.voice,
2020
2054
  instructions: payload.instructions,
2021
- responseFormat: payload.output_format
2022
- });
2023
- // Gemini TTS answers in wav regardless of the requested format; name the
2024
- // artifact after what actually came back.
2055
+ responseFormat: payload.output_format,
2056
+ useWalletCredits: payload.use_wallet_credits
2057
+ });
2058
+ // Route may have resolved to a different provider (e.g. platform ElevenLabs
2059
+ // when use_wallet_credits is on); report what actually ran.
2060
+ const effectiveProvider = generated.effectiveProvider;
2061
+ const effectiveModel = generated.effectiveModel;
2062
+ if (generated.billing) {
2063
+ await ctx.billing.record({
2064
+ type: "ai_generation",
2065
+ costUsd: generated.billing.costUsd,
2066
+ costCenterSlug: generated.billing.costCenterSlug,
2067
+ metadata: { ...generated.billing.metadata, primitive: "tts", voice: payload.voice ?? null }
2068
+ });
2069
+ }
2070
+ // Gemini/ElevenLabs TTS may answer in wav regardless of the requested
2071
+ // format; name the artifact after what actually came back.
2025
2072
  const extension = generated.contentType.includes("wav") ? "wav" : "mp3";
2026
2073
  const stored = await ctx.storage.putBuffer(`speech.${extension}`, generated.bytes, {
2027
2074
  contentType: generated.contentType,
2028
2075
  kind: "audio",
2029
2076
  metadata: {
2030
2077
  primitive: "tts",
2031
- provider,
2032
- model,
2078
+ provider: effectiveProvider,
2079
+ model: effectiveModel,
2033
2080
  voice: payload.voice ?? null,
2034
- styleInstructions: payload.instructions ?? null
2081
+ styleInstructions: payload.instructions ?? null,
2082
+ wallet_billed: Boolean(generated.billing)
2035
2083
  }
2036
2084
  });
2037
2085
  ctx.logger.progress(1, "Speech generation complete");
@@ -2044,11 +2092,72 @@ const ttsPrimitive = definePrimitive({
2044
2092
  audio: {
2045
2093
  file_url: stored.url,
2046
2094
  content_type: generated.contentType,
2047
- provider,
2048
- model,
2095
+ provider: effectiveProvider,
2096
+ model: effectiveModel,
2049
2097
  voice: payload.voice ?? null,
2050
2098
  style_instructions: payload.instructions ?? null,
2051
- text_length: payload.text.length
2099
+ text_length: payload.text.length,
2100
+ wallet_billed: Boolean(generated.billing)
2101
+ }
2102
+ }
2103
+ };
2104
+ }
2105
+ }
2106
+ });
2107
+ const musicPrimitive = definePrimitive({
2108
+ id: PRIMITIVE_MUSIC_ID,
2109
+ kind: "music",
2110
+ operations: {
2111
+ run: {
2112
+ description: "Generate music (instrumental or with lyrics) from a prompt or a composition plan via ElevenLabs, persisted as a reusable platform audio URL.",
2113
+ inputSchema: musicPayloadSchema,
2114
+ workflow: "run"
2115
+ }
2116
+ },
2117
+ jobs: {
2118
+ async run(ctx, input) {
2119
+ const payload = musicPayloadSchema.parse(input);
2120
+ ctx.logger.progress(0.12, "Composing music");
2121
+ const generated = await ctx.providers.generateMusic({
2122
+ prompt: payload.prompt,
2123
+ musicLengthMs: payload.music_length_ms,
2124
+ compositionPlan: payload.composition_plan,
2125
+ model: payload.model,
2126
+ outputFormat: payload.output_format,
2127
+ useWalletCredits: payload.use_wallet_credits
2128
+ });
2129
+ if (generated.billing) {
2130
+ await ctx.billing.record({
2131
+ type: "ai_generation",
2132
+ costUsd: generated.billing.costUsd,
2133
+ costCenterSlug: generated.billing.costCenterSlug,
2134
+ metadata: { ...generated.billing.metadata, primitive: "music", prompt: payload.prompt ?? null }
2135
+ });
2136
+ }
2137
+ const stored = await ctx.storage.putBuffer("music.mp3", generated.bytes, {
2138
+ contentType: generated.contentType,
2139
+ kind: "audio",
2140
+ metadata: {
2141
+ primitive: "music",
2142
+ provider: "elevenlabs",
2143
+ music_length_ms: generated.musicLengthMs,
2144
+ wallet_billed: Boolean(generated.billing)
2145
+ }
2146
+ });
2147
+ ctx.logger.progress(1, "Music generation complete");
2148
+ return {
2149
+ progress: 1,
2150
+ output: {
2151
+ files: compactUrls([stored.url]),
2152
+ primary_file_url: stored.url,
2153
+ audioUrl: stored.url,
2154
+ audio: {
2155
+ file_url: stored.url,
2156
+ content_type: generated.contentType,
2157
+ provider: "elevenlabs",
2158
+ music_length_ms: generated.musicLengthMs,
2159
+ prompt: payload.prompt ?? null,
2160
+ wallet_billed: Boolean(generated.billing)
2052
2161
  }
2053
2162
  }
2054
2163
  };
@@ -2122,18 +2231,29 @@ const sttPrimitive = definePrimitive({
2122
2231
  const payload = sttPayloadSchema.parse(input);
2123
2232
  const { audioBytes, audioContentType, extractionMetadata } = await loadSpeechAudioForPrimitive(ctx, payload);
2124
2233
  ctx.logger.progress(0.45, "Transcribing speech");
2125
- const provider = payload.provider ?? (payload.word_timestamps ? "openai" : "gemini");
2126
- const model = payload.model ?? defaultSpeechModelFor("stt", provider) ?? "";
2234
+ const requestedProvider = payload.provider ?? (payload.word_timestamps ? "openai" : "gemini");
2235
+ const requestedModel = payload.model ?? defaultSpeechModelFor("stt", requestedProvider) ?? "";
2127
2236
  const transcription = await ctx.providers.transcribeSpeech({
2128
- provider,
2129
- model,
2237
+ provider: requestedProvider,
2238
+ model: requestedModel,
2130
2239
  audio: audioBytes,
2131
2240
  contentType: audioContentType,
2132
2241
  prompt: payload.prompt,
2133
2242
  language: payload.language,
2134
2243
  diarize: payload.diarize,
2135
- wordTimestamps: payload.word_timestamps
2136
- });
2244
+ wordTimestamps: payload.word_timestamps,
2245
+ useWalletCredits: payload.use_wallet_credits
2246
+ });
2247
+ const provider = transcription.effectiveProvider;
2248
+ const model = transcription.effectiveModel;
2249
+ if (transcription.billing) {
2250
+ await ctx.billing.record({
2251
+ type: "ai_generation",
2252
+ costUsd: transcription.billing.costUsd,
2253
+ costCenterSlug: transcription.billing.costCenterSlug,
2254
+ metadata: { ...transcription.billing.metadata, primitive: "stt", source_url: payload.source_url }
2255
+ });
2256
+ }
2137
2257
  ctx.logger.progress(0.85, "Storing transcript artifacts");
2138
2258
  const speakers = [...new Set(transcription.segments.map((segment) => segment.speaker))];
2139
2259
  const srt = buildSrtFromSegments(transcription.segments);
@@ -3114,6 +3234,7 @@ class PrimitiveRegistry {
3114
3234
  [audioNormalizePrimitive.id, audioNormalizePrimitive],
3115
3235
  [ttsPrimitive.id, ttsPrimitive],
3116
3236
  [sttPrimitive.id, sttPrimitive],
3237
+ [musicPrimitive.id, musicPrimitive],
3117
3238
  [captionsPrimitive.id, captionsPrimitive],
3118
3239
  [speechRegeneratePrimitive.id, speechRegeneratePrimitive],
3119
3240
  [hyperframesRenderPrimitive.id, hyperframesRenderPrimitive],
@@ -3521,6 +3642,7 @@ function normalizeProductPlacementPayload(raw) {
3521
3642
  };
3522
3643
  }
3523
3644
  function preferredTextProvider(availableProviders) {
3645
+ // Text-only priority — the audio-only elevenlabs provider is never a candidate.
3524
3646
  const priority = ["openai", "gemini", "openrouter", "perplexity"];
3525
3647
  return priority.find((provider) => availableProviders.includes(provider)) ?? "openai";
3526
3648
  }
@@ -59,8 +59,8 @@ export function renderReskinChat(input = DEFAULT_INPUT, opts = {}) {
59
59
  <select class="rk-chat-mode-select" id="rk-chat-mode">
60
60
  <option value="/chat" selected>Chat</option>
61
61
  <option value="/tools/image">Create Image</option>
62
- <option value="/tools/clipper">Clip a Video</option>
63
62
  <option value="/editor/original/fork/new">Create Video</option>
63
+ <option value="/tools/clipper">Clip Media</option>
64
64
  </select>
65
65
  <span class="rk-chat-mode-chev" aria-hidden="true">&#9662;</span>
66
66
  </label>