@mevdragon/vidfarm-devcli 0.2.13 → 0.3.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.
Files changed (200) hide show
  1. package/README.md +11 -4
  2. package/SKILL.director.md +97 -0
  3. package/SKILL.platform.md +66 -6
  4. package/dist/src/cli.js +137 -1524
  5. package/dist/src/dev-serve.js +169 -0
  6. package/package.json +5 -24
  7. package/.env.example +0 -60
  8. package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +0 -275
  9. package/auto-create-hyperframe-templates/extractor-system-prompt.md +0 -128
  10. package/auto-create-hyperframe-templates/input-manifest.schema.json +0 -56
  11. package/auto-create-hyperframe-templates/lambda-memory-estimator.md +0 -118
  12. package/auto-create-hyperframe-templates/production-graph.schema.json +0 -314
  13. package/auto-create-hyperframe-templates/runbook.md +0 -198
  14. package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +0 -368
  15. package/auto-create-hyperframe-templates/template-plan.schema.json +0 -311
  16. package/auto-create-hyperframe-templates/template-planner-prompt.md +0 -144
  17. package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +0 -599
  18. package/auto-create-templates/extractor-system-prompt.md +0 -119
  19. package/auto-create-templates/production-graph.schema.json +0 -276
  20. package/auto-create-templates/runbook.md +0 -140
  21. package/auto-create-templates/template-plan.schema.json +0 -301
  22. package/auto-create-templates/template-planner-prompt.md +0 -137
  23. package/dist/src/account-pages-legacy.js +0 -9336
  24. package/dist/src/account-pages-legacy.js.map +0 -1
  25. package/dist/src/account-pages.js +0 -61
  26. package/dist/src/account-pages.js.map +0 -1
  27. package/dist/src/app.js +0 -14023
  28. package/dist/src/app.js.map +0 -1
  29. package/dist/src/cli.js.map +0 -1
  30. package/dist/src/composition-runtime.js +0 -581
  31. package/dist/src/composition-runtime.js.map +0 -1
  32. package/dist/src/config.js +0 -140
  33. package/dist/src/config.js.map +0 -1
  34. package/dist/src/context.js +0 -492
  35. package/dist/src/context.js.map +0 -1
  36. package/dist/src/dev-app-legacy.js +0 -694
  37. package/dist/src/dev-app-legacy.js.map +0 -1
  38. package/dist/src/dev-app.js +0 -6
  39. package/dist/src/dev-app.js.map +0 -1
  40. package/dist/src/domain.js +0 -2
  41. package/dist/src/domain.js.map +0 -1
  42. package/dist/src/editor-chat-history.js +0 -82
  43. package/dist/src/editor-chat-history.js.map +0 -1
  44. package/dist/src/editor-chat.js +0 -311
  45. package/dist/src/editor-chat.js.map +0 -1
  46. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  47. package/dist/src/frontend/flockposter-cache-store.js.map +0 -1
  48. package/dist/src/frontend/homepage-client.js +0 -117
  49. package/dist/src/frontend/homepage-client.js.map +0 -1
  50. package/dist/src/frontend/homepage-shared.js +0 -4
  51. package/dist/src/frontend/homepage-shared.js.map +0 -1
  52. package/dist/src/frontend/homepage-store.js +0 -28
  53. package/dist/src/frontend/homepage-store.js.map +0 -1
  54. package/dist/src/frontend/homepage-view.js +0 -260
  55. package/dist/src/frontend/homepage-view.js.map +0 -1
  56. package/dist/src/frontend/page-runtime-client.js +0 -103
  57. package/dist/src/frontend/page-runtime-client.js.map +0 -1
  58. package/dist/src/frontend/page-runtime-store.js +0 -9
  59. package/dist/src/frontend/page-runtime-store.js.map +0 -1
  60. package/dist/src/frontend/sentry.js +0 -42
  61. package/dist/src/frontend/sentry.js.map +0 -1
  62. package/dist/src/frontend/template-editor-chat.js +0 -3767
  63. package/dist/src/frontend/template-editor-chat.js.map +0 -1
  64. package/dist/src/help-page.js +0 -333
  65. package/dist/src/help-page.js.map +0 -1
  66. package/dist/src/homepage.js +0 -919
  67. package/dist/src/homepage.js.map +0 -1
  68. package/dist/src/hyperframes/composition.js +0 -180
  69. package/dist/src/hyperframes/composition.js.map +0 -1
  70. package/dist/src/index.js +0 -16
  71. package/dist/src/index.js.map +0 -1
  72. package/dist/src/instrument.js +0 -30
  73. package/dist/src/instrument.js.map +0 -1
  74. package/dist/src/lib/crypto.js +0 -45
  75. package/dist/src/lib/crypto.js.map +0 -1
  76. package/dist/src/lib/dev-log.js +0 -54
  77. package/dist/src/lib/dev-log.js.map +0 -1
  78. package/dist/src/lib/display-name.js +0 -11
  79. package/dist/src/lib/display-name.js.map +0 -1
  80. package/dist/src/lib/ids.js +0 -24
  81. package/dist/src/lib/ids.js.map +0 -1
  82. package/dist/src/lib/images.js +0 -19
  83. package/dist/src/lib/images.js.map +0 -1
  84. package/dist/src/lib/json.js +0 -15
  85. package/dist/src/lib/json.js.map +0 -1
  86. package/dist/src/lib/template-dna.js +0 -543
  87. package/dist/src/lib/template-dna.js.map +0 -1
  88. package/dist/src/lib/template-paths.js +0 -28
  89. package/dist/src/lib/template-paths.js.map +0 -1
  90. package/dist/src/lib/template-style-options.js +0 -203
  91. package/dist/src/lib/template-style-options.js.map +0 -1
  92. package/dist/src/lib/time.js +0 -7
  93. package/dist/src/lib/time.js.map +0 -1
  94. package/dist/src/lib/video-quality-harness.js +0 -60
  95. package/dist/src/lib/video-quality-harness.js.map +0 -1
  96. package/dist/src/page-runtime.js +0 -2
  97. package/dist/src/page-runtime.js.map +0 -1
  98. package/dist/src/page-shell.js +0 -2393
  99. package/dist/src/page-shell.js.map +0 -1
  100. package/dist/src/primitive-context.js +0 -299
  101. package/dist/src/primitive-context.js.map +0 -1
  102. package/dist/src/primitive-registry.js +0 -2181
  103. package/dist/src/primitive-registry.js.map +0 -1
  104. package/dist/src/primitive-sdk.js +0 -4
  105. package/dist/src/primitive-sdk.js.map +0 -1
  106. package/dist/src/primitives/remotion/html-image.js +0 -30
  107. package/dist/src/primitives/remotion/html-image.js.map +0 -1
  108. package/dist/src/primitives/remotion/media-slideshow.js +0 -60
  109. package/dist/src/primitives/remotion/media-slideshow.js.map +0 -1
  110. package/dist/src/react-page-shell.js +0 -35
  111. package/dist/src/react-page-shell.js.map +0 -1
  112. package/dist/src/ready-post-schedule-component.js +0 -1515
  113. package/dist/src/ready-post-schedule-component.js.map +0 -1
  114. package/dist/src/registry.js +0 -296
  115. package/dist/src/registry.js.map +0 -1
  116. package/dist/src/runtime.js +0 -31
  117. package/dist/src/runtime.js.map +0 -1
  118. package/dist/src/services/api-call-history.js +0 -249
  119. package/dist/src/services/api-call-history.js.map +0 -1
  120. package/dist/src/services/auth.js +0 -152
  121. package/dist/src/services/auth.js.map +0 -1
  122. package/dist/src/services/billing.js +0 -231
  123. package/dist/src/services/billing.js.map +0 -1
  124. package/dist/src/services/chat-threads.js +0 -92
  125. package/dist/src/services/chat-threads.js.map +0 -1
  126. package/dist/src/services/fork-access.js +0 -94
  127. package/dist/src/services/fork-access.js.map +0 -1
  128. package/dist/src/services/ghostcut.js +0 -132
  129. package/dist/src/services/ghostcut.js.map +0 -1
  130. package/dist/src/services/hyperframes.js +0 -1014
  131. package/dist/src/services/hyperframes.js.map +0 -1
  132. package/dist/src/services/job-capacity.js +0 -14
  133. package/dist/src/services/job-capacity.js.map +0 -1
  134. package/dist/src/services/job-logs.js +0 -197
  135. package/dist/src/services/job-logs.js.map +0 -1
  136. package/dist/src/services/jobs.js +0 -136
  137. package/dist/src/services/jobs.js.map +0 -1
  138. package/dist/src/services/media-processing.js +0 -743
  139. package/dist/src/services/media-processing.js.map +0 -1
  140. package/dist/src/services/primitive-media-lambda.js +0 -280
  141. package/dist/src/services/primitive-media-lambda.js.map +0 -1
  142. package/dist/src/services/providers.js +0 -2870
  143. package/dist/src/services/providers.js.map +0 -1
  144. package/dist/src/services/rate-limits.js +0 -237
  145. package/dist/src/services/rate-limits.js.map +0 -1
  146. package/dist/src/services/remotion.js +0 -688
  147. package/dist/src/services/remotion.js.map +0 -1
  148. package/dist/src/services/serverless-auth.js +0 -374
  149. package/dist/src/services/serverless-auth.js.map +0 -1
  150. package/dist/src/services/serverless-jobs.js +0 -1074
  151. package/dist/src/services/serverless-jobs.js.map +0 -1
  152. package/dist/src/services/serverless-provider-keys.js +0 -401
  153. package/dist/src/services/serverless-provider-keys.js.map +0 -1
  154. package/dist/src/services/serverless-records.js +0 -1086
  155. package/dist/src/services/serverless-records.js.map +0 -1
  156. package/dist/src/services/serverless-template-configs.js +0 -67
  157. package/dist/src/services/serverless-template-configs.js.map +0 -1
  158. package/dist/src/services/storage.js +0 -382
  159. package/dist/src/services/storage.js.map +0 -1
  160. package/dist/src/services/template-certification.js +0 -475
  161. package/dist/src/services/template-certification.js.map +0 -1
  162. package/dist/src/services/template-loader.js +0 -99
  163. package/dist/src/services/template-loader.js.map +0 -1
  164. package/dist/src/services/template-runtime-bundles.js +0 -217
  165. package/dist/src/services/template-runtime-bundles.js.map +0 -1
  166. package/dist/src/services/template-sources.js +0 -1017
  167. package/dist/src/services/template-sources.js.map +0 -1
  168. package/dist/src/services/video-normalization.js +0 -2
  169. package/dist/src/services/video-normalization.js.map +0 -1
  170. package/dist/src/services/webhooks.js +0 -62
  171. package/dist/src/services/webhooks.js.map +0 -1
  172. package/dist/src/template-editor-pages.js +0 -6624
  173. package/dist/src/template-editor-pages.js.map +0 -1
  174. package/dist/src/template-editor-shell.js +0 -2713
  175. package/dist/src/template-editor-shell.js.map +0 -1
  176. package/dist/src/template-sdk.js +0 -4
  177. package/dist/src/template-sdk.js.map +0 -1
  178. package/dist/src/worker.js +0 -17
  179. package/dist/src/worker.js.map +0 -1
  180. package/public/assets/homepage-app.js +0 -54
  181. package/public/assets/homepage-client-app.js +0 -80
  182. package/public/assets/page-runtime-client-app.js +0 -94
  183. package/readme.secret.md +0 -89
  184. package/templates/vidfarm_template_0000/README.md +0 -106
  185. package/templates/vidfarm_template_0000/SKILL.md +0 -266
  186. package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
  187. package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  188. package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
  189. package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
  190. package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  191. package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
  192. package/templates/vidfarm_template_0000/composition.json +0 -11
  193. package/templates/vidfarm_template_0000/src/lib/images.js +0 -221
  194. package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
  195. package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
  196. package/templates/vidfarm_template_0000/src/sdk.js +0 -3
  197. package/templates/vidfarm_template_0000/src/style-options.js +0 -200
  198. package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
  199. package/templates/vidfarm_template_0000/src/template.js +0 -1566
  200. package/templates/vidfarm_template_0000/template.config.json +0 -21
