varg.ai-sdk 0.1.0 → 0.4.0-alpha.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 (236) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +48 -8
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +63 -90
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -227
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -493
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -112
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -187
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -135
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -201
  217. package/index.ts +0 -38
  218. package/lib/README.md +0 -144
  219. package/lib/ai-sdk/fal.ts +0 -106
  220. package/lib/ai-sdk/replicate.ts +0 -107
  221. package/lib/elevenlabs.ts +0 -382
  222. package/lib/fal.ts +0 -478
  223. package/lib/ffmpeg.ts +0 -467
  224. package/lib/fireworks.ts +0 -235
  225. package/lib/groq.ts +0 -246
  226. package/lib/higgsfield.ts +0 -176
  227. package/lib/remotion/SKILL.md +0 -823
  228. package/lib/remotion/cli.ts +0 -115
  229. package/lib/remotion/functions.ts +0 -283
  230. package/lib/remotion/index.ts +0 -19
  231. package/lib/remotion/templates.ts +0 -73
  232. package/lib/replicate.ts +0 -304
  233. package/output.txt +0 -1
  234. package/test-import.ts +0 -7
  235. package/test-services.ts +0 -97
  236. package/utilities/s3.ts +0 -147
@@ -1,493 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * video editing service
5
- * combines multiple ffmpeg operations into common workflows
6
- */
7
-
8
- import { existsSync } from "node:fs";
9
- import { extname } from "node:path";
10
- import {
11
- type AddAudioOptions,
12
- addAudio,
13
- type ConcatVideosOptions,
14
- type ConvertFormatOptions,
15
- concatVideos,
16
- convertFormat,
17
- extractAudio,
18
- type ResizeVideoOptions,
19
- resizeVideo,
20
- type TrimVideoOptions,
21
- trimVideo,
22
- } from "../../lib/ffmpeg";
23
-
24
- // types
25
- export interface EditPipelineStep {
26
- operation:
27
- | "concat"
28
- | "add_audio"
29
- | "resize"
30
- | "trim"
31
- | "convert"
32
- | "extract_audio";
33
- // options should contain all parameters except 'output' which is added by the pipeline
34
- // biome-ignore lint/suspicious/noExplicitAny: pipeline options are validated at runtime by underlying ffmpeg functions
35
- options: Record<string, any>;
36
- }
37
-
38
- export interface EditPipelineOptions {
39
- steps: EditPipelineStep[];
40
- finalOutput: string;
41
- }
42
-
43
- export interface PrepareForSocialOptions {
44
- input: string;
45
- output: string;
46
- platform: "tiktok" | "instagram" | "youtube-shorts" | "youtube" | "twitter";
47
- withAudio?: string;
48
- }
49
-
50
- export interface CreateMontageOptions {
51
- clips: string[];
52
- output: string;
53
- maxClipDuration?: number;
54
- targetResolution?: { width: number; height: number };
55
- }
56
-
57
- // core functions
58
-
59
- /**
60
- * run a series of editing operations in sequence
61
- * each step uses output from previous step as input
62
- */
63
- export async function editPipeline(
64
- options: EditPipelineOptions,
65
- ): Promise<string> {
66
- const { steps, finalOutput } = options;
67
-
68
- if (!steps || steps.length === 0) {
69
- throw new Error("at least one step is required");
70
- }
71
-
72
- console.log(`[edit] running ${steps.length} editing steps...`);
73
-
74
- for (let i = 0; i < steps.length; i++) {
75
- const step = steps[i];
76
- if (!step) {
77
- throw new Error(`step ${i} is undefined`);
78
- }
79
-
80
- const isLastStep = i === steps.length - 1;
81
- const output = isLastStep
82
- ? finalOutput
83
- : `/tmp/edit-step-${i}${extname(finalOutput)}`;
84
-
85
- console.log(`[edit] step ${i + 1}/${steps.length}: ${step.operation}`);
86
-
87
- switch (step.operation) {
88
- case "concat":
89
- await concatVideos({
90
- ...step.options,
91
- output,
92
- } as ConcatVideosOptions);
93
- break;
94
-
95
- case "add_audio":
96
- await addAudio({
97
- ...step.options,
98
- output,
99
- } as AddAudioOptions);
100
- break;
101
-
102
- case "resize":
103
- await resizeVideo({
104
- ...step.options,
105
- output,
106
- } as ResizeVideoOptions);
107
- break;
108
-
109
- case "trim":
110
- await trimVideo({
111
- ...step.options,
112
- output,
113
- } as TrimVideoOptions);
114
- break;
115
-
116
- case "convert":
117
- await convertFormat({
118
- ...step.options,
119
- output,
120
- } as ConvertFormatOptions);
121
- break;
122
-
123
- case "extract_audio":
124
- await extractAudio((step.options as { input: string }).input, output);
125
- break;
126
-
127
- default:
128
- throw new Error(`unknown operation: ${step.operation}`);
129
- }
130
- }
131
-
132
- console.log(`[edit] pipeline complete: ${finalOutput}`);
133
- return finalOutput;
134
- }
135
-
136
- /**
137
- * prepare video for social media platform
138
- * automatically sets correct aspect ratio and resolution
139
- */
140
- export async function prepareForSocial(
141
- options: PrepareForSocialOptions,
142
- ): Promise<string> {
143
- const { input, output, platform, withAudio } = options;
144
-
145
- if (!input || !output || !platform) {
146
- throw new Error("input, output, and platform are required");
147
- }
148
-
149
- if (!existsSync(input)) {
150
- throw new Error(`input file not found: ${input}`);
151
- }
152
-
153
- console.log(`[edit] preparing video for ${platform}...`);
154
-
155
- const platformSpecs: Record<
156
- string,
157
- { width: number; height: number; aspectRatio: string }
158
- > = {
159
- tiktok: { width: 1080, height: 1920, aspectRatio: "9:16" },
160
- instagram: { width: 1080, height: 1920, aspectRatio: "9:16" },
161
- "youtube-shorts": { width: 1080, height: 1920, aspectRatio: "9:16" },
162
- youtube: { width: 1920, height: 1080, aspectRatio: "16:9" },
163
- twitter: { width: 1280, height: 720, aspectRatio: "16:9" },
164
- };
165
-
166
- const spec = platformSpecs[platform];
167
- if (!spec) {
168
- throw new Error(`unknown platform: ${platform}`);
169
- }
170
-
171
- const steps: EditPipelineStep[] = [];
172
-
173
- // resize to platform specs
174
- steps.push({
175
- operation: "resize",
176
- options: {
177
- input,
178
- width: spec.width,
179
- height: spec.height,
180
- },
181
- });
182
-
183
- // add audio if provided
184
- if (withAudio) {
185
- if (!existsSync(withAudio)) {
186
- throw new Error(`audio file not found: ${withAudio}`);
187
- }
188
- steps.push({
189
- operation: "add_audio",
190
- options: {
191
- videoPath: input,
192
- audioPath: withAudio,
193
- },
194
- });
195
- }
196
-
197
- return editPipeline({ steps, finalOutput: output });
198
- }
199
-
200
- /**
201
- * create a montage from multiple video clips
202
- * optionally trim clips and resize to consistent resolution
203
- */
204
- export async function createMontage(
205
- options: CreateMontageOptions,
206
- ): Promise<string> {
207
- const { clips, output, maxClipDuration, targetResolution } = options;
208
-
209
- if (!clips || clips.length === 0) {
210
- throw new Error("at least one clip is required");
211
- }
212
- if (!output) {
213
- throw new Error("output is required");
214
- }
215
-
216
- console.log(`[edit] creating montage from ${clips.length} clips...`);
217
-
218
- // validate all clips exist
219
- for (const clip of clips) {
220
- if (!existsSync(clip)) {
221
- throw new Error(`clip not found: ${clip}`);
222
- }
223
- }
224
-
225
- let processedClips = clips;
226
-
227
- // trim clips if max duration specified
228
- if (maxClipDuration) {
229
- console.log(`[edit] trimming clips to ${maxClipDuration}s...`);
230
- processedClips = [];
231
-
232
- for (let i = 0; i < clips.length; i++) {
233
- const clip = clips[i];
234
- if (!clip) {
235
- throw new Error(`clip ${i} is undefined`);
236
- }
237
- const trimmedPath = `/tmp/montage-clip-${i}${extname(clip)}`;
238
- await trimVideo({
239
- input: clip,
240
- output: trimmedPath,
241
- start: 0,
242
- duration: maxClipDuration,
243
- });
244
- processedClips.push(trimmedPath);
245
- }
246
- }
247
-
248
- // resize clips if target resolution specified
249
- if (targetResolution) {
250
- console.log(
251
- `[edit] resizing clips to ${targetResolution.width}x${targetResolution.height}...`,
252
- );
253
- const resizedClips = [];
254
-
255
- for (let i = 0; i < processedClips.length; i++) {
256
- const clip = processedClips[i];
257
- if (!clip) {
258
- throw new Error(`clip ${i} is undefined`);
259
- }
260
- const resizedPath = `/tmp/montage-resized-${i}${extname(clip)}`;
261
- await resizeVideo({
262
- input: clip,
263
- output: resizedPath,
264
- width: targetResolution.width,
265
- height: targetResolution.height,
266
- });
267
- resizedClips.push(resizedPath);
268
- }
269
-
270
- processedClips = resizedClips;
271
- }
272
-
273
- // concatenate all clips
274
- return concatVideos({
275
- inputs: processedClips,
276
- output,
277
- });
278
- }
279
-
280
- /**
281
- * quick trim: trim video to specific segment
282
- */
283
- export async function quickTrim(
284
- input: string,
285
- output: string,
286
- start: number,
287
- end?: number,
288
- ): Promise<string> {
289
- if (!input || !output) {
290
- throw new Error("input and output are required");
291
- }
292
-
293
- const duration = end ? end - start : undefined;
294
-
295
- console.log(
296
- `[edit] trimming video from ${start}s${duration ? ` for ${duration}s` : ""}...`,
297
- );
298
-
299
- return trimVideo({ input, output, start, duration });
300
- }
301
-
302
- /**
303
- * quick resize: resize video to common aspect ratios
304
- */
305
- export async function quickResize(
306
- input: string,
307
- output: string,
308
- preset: "vertical" | "square" | "landscape" | "4k",
309
- ): Promise<string> {
310
- if (!input || !output) {
311
- throw new Error("input and output are required");
312
- }
313
-
314
- const presets: Record<
315
- string,
316
- { width: number; height: number; label: string }
317
- > = {
318
- vertical: { width: 1080, height: 1920, label: "9:16 (1080x1920)" },
319
- square: { width: 1080, height: 1080, label: "1:1 (1080x1080)" },
320
- landscape: { width: 1920, height: 1080, label: "16:9 (1920x1080)" },
321
- "4k": { width: 3840, height: 2160, label: "4K (3840x2160)" },
322
- };
323
-
324
- const spec = presets[preset];
325
- if (!spec) {
326
- throw new Error(`unknown preset: ${preset}`);
327
- }
328
-
329
- console.log(`[edit] resizing to ${spec.label}...`);
330
-
331
- return resizeVideo({
332
- input,
333
- output,
334
- width: spec.width,
335
- height: spec.height,
336
- });
337
- }
338
-
339
- /**
340
- * merge multiple videos with optional audio overlay
341
- */
342
- export async function mergeWithAudio(
343
- videos: string[],
344
- audio: string,
345
- output: string,
346
- ): Promise<string> {
347
- if (!videos || videos.length === 0) {
348
- throw new Error("at least one video is required");
349
- }
350
- if (!audio || !output) {
351
- throw new Error("audio and output are required");
352
- }
353
-
354
- console.log(`[edit] merging ${videos.length} videos with audio...`);
355
-
356
- // first concatenate videos
357
- const tempVideo = `/tmp/merged-video${extname(output)}`;
358
- await concatVideos({
359
- inputs: videos,
360
- output: tempVideo,
361
- });
362
-
363
- // then add audio
364
- return addAudio({
365
- videoPath: tempVideo,
366
- audioPath: audio,
367
- output,
368
- });
369
- }
370
-
371
- // cli
372
- async function cli() {
373
- const args = process.argv.slice(2);
374
- const command = args[0];
375
-
376
- if (!command || command === "help") {
377
- console.log(`
378
- usage:
379
- bun run service/edit.ts <command> [args]
380
-
381
- commands:
382
- social <input> <output> <platform> [audioPath] prepare for social media
383
- montage <output> <clip1> <clip2> [clip3...] create montage from clips
384
- trim <input> <output> <start> [end] quick trim
385
- resize <input> <output> <preset> quick resize
386
- merge_audio <audio> <output> <video1> [video2...] merge videos with audio
387
-
388
- platforms:
389
- tiktok, instagram, youtube-shorts, youtube, twitter
390
-
391
- resize presets:
392
- vertical (9:16), square (1:1), landscape (16:9), 4k
393
-
394
- examples:
395
- bun run service/edit.ts social raw.mp4 tiktok.mp4 tiktok
396
- bun run service/edit.ts social raw.mp4 ig.mp4 instagram audio.mp3
397
- bun run service/edit.ts montage output.mp4 clip1.mp4 clip2.mp4 clip3.mp4
398
- bun run service/edit.ts trim long.mp4 short.mp4 10 30
399
- bun run service/edit.ts resize raw.mp4 vertical.mp4 vertical
400
- bun run service/edit.ts merge_audio song.mp3 final.mp4 clip1.mp4 clip2.mp4
401
- `);
402
- process.exit(0);
403
- }
404
-
405
- try {
406
- switch (command) {
407
- case "social": {
408
- const input = args[1];
409
- const output = args[2];
410
- const platform = args[3] as PrepareForSocialOptions["platform"];
411
- const withAudio = args[4];
412
-
413
- if (!input || !output || !platform) {
414
- throw new Error("input, output, and platform are required");
415
- }
416
-
417
- await prepareForSocial({ input, output, platform, withAudio });
418
- break;
419
- }
420
-
421
- case "montage": {
422
- const output = args[1];
423
- const clips = args.slice(2);
424
-
425
- if (!output || clips.length === 0) {
426
- throw new Error("output and at least one clip are required");
427
- }
428
-
429
- await createMontage({ clips, output });
430
- break;
431
- }
432
-
433
- case "trim": {
434
- const input = args[1];
435
- const output = args[2];
436
- const startArg = args[3];
437
- const endArg = args[4];
438
-
439
- if (!input || !output || !startArg) {
440
- throw new Error("input, output, and start are required");
441
- }
442
-
443
- const start = Number.parseFloat(startArg);
444
- const end = endArg ? Number.parseFloat(endArg) : undefined;
445
-
446
- if (Number.isNaN(start) || (endArg && Number.isNaN(end))) {
447
- throw new Error("start and end must be valid numbers");
448
- }
449
-
450
- await quickTrim(input, output, start, end);
451
- break;
452
- }
453
-
454
- case "resize": {
455
- const input = args[1];
456
- const output = args[2];
457
- const preset = args[3] as "vertical" | "square" | "landscape" | "4k";
458
-
459
- if (!input || !output || !preset) {
460
- throw new Error("input, output, and preset are required");
461
- }
462
-
463
- await quickResize(input, output, preset);
464
- break;
465
- }
466
-
467
- case "merge_audio": {
468
- const audio = args[1];
469
- const output = args[2];
470
- const videos = args.slice(3);
471
-
472
- if (!audio || !output || videos.length === 0) {
473
- throw new Error("audio, output, and at least one video are required");
474
- }
475
-
476
- await mergeWithAudio(videos, audio, output);
477
- break;
478
- }
479
-
480
- default:
481
- console.error(`unknown command: ${command}`);
482
- console.log("run 'bun run service/edit.ts help' for usage");
483
- process.exit(1);
484
- }
485
- } catch (error) {
486
- console.error("[edit] error:", error);
487
- process.exit(1);
488
- }
489
- }
490
-
491
- if (import.meta.main) {
492
- cli();
493
- }
@@ -1,140 +0,0 @@
1
- ---
2
- name: image-generation
3
- description: generate ai images using fal (flux models) or higgsfield soul characters. use when user wants to create images, headshots, character portraits, or needs image generation with specific models.
4
- allowed-tools: Read, Bash
5
- ---
6
-
7
- # image generation
8
-
9
- generate ai images using multiple providers with automatic s3 upload support.
10
-
11
- ## providers
12
-
13
- ### fal (flux models)
14
- - high quality image generation
15
- - supports flux-pro, flux-dev, and other flux models
16
- - configurable model selection
17
- - automatic image opening on generation
18
-
19
- ### higgsfield soul
20
- - character headshot generation
21
- - consistent character style
22
- - professional portrait quality
23
- - custom style references
24
-
25
- ## usage
26
-
27
- ### generate with fal
28
- ```bash
29
- bun run service/image.ts fal "a beautiful sunset over mountains" [model] [upload]
30
- ```
31
-
32
- **parameters:**
33
- - `prompt` (required): text description of the image
34
- - `model` (optional): fal model to use (default: flux-pro)
35
- - `upload` (optional): "true" to upload to s3
36
-
37
- **example:**
38
- ```bash
39
- bun run service/image.ts fal "professional headshot, studio lighting" true
40
- ```
41
-
42
- ### generate with soul
43
- ```bash
44
- bun run service/image.ts soul "friendly person smiling" [styleId] [upload]
45
- ```
46
-
47
- **parameters:**
48
- - `prompt` (required): character description
49
- - `styleId` (optional): custom higgsfield style reference
50
- - `upload` (optional): "true" to upload to s3
51
-
52
- **example:**
53
- ```bash
54
- bun run service/image.ts soul "professional business woman" true
55
- ```
56
-
57
- ## as library
58
-
59
- ```typescript
60
- import { generateWithFal, generateWithSoul } from "./service/image"
61
-
62
- // fal generation
63
- const falResult = await generateWithFal("sunset over ocean", {
64
- model: "fal-ai/flux-pro/v1.1",
65
- upload: true
66
- })
67
- console.log(falResult.imageUrl)
68
- console.log(falResult.uploaded) // s3 url if upload=true
69
-
70
- // soul generation
71
- const soulResult = await generateWithSoul("friendly character", {
72
- upload: true
73
- })
74
- console.log(soulResult.imageUrl)
75
- ```
76
-
77
- ## output
78
-
79
- returns `ImageGenerationResult`:
80
- ```typescript
81
- {
82
- imageUrl: string, // direct image url
83
- uploaded?: string // s3 url if upload requested
84
- }
85
- ```
86
-
87
- ## when to use
88
-
89
- use this skill when:
90
- - generating images from text descriptions
91
- - creating character headshots or portraits
92
- - need consistent character style (use soul)
93
- - need high quality photorealistic images (use fal)
94
- - preparing images for video generation pipeline
95
-
96
- ## nsfw filtering and content moderation
97
-
98
- fal.ai has content safety filters that may flag images as nsfw:
99
-
100
- **common triggers:**
101
- - prompts mentioning "athletic wear", "fitted sportswear", "gym clothes"
102
- - certain body descriptions even when clothed
103
- - prompts that could be interpreted as revealing clothing
104
-
105
- **symptoms:**
106
- - image generation returns but file is empty (often 7.6KB)
107
- - no error message, just an unusable file
108
- - happens inconsistently across similar prompts
109
-
110
- **solutions:**
111
- - specify modest, full-coverage clothing explicitly:
112
- - ✅ "long sleeve athletic top and full length leggings"
113
- - ✅ "fully covered in modest workout attire"
114
- - ❌ "athletic wear" (too vague, may trigger filter)
115
- - ❌ "fitted sportswear" (may trigger filter)
116
- - add "professional", "modest", "appropriate" to descriptions
117
- - if multiple images in batch get flagged, adjust prompts to be more explicit about coverage
118
- - always check output file sizes - empty files (< 10KB) indicate nsfw filtering
119
-
120
- **example:**
121
- ```bash
122
- # ❌ may get flagged as nsfw
123
- bun run service/image.ts fal "woman in athletic wear"
124
-
125
- # ✅ less likely to trigger filter
126
- bun run service/image.ts fal "woman wearing long sleeve athletic top and full length leggings"
127
- ```
128
-
129
- ## environment variables
130
-
131
- required:
132
- - `FAL_API_KEY` - for fal image generation
133
- - `HIGGSFIELD_API_KEY` - for soul character generation
134
- - `HIGGSFIELD_SECRET` - for higgsfield authentication
135
-
136
- optional (for s3 upload):
137
- - `CLOUDFLARE_R2_API_URL`
138
- - `CLOUDFLARE_ACCESS_KEY_ID`
139
- - `CLOUDFLARE_ACCESS_SECRET`
140
- - `CLOUDFLARE_R2_BUCKET`