@vargai/sdk 0.1.1 → 0.2.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.
- package/.github/workflows/ci.yml +23 -0
- package/.husky/README.md +102 -0
- package/.husky/commit-msg +9 -0
- package/.husky/pre-commit +12 -0
- package/.husky/pre-push +9 -0
- package/.size-limit.json +8 -0
- package/.test-hooks.ts +5 -0
- package/CONTRIBUTING.md +150 -0
- package/LICENSE.md +53 -0
- package/README.md +7 -0
- package/action/captions/index.ts +202 -12
- package/action/captions/tiktok.ts +538 -0
- package/action/cut/index.ts +119 -0
- package/action/fade/index.ts +116 -0
- package/action/merge/index.ts +177 -0
- package/action/remove/index.ts +184 -0
- package/action/split/index.ts +133 -0
- package/action/transition/index.ts +154 -0
- package/action/trim/index.ts +117 -0
- package/bun.lock +299 -8
- package/cli/commands/upload.ts +215 -0
- package/cli/index.ts +3 -1
- package/commitlint.config.js +22 -0
- package/index.ts +12 -0
- package/lib/ass.ts +547 -0
- package/lib/fal.ts +75 -1
- package/lib/ffmpeg.ts +400 -0
- package/lib/higgsfield/example.ts +22 -29
- package/lib/higgsfield/index.ts +3 -2
- package/lib/higgsfield/soul.ts +0 -5
- package/lib/remotion/SKILL.md +240 -21
- package/lib/remotion/cli.ts +34 -0
- package/package.json +20 -3
- package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +83 -0
- package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
- package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +98 -0
- package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
- package/pipeline/cookbooks/text-to-tiktok.md +669 -0
- package/scripts/.gitkeep +0 -0
- package/service/music/index.ts +29 -14
- package/tsconfig.json +1 -1
- package/utilities/s3.ts +2 -2
- package/HIGGSFIELD_REWRITE_SUMMARY.md +0 -300
- package/TEST_RESULTS.md +0 -122
- package/output.txt +0 -1
- package/scripts/produce-menopause-campaign.sh +0 -202
- package/test-import.ts +0 -7
- package/test-services.ts +0 -97
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* transition action
|
|
5
|
+
* join two videos with a transition effect
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import type { ActionMeta } from "../../cli/types";
|
|
10
|
+
import { xfadeVideos } from "../../lib/ffmpeg";
|
|
11
|
+
|
|
12
|
+
export const meta: ActionMeta = {
|
|
13
|
+
name: "transition",
|
|
14
|
+
type: "action",
|
|
15
|
+
description: "join two videos with a transition effect",
|
|
16
|
+
inputType: "video",
|
|
17
|
+
outputType: "video",
|
|
18
|
+
schema: {
|
|
19
|
+
input: {
|
|
20
|
+
type: "object",
|
|
21
|
+
required: ["video1", "video2", "type", "output"],
|
|
22
|
+
properties: {
|
|
23
|
+
video1: {
|
|
24
|
+
type: "string",
|
|
25
|
+
format: "file-path",
|
|
26
|
+
description: "first video file",
|
|
27
|
+
},
|
|
28
|
+
video2: {
|
|
29
|
+
type: "string",
|
|
30
|
+
format: "file-path",
|
|
31
|
+
description: "second video file",
|
|
32
|
+
},
|
|
33
|
+
type: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: [
|
|
36
|
+
"crossfade",
|
|
37
|
+
"dissolve",
|
|
38
|
+
"wipeleft",
|
|
39
|
+
"wiperight",
|
|
40
|
+
"slideup",
|
|
41
|
+
"slidedown",
|
|
42
|
+
],
|
|
43
|
+
description: "transition effect type",
|
|
44
|
+
},
|
|
45
|
+
duration: {
|
|
46
|
+
type: "number",
|
|
47
|
+
default: 1,
|
|
48
|
+
description: "transition duration in seconds",
|
|
49
|
+
},
|
|
50
|
+
fit: {
|
|
51
|
+
type: "string",
|
|
52
|
+
enum: ["pad", "crop", "blur", "stretch"],
|
|
53
|
+
default: "pad",
|
|
54
|
+
description:
|
|
55
|
+
"how to handle different resolutions: pad (black bars), crop (center), blur (TikTok style), stretch",
|
|
56
|
+
},
|
|
57
|
+
output: {
|
|
58
|
+
type: "string",
|
|
59
|
+
format: "file-path",
|
|
60
|
+
description: "output video path",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
output: { type: "string", format: "file-path", description: "video path" },
|
|
65
|
+
},
|
|
66
|
+
async run(options) {
|
|
67
|
+
const { video1, video2, type, duration, fit, output } = options as {
|
|
68
|
+
video1: string;
|
|
69
|
+
video2: string;
|
|
70
|
+
type:
|
|
71
|
+
| "crossfade"
|
|
72
|
+
| "dissolve"
|
|
73
|
+
| "wipeleft"
|
|
74
|
+
| "wiperight"
|
|
75
|
+
| "slideup"
|
|
76
|
+
| "slidedown";
|
|
77
|
+
duration?: number;
|
|
78
|
+
fit?: "pad" | "crop" | "blur" | "stretch";
|
|
79
|
+
output: string;
|
|
80
|
+
};
|
|
81
|
+
return transition({ video1, video2, type, duration, fit, output });
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export interface TransitionOptions {
|
|
86
|
+
video1: string;
|
|
87
|
+
video2: string;
|
|
88
|
+
type:
|
|
89
|
+
| "crossfade"
|
|
90
|
+
| "dissolve"
|
|
91
|
+
| "wipeleft"
|
|
92
|
+
| "wiperight"
|
|
93
|
+
| "slideup"
|
|
94
|
+
| "slidedown";
|
|
95
|
+
duration?: number;
|
|
96
|
+
fit?: "pad" | "crop" | "blur" | "stretch";
|
|
97
|
+
output: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface TransitionResult {
|
|
101
|
+
output: string;
|
|
102
|
+
transitionType: string;
|
|
103
|
+
transitionDuration: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* join two videos with a transition effect
|
|
108
|
+
*/
|
|
109
|
+
export async function transition(
|
|
110
|
+
options: TransitionOptions,
|
|
111
|
+
): Promise<TransitionResult> {
|
|
112
|
+
const { video1, video2, type, duration = 1, fit = "pad", output } = options;
|
|
113
|
+
|
|
114
|
+
if (!video1 || !video2) {
|
|
115
|
+
throw new Error("video1 and video2 are required");
|
|
116
|
+
}
|
|
117
|
+
if (!type) {
|
|
118
|
+
throw new Error("type is required");
|
|
119
|
+
}
|
|
120
|
+
if (!output) {
|
|
121
|
+
throw new Error("output is required");
|
|
122
|
+
}
|
|
123
|
+
if (!existsSync(video1)) {
|
|
124
|
+
throw new Error(`video file not found: ${video1}`);
|
|
125
|
+
}
|
|
126
|
+
if (!existsSync(video2)) {
|
|
127
|
+
throw new Error(`video file not found: ${video2}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(
|
|
131
|
+
`[transition] applying ${type} effect (${duration}s, fit: ${fit})`,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
await xfadeVideos({
|
|
135
|
+
input1: video1,
|
|
136
|
+
input2: video2,
|
|
137
|
+
output,
|
|
138
|
+
transition: type,
|
|
139
|
+
duration,
|
|
140
|
+
fit,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
output,
|
|
145
|
+
transitionType: type,
|
|
146
|
+
transitionDuration: duration,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// cli
|
|
151
|
+
if (import.meta.main) {
|
|
152
|
+
const { runCli } = await import("../../cli/runner");
|
|
153
|
+
runCli(meta);
|
|
154
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* trim action
|
|
5
|
+
* extract a segment from video (keep only start-end)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
10
|
+
import type { ActionMeta } from "../../cli/types";
|
|
11
|
+
import { trimVideo } from "../../lib/ffmpeg";
|
|
12
|
+
|
|
13
|
+
export const meta: ActionMeta = {
|
|
14
|
+
name: "trim",
|
|
15
|
+
type: "action",
|
|
16
|
+
description: "extract a segment from video (keep only start-end)",
|
|
17
|
+
inputType: "video",
|
|
18
|
+
outputType: "video",
|
|
19
|
+
schema: {
|
|
20
|
+
input: {
|
|
21
|
+
type: "object",
|
|
22
|
+
required: ["video", "start", "end"],
|
|
23
|
+
properties: {
|
|
24
|
+
video: {
|
|
25
|
+
type: "string",
|
|
26
|
+
format: "file-path",
|
|
27
|
+
description: "input video file",
|
|
28
|
+
},
|
|
29
|
+
start: {
|
|
30
|
+
type: "number",
|
|
31
|
+
description: "start time in seconds",
|
|
32
|
+
},
|
|
33
|
+
end: {
|
|
34
|
+
type: "number",
|
|
35
|
+
description: "end time in seconds",
|
|
36
|
+
},
|
|
37
|
+
output: {
|
|
38
|
+
type: "string",
|
|
39
|
+
format: "file-path",
|
|
40
|
+
description: "output video path (auto-generated if not provided)",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
output: { type: "string", format: "file-path", description: "video path" },
|
|
45
|
+
},
|
|
46
|
+
async run(options) {
|
|
47
|
+
const { video, start, end, output } = options as {
|
|
48
|
+
video: string;
|
|
49
|
+
start: number;
|
|
50
|
+
end: number;
|
|
51
|
+
output?: string;
|
|
52
|
+
};
|
|
53
|
+
return trim({ video, start, end, output });
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export interface TrimOptions {
|
|
58
|
+
video: string;
|
|
59
|
+
start: number;
|
|
60
|
+
end: number;
|
|
61
|
+
output?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TrimResult {
|
|
65
|
+
output: string;
|
|
66
|
+
duration: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* extract a segment from video
|
|
71
|
+
*/
|
|
72
|
+
export async function trim(options: TrimOptions): Promise<TrimResult> {
|
|
73
|
+
const { video, start, end, output } = options;
|
|
74
|
+
|
|
75
|
+
if (!video) {
|
|
76
|
+
throw new Error("video is required");
|
|
77
|
+
}
|
|
78
|
+
if (start === undefined || end === undefined) {
|
|
79
|
+
throw new Error("start and end are required");
|
|
80
|
+
}
|
|
81
|
+
if (start >= end) {
|
|
82
|
+
throw new Error("start must be less than end");
|
|
83
|
+
}
|
|
84
|
+
if (!existsSync(video)) {
|
|
85
|
+
throw new Error(`video file not found: ${video}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const duration = end - start;
|
|
89
|
+
|
|
90
|
+
// Generate output path if not provided
|
|
91
|
+
const outputPath =
|
|
92
|
+
output ||
|
|
93
|
+
join(
|
|
94
|
+
dirname(video),
|
|
95
|
+
`${basename(video, extname(video))}_trimmed${extname(video)}`,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
console.log(`[trim] extracting ${start}s - ${end}s (${duration}s)...`);
|
|
99
|
+
|
|
100
|
+
await trimVideo({
|
|
101
|
+
input: video,
|
|
102
|
+
output: outputPath,
|
|
103
|
+
start,
|
|
104
|
+
duration,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
output: outputPath,
|
|
109
|
+
duration,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// cli
|
|
114
|
+
if (import.meta.main) {
|
|
115
|
+
const { runCli } = await import("../../cli/runner");
|
|
116
|
+
runCli(meta);
|
|
117
|
+
}
|