@@ -1,743 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { createWriteStream } from "node:fs";
3
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import { Readable } from "node:stream";
7
- import { pipeline } from "node:stream/promises";
8
- import ffmpegPath from "ffmpeg-static";
9
- import ffprobe from "ffprobe-static";
10
- const DEFAULT_MAX_WIDTH = 1080;
11
- const DEFAULT_MAX_HEIGHT = 1920;
12
- const DEFAULT_CRF = 23;
13
- const DEFAULT_MAX_VIDEO_BITRATE_KBPS = 8_000;
14
- const DEFAULT_AUDIO_BITRATE_KBPS = 128;
15
- const DEFAULT_AUDIO_SAMPLE_RATE = 48_000;
16
- const resolvedFfmpegPath = typeof ffmpegPath === "string" && ffmpegPath.trim() ? ffmpegPath : "ffmpeg";
17
- const ffprobePath = typeof ffprobe.path === "string" ? ffprobe.path : "";
18
- const resolvedFfprobePath = ffprobePath.trim() ? ffprobePath : "ffprobe";
19
- export function estimateNormalizationCostUsd(metadata) {
20
- const durationSeconds = Math.max(metadata.source.durationSeconds ?? 0, metadata.normalized.durationSeconds ?? 0);
21
- const largerFootprintMb = Math.max(metadata.source.bytes, metadata.normalized.bytes) / (1024 * 1024);
22
- const estimated = 0.003 + (durationSeconds * 0.0001) + (largerFootprintMb * 0.00005);
23
- return Number(estimated.toFixed(4));
24
- }
25
- export async function probeMediaAsset(input) {
26
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-media-probe-"));
27
- const sourcePath = path.join(tempDir, "source.bin");
28
- try {
29
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
30
- const metadata = await ffprobeMedia(sourcePath);
31
- return {
32
- metadata: {
33
- ...metadata,
34
- bytes: sourceBytes,
35
- url: input.sourceUrl,
36
- contentType: inferContentTypeFromPath(sourcePath, metadata)
37
- }
38
- };
39
- }
40
- finally {
41
- await rm(tempDir, { recursive: true, force: true });
42
- }
43
- }
44
- export async function normalizeVideoAsset(input) {
45
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-video-normalize-"));
46
- const sourcePath = path.join(tempDir, "source.bin");
47
- const outputPath = path.join(tempDir, "normalized.mp4");
48
- const preset = input.preset ?? "veryfast";
49
- try {
50
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
51
- const sourceProbe = await ffprobeMedia(sourcePath);
52
- await runFfmpeg([
53
- "-hide_banner",
54
- "-y",
55
- "-i", sourcePath,
56
- "-map", "0:v:0",
57
- "-map", "0:a:0?",
58
- "-vf", buildNormalizationVideoFilter({
59
- width: sourceProbe.width,
60
- height: sourceProbe.height,
61
- maxWidth: input.maxWidth ?? DEFAULT_MAX_WIDTH,
62
- maxHeight: input.maxHeight ?? DEFAULT_MAX_HEIGHT
63
- }),
64
- "-c:v", "libx264",
65
- "-preset", preset,
66
- "-crf", String(input.crf ?? DEFAULT_CRF),
67
- "-maxrate", `${input.maxVideoBitrateKbps ?? DEFAULT_MAX_VIDEO_BITRATE_KBPS}k`,
68
- "-bufsize", `${(input.maxVideoBitrateKbps ?? DEFAULT_MAX_VIDEO_BITRATE_KBPS) * 2}k`,
69
- "-pix_fmt", "yuv420p",
70
- "-movflags", "+faststart",
71
- "-g", "60",
72
- "-keyint_min", "60",
73
- "-sc_threshold", "0",
74
- "-c:a", "aac",
75
- "-b:a", `${input.audioBitrateKbps ?? DEFAULT_AUDIO_BITRATE_KBPS}k`,
76
- "-ar", String(DEFAULT_AUDIO_SAMPLE_RATE),
77
- outputPath
78
- ]);
79
- const outputBytes = await readFile(outputPath);
80
- const normalizedProbe = await ffprobeMedia(outputPath);
81
- return {
82
- bytes: outputBytes,
83
- contentType: "video/mp4",
84
- metadata: {
85
- source: {
86
- ...sourceProbe,
87
- bytes: sourceBytes,
88
- url: input.sourceUrl
89
- },
90
- normalized: {
91
- ...normalizedProbe,
92
- bytes: outputBytes.byteLength
93
- },
94
- compressionRatio: sourceBytes > 0 ? outputBytes.byteLength / sourceBytes : null
95
- }
96
- };
97
- }
98
- finally {
99
- await rm(tempDir, { recursive: true, force: true });
100
- }
101
- }
102
- export async function trimVideoAsset(input) {
103
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-video-trim-"));
104
- const sourcePath = path.join(tempDir, "source.bin");
105
- const outputPath = path.join(tempDir, "trimmed.mp4");
106
- try {
107
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
108
- const sourceProbe = await ffprobeMedia(sourcePath);
109
- const clip = resolveTimeWindow(input);
110
- const startSeconds = clip.startMs / 1000;
111
- const durationSeconds = clip.durationMs / 1000;
112
- await runFfmpeg([
113
- "-hide_banner",
114
- "-y",
115
- "-ss", formatSeconds(startSeconds),
116
- "-i", sourcePath,
117
- "-t", formatSeconds(durationSeconds),
118
- "-map", "0:v:0",
119
- "-map", "0:a:0?",
120
- "-vf", buildNormalizationVideoFilter({
121
- width: sourceProbe.width,
122
- height: sourceProbe.height,
123
- maxWidth: input.maxWidth ?? sourceProbe.width ?? DEFAULT_MAX_WIDTH,
124
- maxHeight: input.maxHeight ?? sourceProbe.height ?? DEFAULT_MAX_HEIGHT
125
- }),
126
- "-c:v", "libx264",
127
- "-preset", input.preset ?? "veryfast",
128
- "-crf", String(input.crf ?? DEFAULT_CRF),
129
- "-pix_fmt", "yuv420p",
130
- "-movflags", "+faststart",
131
- "-c:a", "aac",
132
- "-b:a", `${input.audioBitrateKbps ?? DEFAULT_AUDIO_BITRATE_KBPS}k`,
133
- "-ar", String(DEFAULT_AUDIO_SAMPLE_RATE),
134
- outputPath
135
- ]);
136
- const outputBytes = await readFile(outputPath);
137
- const trimmedProbe = await ffprobeMedia(outputPath);
138
- return {
139
- bytes: outputBytes,
140
- contentType: "video/mp4",
141
- metadata: {
142
- source: { ...sourceProbe, bytes: sourceBytes, url: input.sourceUrl },
143
- trimmed: { ...trimmedProbe, bytes: outputBytes.byteLength },
144
- startMs: clip.startMs,
145
- durationMs: clip.durationMs,
146
- endMs: clip.startMs + clip.durationMs
147
- }
148
- };
149
- }
150
- finally {
151
- await rm(tempDir, { recursive: true, force: true });
152
- }
153
- }
154
- export async function extractAudioFromVideoAsset(input) {
155
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-extract-audio-"));
156
- const sourcePath = path.join(tempDir, "source.bin");
157
- const format = input.format ?? "mp3";
158
- const outputPath = path.join(tempDir, `audio.${audioExtension(format)}`);
159
- try {
160
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
161
- const sourceProbe = await ffprobeMedia(sourcePath);
162
- await runFfmpeg([
163
- "-hide_banner",
164
- "-y",
165
- "-i", sourcePath,
166
- "-vn",
167
- ...audioEncodeArgs(format, input.bitrateKbps),
168
- outputPath
169
- ]);
170
- const outputBytes = await readFile(outputPath);
171
- const outputProbe = await ffprobeMedia(outputPath);
172
- return {
173
- bytes: outputBytes,
174
- contentType: audioContentType(format),
175
- metadata: {
176
- source: { ...sourceProbe, bytes: sourceBytes, url: input.sourceUrl },
177
- extracted: { ...outputProbe, bytes: outputBytes.byteLength },
178
- format
179
- }
180
- };
181
- }
182
- finally {
183
- await rm(tempDir, { recursive: true, force: true });
184
- }
185
- }
186
- export async function trimAudioAsset(input) {
187
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-audio-trim-"));
188
- const sourcePath = path.join(tempDir, "source.bin");
189
- const format = input.format ?? "mp3";
190
- const outputPath = path.join(tempDir, `trimmed.${audioExtension(format)}`);
191
- try {
192
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
193
- const sourceProbe = await ffprobeMedia(sourcePath);
194
- const clip = resolveTimeWindow(input);
195
- await runFfmpeg([
196
- "-hide_banner",
197
- "-y",
198
- "-ss", formatSeconds(clip.startMs / 1000),
199
- "-i", sourcePath,
200
- "-t", formatSeconds(clip.durationMs / 1000),
201
- ...audioEncodeArgs(format, input.bitrateKbps),
202
- outputPath
203
- ]);
204
- const outputBytes = await readFile(outputPath);
205
- const outputProbe = await ffprobeMedia(outputPath);
206
- return {
207
- bytes: outputBytes,
208
- contentType: audioContentType(format),
209
- metadata: {
210
- source: { ...sourceProbe, bytes: sourceBytes, url: input.sourceUrl },
211
- trimmed: { ...outputProbe, bytes: outputBytes.byteLength },
212
- startMs: clip.startMs,
213
- durationMs: clip.durationMs,
214
- endMs: clip.startMs + clip.durationMs,
215
- format
216
- }
217
- };
218
- }
219
- finally {
220
- await rm(tempDir, { recursive: true, force: true });
221
- }
222
- }
223
- export async function normalizeAudioAsset(input) {
224
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-audio-normalize-"));
225
- const sourcePath = path.join(tempDir, "source.bin");
226
- const format = input.format ?? "mp3";
227
- const outputPath = path.join(tempDir, `normalized.${audioExtension(format)}`);
228
- try {
229
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
230
- const sourceProbe = await ffprobeMedia(sourcePath);
231
- await runFfmpeg([
232
- "-hide_banner",
233
- "-y",
234
- "-i", sourcePath,
235
- "-af", "loudnorm=I=-16:LRA=11:TP=-1.5",
236
- ...audioEncodeArgs(format, input.bitrateKbps, input.sampleRateHz ?? DEFAULT_AUDIO_SAMPLE_RATE),
237
- outputPath
238
- ]);
239
- const outputBytes = await readFile(outputPath);
240
- const outputProbe = await ffprobeMedia(outputPath);
241
- return {
242
- bytes: outputBytes,
243
- contentType: audioContentType(format),
244
- metadata: {
245
- source: { ...sourceProbe, bytes: sourceBytes, url: input.sourceUrl },
246
- normalized: { ...outputProbe, bytes: outputBytes.byteLength },
247
- format
248
- }
249
- };
250
- }
251
- finally {
252
- await rm(tempDir, { recursive: true, force: true });
253
- }
254
- }
255
- export async function muteVideoAsset(input) {
256
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-video-mute-"));
257
- const sourcePath = path.join(tempDir, "source.bin");
258
- const outputPath = path.join(tempDir, "muted.mp4");
259
- try {
260
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
261
- const sourceProbe = await ffprobeMedia(sourcePath);
262
- await runFfmpeg([
263
- "-hide_banner",
264
- "-y",
265
- "-i", sourcePath,
266
- "-map", "0:v:0",
267
- "-an",
268
- "-c:v", "copy",
269
- "-movflags", "+faststart",
270
- outputPath
271
- ]);
272
- const outputBytes = await readFile(outputPath);
273
- const outputProbe = await ffprobeMedia(outputPath);
274
- return {
275
- bytes: outputBytes,
276
- contentType: "video/mp4",
277
- metadata: {
278
- source: { ...sourceProbe, bytes: sourceBytes, url: input.sourceUrl },
279
- muted: { ...outputProbe, bytes: outputBytes.byteLength }
280
- }
281
- };
282
- }
283
- finally {
284
- await rm(tempDir, { recursive: true, force: true });
285
- }
286
- }
287
- export async function replaceVideoAudioAsset(input) {
288
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-video-replace-audio-"));
289
- const videoPath = path.join(tempDir, "video.bin");
290
- const audioPath = path.join(tempDir, "audio.bin");
291
- const outputPath = path.join(tempDir, "output.mp4");
292
- try {
293
- const videoBytes = await downloadUrlToFile(input.sourceVideoUrl, videoPath);
294
- const audioBytes = await downloadUrlToFile(input.audioUrl, audioPath);
295
- const videoProbe = await ffprobeMedia(videoPath);
296
- const audioProbe = await ffprobeMedia(audioPath);
297
- await runFfmpeg([
298
- "-hide_banner",
299
- "-y",
300
- "-i", videoPath,
301
- "-i", audioPath,
302
- "-map", "0:v:0",
303
- "-map", "1:a:0",
304
- "-c:v", "copy",
305
- "-c:a", "aac",
306
- "-b:a", `${input.audioBitrateKbps ?? DEFAULT_AUDIO_BITRATE_KBPS}k`,
307
- "-shortest",
308
- "-movflags", "+faststart",
309
- outputPath
310
- ]);
311
- const outputBytes = await readFile(outputPath);
312
- const outputProbe = await ffprobeMedia(outputPath);
313
- return {
314
- bytes: outputBytes,
315
- contentType: "video/mp4",
316
- metadata: {
317
- sourceVideo: { ...videoProbe, bytes: videoBytes, url: input.sourceVideoUrl },
318
- sourceAudio: { ...audioProbe, bytes: audioBytes, url: input.audioUrl },
319
- replaced: { ...outputProbe, bytes: outputBytes.byteLength }
320
- }
321
- };
322
- }
323
- finally {
324
- await rm(tempDir, { recursive: true, force: true });
325
- }
326
- }
327
- export async function extractVideoFrameAsset(input) {
328
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-video-frame-"));
329
- const sourcePath = path.join(tempDir, "source.bin");
330
- const format = input.format ?? "png";
331
- const outputPath = path.join(tempDir, `frame.${format === "jpeg" ? "jpg" : format}`);
332
- try {
333
- const sourceBytes = await downloadUrlToFile(input.sourceUrl, sourcePath);
334
- const sourceProbe = await ffprobeMedia(sourcePath);
335
- await runFfmpeg([
336
- "-hide_banner",
337
- "-y",
338
- ...(typeof input.atMs === "number" && input.atMs > 0 ? ["-ss", formatSeconds(input.atMs / 1000)] : []),
339
- "-i", sourcePath,
340
- "-frames:v", "1",
341
- outputPath
342
- ]);
343
- const outputBytes = await readFile(outputPath);
344
- return {
345
- bytes: outputBytes,
346
- contentType: format === "jpeg" ? "image/jpeg" : "image/png",
347
- metadata: {
348
- source: { ...sourceProbe, bytes: sourceBytes, url: input.sourceUrl },
349
- atMs: Math.max(0, Math.round(input.atMs ?? 0)),
350
- format
351
- }
352
- };
353
- }
354
- finally {
355
- await rm(tempDir, { recursive: true, force: true });
356
- }
357
- }
358
- export async function concatAudioAssets(input) {
359
- if (!input.sourceUrls.length) {
360
- throw new Error("At least one audio source URL is required.");
361
- }
362
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-audio-concat-"));
363
- const format = input.format ?? "mp3";
364
- const outputPath = path.join(tempDir, `concat.${audioExtension(format)}`);
365
- try {
366
- const inputs = [];
367
- for (let index = 0; index < input.sourceUrls.length; index += 1) {
368
- const filePath = path.join(tempDir, `audio-${String(index + 1).padStart(3, "0")}.bin`);
369
- const bytes = await downloadUrlToFile(input.sourceUrls[index], filePath);
370
- const metadata = await ffprobeMedia(filePath);
371
- inputs.push({ url: input.sourceUrls[index], filePath, bytes, metadata });
372
- }
373
- const args = ["-hide_banner", "-y"];
374
- for (const item of inputs) {
375
- args.push("-i", item.filePath);
376
- }
377
- const filterInputs = inputs.map((_, index) => `[${index}:a]`).join("");
378
- args.push("-filter_complex", `${filterInputs}concat=n=${inputs.length}:v=0:a=1[outa]`, "-map", "[outa]", ...audioEncodeArgs(format, input.bitrateKbps), outputPath);
379
- await runFfmpeg(args);
380
- const outputBytes = await readFile(outputPath);
381
- const outputProbe = await ffprobeMedia(outputPath);
382
- return {
383
- bytes: outputBytes,
384
- contentType: audioContentType(format),
385
- metadata: {
386
- sources: inputs.map((item) => ({ ...item.metadata, bytes: item.bytes, url: item.url })),
387
- concatenated: { ...outputProbe, bytes: outputBytes.byteLength },
388
- format,
389
- sourceCount: inputs.length
390
- }
391
- };
392
- }
393
- finally {
394
- await rm(tempDir, { recursive: true, force: true });
395
- }
396
- }
397
- export async function concatVideoAssets(input) {
398
- if (!input.sourceUrls.length) {
399
- throw new Error("At least one video source URL is required.");
400
- }
401
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-video-concat-"));
402
- const outputPath = path.join(tempDir, "concat.mp4");
403
- try {
404
- const inputs = [];
405
- for (let index = 0; index < input.sourceUrls.length; index += 1) {
406
- const filePath = path.join(tempDir, `video-${String(index + 1).padStart(3, "0")}.bin`);
407
- const bytes = await downloadUrlToFile(input.sourceUrls[index], filePath);
408
- const metadata = await ffprobeMedia(filePath);
409
- inputs.push({ url: input.sourceUrls[index], filePath, bytes, metadata });
410
- }
411
- const width = Math.max(16, Math.round(input.width ?? inputs[0]?.metadata.width ?? DEFAULT_MAX_WIDTH));
412
- const height = Math.max(16, Math.round(input.height ?? inputs[0]?.metadata.height ?? DEFAULT_MAX_HEIGHT));
413
- const fit = input.fit ?? "contain";
414
- const backgroundColor = normalizeFfmpegColor(input.backgroundColor || "black");
415
- const args = ["-hide_banner", "-y"];
416
- for (const item of inputs) {
417
- args.push("-i", item.filePath);
418
- }
419
- const filters = inputs.map((_, index) => (`[${index}:v]${videoCanvasFilter({ width, height, fit, backgroundColor })}[v${index}]`));
420
- const concatInputs = inputs.map((_, index) => `[v${index}]`).join("");
421
- args.push("-filter_complex", `${filters.join(";")};${concatInputs}concat=n=${inputs.length}:v=1:a=0[outv]`, "-map", "[outv]", "-an", "-r", "30", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-movflags", "+faststart", outputPath);
422
- await runFfmpeg(args);
423
- const outputBytes = await readFile(outputPath);
424
- const outputProbe = await ffprobeMedia(outputPath);
425
- return {
426
- bytes: outputBytes,
427
- contentType: "video/mp4",
428
- metadata: {
429
- sources: inputs.map((item) => ({ ...item.metadata, bytes: item.bytes, url: item.url })),
430
- concatenated: { ...outputProbe, bytes: outputBytes.byteLength },
431
- width,
432
- height,
433
- fit,
434
- sourceCount: inputs.length,
435
- includesAudio: false
436
- }
437
- };
438
- }
439
- finally {
440
- await rm(tempDir, { recursive: true, force: true });
441
- }
442
- }
443
- export async function renderSlidesVideoAsset(input) {
444
- if (!input.slides.length) {
445
- throw new Error("At least one slide is required.");
446
- }
447
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "vidfarm-primitive-slides-"));
448
- const outputPath = path.join(tempDir, "output.mp4");
449
- try {
450
- const downloadedSlides = [];
451
- for (let index = 0; index < input.slides.length; index += 1) {
452
- const slide = input.slides[index];
453
- const filePath = path.join(tempDir, `slide-${String(index + 1).padStart(3, "0")}${extensionForMediaUrl(slide.mediaUrl, slide.mediaType)}`);
454
- await downloadMediaToFile(slide.mediaUrl, filePath);
455
- downloadedSlides.push({ ...slide, filePath });
456
- }
457
- await runFfmpeg(buildSlideRenderArgs({
458
- slides: downloadedSlides,
459
- outputPath,
460
- width: input.width,
461
- height: input.height,
462
- backgroundColor: input.backgroundColor,
463
- defaultFit: input.defaultFit
464
- }));
465
- const outputBytes = await readFile(outputPath);
466
- const outputProbe = await ffprobeMedia(outputPath);
467
- return {
468
- bytes: outputBytes,
469
- contentType: "video/mp4",
470
- metadata: {
471
- rendered: { ...outputProbe, bytes: outputBytes.byteLength },
472
- width: input.width,
473
- height: input.height,
474
- slideCount: input.slides.length,
475
- durationMs: input.slides.reduce((sum, slide) => sum + slide.durationMs, 0)
476
- }
477
- };
478
- }
479
- finally {
480
- await rm(tempDir, { recursive: true, force: true });
481
- }
482
- }
483
- function buildNormalizationVideoFilter(input) {
484
- const width = input.width ?? input.maxWidth;
485
- const height = input.height ?? input.maxHeight;
486
- if (width <= input.maxWidth && height <= input.maxHeight) {
487
- return "scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=1";
488
- }
489
- return `scale=w=${input.maxWidth}:h=${input.maxHeight}:force_original_aspect_ratio=decrease:force_divisible_by=2,setsar=1`;
490
- }
491
- function resolveTimeWindow(input) {
492
- const startMs = Math.max(0, Math.round(input.startMs ?? 0));
493
- const durationMs = input.durationMs == null
494
- ? Math.max(1, Math.round((input.endMs ?? 0) - startMs))
495
- : Math.max(1, Math.round(input.durationMs));
496
- if (input.durationMs == null && (input.endMs == null || input.endMs <= startMs)) {
497
- throw new Error("Provide either durationMs or an endMs greater than startMs.");
498
- }
499
- return {
500
- startMs,
501
- durationMs
502
- };
503
- }
504
- function audioEncodeArgs(format, bitrateKbps, sampleRateHz = DEFAULT_AUDIO_SAMPLE_RATE) {
505
- if (format === "wav") {
506
- return ["-c:a", "pcm_s16le", "-ar", String(sampleRateHz)];
507
- }
508
- if (format === "m4a") {
509
- return ["-c:a", "aac", "-b:a", `${bitrateKbps ?? DEFAULT_AUDIO_BITRATE_KBPS}k`, "-ar", String(sampleRateHz)];
510
- }
511
- return ["-c:a", "libmp3lame", "-b:a", `${bitrateKbps ?? DEFAULT_AUDIO_BITRATE_KBPS}k`, "-ar", String(sampleRateHz)];
512
- }
513
- function audioContentType(format) {
514
- if (format === "wav")
515
- return "audio/wav";
516
- if (format === "m4a")
517
- return "audio/mp4";
518
- return "audio/mpeg";
519
- }
520
- function audioExtension(format) {
521
- if (format === "wav")
522
- return "wav";
523
- if (format === "m4a")
524
- return "m4a";
525
- return "mp3";
526
- }
527
- function videoCanvasFilter(input) {
528
- if (input.fit === "fill") {
529
- return `scale=${input.width}:${input.height},setsar=1,fps=30,setpts=PTS-STARTPTS`;
530
- }
531
- if (input.fit === "contain") {
532
- return `scale=${input.width}:${input.height}:force_original_aspect_ratio=decrease,pad=${input.width}:${input.height}:(ow-iw)/2:(oh-ih)/2:color=${input.backgroundColor},setsar=1,fps=30,setpts=PTS-STARTPTS`;
533
- }
534
- return `scale=${input.width}:${input.height}:force_original_aspect_ratio=increase,crop=${input.width}:${input.height},setsar=1,fps=30,setpts=PTS-STARTPTS`;
535
- }
536
- function buildSlideRenderArgs(input) {
537
- const args = ["-hide_banner", "-y"];
538
- for (const slide of input.slides) {
539
- const durationSeconds = slide.durationMs / 1000;
540
- if (slide.mediaType === "video") {
541
- args.push("-stream_loop", "-1", "-t", durationSeconds.toFixed(3), "-i", slide.filePath);
542
- }
543
- else {
544
- args.push("-loop", "1", "-t", durationSeconds.toFixed(3), "-i", slide.filePath);
545
- }
546
- }
547
- const filters = input.slides.map((slide, index) => {
548
- const durationSeconds = slide.durationMs / 1000;
549
- const fit = slide.fit ?? input.defaultFit;
550
- const backgroundColor = normalizeFfmpegColor(slide.backgroundColor || input.backgroundColor);
551
- return `[${index}:v]${primitiveSlideVideoFilter({
552
- width: input.width,
553
- height: input.height,
554
- fit,
555
- backgroundColor,
556
- durationSeconds
557
- })}[v${index}]`;
558
- });
559
- const concatInputs = input.slides.map((_, index) => `[v${index}]`).join("");
560
- const filterComplex = `${filters.join(";")};${concatInputs}concat=n=${input.slides.length}:v=1:a=0[outv]`;
561
- args.push("-filter_complex", filterComplex, "-map", "[outv]", "-an", "-r", "30", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-movflags", "+faststart", input.outputPath);
562
- return args;
563
- }
564
- function primitiveSlideVideoFilter(input) {
565
- const base = input.fit === "fill"
566
- ? `scale=${input.width}:${input.height}`
567
- : input.fit === "contain"
568
- ? `scale=${input.width}:${input.height}:force_original_aspect_ratio=decrease,pad=${input.width}:${input.height}:(ow-iw)/2:(oh-ih)/2:color=${input.backgroundColor}`
569
- : `scale=${input.width}:${input.height}:force_original_aspect_ratio=increase,crop=${input.width}:${input.height}`;
570
- return `${base},setsar=1,fps=30,trim=duration=${input.durationSeconds.toFixed(3)},setpts=PTS-STARTPTS`;
571
- }
572
- async function downloadMediaToFile(url, filePath) {
573
- const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });
574
- if (!response.ok) {
575
- throw new Error(`Media returned ${response.status} ${response.statusText || "HTTP error"} for ${formatUrlForError(url)}`);
576
- }
577
- const bytes = new Uint8Array(await response.arrayBuffer());
578
- if (!bytes.byteLength) {
579
- throw new Error(`Media was empty for ${formatUrlForError(url)}`);
580
- }
581
- await writeFile(filePath, bytes);
582
- }
583
- export async function ffprobeMedia(filePath) {
584
- const stdout = await runCommand(resolvedFfprobePath, [
585
- "-v", "error",
586
- "-print_format", "json",
587
- "-show_streams",
588
- "-show_format",
589
- filePath
590
- ]);
591
- const parsed = JSON.parse(stdout);
592
- const streams = Array.isArray(parsed.streams) ? parsed.streams : [];
593
- const video = streams.find((stream) => stream.codec_type === "video") ?? null;
594
- const audio = streams.find((stream) => stream.codec_type === "audio") ?? null;
595
- const format = parsed.format ?? {};
596
- return {
597
- durationSeconds: parseNullableNumber(format.duration),
598
- width: parseNullableInteger(video?.width),
599
- height: parseNullableInteger(video?.height),
600
- videoCodec: typeof video?.codec_name === "string" ? video.codec_name : null,
601
- audioCodec: typeof audio?.codec_name === "string" ? audio.codec_name : null,
602
- videoBitrateKbps: toKbps(parseNullableNumber(video?.bit_rate)),
603
- audioBitrateKbps: toKbps(parseNullableNumber(audio?.bit_rate)),
604
- formatBitrateKbps: toKbps(parseNullableNumber(format.bit_rate)),
605
- hasAudio: Boolean(audio),
606
- hasVideo: Boolean(video),
607
- sampleRateHz: parseNullableInteger(audio?.sample_rate),
608
- channels: parseNullableInteger(audio?.channels),
609
- frameRate: parseFrameRate(video?.avg_frame_rate ?? video?.r_frame_rate),
610
- formatName: typeof format.format_name === "string" ? format.format_name : null
611
- };
612
- }
613
- async function downloadUrlToFile(url, filePath) {
614
- const response = await fetch(url, { signal: AbortSignal.timeout(10 * 60 * 1000) });
615
- if (!response.ok) {
616
- throw new Error(`Media download returned ${response.status} ${response.statusText || "HTTP error"} for ${formatUrlForError(url)}`);
617
- }
618
- if (!response.body) {
619
- throw new Error(`Media download returned no body for ${formatUrlForError(url)}`);
620
- }
621
- const source = Readable.fromWeb(response.body);
622
- const sink = createWriteStream(filePath);
623
- await pipeline(source, sink);
624
- const bytes = Number.parseInt(response.headers.get("content-length") || "", 10);
625
- if (Number.isFinite(bytes) && bytes > 0) {
626
- return bytes;
627
- }
628
- const written = await readFile(filePath);
629
- return written.byteLength;
630
- }
631
- export async function runFfmpeg(args) {
632
- await runCommand(resolvedFfmpegPath, args, true);
633
- }
634
- async function runCommand(command, args, allowStderr = false) {
635
- const result = await new Promise((resolve, reject) => {
636
- const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
637
- let stdout = "";
638
- let stderr = "";
639
- child.stdout.setEncoding("utf8");
640
- child.stderr.setEncoding("utf8");
641
- child.stdout.on("data", (chunk) => {
642
- stdout += chunk;
643
- if (stdout.length > 200_000)
644
- stdout = stdout.slice(-200_000);
645
- });
646
- child.stderr.on("data", (chunk) => {
647
- stderr += chunk;
648
- if (stderr.length > 100_000)
649
- stderr = stderr.slice(-100_000);
650
- });
651
- child.on("error", reject);
652
- child.on("close", (code) => resolve({ code, stdout, stderr }));
653
- });
654
- if (result.code !== 0) {
655
- throw new Error(`${path.basename(command)} failed with exit code ${result.code ?? "unknown"}: ${result.stderr.slice(-4000)}`);
656
- }
657
- if (!allowStderr && result.stderr.trim()) {
658
- return result.stdout;
659
- }
660
- return result.stdout;
661
- }
662
- function parseNullableNumber(value) {
663
- if (typeof value === "number" && Number.isFinite(value))
664
- return value;
665
- if (typeof value === "string") {
666
- const parsed = Number.parseFloat(value);
667
- return Number.isFinite(parsed) ? parsed : null;
668
- }
669
- return null;
670
- }
671
- function parseNullableInteger(value) {
672
- if (typeof value === "number" && Number.isFinite(value))
673
- return Math.trunc(value);
674
- if (typeof value === "string") {
675
- const parsed = Number.parseInt(value, 10);
676
- return Number.isFinite(parsed) ? parsed : null;
677
- }
678
- return null;
679
- }
680
- function parseFrameRate(value) {
681
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
682
- return value;
683
- }
684
- if (typeof value !== "string" || !value.includes("/")) {
685
- return null;
686
- }
687
- const [numeratorText, denominatorText] = value.split("/");
688
- const numerator = Number.parseFloat(numeratorText || "");
689
- const denominator = Number.parseFloat(denominatorText || "");
690
- if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {
691
- return null;
692
- }
693
- return Number((numerator / denominator).toFixed(4));
694
- }
695
- function toKbps(value) {
696
- return value == null ? null : Math.round(value / 1000);
697
- }
698
- function formatSeconds(value) {
699
- return value.toFixed(3);
700
- }
701
- function extensionForMediaUrl(mediaUrl, mediaType) {
702
- try {
703
- const extension = path.extname(new URL(mediaUrl).pathname).toLowerCase();
704
- if (/^\.[a-z0-9]{2,5}$/i.test(extension)) {
705
- return extension;
706
- }
707
- }
708
- catch {
709
- // Fall through to defaults.
710
- }
711
- return mediaType === "video" ? ".mp4" : ".img";
712
- }
713
- function normalizeFfmpegColor(value) {
714
- const trimmed = value.trim();
715
- const hex = /^#?([0-9a-f]{6})$/i.exec(trimmed);
716
- if (hex) {
717
- return `0x${hex[1]}`;
718
- }
719
- return /^[a-z]+$/i.test(trimmed) ? trimmed : "black";
720
- }
721
- function inferContentTypeFromPath(filePath, metadata) {
722
- if (metadata.hasVideo) {
723
- if (filePath.endsWith(".webm"))
724
- return "video/webm";
725
- if (filePath.endsWith(".mov"))
726
- return "video/quicktime";
727
- return "video/mp4";
728
- }
729
- if (metadata.hasAudio) {
730
- if (filePath.endsWith(".wav"))
731
- return "audio/wav";
732
- if (filePath.endsWith(".m4a"))
733
- return "audio/mp4";
734
- if (filePath.endsWith(".ogg"))
735
- return "audio/ogg";
736
- return "audio/mpeg";
737
- }
738
- return null;
739
- }
740
- function formatUrlForError(url) {
741
- return url.length > 300 ? `${url.slice(0, 300)}...` : url;
742
- }
743
- //# sourceMappingURL=media-processing.js.map