varg.ai-sdk 0.1.1 → 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.
- package/.claude/settings.local.json +1 -1
- package/.env.example +3 -0
- package/.github/workflows/ci.yml +23 -0
- package/.husky/README.md +102 -0
- package/.husky/commit-msg +6 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +6 -0
- package/.size-limit.json +8 -0
- package/.test-hooks.ts +5 -0
- package/CLAUDE.md +10 -3
- package/CONTRIBUTING.md +150 -0
- package/LICENSE.md +53 -0
- package/README.md +56 -209
- package/SKILLS.md +26 -10
- package/biome.json +7 -1
- package/bun.lock +1286 -0
- package/commitlint.config.js +22 -0
- package/docs/index.html +1130 -0
- package/docs/prompting.md +326 -0
- package/docs/react.md +834 -0
- package/docs/sdk.md +812 -0
- package/ffmpeg/CLAUDE.md +68 -0
- package/package.json +43 -10
- package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
- package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
- package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
- package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
- package/pipeline/cookbooks/text-to-tiktok.md +669 -0
- package/pipeline/cookbooks/trendwatching.md +156 -0
- package/plan.md +281 -0
- package/scripts/.gitkeep +0 -0
- package/src/ai-sdk/cache.ts +142 -0
- package/src/ai-sdk/examples/cached-generation.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
- package/src/ai-sdk/examples/duet-video.ts +56 -0
- package/src/ai-sdk/examples/editly-composition.ts +63 -0
- package/src/ai-sdk/examples/editly-test.ts +57 -0
- package/src/ai-sdk/examples/editly-video-test.ts +52 -0
- package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
- package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
- package/src/ai-sdk/examples/music-generation.ts +19 -0
- package/src/ai-sdk/examples/openai-sora.ts +34 -0
- package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
- package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
- package/src/ai-sdk/examples/talking-lion.ts +55 -0
- package/src/ai-sdk/examples/video-generation.ts +39 -0
- package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
- package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
- package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
- package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
- package/src/ai-sdk/file-cache.ts +112 -0
- package/src/ai-sdk/file.ts +238 -0
- package/src/ai-sdk/generate-element.ts +92 -0
- package/src/ai-sdk/generate-music.ts +46 -0
- package/src/ai-sdk/generate-video.ts +165 -0
- package/src/ai-sdk/index.ts +72 -0
- package/src/ai-sdk/music-model.ts +110 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
- package/src/ai-sdk/providers/editly/index.ts +817 -0
- package/src/ai-sdk/providers/editly/layers.ts +776 -0
- package/src/ai-sdk/providers/editly/plan.md +144 -0
- package/src/ai-sdk/providers/editly/types.ts +328 -0
- package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
- package/src/ai-sdk/providers/fal-provider.ts +512 -0
- package/src/ai-sdk/providers/higgsfield.ts +379 -0
- package/src/ai-sdk/providers/openai.ts +251 -0
- package/src/ai-sdk/providers/replicate.ts +16 -0
- package/src/ai-sdk/video-model.ts +185 -0
- package/src/cli/commands/find.tsx +137 -0
- package/src/cli/commands/help.tsx +85 -0
- package/src/cli/commands/index.ts +6 -0
- package/src/cli/commands/list.tsx +238 -0
- package/src/cli/commands/render.tsx +71 -0
- package/src/cli/commands/run.tsx +511 -0
- package/src/cli/commands/which.tsx +253 -0
- package/src/cli/index.ts +114 -0
- package/src/cli/quiet.ts +44 -0
- package/src/cli/types.ts +32 -0
- package/src/cli/ui/components/Badge.tsx +29 -0
- package/src/cli/ui/components/DataTable.tsx +51 -0
- package/src/cli/ui/components/Header.tsx +23 -0
- package/src/cli/ui/components/HelpBlock.tsx +44 -0
- package/src/cli/ui/components/KeyValue.tsx +33 -0
- package/src/cli/ui/components/OptionRow.tsx +81 -0
- package/src/cli/ui/components/Separator.tsx +23 -0
- package/src/cli/ui/components/StatusBox.tsx +108 -0
- package/src/cli/ui/components/VargBox.tsx +51 -0
- package/src/cli/ui/components/VargProgress.tsx +36 -0
- package/src/cli/ui/components/VargSpinner.tsx +34 -0
- package/src/cli/ui/components/VargText.tsx +56 -0
- package/src/cli/ui/components/index.ts +19 -0
- package/src/cli/ui/index.ts +12 -0
- package/src/cli/ui/render.ts +35 -0
- package/src/cli/ui/theme.ts +63 -0
- package/src/cli/utils.ts +78 -0
- package/src/core/executor/executor.ts +201 -0
- package/src/core/executor/index.ts +13 -0
- package/src/core/executor/job.ts +214 -0
- package/src/core/executor/pipeline.ts +222 -0
- package/src/core/index.ts +11 -0
- package/src/core/registry/index.ts +9 -0
- package/src/core/registry/loader.ts +149 -0
- package/src/core/registry/registry.ts +221 -0
- package/src/core/registry/resolver.ts +206 -0
- package/src/core/schema/helpers.ts +134 -0
- package/src/core/schema/index.ts +8 -0
- package/src/core/schema/shared.ts +102 -0
- package/src/core/schema/types.ts +279 -0
- package/src/core/schema/validator.ts +92 -0
- package/src/definitions/actions/captions.ts +261 -0
- package/src/definitions/actions/edit.ts +298 -0
- package/src/definitions/actions/image.ts +125 -0
- package/src/definitions/actions/index.ts +114 -0
- package/src/definitions/actions/music.ts +205 -0
- package/src/definitions/actions/sync.ts +128 -0
- package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +58 -68
- package/src/definitions/actions/upload.ts +111 -0
- package/src/definitions/actions/video.ts +163 -0
- package/src/definitions/actions/voice.ts +119 -0
- package/src/definitions/index.ts +23 -0
- package/src/definitions/models/elevenlabs.ts +50 -0
- package/src/definitions/models/flux.ts +56 -0
- package/src/definitions/models/index.ts +36 -0
- package/src/definitions/models/kling.ts +56 -0
- package/src/definitions/models/llama.ts +54 -0
- package/src/definitions/models/nano-banana-pro.ts +102 -0
- package/src/definitions/models/sonauto.ts +68 -0
- package/src/definitions/models/soul.ts +65 -0
- package/src/definitions/models/wan.ts +54 -0
- package/src/definitions/models/whisper.ts +44 -0
- package/src/definitions/skills/index.ts +12 -0
- package/src/definitions/skills/talking-character.ts +87 -0
- package/src/definitions/skills/text-to-tiktok.ts +97 -0
- package/src/index.ts +118 -0
- package/src/providers/apify.ts +269 -0
- package/src/providers/base.ts +264 -0
- package/src/providers/elevenlabs.ts +217 -0
- package/src/providers/fal.ts +392 -0
- package/src/providers/ffmpeg.ts +544 -0
- package/src/providers/fireworks.ts +193 -0
- package/src/providers/groq.ts +149 -0
- package/src/providers/higgsfield.ts +145 -0
- package/src/providers/index.ts +143 -0
- package/src/providers/replicate.ts +147 -0
- package/src/providers/storage.ts +206 -0
- package/src/react/cli.ts +52 -0
- package/src/react/elements.ts +146 -0
- package/src/react/examples/branching.tsx +66 -0
- package/src/react/examples/captions-demo.tsx +37 -0
- package/src/react/examples/character-video.tsx +84 -0
- package/src/react/examples/grid.tsx +53 -0
- package/src/react/examples/layouts-demo.tsx +57 -0
- package/src/react/examples/madi.tsx +60 -0
- package/src/react/examples/music-test.tsx +35 -0
- package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
- package/src/react/examples/orange-portrait.tsx +41 -0
- package/src/react/examples/split-element-demo.tsx +60 -0
- package/src/react/examples/split-layout-demo.tsx +60 -0
- package/src/react/examples/split.tsx +41 -0
- package/src/react/examples/video-grid.tsx +46 -0
- package/src/react/index.ts +43 -0
- package/src/react/layouts/grid.tsx +28 -0
- package/src/react/layouts/index.ts +2 -0
- package/src/react/layouts/split.tsx +20 -0
- package/src/react/react.test.ts +309 -0
- package/src/react/render.ts +21 -0
- package/src/react/renderers/animate.ts +59 -0
- package/src/react/renderers/captions.ts +297 -0
- package/src/react/renderers/clip.ts +248 -0
- package/src/react/renderers/context.ts +17 -0
- package/src/react/renderers/image.ts +109 -0
- package/src/react/renderers/index.ts +22 -0
- package/src/react/renderers/music.ts +60 -0
- package/src/react/renderers/packshot.ts +84 -0
- package/src/react/renderers/progress.ts +173 -0
- package/src/react/renderers/render.ts +243 -0
- package/src/react/renderers/slider.ts +69 -0
- package/src/react/renderers/speech.ts +53 -0
- package/src/react/renderers/split.ts +91 -0
- package/src/react/renderers/subtitle.ts +16 -0
- package/src/react/renderers/swipe.ts +75 -0
- package/src/react/renderers/title.ts +17 -0
- package/src/react/renderers/utils.ts +124 -0
- package/src/react/renderers/video.ts +127 -0
- package/src/react/runtime/jsx-dev-runtime.ts +43 -0
- package/src/react/runtime/jsx-runtime.ts +35 -0
- package/src/react/types.ts +232 -0
- package/src/studio/index.ts +26 -0
- package/src/studio/scanner.ts +102 -0
- package/src/studio/server.ts +554 -0
- package/src/studio/stages.ts +251 -0
- package/src/studio/step-renderer.ts +279 -0
- package/src/studio/types.ts +60 -0
- package/src/studio/ui/cache.html +303 -0
- package/src/studio/ui/index.html +1820 -0
- package/src/tests/all.test.ts +509 -0
- package/src/tests/index.ts +33 -0
- package/src/tests/unit.test.ts +403 -0
- package/tsconfig.cli.json +8 -0
- package/tsconfig.json +21 -3
- package/TEST_RESULTS.md +0 -122
- package/action/captions/SKILL.md +0 -170
- package/action/captions/index.ts +0 -169
- package/action/edit/SKILL.md +0 -235
- package/action/edit/index.ts +0 -437
- package/action/image/SKILL.md +0 -140
- package/action/image/index.ts +0 -105
- package/action/sync/SKILL.md +0 -136
- package/action/sync/index.ts +0 -145
- package/action/transcribe/SKILL.md +0 -179
- package/action/video/SKILL.md +0 -116
- package/action/video/index.ts +0 -125
- package/action/voice/SKILL.md +0 -125
- package/action/voice/index.ts +0 -136
- package/cli/commands/find.ts +0 -58
- package/cli/commands/help.ts +0 -70
- package/cli/commands/list.ts +0 -49
- package/cli/commands/run.ts +0 -237
- package/cli/commands/which.ts +0 -66
- package/cli/discover.ts +0 -66
- package/cli/index.ts +0 -33
- package/cli/runner.ts +0 -65
- package/cli/types.ts +0 -49
- package/cli/ui.ts +0 -185
- package/index.ts +0 -75
- package/lib/README.md +0 -144
- package/lib/ai-sdk/fal.ts +0 -106
- package/lib/ai-sdk/replicate.ts +0 -107
- package/lib/elevenlabs.ts +0 -382
- package/lib/fal.ts +0 -467
- package/lib/ffmpeg.ts +0 -467
- package/lib/fireworks.ts +0 -235
- package/lib/groq.ts +0 -246
- package/lib/higgsfield.ts +0 -176
- package/lib/remotion/SKILL.md +0 -823
- package/lib/remotion/cli.ts +0 -115
- package/lib/remotion/functions.ts +0 -283
- package/lib/remotion/index.ts +0 -19
- package/lib/remotion/templates.ts +0 -73
- package/lib/replicate.ts +0 -304
- package/output.txt +0 -1
- package/test-import.ts +0 -7
- package/test-services.ts +0 -97
- package/utilities/s3.ts +0 -147
package/ffmpeg/CLAUDE.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# ffmpeg video mixing lessons
|
|
2
|
+
|
|
3
|
+
## problem: audio/video desync when mixing clips
|
|
4
|
+
|
|
5
|
+
### what went wrong
|
|
6
|
+
|
|
7
|
+
using `-ss X -t Y` to pre-trim input, then applying relative trim filters caused timing drift:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# BAD: relative timestamps after pre-trim
|
|
11
|
+
ffmpeg -ss 64 -t 36 -i original.mp4 ...
|
|
12
|
+
-filter_complex "
|
|
13
|
+
[0:v]split=5[orig1][orig2]...;
|
|
14
|
+
[orig1]trim=0:4,setpts=PTS-STARTPTS[o1];
|
|
15
|
+
[orig2]trim=4:11,setpts=PTS-STARTPTS[o2];
|
|
16
|
+
..."
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
this produced wrong duration (38s instead of 36s) with audio desync.
|
|
20
|
+
|
|
21
|
+
### solution: use absolute timestamps from full input
|
|
22
|
+
|
|
23
|
+
trim directly from full original using absolute timestamps:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# GOOD: absolute timestamps from full file
|
|
27
|
+
ffmpeg -i original.mp4 -i scene1.mp4 -i scene2.mp4 ...
|
|
28
|
+
-filter_complex "
|
|
29
|
+
[0:v]trim=64:68,setpts=PTS-STARTPTS[o1];
|
|
30
|
+
[1:v]scale=1280:720,trim=4:6,setpts=PTS-STARTPTS[s1];
|
|
31
|
+
[0:v]trim=70:75,setpts=PTS-STARTPTS[o2];
|
|
32
|
+
...
|
|
33
|
+
[o1][s1][o2]...concat=n=N:v=1:a=0[outv];
|
|
34
|
+
[0:a]atrim=64:100,asetpts=PTS-STARTPTS[outa]
|
|
35
|
+
"
|
|
36
|
+
-map "[outv]" -map "[outa]"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### key points
|
|
40
|
+
|
|
41
|
+
1. **absolute timestamps**: trim from full input file, not pre-trimmed
|
|
42
|
+
2. **separate audio handling**: use `atrim` on audio stream independently
|
|
43
|
+
3. **setpts reset**: always use `setpts=PTS-STARTPTS` after trim to reset timestamps
|
|
44
|
+
4. **scale before trim**: when mixing different resolutions, scale first then trim
|
|
45
|
+
5. **video duration = audio duration**: ensure total video segments match audio segment length
|
|
46
|
+
|
|
47
|
+
### example: inserting clips into original
|
|
48
|
+
|
|
49
|
+
to insert generated clips at specific timestamps while keeping continuous audio:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
ffmpeg -y \
|
|
53
|
+
-i original.mp4 \
|
|
54
|
+
-i generated-scene.mp4 \
|
|
55
|
+
-filter_complex "
|
|
56
|
+
[0:v]trim=START1:END1,setpts=PTS-STARTPTS[o1];
|
|
57
|
+
[1:v]scale=1280:720,trim=0:DURATION,setpts=PTS-STARTPTS[s1];
|
|
58
|
+
[0:v]trim=START2:END2,setpts=PTS-STARTPTS[o2];
|
|
59
|
+
[o1][s1][o2]concat=n=3:v=1:a=0[outv];
|
|
60
|
+
[0:a]atrim=AUDIO_START:AUDIO_END,asetpts=PTS-STARTPTS[outa]
|
|
61
|
+
" \
|
|
62
|
+
-map "[outv]" -map "[outa]" \
|
|
63
|
+
-c:v libx264 -preset fast -crf 18 \
|
|
64
|
+
-c:a aac -b:a 192k \
|
|
65
|
+
output.mp4
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
timestamps must add up: `(END1-START1) + DURATION + (END2-START2) = AUDIO_END - AUDIO_START`
|
package/package.json
CHANGED
|
@@ -1,42 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "varg.ai-sdk",
|
|
3
|
-
"module": "index.ts",
|
|
3
|
+
"module": "src/index.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"varg": "./cli/index.ts"
|
|
6
|
+
"varg": "./src/cli/index.ts"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
+
"check": "biome check . && tsc --noEmit",
|
|
9
10
|
"lint": "biome check .",
|
|
10
|
-
"format": "biome format --write ."
|
|
11
|
+
"format": "biome format --write .",
|
|
12
|
+
"type-check": "tsc --noEmit",
|
|
13
|
+
"prepare": "husky || true",
|
|
14
|
+
"size": "size-limit",
|
|
15
|
+
"studio": "bun run src/studio/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"lint-staged": {
|
|
18
|
+
"*.{js,ts,tsx}": [
|
|
19
|
+
"biome check --write --no-errors-on-unmatched"
|
|
20
|
+
],
|
|
21
|
+
"*.json": [
|
|
22
|
+
"biome format --write"
|
|
23
|
+
]
|
|
11
24
|
},
|
|
12
25
|
"devDependencies": {
|
|
13
26
|
"@biomejs/biome": "^2.3.7",
|
|
14
|
-
"@
|
|
27
|
+
"@commitlint/cli": "^20.1.0",
|
|
28
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
29
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
30
|
+
"@types/bun": "latest",
|
|
31
|
+
"@types/react": "^19.2.7",
|
|
32
|
+
"husky": "^9.1.7",
|
|
33
|
+
"lint-staged": "^16.2.7"
|
|
15
34
|
},
|
|
16
35
|
"peerDependencies": {
|
|
17
36
|
"typescript": "^5"
|
|
18
37
|
},
|
|
19
38
|
"dependencies": {
|
|
20
39
|
"@ai-sdk/fal": "^1.0.23",
|
|
21
|
-
"@ai-sdk/
|
|
40
|
+
"@ai-sdk/fireworks": "^2.0.16",
|
|
41
|
+
"@ai-sdk/groq": "^3.0.12",
|
|
42
|
+
"@ai-sdk/openai": "^3.0.9",
|
|
43
|
+
"@ai-sdk/provider": "^3.0.2",
|
|
44
|
+
"@ai-sdk/replicate": "^2.0.5",
|
|
22
45
|
"@aws-sdk/client-s3": "^3.937.0",
|
|
23
46
|
"@aws-sdk/s3-request-presigner": "^3.937.0",
|
|
24
|
-
"@elevenlabs/elevenlabs-js": "^2.
|
|
47
|
+
"@elevenlabs/elevenlabs-js": "^2.28.0",
|
|
25
48
|
"@fal-ai/client": "^1.7.2",
|
|
26
49
|
"@higgsfield/client": "^0.1.2",
|
|
50
|
+
"@inkjs/ui": "^2.0.0",
|
|
27
51
|
"@remotion/cli": "^4.0.377",
|
|
28
52
|
"@types/fluent-ffmpeg": "^2.1.28",
|
|
29
|
-
"ai": "^
|
|
53
|
+
"ai": "^6.0.26",
|
|
54
|
+
"apify-client": "^2.20.0",
|
|
30
55
|
"citty": "^0.1.6",
|
|
31
56
|
"fluent-ffmpeg": "^2.1.3",
|
|
32
57
|
"groq-sdk": "^0.36.0",
|
|
58
|
+
"ink": "^6.5.1",
|
|
33
59
|
"react": "^19.2.0",
|
|
34
60
|
"react-dom": "^19.2.0",
|
|
35
61
|
"remotion": "^4.0.377",
|
|
36
|
-
"replicate": "^1.4.0"
|
|
62
|
+
"replicate": "^1.4.0",
|
|
63
|
+
"zod": "^4.2.1"
|
|
37
64
|
},
|
|
38
|
-
"version": "0.
|
|
65
|
+
"version": "0.4.0-alpha.1",
|
|
39
66
|
"exports": {
|
|
40
|
-
".": "./index.ts"
|
|
67
|
+
".": "./src/index.ts",
|
|
68
|
+
"./ai": "./src/ai-sdk/index.ts",
|
|
69
|
+
"./core": "./src/core/index.ts",
|
|
70
|
+
"./providers": "./src/providers/index.ts",
|
|
71
|
+
"./definitions": "./src/definitions/index.ts",
|
|
72
|
+
"./react": "./src/react/index.ts",
|
|
73
|
+
"./studio": "./src/studio/index.ts"
|
|
41
74
|
}
|
|
42
75
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animate multiple frames in parallel using kling
|
|
3
|
+
* Usage: bun run pipeline/cookbooks/scripts/animate-frames-parallel.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { fal } from "@fal-ai/client";
|
|
7
|
+
|
|
8
|
+
interface VideoConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
framePath: string;
|
|
11
|
+
prompt: string;
|
|
12
|
+
duration?: "5" | "10";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function animateFrames(configs: VideoConfig[], outputDir: string) {
|
|
16
|
+
console.log(`Animating ${configs.length} frames in parallel...\n`);
|
|
17
|
+
|
|
18
|
+
// Upload all frames first
|
|
19
|
+
const frameUrls: string[] = [];
|
|
20
|
+
for (const config of configs) {
|
|
21
|
+
const url = await fal.storage.upload(Bun.file(config.framePath));
|
|
22
|
+
frameUrls.push(url);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const promises = configs.map((config, i) => {
|
|
26
|
+
return fal.subscribe("fal-ai/kling-video/v2.5-turbo/pro/image-to-video", {
|
|
27
|
+
input: {
|
|
28
|
+
prompt: `${config.prompt}, NO talking NO lip movement`,
|
|
29
|
+
image_url: frameUrls[i],
|
|
30
|
+
duration: config.duration || "5",
|
|
31
|
+
// note: aspect_ratio is determined by input image dimensions
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const results = await Promise.all(promises);
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < results.length; i++) {
|
|
39
|
+
const result = results[i] as { data?: { video?: { url?: string } } };
|
|
40
|
+
const url = result.data?.video?.url;
|
|
41
|
+
const config = configs[i];
|
|
42
|
+
if (url && config) {
|
|
43
|
+
const response = await fetch(url);
|
|
44
|
+
const buffer = await response.arrayBuffer();
|
|
45
|
+
await Bun.write(`${outputDir}/${config.name}_video.mp4`, buffer);
|
|
46
|
+
console.log(`${config.name}_video.mp4 saved`);
|
|
47
|
+
} else {
|
|
48
|
+
console.error(`No URL for ${config?.name ?? "unknown"}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log("\nAll videos saved!");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Example usage:
|
|
56
|
+
async function main() {
|
|
57
|
+
const outputDir = "media/girl-ruined-you";
|
|
58
|
+
|
|
59
|
+
const configs: VideoConfig[] = [
|
|
60
|
+
{
|
|
61
|
+
name: "scene6",
|
|
62
|
+
framePath: `${outputDir}/scene6_frame.jpg`,
|
|
63
|
+
prompt:
|
|
64
|
+
"3D pixar animation, two cats meet eyes in coffee shop, warm romantic moment",
|
|
65
|
+
duration: "5",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "scene7",
|
|
69
|
+
framePath: `${outputDir}/scene7_frame.jpg`,
|
|
70
|
+
prompt: "3D pixar animation, two cats walking together, sunset, romantic",
|
|
71
|
+
duration: "5",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "scene14",
|
|
75
|
+
framePath: `${outputDir}/scene14_frame.jpg`,
|
|
76
|
+
prompt: "3D pixar animation, cat looks at sunrise, hopeful realization",
|
|
77
|
+
duration: "5",
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
await animateFrames(configs, outputDir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Combine multiple scene videos with audio clips
|
|
3
|
+
# Usage: ./combine-scenes.sh <project_dir>
|
|
4
|
+
|
|
5
|
+
PROJECT_DIR=${1:-"media/girl-ruined-you"}
|
|
6
|
+
|
|
7
|
+
# Scene timing configuration (adjust as needed)
|
|
8
|
+
# Format: scene_num:start_time:duration
|
|
9
|
+
SCENES=(
|
|
10
|
+
"1:0:3.5"
|
|
11
|
+
"2:3.5:6.5"
|
|
12
|
+
"3:10:10"
|
|
13
|
+
"4:20:15"
|
|
14
|
+
"5:35:7"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
echo "Extracting audio clips..."
|
|
18
|
+
for scene_config in "${SCENES[@]}"; do
|
|
19
|
+
IFS=':' read -r num start dur <<< "$scene_config"
|
|
20
|
+
ffmpeg -y -i "$PROJECT_DIR/voiceover.mp3" -ss "$start" -t "$dur" "$PROJECT_DIR/audio_scene${num}.mp3" 2>/dev/null
|
|
21
|
+
echo " audio_scene${num}.mp3 ($dur sec)"
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
echo ""
|
|
25
|
+
echo "Combining videos with audio..."
|
|
26
|
+
for scene_config in "${SCENES[@]}"; do
|
|
27
|
+
IFS=':' read -r num start dur <<< "$scene_config"
|
|
28
|
+
|
|
29
|
+
# Calculate loop count needed (5s videos)
|
|
30
|
+
loops=$(echo "($dur / 5) - 1" | bc)
|
|
31
|
+
if [ "$loops" -lt 0 ]; then loops=0; fi
|
|
32
|
+
|
|
33
|
+
ffmpeg -y -stream_loop "$loops" -i "$PROJECT_DIR/scene${num}_video.mp4" \
|
|
34
|
+
-i "$PROJECT_DIR/audio_scene${num}.mp3" \
|
|
35
|
+
-t "$dur" -c:v libx264 -preset fast -crf 20 -c:a aac -b:a 128k -shortest \
|
|
36
|
+
"$PROJECT_DIR/scene${num}_final.mp4" 2>/dev/null
|
|
37
|
+
echo " scene${num}_final.mp4"
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Creating concat file..."
|
|
42
|
+
rm -f "$PROJECT_DIR/scenes.txt"
|
|
43
|
+
for scene_config in "${SCENES[@]}"; do
|
|
44
|
+
IFS=':' read -r num start dur <<< "$scene_config"
|
|
45
|
+
echo "file 'scene${num}_final.mp4'" >> "$PROJECT_DIR/scenes.txt"
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
echo "Concatenating all scenes..."
|
|
49
|
+
cd "$PROJECT_DIR" && ffmpeg -y -f concat -safe 0 -i scenes.txt -c copy combined_scenes.mp4 2>/dev/null
|
|
50
|
+
|
|
51
|
+
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 combined_scenes.mp4)
|
|
52
|
+
echo ""
|
|
53
|
+
echo "Done! combined_scenes.mp4 ($duration sec)"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate multiple scene frames in parallel using flux kontext
|
|
3
|
+
* Usage: bun run pipeline/cookbooks/scripts/generate-frames-parallel.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { fal } from "@fal-ai/client";
|
|
7
|
+
|
|
8
|
+
interface FrameConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
imageUrls: string[]; // character reference URLs
|
|
12
|
+
multi?: boolean; // use kontext/multi for multiple characters
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function generateFrames(configs: FrameConfig[], outputDir: string) {
|
|
16
|
+
console.log(`Generating ${configs.length} frames in parallel...\n`);
|
|
17
|
+
|
|
18
|
+
const promises = configs.map((config) => {
|
|
19
|
+
if (config.multi) {
|
|
20
|
+
return fal.subscribe("fal-ai/flux-pro/kontext/multi", {
|
|
21
|
+
input: {
|
|
22
|
+
prompt: config.prompt,
|
|
23
|
+
image_urls: config.imageUrls,
|
|
24
|
+
aspect_ratio: "9:16" as const,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
} else {
|
|
28
|
+
return fal.subscribe("fal-ai/flux-pro/kontext", {
|
|
29
|
+
input: {
|
|
30
|
+
prompt: config.prompt,
|
|
31
|
+
image_url: config.imageUrls[0] ?? "",
|
|
32
|
+
aspect_ratio: "9:16" as const,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const results = await Promise.all(promises);
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < results.length; i++) {
|
|
41
|
+
const result = results[i] as {
|
|
42
|
+
data?: { images?: Array<{ url?: string }> };
|
|
43
|
+
};
|
|
44
|
+
const url = result.data?.images?.[0]?.url;
|
|
45
|
+
const config = configs[i];
|
|
46
|
+
if (url && config) {
|
|
47
|
+
const response = await fetch(url);
|
|
48
|
+
const buffer = await response.arrayBuffer();
|
|
49
|
+
await Bun.write(`${outputDir}/${config.name}_frame.jpg`, buffer);
|
|
50
|
+
console.log(`${config.name}_frame.jpg saved`);
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`No URL for ${config?.name ?? "unknown"}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log("\nAll frames saved!");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Example usage:
|
|
60
|
+
async function main() {
|
|
61
|
+
const outputDir = "media/girl-ruined-you";
|
|
62
|
+
|
|
63
|
+
// Upload character references first
|
|
64
|
+
const protagonist = await fal.storage.upload(
|
|
65
|
+
Bun.file(`${outputDir}/cat_protagonist.png`),
|
|
66
|
+
);
|
|
67
|
+
const secondGirl = await fal.storage.upload(
|
|
68
|
+
Bun.file(`${outputDir}/cat_second_girl.png`),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const configs: FrameConfig[] = [
|
|
72
|
+
{
|
|
73
|
+
name: "scene6",
|
|
74
|
+
prompt:
|
|
75
|
+
"3D pixar style: male cat in hoodie (first) and elegant female cat (second) meeting eyes in coffee shop, warm golden lighting, vertical portrait 9:16",
|
|
76
|
+
imageUrls: [protagonist, secondGirl],
|
|
77
|
+
multi: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "scene7",
|
|
81
|
+
prompt:
|
|
82
|
+
"3D pixar style: male cat and female cat walking together, sunset, romantic, vertical portrait 9:16",
|
|
83
|
+
imageUrls: [protagonist, secondGirl],
|
|
84
|
+
multi: true,
|
|
85
|
+
},
|
|
86
|
+
// Single character scene
|
|
87
|
+
{
|
|
88
|
+
name: "scene14",
|
|
89
|
+
prompt:
|
|
90
|
+
"Place this cat looking at sunrise through window, hopeful, vertical portrait 9:16",
|
|
91
|
+
imageUrls: [protagonist],
|
|
92
|
+
multi: false,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
await generateFrames(configs, outputDir);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Convert still frame to video with ken burns effect (slow zoom)
|
|
3
|
+
# Usage: ./still-to-video.sh <input.jpg> <output.mp4> <duration> [zoom_direction]
|
|
4
|
+
# zoom_direction: in (default), out
|
|
5
|
+
|
|
6
|
+
INPUT=$1
|
|
7
|
+
OUTPUT=$2
|
|
8
|
+
DURATION=$3
|
|
9
|
+
ZOOM=${4:-"in"}
|
|
10
|
+
|
|
11
|
+
if [ -z "$INPUT" ] || [ -z "$OUTPUT" ] || [ -z "$DURATION" ]; then
|
|
12
|
+
echo "Usage: ./still-to-video.sh <input.jpg> <output.mp4> <duration> [in|out]"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Get input dimensions
|
|
17
|
+
WIDTH=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$INPUT")
|
|
18
|
+
HEIGHT=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$INPUT")
|
|
19
|
+
|
|
20
|
+
echo "Creating $DURATION sec video from $INPUT ($WIDTH x $HEIGHT)..."
|
|
21
|
+
|
|
22
|
+
if [ "$ZOOM" = "out" ]; then
|
|
23
|
+
# Zoom out: start zoomed in, end at normal
|
|
24
|
+
FILTER="zoompan=z='1.2-0.2*on/(${DURATION}*25)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=${DURATION}*25:s=${WIDTH}x${HEIGHT}:fps=25"
|
|
25
|
+
else
|
|
26
|
+
# Zoom in: start normal, end zoomed
|
|
27
|
+
FILTER="zoompan=z='1+0.2*on/(${DURATION}*25)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=${DURATION}*25:s=${WIDTH}x${HEIGHT}:fps=25"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
ffmpeg -y -loop 1 -i "$INPUT" \
|
|
31
|
+
-vf "$FILTER" \
|
|
32
|
+
-t "$DURATION" \
|
|
33
|
+
-c:v libx264 -preset fast -crf 20 \
|
|
34
|
+
-pix_fmt yuv420p \
|
|
35
|
+
"$OUTPUT"
|
|
36
|
+
|
|
37
|
+
echo "Done: $OUTPUT"
|