vargai 0.4.0-alpha106 → 0.4.0-alpha108
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/package.json +1 -4
- package/pricing_fix.txt +197 -0
- package/src/ai-sdk/providers/editly/backends/types.ts +0 -5
- package/src/ai-sdk/providers/editly/rendi/index.ts +0 -204
- package/src/react/elements.ts +2 -2
- package/src/react/renderers/burn-captions.ts +30 -224
- package/src/react/renderers/captions.ts +26 -276
- package/src/react/renderers/render.ts +0 -31
- package/src/react/renderers/subtitle.ts +1 -1
- package/src/react/renderers/title.ts +1 -1
- package/src/react/types.ts +4 -3
- package/src/react/renderers/emoji.ts +0 -297
- package/src/react/renderers/fonts.ts +0 -509
package/package.json
CHANGED
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"@commitlint/config-conventional": "^20.0.0",
|
|
29
29
|
"@size-limit/preset-small-lib": "^11.2.0",
|
|
30
30
|
"@types/bun": "latest",
|
|
31
|
-
"@types/opentype.js": "^1.3.9",
|
|
32
31
|
"@types/react": "^19.2.7",
|
|
33
32
|
"husky": "^9.1.7",
|
|
34
33
|
"lint-staged": "^16.2.7"
|
|
@@ -59,11 +58,9 @@
|
|
|
59
58
|
"ai": "^6.0.26",
|
|
60
59
|
"apify-client": "^2.20.0",
|
|
61
60
|
"citty": "^0.1.6",
|
|
62
|
-
"fflate": "^0.8.2",
|
|
63
61
|
"fluent-ffmpeg": "^2.1.3",
|
|
64
62
|
"groq-sdk": "^0.36.0",
|
|
65
63
|
"ink": "^6.5.1",
|
|
66
|
-
"opentype.js": "^1.3.4",
|
|
67
64
|
"p-limit": "^6.2.0",
|
|
68
65
|
"p-map": "^7.0.4",
|
|
69
66
|
"react": "^19.2.0",
|
|
@@ -107,7 +104,7 @@
|
|
|
107
104
|
"license": "Apache-2.0",
|
|
108
105
|
"author": "varg.ai <hello@varg.ai> (https://varg.ai)",
|
|
109
106
|
"sideEffects": false,
|
|
110
|
-
"version": "0.4.0-
|
|
107
|
+
"version": "0.4.0-alpha108",
|
|
111
108
|
"exports": {
|
|
112
109
|
".": "./src/index.ts",
|
|
113
110
|
"./ai": "./src/ai-sdk/index.ts",
|
package/pricing_fix.txt
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
================================================================================
|
|
2
|
+
PRICING FIX LOG - Verified from Official Provider llms.txt & Pricing Pages
|
|
3
|
+
================================================================================
|
|
4
|
+
Date: 2026-04-22
|
|
5
|
+
Sources: fal.ai/models/*/llms.txt, elevenlabs.io/pricing/api, groq.com/pricing,
|
|
6
|
+
fireworks.ai/pricing, replicate.com/pricing
|
|
7
|
+
|
|
8
|
+
================================================================================
|
|
9
|
+
SUMMARY: 13 model definitions updated, 4 confirmed correct
|
|
10
|
+
================================================================================
|
|
11
|
+
|
|
12
|
+
Models confirmed CORRECT (no changes needed):
|
|
13
|
+
- reve ($0.04/image) src/definitions/models/reve.ts
|
|
14
|
+
- phota ($0.09/1K image) src/definitions/models/phota.ts
|
|
15
|
+
- phota/edit ($0.09/1K image) src/definitions/models/phota.ts
|
|
16
|
+
- phota/enhance ($0.13/image) src/definitions/models/phota.ts
|
|
17
|
+
|
|
18
|
+
================================================================================
|
|
19
|
+
CHANGES APPLIED
|
|
20
|
+
================================================================================
|
|
21
|
+
|
|
22
|
+
--- FIX #1: nano-banana-pro.ts (CRITICAL - was 3.75x underpriced) ---
|
|
23
|
+
File: src/definitions/models/nano-banana-pro.ts
|
|
24
|
+
Source: https://fal.ai/models/fal-ai/nano-banana-pro/llms.txt
|
|
25
|
+
Old: $0.04 per image flat
|
|
26
|
+
New: $0.15 per image (1K), $0.30 per image (4K)
|
|
27
|
+
Web search adds $0.015 per request
|
|
28
|
+
Formula: resolution === "4K" ? 0.30 : 0.15, multiplied by numImages
|
|
29
|
+
|
|
30
|
+
--- FIX #2: recraft-v4.ts (CRITICAL - was 3.1x underpriced) ---
|
|
31
|
+
File: src/definitions/models/recraft-v4.ts
|
|
32
|
+
Source: https://fal.ai/models/fal-ai/recraft/v4/pro/text-to-image/llms.txt
|
|
33
|
+
Old: $0.08 per image flat
|
|
34
|
+
New: $0.25 per image flat
|
|
35
|
+
Formula: flat 0.25
|
|
36
|
+
|
|
37
|
+
--- FIX #3: kling.ts (was flat, now per-second) ---
|
|
38
|
+
File: src/definitions/models/kling.ts
|
|
39
|
+
Source: https://fal.ai/models/fal-ai/kling-video/o3/pro/text-to-video/llms.txt
|
|
40
|
+
Old: $1.20 flat per generation
|
|
41
|
+
New: $0.112 per second (audio off), $0.14 per second (audio on)
|
|
42
|
+
5s video = $0.56 (no audio) or $0.70 (audio)
|
|
43
|
+
Duration range: 3-15 seconds
|
|
44
|
+
Formula: 0.112 * duration
|
|
45
|
+
Note: Using base rate $0.112 (no audio). Audio surcharge not auto-detected.
|
|
46
|
+
Kling v2.5 Turbo Pro ($0.07/sec) is a different endpoint used in
|
|
47
|
+
provider code, not captured in this model definition.
|
|
48
|
+
|
|
49
|
+
--- FIX #4: wan.ts (was flat, now per-second with resolution tiers) ---
|
|
50
|
+
File: src/definitions/models/wan.ts
|
|
51
|
+
Source: https://fal.ai/models/fal-ai/wan-25-preview/image-to-video/llms.txt
|
|
52
|
+
Old: $0.60 flat per generation
|
|
53
|
+
New: $0.05/sec (480p), $0.10/sec (720p), $0.15/sec (1080p)
|
|
54
|
+
Duration options: 5s or 10s
|
|
55
|
+
Formula: rateMap[resolution] * duration
|
|
56
|
+
rateMap = { "480p": 0.05, "720p": 0.10, "1080p": 0.15 }
|
|
57
|
+
Default resolution: "480p"
|
|
58
|
+
|
|
59
|
+
--- FIX #5: omnihuman.ts (was flat, now per-second) ---
|
|
60
|
+
File: src/definitions/models/omnihuman.ts
|
|
61
|
+
Source: https://fal.ai/models/fal-ai/bytedance/omnihuman/v1.5/llms.txt
|
|
62
|
+
Old: $0.80 flat per generation
|
|
63
|
+
New: $0.16 per second of output video
|
|
64
|
+
Max audio: 30s (1080p), 60s (720p)
|
|
65
|
+
Formula: 0.16 * duration
|
|
66
|
+
Note: Duration driven by audio length. Output includes billing duration field.
|
|
67
|
+
|
|
68
|
+
--- FIX #6: veed-fabric.ts (was flat, now per-second with resolution tiers) ---
|
|
69
|
+
File: src/definitions/models/veed-fabric.ts
|
|
70
|
+
Source: https://fal.ai/models/veed/fabric-1.0/llms.txt
|
|
71
|
+
Old: $0.80 flat per generation
|
|
72
|
+
New: $0.08/sec (480p), $0.15/sec (720p)
|
|
73
|
+
Formula: (resolution === "720p" ? 0.15 : 0.08) * duration
|
|
74
|
+
Note: Duration driven by audio length. Resolution is required input.
|
|
75
|
+
|
|
76
|
+
--- FIX #7: nano-banana-2.ts (was 2x underpriced, now has resolution tiers) ---
|
|
77
|
+
File: src/definitions/models/nano-banana-2.ts
|
|
78
|
+
Source: https://fal.ai/models/fal-ai/nano-banana-2/llms.txt
|
|
79
|
+
Old: $0.04 per image flat
|
|
80
|
+
New: $0.06 (0.5K), $0.08 (1K), $0.12 (2K), $0.16 (4K) per image
|
|
81
|
+
Web search adds $0.015, high thinking adds $0.002
|
|
82
|
+
Formula: rateMap[resolution] * numImages
|
|
83
|
+
rateMap = { "0.5K": 0.06, "1K": 0.08, "2K": 0.12, "4K": 0.16 }
|
|
84
|
+
Default resolution: "1K"
|
|
85
|
+
|
|
86
|
+
--- FIX #8: elevenlabs.ts (was 1.8x overpriced) ---
|
|
87
|
+
File: src/definitions/models/elevenlabs.ts
|
|
88
|
+
Source: https://elevenlabs.io/pricing/api
|
|
89
|
+
Old: $0.18 per 1,000 characters ($0.00018/char)
|
|
90
|
+
New: $0.10 per 1,000 characters ($0.0001/char) for Multilingual v2/v3
|
|
91
|
+
Flash/Turbo models: $0.05 per 1,000 characters
|
|
92
|
+
Formula: 0.0001 * characters
|
|
93
|
+
Note: Default model is eleven_multilingual_v2, so v2 rate is used.
|
|
94
|
+
Flash/Turbo pricing noted in description for reference.
|
|
95
|
+
|
|
96
|
+
--- FIX #9: ltx-a2v.ts (was flat, now per-megapixel of video data) ---
|
|
97
|
+
File: src/definitions/models/ltx-a2v.ts
|
|
98
|
+
Source: https://fal.ai/models/fal-ai/ltx-2-19b/audio-to-video/llms.txt
|
|
99
|
+
Old: $0.40 flat per generation
|
|
100
|
+
New: $0.0018 per megapixel of generated video data (width x height x frames)
|
|
101
|
+
Example: 121 frames at 1280x720 = ~112 MP = $0.2016
|
|
102
|
+
Formula: Math.ceil((width * height * numFrames) / 1_000_000) * 0.0018
|
|
103
|
+
Default: width=1024, height=768, numFrames=121
|
|
104
|
+
|
|
105
|
+
--- FIX #10: flux.ts (unit changed from per-image to per-megapixel) ---
|
|
106
|
+
File: src/definitions/models/flux.ts
|
|
107
|
+
Source: https://fal.ai/models/fal-ai/flux-pro/v1.1/llms.txt
|
|
108
|
+
Old: $0.05 per image flat
|
|
109
|
+
New: $0.04 per megapixel (rounded up to nearest MP)
|
|
110
|
+
Standard 1MP image (1024x1024) = $0.04
|
|
111
|
+
Formula: 0.04 * Math.ceil((width * height) / 1_000_000) * numImages
|
|
112
|
+
Default: width=1024, height=768 -> 1MP -> $0.04
|
|
113
|
+
|
|
114
|
+
--- FIX #11: whisper.ts / groq (was flat, now per-hour) ---
|
|
115
|
+
File: src/definitions/models/whisper.ts
|
|
116
|
+
Source: https://groq.com/pricing
|
|
117
|
+
Old: $0.03 flat per transcription
|
|
118
|
+
New: $0.111 per hour of audio (minimum 10s billing per request)
|
|
119
|
+
Formula: (Math.max(duration, 10) / 3600) * 0.111
|
|
120
|
+
Default duration: 60 seconds
|
|
121
|
+
|
|
122
|
+
--- FIX #12: whisper.ts / fireworks (was flat, now per-minute) ---
|
|
123
|
+
File: src/definitions/models/whisper.ts
|
|
124
|
+
Source: https://fireworks.ai/pricing
|
|
125
|
+
Old: $0.02 flat per transcription
|
|
126
|
+
New: $0.0015 per audio minute (billed per second)
|
|
127
|
+
Formula: (duration / 60) * 0.0015
|
|
128
|
+
Default duration: 60 seconds
|
|
129
|
+
$0.0015/min = $0.000025/sec = $0.09/hour
|
|
130
|
+
|
|
131
|
+
--- FIX #13: qwen-image-2.ts (minor adjustment) ---
|
|
132
|
+
File: src/definitions/models/qwen-image-2.ts
|
|
133
|
+
Source: https://fal.ai/models/fal-ai/qwen-image-2/text-to-image/llms.txt
|
|
134
|
+
Old: $0.04 per image
|
|
135
|
+
New: $0.035 per image (standard endpoint)
|
|
136
|
+
Pro endpoint: $0.075/image (separate endpoint, not this definition)
|
|
137
|
+
Formula: 0.035 * numImages
|
|
138
|
+
|
|
139
|
+
================================================================================
|
|
140
|
+
MODELS WITHOUT DEFINITIONS (AI-SDK only, not updated)
|
|
141
|
+
================================================================================
|
|
142
|
+
|
|
143
|
+
These are registered in src/ai-sdk/providers/ but have no model definition
|
|
144
|
+
files in src/definitions/models/. Verified pricing from llms.txt for reference:
|
|
145
|
+
|
|
146
|
+
Grok Imagine Video (fal):
|
|
147
|
+
- text-to-video: $0.05/sec (480p), $0.07/sec (720p)
|
|
148
|
+
- image-to-video: $0.05/sec (480p) + $0.002/image, $0.07/sec (720p) + $0.002
|
|
149
|
+
- edit-video: $0.06/sec (480p), $0.08/sec (720p) (includes input+output)
|
|
150
|
+
|
|
151
|
+
Sync Lipsync (fal):
|
|
152
|
+
- $0.70 per minute of video processed
|
|
153
|
+
|
|
154
|
+
ElevenLabs Music (elevenlabs):
|
|
155
|
+
- $0.30 per minute of music generated
|
|
156
|
+
|
|
157
|
+
Replicate Flux models:
|
|
158
|
+
- flux-1.1-pro: $0.04/output image
|
|
159
|
+
- flux-dev: $0.025/output image
|
|
160
|
+
- flux-schnell: $0.003/output image ($3/1K images)
|
|
161
|
+
|
|
162
|
+
================================================================================
|
|
163
|
+
UNVERIFIED MODELS (no public pricing available)
|
|
164
|
+
================================================================================
|
|
165
|
+
|
|
166
|
+
seedance.ts - PiAPI credit-based pricing, not publicly listed per-second
|
|
167
|
+
heygen.ts - HeyGen API uses pay-as-you-go credits, no public $/sec rate
|
|
168
|
+
soul.ts - Higgsfield has no public pricing page
|
|
169
|
+
sonauto.ts - fal model page returned 404, may be deprecated/moved
|
|
170
|
+
llama.ts - Groq LLM pricing is negligible, current approx is acceptable
|
|
171
|
+
|
|
172
|
+
================================================================================
|
|
173
|
+
PRICING PARAMS INTERFACE
|
|
174
|
+
================================================================================
|
|
175
|
+
|
|
176
|
+
The PricingParams interface (src/core/schema/types.ts) already has all fields
|
|
177
|
+
used in the updated calculate functions:
|
|
178
|
+
- duration: number (video/audio seconds)
|
|
179
|
+
- resolution: string (e.g. "480p", "720p", "1080p", "1K", "4K")
|
|
180
|
+
- width: number (image/video width in pixels)
|
|
181
|
+
- height: number (image/video height in pixels)
|
|
182
|
+
- numFrames: number (video frame count)
|
|
183
|
+
- numImages: number (batch image count)
|
|
184
|
+
- characters: number (TTS text length)
|
|
185
|
+
|
|
186
|
+
No changes were needed to the interface.
|
|
187
|
+
|
|
188
|
+
================================================================================
|
|
189
|
+
TYPE CHECK
|
|
190
|
+
================================================================================
|
|
191
|
+
|
|
192
|
+
All 13 model definition files pass TypeScript type checking with zero errors.
|
|
193
|
+
Pre-existing errors in ai-sdk provider files (Uint8Array/BlobPart) are unrelated.
|
|
194
|
+
|
|
195
|
+
================================================================================
|
|
196
|
+
END OF PRICING FIX LOG
|
|
197
|
+
================================================================================
|
|
@@ -47,11 +47,6 @@ export interface FFmpegRunOptions {
|
|
|
47
47
|
verbose?: boolean;
|
|
48
48
|
/** Max execution time in seconds (used by cloud backends like Rendi, ignored by local) */
|
|
49
49
|
timeoutSeconds?: number;
|
|
50
|
-
/** Extra files (e.g. fonts, ASS subtitles) to include alongside inputs.
|
|
51
|
-
* When present, cloud backends like Rendi use compressed folder mode
|
|
52
|
-
* (input_compressed_folder) to bundle all files together.
|
|
53
|
-
* Each entry provides either a `url` to download or raw `data` bytes. */
|
|
54
|
-
auxiliaryFiles?: { url?: string; data?: Uint8Array; fileName: string }[];
|
|
55
50
|
}
|
|
56
51
|
|
|
57
52
|
export type FFmpegOutput =
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { zipSync } from "fflate";
|
|
2
1
|
import sharp from "sharp";
|
|
3
2
|
import { File } from "../../../file";
|
|
4
3
|
import type { StorageProvider } from "../../../storage/types";
|
|
@@ -129,11 +128,6 @@ export class RendiBackend implements FFmpegBackend {
|
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
async run(options: FFmpegRunOptions): Promise<FFmpegRunResult> {
|
|
132
|
-
// When auxiliary files (e.g. fonts) are present, use compressed folder mode
|
|
133
|
-
if (options.auxiliaryFiles && options.auxiliaryFiles.length > 0) {
|
|
134
|
-
return this.runWithCompressedFolder(options);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
131
|
let {
|
|
138
132
|
inputs,
|
|
139
133
|
filterComplex,
|
|
@@ -293,204 +287,6 @@ export class RendiBackend implements FFmpegBackend {
|
|
|
293
287
|
throw new Error("Rendi command timed out");
|
|
294
288
|
}
|
|
295
289
|
|
|
296
|
-
/**
|
|
297
|
-
* Run an FFmpeg command using Rendi's input_compressed_folder mode.
|
|
298
|
-
*
|
|
299
|
-
* Used when auxiliary files (e.g. fonts for subtitle rendering) need to be
|
|
300
|
-
* bundled alongside regular inputs. Creates a ZIP containing all input files
|
|
301
|
-
* and auxiliary files, uploads it to storage, and submits to Rendi with
|
|
302
|
-
* `input_compressed_folder` instead of `input_files`.
|
|
303
|
-
*
|
|
304
|
-
* Inside the ZIP, all files are at the root level. The ffmpeg command
|
|
305
|
-
* references files by their bare filenames (not placeholders).
|
|
306
|
-
*/
|
|
307
|
-
private async runWithCompressedFolder(
|
|
308
|
-
options: FFmpegRunOptions,
|
|
309
|
-
): Promise<FFmpegRunResult> {
|
|
310
|
-
const {
|
|
311
|
-
inputs,
|
|
312
|
-
videoFilter,
|
|
313
|
-
filterComplex,
|
|
314
|
-
outputArgs = [],
|
|
315
|
-
outputPath,
|
|
316
|
-
verbose,
|
|
317
|
-
auxiliaryFiles = [],
|
|
318
|
-
} = options;
|
|
319
|
-
|
|
320
|
-
// 1. Resolve all input files to URLs
|
|
321
|
-
const inputEntries: { fileName: string; url: string }[] = [];
|
|
322
|
-
for (const input of inputs ?? []) {
|
|
323
|
-
const path = this.getInputPath(input);
|
|
324
|
-
const url = await this.resolvePath(path);
|
|
325
|
-
// Extract filename from URL or path
|
|
326
|
-
const fileName =
|
|
327
|
-
url.split("/").pop()?.split("?")[0] ?? `input_${inputEntries.length}`;
|
|
328
|
-
inputEntries.push({ fileName, url });
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// 2. Download all files (inputs + auxiliary) into memory
|
|
332
|
-
const zipContents: Record<string, Uint8Array> = {};
|
|
333
|
-
|
|
334
|
-
const downloadTasks = [
|
|
335
|
-
...inputEntries.map(async (entry) => {
|
|
336
|
-
const res = await fetch(entry.url);
|
|
337
|
-
if (!res.ok)
|
|
338
|
-
throw new Error(
|
|
339
|
-
`Failed to download input ${entry.fileName}: ${res.status}`,
|
|
340
|
-
);
|
|
341
|
-
zipContents[entry.fileName] = new Uint8Array(await res.arrayBuffer());
|
|
342
|
-
}),
|
|
343
|
-
...auxiliaryFiles.map(async (file) => {
|
|
344
|
-
if (file.data) {
|
|
345
|
-
// Inline data — no download needed
|
|
346
|
-
zipContents[file.fileName] = file.data;
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (!file.url) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`Auxiliary file ${file.fileName} has neither url nor data`,
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
const res = await fetch(file.url);
|
|
355
|
-
if (!res.ok)
|
|
356
|
-
throw new Error(
|
|
357
|
-
`Failed to download auxiliary file ${file.fileName}: ${res.status}`,
|
|
358
|
-
);
|
|
359
|
-
zipContents[file.fileName] = new Uint8Array(await res.arrayBuffer());
|
|
360
|
-
}),
|
|
361
|
-
];
|
|
362
|
-
|
|
363
|
-
await Promise.all(downloadTasks);
|
|
364
|
-
|
|
365
|
-
if (verbose) {
|
|
366
|
-
const totalSize = Object.values(zipContents).reduce(
|
|
367
|
-
(sum, buf) => sum + buf.length,
|
|
368
|
-
0,
|
|
369
|
-
);
|
|
370
|
-
console.log(
|
|
371
|
-
`[rendi] creating ZIP with ${Object.keys(zipContents).length} files (${(totalSize / 1024 / 1024).toFixed(1)} MB)`,
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// 3. Create ZIP
|
|
376
|
-
const zipData = zipSync(zipContents, { level: 1 }); // fast compression
|
|
377
|
-
|
|
378
|
-
// 4. Upload ZIP to storage
|
|
379
|
-
const zipKey = `internal/rendi-compressed-${Date.now()}.zip`;
|
|
380
|
-
const zipUrl = await this.storage.upload(
|
|
381
|
-
zipData,
|
|
382
|
-
zipKey,
|
|
383
|
-
"application/zip",
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
if (verbose) {
|
|
387
|
-
console.log(
|
|
388
|
-
`[rendi] uploaded ZIP (${(zipData.length / 1024 / 1024).toFixed(1)} MB) -> ${zipUrl}`,
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// 5. Build ffmpeg command using bare filenames (not {{in_X}} placeholders)
|
|
393
|
-
const inputArgs: string[] = [];
|
|
394
|
-
for (const [i, input] of (inputs ?? []).entries()) {
|
|
395
|
-
if (typeof input !== "string" && "options" in input && input.options) {
|
|
396
|
-
inputArgs.push(...input.options);
|
|
397
|
-
}
|
|
398
|
-
inputArgs.push("-i", inputEntries[i]!.fileName);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const filterArgs: string[] = [];
|
|
402
|
-
if (filterComplex) {
|
|
403
|
-
filterArgs.push("-filter_complex", filterComplex);
|
|
404
|
-
}
|
|
405
|
-
if (videoFilter) {
|
|
406
|
-
// For compressed folder mode, the video filter references files by
|
|
407
|
-
// their bare filenames (already resolved in the working directory)
|
|
408
|
-
filterArgs.push("-vf", videoFilter);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const processedOutputArgs = outputArgs.filter((arg) => arg !== "-y");
|
|
412
|
-
|
|
413
|
-
const commandParts = [
|
|
414
|
-
...inputArgs,
|
|
415
|
-
...filterArgs,
|
|
416
|
-
...processedOutputArgs,
|
|
417
|
-
"{{out_1}}",
|
|
418
|
-
];
|
|
419
|
-
const ffmpegCommand = this.buildCommandString(commandParts);
|
|
420
|
-
const outputFilename = outputPath?.split("/").pop() ?? "output.mp4";
|
|
421
|
-
|
|
422
|
-
if (verbose) {
|
|
423
|
-
console.log("[rendi] input_compressed_folder:", zipUrl);
|
|
424
|
-
console.log("[rendi] ffmpeg_command:", ffmpegCommand);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// 6. Submit to Rendi with input_compressed_folder
|
|
428
|
-
const submitResponse = await fetch(`${RENDI_API_BASE}/run-ffmpeg-command`, {
|
|
429
|
-
method: "POST",
|
|
430
|
-
headers: {
|
|
431
|
-
"X-API-KEY": this.apiKey,
|
|
432
|
-
"Content-Type": "application/json",
|
|
433
|
-
},
|
|
434
|
-
body: JSON.stringify({
|
|
435
|
-
input_compressed_folder: zipUrl,
|
|
436
|
-
output_files: { out_1: outputFilename },
|
|
437
|
-
ffmpeg_command: ffmpegCommand,
|
|
438
|
-
max_command_run_seconds:
|
|
439
|
-
options.timeoutSeconds ?? this.maxCommandRunSeconds,
|
|
440
|
-
}),
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
if (!submitResponse.ok) {
|
|
444
|
-
const errorText = await submitResponse.text();
|
|
445
|
-
throw new Error(
|
|
446
|
-
`Rendi submit failed: ${submitResponse.status} - ${errorText}`,
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const { command_id } =
|
|
451
|
-
(await submitResponse.json()) as RendiCommandResponse;
|
|
452
|
-
|
|
453
|
-
if (verbose) {
|
|
454
|
-
console.log("[rendi] command_id:", command_id);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// 7. Poll for completion (same as standard run)
|
|
458
|
-
let attempts = 0;
|
|
459
|
-
while (attempts < MAX_POLL_ATTEMPTS) {
|
|
460
|
-
const statusResponse = await fetch(
|
|
461
|
-
`${RENDI_API_BASE}/commands/${command_id}`,
|
|
462
|
-
{
|
|
463
|
-
headers: { "X-API-KEY": this.apiKey },
|
|
464
|
-
},
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
if (!statusResponse.ok) {
|
|
468
|
-
throw new Error(`Rendi poll failed: ${statusResponse.status}`);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const status = (await statusResponse.json()) as RendiStatusResponse;
|
|
472
|
-
|
|
473
|
-
if (status.status === "SUCCESS") {
|
|
474
|
-
const outputFile = status.output_files?.out_1;
|
|
475
|
-
if (!outputFile?.storage_url) {
|
|
476
|
-
throw new Error("Rendi completed but no output URL");
|
|
477
|
-
}
|
|
478
|
-
return { output: { type: "url", url: outputFile.storage_url } };
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (status.status === "FAILED") {
|
|
482
|
-
throw new Error(
|
|
483
|
-
`Rendi command failed: ${status.error_message ?? "unknown error"}`,
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
await this.sleep(POLL_INTERVAL_MS);
|
|
488
|
-
attempts++;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
throw new Error("Rendi command timed out");
|
|
492
|
-
}
|
|
493
|
-
|
|
494
290
|
async resolvePath(input: FilePath): Promise<string> {
|
|
495
291
|
if (input instanceof File) {
|
|
496
292
|
return input.upload(this.storage);
|
package/src/react/elements.ts
CHANGED
|
@@ -157,7 +157,7 @@ export function Title(props: TitleProps): VargElement<"title"> {
|
|
|
157
157
|
return createElement(
|
|
158
158
|
"title",
|
|
159
159
|
props as Record<string, unknown>,
|
|
160
|
-
props.children,
|
|
160
|
+
props.children ?? props.text,
|
|
161
161
|
);
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -165,7 +165,7 @@ export function Subtitle(props: SubtitleProps): VargElement<"subtitle"> {
|
|
|
165
165
|
return createElement(
|
|
166
166
|
"subtitle",
|
|
167
167
|
props as Record<string, unknown>,
|
|
168
|
-
props.children,
|
|
168
|
+
props.children ?? props.text,
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
171
|
|