waha-shared 1.0.335 → 1.0.336

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 (32) hide show
  1. package/dist/data/bibleAudios/bibleAudios.json +0 -10
  2. package/dist/data/bibleStatuses/bibleStatuses.json +2013 -32
  3. package/dist/data/bibleTexts/bibleTexts.json +0 -10
  4. package/dist/data/languages/index.d.ts +1 -5
  5. package/dist/data/languages/languages.json +48 -139
  6. package/dist/data/languages/languages.schema.json +3 -24
  7. package/dist/data/languages/languages.zod.d.ts +1 -10
  8. package/dist/data/languages/languages.zod.js +5 -18
  9. package/dist/data/mediaDurations/mediaDurations.json +675 -24823
  10. package/dist/data/releaseNotes/releaseNotes.json +9 -2
  11. package/dist/data/translationsApp/index.d.ts +0 -1
  12. package/dist/data/translationsApp/translationsApp.json +0 -41
  13. package/dist/data/translationsApp/translationsApp.zod.d.ts +0 -2
  14. package/dist/data/translationsApp/translationsApp.zod.js +0 -1
  15. package/dist/data/youtubeVideos/youtubeVideos.json +115 -55
  16. package/dist/functions/scripturePassages.js +11 -9
  17. package/dist/functions/sets.d.ts +4 -15
  18. package/dist/functions/sets.js +228 -168
  19. package/dist/functions/utils.d.ts +1 -6
  20. package/dist/functions/utils.js +4 -18
  21. package/dist/types/sets.d.ts +19 -22
  22. package/package.json +2 -3
  23. package/dist/data/languageAssets/index.d.ts +0 -1
  24. package/dist/data/languageAssets/index.js +0 -7
  25. package/dist/data/languageAssets/languageAssets.json +0 -44404
  26. package/dist/data/languageAssets/languageAssets.schema.json +0 -19
  27. package/dist/data/languageAssets/languageAssets.zod.d.ts +0 -3
  28. package/dist/data/languageAssets/languageAssets.zod.js +0 -7
  29. package/dist/functions/ffmpeg.d.ts +0 -104
  30. package/dist/functions/ffmpeg.js +0 -307
  31. package/dist/functions/upload.d.ts +0 -34
  32. package/dist/functions/upload.js +0 -49
@@ -1,19 +0,0 @@
1
- {
2
- "description": "This file is auto-generated by scripts/prep. Do not edit manually.",
3
- "$schema": "http://json-schema.org/draft-07/schema#",
4
- "type": "object",
5
- "properties": {
6
- "$schema": { "type": "string" },
7
- "data": {
8
- "$schema": "https://json-schema.org/draft/2020-12/schema",
9
- "type": "object",
10
- "propertyNames": { "type": "string", "pattern": "^[a-z]{3}$" },
11
- "additionalProperties": {
12
- "type": "array",
13
- "items": { "type": "string" },
14
- "description": "Language code (3-letter ISO code) containing assets"
15
- }
16
- }
17
- },
18
- "required": ["$schema", "data"]
19
- }
@@ -1,3 +0,0 @@
1
- import { z } from 'zod';
2
- export declare const LanguageAssets: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
3
- export type LanguageAssets = z.infer<typeof LanguageAssets>;
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LanguageAssets = void 0;
4
- const zod_1 = require("zod");
5
- exports.LanguageAssets = zod_1.z.record(zod_1.z.string().regex(new RegExp('^[a-z]{3}$')), zod_1.z
6
- .array(zod_1.z.string())
7
- .describe('Language code (3-letter ISO code) containing assets'));
@@ -1,104 +0,0 @@
1
- /** Concatenate audio files using ffmpeg's concat demuxer. */
2
- export declare function concatAudio(files: string[], output: string): Promise<void>;
3
- /** Generate a silence MP3 of the given duration in seconds. */
4
- export declare function generateSilence(duration: number, output: string): Promise<void>;
5
- /** Extract an audio segment from a file by start/end times (seconds). */
6
- export declare function extractAudioSegment({ endTime, input, output, startTime, }: {
7
- input: string;
8
- startTime: number;
9
- endTime: number;
10
- output: string;
11
- }): Promise<void>;
12
- /** Extract the audio track from a video file and re-encode to MP3. */
13
- export declare function extractVideoAudio(input: string, output: string): Promise<void>;
14
- /**
15
- * Normalize audio to EBU R128 standard using ffmpeg-normalize. Returns the path
16
- * to the normalized file. Skips if already exists.
17
- */
18
- export declare function normalizeAudio(input: string): Promise<string>;
19
- /**
20
- * Source-of-truth for our standard H.264 / AAC video encode profile. Use these
21
- * named values when calling APIs that take typed options (e.g. Remotion's
22
- * `renderMedia`, which surfaces some flags as `codec` / `x264Preset` / `crf` /
23
- * `pixelFormat` / `audioCodec` / `audioBitrate`). For direct ffmpeg shell-out,
24
- * use the pre-flattened arg arrays below.
25
- *
26
- * The settings are tuned for concat-safe output:
27
- *
28
- * - `gop` of 30 puts a keyframe every 1s at 30fps, required for seekability and
29
- * `-c:v copy` concat
30
- * - `bFrames: 0` prevents non-monotonic DTS at concat seams
31
- * - `videoTrackTimescale` of 90000 matches Remotion's default timebase
32
- */
33
- export declare const VIDEO_ENCODE_SETTINGS: {
34
- readonly videoCodec: "libx264";
35
- /**
36
- * Value for Remotion's `codec` option (which uses short names, not ffmpeg
37
- * codec ids).
38
- */
39
- readonly remotionCodec: "h264";
40
- readonly crf: 23;
41
- readonly x264Preset: "medium";
42
- readonly gop: 30;
43
- readonly bFrames: 0;
44
- readonly pixelFormat: "yuv420p";
45
- readonly videoTrackTimescale: 90000;
46
- readonly audioCodec: "aac";
47
- readonly audioSampleRate: 48000;
48
- readonly audioBitrate: "128k";
49
- };
50
- /**
51
- * Ffmpeg args for H.264 video encoding (concat-safe). Includes `-movflags
52
- * +faststart` so the muxed MP4 supports progressive HTTP playback.
53
- */
54
- export declare const VIDEO_ENCODE_ARGS: string[];
55
- /** Standard AAC audio encode args to pair with {@link VIDEO_ENCODE_ARGS}. */
56
- export declare const VIDEO_AUDIO_ENCODE_ARGS: string[];
57
- /**
58
- * Args for use inside Remotion's `ffmpegOverride` — just the flags that
59
- * `renderMedia`'s typed options don't surface natively. Currently only `-bf 0`
60
- * (no B-frames), which prevents non-monotonic DTS at concat seams between
61
- * Remotion-rendered clips and the B-frame-free training videos compressed by
62
- * {@link compressVideo}. Pair with the typed options fed from
63
- * {@link VIDEO_ENCODE_SETTINGS} for a render that matches our standard profile.
64
- *
65
- * Example:
66
- *
67
- * renderMedia({
68
- * codec: VIDEO_ENCODE_SETTINGS.remotionCodec,
69
- * x264Preset: VIDEO_ENCODE_SETTINGS.x264Preset,
70
- * crf: VIDEO_ENCODE_SETTINGS.crf,
71
- * pixelFormat: VIDEO_ENCODE_SETTINGS.pixelFormat,
72
- * audioCodec: VIDEO_ENCODE_SETTINGS.audioCodec,
73
- * audioBitrate: VIDEO_ENCODE_SETTINGS.audioBitrate,
74
- * ffmpegOverride: ({ args }) => {
75
- * const out = args[args.length - 1]
76
- * const yflag = args[args.length - 2]
77
- * return [...args.slice(0, -2), ...VIDEO_REMOTION_OVERRIDE_ARGS, yflag, out]
78
- * },
79
- * ...
80
- * })
81
- */
82
- export declare const VIDEO_REMOTION_OVERRIDE_ARGS: string[];
83
- /**
84
- * Compress a video using the standard H.264 settings. If `audio` is provided,
85
- * its track replaces the source video's audio.
86
- */
87
- export declare function compressVideo({ input, output, audio, }: {
88
- input: string;
89
- output: string;
90
- audio?: string;
91
- }): Promise<void>;
92
- /**
93
- * Mix narration audio with background music at a fixed volume ratio. Music is
94
- * trimmed to the shorter of the two inputs; if `targetDuration` is provided,
95
- * the output is padded with silence to match exactly.
96
- */
97
- export declare function mixAudioWithMusic({ music, narration, output, targetDuration, }: {
98
- narration: string;
99
- music: string;
100
- output: string;
101
- targetDuration: number;
102
- }): Promise<void>;
103
- /** Get the duration of an audio file in seconds using ffprobe. */
104
- export declare function getDurationFromFile(file: string): Promise<number>;
@@ -1,307 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VIDEO_REMOTION_OVERRIDE_ARGS = exports.VIDEO_AUDIO_ENCODE_ARGS = exports.VIDEO_ENCODE_ARGS = exports.VIDEO_ENCODE_SETTINGS = void 0;
4
- exports.concatAudio = concatAudio;
5
- exports.generateSilence = generateSilence;
6
- exports.extractAudioSegment = extractAudioSegment;
7
- exports.extractVideoAudio = extractVideoAudio;
8
- exports.normalizeAudio = normalizeAudio;
9
- exports.compressVideo = compressVideo;
10
- exports.mixAudioWithMusic = mixAudioWithMusic;
11
- exports.getDurationFromFile = getDurationFromFile;
12
- const child_process_1 = require("child_process");
13
- const fs_1 = require("fs");
14
- const promises_1 = require("fs/promises");
15
- const os_1 = require("os");
16
- const path_1 = require("path");
17
- function exec(cmd, args) {
18
- return new Promise((resolve, reject) => {
19
- (0, child_process_1.execFile)(cmd, args, { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
20
- if (err)
21
- reject(new Error(`${cmd} failed: ${stderr}`));
22
- else
23
- resolve({ stdout, stderr });
24
- });
25
- });
26
- }
27
- /** MP3 encoder used across all ffmpeg commands. */
28
- const CODEC = ['-c:a', 'libmp3lame'];
29
- /**
30
- * Target audio bitrate — 64 kbps is a good balance of quality and file size for
31
- * speech.
32
- */
33
- const BITRATE = ['-b:a', '64k'];
34
- /** Sample rate — 22050 Hz is sufficient for speech content. */
35
- const SAMPLE_RATE = ['-ar', '22050'];
36
- /** Audio channels: stereo (2 channels). */
37
- const CHANNELS = ['-ac', '2'];
38
- /** Concatenate audio files using ffmpeg's concat demuxer. */
39
- async function concatAudio(files, output) {
40
- const dir = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), 'ffconcat-'));
41
- const listFile = (0, path_1.join)(dir, 'list.txt');
42
- await (0, promises_1.writeFile)(listFile, files.map((f) => `file '${f}'`).join('\n'));
43
- await exec('ffmpeg', [
44
- // Use the concat demuxer to join multiple input files
45
- '-f',
46
- 'concat',
47
- // Allow absolute paths and paths outside the working directory in the list file
48
- '-safe',
49
- '0',
50
- // Input: the generated list file containing paths to all audio segments
51
- '-i',
52
- listFile,
53
- // FFmpeg 8.0 introduced stricter AVFrame buffer alignment requirements for
54
- // libmp3lame. The concat demuxer produces frames that don't meet this new
55
- // padding requirement, causing "inadequate AVFrame plane padding" errors.
56
- // aresample forces frames to be reallocated with proper alignment as a workaround.
57
- '-af',
58
- 'aresample=resampler=swr',
59
- ...CODEC,
60
- ...BITRATE,
61
- ...SAMPLE_RATE,
62
- ...CHANNELS,
63
- '-y',
64
- output,
65
- ]);
66
- }
67
- /** Generate a silence MP3 of the given duration in seconds. */
68
- async function generateSilence(duration, output) {
69
- await exec('ffmpeg', [
70
- // Use the Libavfilter virtual device as the input format
71
- '-f',
72
- 'lavfi',
73
- // Input: null audio source generating silence at 22050 Hz in stereo
74
- '-i',
75
- 'anullsrc=r=22050:cl=stereo',
76
- // Duration: stop after this many seconds
77
- '-t',
78
- String(duration),
79
- ...CODEC,
80
- ...BITRATE,
81
- ...CHANNELS,
82
- '-y',
83
- output,
84
- ]);
85
- }
86
- /** Extract an audio segment from a file by start/end times (seconds). */
87
- async function extractAudioSegment({ endTime, input, output, startTime, }) {
88
- await exec('ffmpeg', [
89
- // Seek to this start time (seconds) before reading input — placed before -i for fast seeking
90
- '-ss',
91
- String(startTime),
92
- // Duration: read this many seconds of audio (endTime - startTime)
93
- '-t',
94
- String(endTime - startTime),
95
- // Input audio file to extract from
96
- '-i',
97
- input,
98
- // FFmpeg 8.0 workaround: force frame reallocation to satisfy libmp3lame's
99
- // stricter AVFrame buffer alignment requirements (see concatAudio).
100
- '-af',
101
- 'aresample=resampler=swr',
102
- ...CODEC,
103
- ...BITRATE,
104
- ...SAMPLE_RATE,
105
- ...CHANNELS,
106
- '-y',
107
- output,
108
- ]);
109
- }
110
- /** Extract the audio track from a video file and re-encode to MP3. */
111
- async function extractVideoAudio(input, output) {
112
- await exec('ffmpeg', [
113
- '-i',
114
- input,
115
- // Drop the video stream — we only want the audio track.
116
- '-vn',
117
- '-af',
118
- 'aresample=resampler=swr',
119
- ...CODEC,
120
- ...BITRATE,
121
- ...SAMPLE_RATE,
122
- ...CHANNELS,
123
- '-y',
124
- output,
125
- ]);
126
- }
127
- /**
128
- * Normalize audio to EBU R128 standard using ffmpeg-normalize. Returns the path
129
- * to the normalized file. Skips if already exists.
130
- */
131
- async function normalizeAudio(input) {
132
- const output = input.replace(/\.mp3$/, '.normalized.mp3');
133
- if ((0, fs_1.existsSync)(output))
134
- return output;
135
- await exec('ffmpeg-normalize', [
136
- // Input file to normalize
137
- input,
138
- // Output file path
139
- '-o',
140
- output,
141
- // Normalization type: EBU R128 loudness standard
142
- '-nt',
143
- 'ebu',
144
- // Target integrated loudness level: -15 LUFS
145
- '-t',
146
- '-15',
147
- // True peak ceiling: -1.0 dBTP (prevents clipping after encoding)
148
- '-tp',
149
- '-1.0',
150
- // Loudness range target: 7.0 LU (controls dynamic range)
151
- '-lrt',
152
- '7.0',
153
- ...CODEC,
154
- ...SAMPLE_RATE,
155
- ]);
156
- return output;
157
- }
158
- /**
159
- * Source-of-truth for our standard H.264 / AAC video encode profile. Use these
160
- * named values when calling APIs that take typed options (e.g. Remotion's
161
- * `renderMedia`, which surfaces some flags as `codec` / `x264Preset` / `crf` /
162
- * `pixelFormat` / `audioCodec` / `audioBitrate`). For direct ffmpeg shell-out,
163
- * use the pre-flattened arg arrays below.
164
- *
165
- * The settings are tuned for concat-safe output:
166
- *
167
- * - `gop` of 30 puts a keyframe every 1s at 30fps, required for seekability and
168
- * `-c:v copy` concat
169
- * - `bFrames: 0` prevents non-monotonic DTS at concat seams
170
- * - `videoTrackTimescale` of 90000 matches Remotion's default timebase
171
- */
172
- exports.VIDEO_ENCODE_SETTINGS = {
173
- videoCodec: 'libx264',
174
- /**
175
- * Value for Remotion's `codec` option (which uses short names, not ffmpeg
176
- * codec ids).
177
- */
178
- remotionCodec: 'h264',
179
- crf: 23,
180
- x264Preset: 'medium',
181
- gop: 30,
182
- bFrames: 0,
183
- pixelFormat: 'yuv420p',
184
- videoTrackTimescale: 90000,
185
- audioCodec: 'aac',
186
- audioSampleRate: 48000,
187
- audioBitrate: '128k',
188
- };
189
- /**
190
- * Ffmpeg args for H.264 video encoding (concat-safe). Includes `-movflags
191
- * +faststart` so the muxed MP4 supports progressive HTTP playback.
192
- */
193
- exports.VIDEO_ENCODE_ARGS = [
194
- '-c:v',
195
- exports.VIDEO_ENCODE_SETTINGS.videoCodec,
196
- '-crf',
197
- String(exports.VIDEO_ENCODE_SETTINGS.crf),
198
- '-preset',
199
- exports.VIDEO_ENCODE_SETTINGS.x264Preset,
200
- '-g',
201
- String(exports.VIDEO_ENCODE_SETTINGS.gop),
202
- '-bf',
203
- String(exports.VIDEO_ENCODE_SETTINGS.bFrames),
204
- '-pix_fmt',
205
- exports.VIDEO_ENCODE_SETTINGS.pixelFormat,
206
- '-video_track_timescale',
207
- String(exports.VIDEO_ENCODE_SETTINGS.videoTrackTimescale),
208
- '-movflags',
209
- '+faststart',
210
- ];
211
- /** Standard AAC audio encode args to pair with {@link VIDEO_ENCODE_ARGS}. */
212
- exports.VIDEO_AUDIO_ENCODE_ARGS = [
213
- '-c:a',
214
- exports.VIDEO_ENCODE_SETTINGS.audioCodec,
215
- '-ar',
216
- String(exports.VIDEO_ENCODE_SETTINGS.audioSampleRate),
217
- '-b:a',
218
- exports.VIDEO_ENCODE_SETTINGS.audioBitrate,
219
- ];
220
- /**
221
- * Args for use inside Remotion's `ffmpegOverride` — just the flags that
222
- * `renderMedia`'s typed options don't surface natively. Currently only `-bf 0`
223
- * (no B-frames), which prevents non-monotonic DTS at concat seams between
224
- * Remotion-rendered clips and the B-frame-free training videos compressed by
225
- * {@link compressVideo}. Pair with the typed options fed from
226
- * {@link VIDEO_ENCODE_SETTINGS} for a render that matches our standard profile.
227
- *
228
- * Example:
229
- *
230
- * renderMedia({
231
- * codec: VIDEO_ENCODE_SETTINGS.remotionCodec,
232
- * x264Preset: VIDEO_ENCODE_SETTINGS.x264Preset,
233
- * crf: VIDEO_ENCODE_SETTINGS.crf,
234
- * pixelFormat: VIDEO_ENCODE_SETTINGS.pixelFormat,
235
- * audioCodec: VIDEO_ENCODE_SETTINGS.audioCodec,
236
- * audioBitrate: VIDEO_ENCODE_SETTINGS.audioBitrate,
237
- * ffmpegOverride: ({ args }) => {
238
- * const out = args[args.length - 1]
239
- * const yflag = args[args.length - 2]
240
- * return [...args.slice(0, -2), ...VIDEO_REMOTION_OVERRIDE_ARGS, yflag, out]
241
- * },
242
- * ...
243
- * })
244
- */
245
- exports.VIDEO_REMOTION_OVERRIDE_ARGS = [
246
- '-bf',
247
- String(exports.VIDEO_ENCODE_SETTINGS.bFrames),
248
- ];
249
- /**
250
- * Compress a video using the standard H.264 settings. If `audio` is provided,
251
- * its track replaces the source video's audio.
252
- */
253
- async function compressVideo({ input, output, audio, }) {
254
- const args = ['-i', input];
255
- if (audio)
256
- args.push('-i', audio, '-map', '0:v:0', '-map', '1:a:0');
257
- args.push(...exports.VIDEO_ENCODE_ARGS, ...exports.VIDEO_AUDIO_ENCODE_ARGS, '-y', output);
258
- await exec('ffmpeg', args);
259
- }
260
- /**
261
- * Mix narration audio with background music at a fixed volume ratio. Music is
262
- * trimmed to the shorter of the two inputs; if `targetDuration` is provided,
263
- * the output is padded with silence to match exactly.
264
- */
265
- async function mixAudioWithMusic({ music, narration, output, targetDuration, }) {
266
- const narrationDuration = await getDurationFromFile(narration);
267
- const musicDuration = await getDurationFromFile(music);
268
- const minDuration = Math.min(narrationDuration, musicDuration);
269
- const filterComplex = [
270
- `[0:a]volume=1.0[a1]`,
271
- `[1:a]atrim=0:${minDuration},volume=0.1[a2]`,
272
- // duration=first keeps narration length; normalize=0 preserves its volume.
273
- // apad pads the mix with silence to the exact target duration.
274
- `[a1][a2]amix=inputs=2:duration=first:dropout_transition=0:normalize=0,apad=whole_dur=${targetDuration}`,
275
- ].join(';');
276
- await exec('ffmpeg', [
277
- '-i',
278
- narration,
279
- '-i',
280
- music,
281
- '-filter_complex',
282
- filterComplex,
283
- ...CODEC,
284
- ...BITRATE,
285
- ...SAMPLE_RATE,
286
- ...CHANNELS,
287
- '-y',
288
- output,
289
- ]);
290
- }
291
- /** Get the duration of an audio file in seconds using ffprobe. */
292
- async function getDurationFromFile(file) {
293
- const { stdout } = await exec('ffprobe', [
294
- // Verbosity: only show errors (suppress informational output)
295
- '-v',
296
- 'error',
297
- // Only extract the duration field from the format section
298
- '-show_entries',
299
- 'format=duration',
300
- // Output format: plain value with no section wrappers or key name
301
- '-of',
302
- 'default=noprint_wrappers=1:nokey=1',
303
- // Input file to probe
304
- file,
305
- ]);
306
- return parseFloat(stdout.trim());
307
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * Minimal structural shape of `@google-cloud/storage`'s `Bucket`. Defined
3
- * structurally so this module doesn't pull `@google-cloud/storage` into the
4
- * shared package's dependency graph — callers pass the real bucket.
5
- */
6
- export interface UploadBucket {
7
- file(path: string): {
8
- exists(): Promise<[boolean]>;
9
- setMetadata(meta: {
10
- metadata: Record<string, string>;
11
- }): Promise<unknown>;
12
- };
13
- upload(localPath: string, options: {
14
- destination: string;
15
- contentType: string;
16
- public?: boolean;
17
- }): Promise<unknown>;
18
- }
19
- export type UploadResult = 'uploaded' | 'skipped';
20
- /**
21
- * Upload a local file to a Cloud Storage bucket. The content type is inferred
22
- * from the file extension via {@link CONTENT_TYPE_BY_EXTENSION}. Returns
23
- * `'skipped'` if the remote file already exists and `overwrite` is falsy,
24
- * `'uploaded'` otherwise. Throws if the underlying bucket operation fails.
25
- *
26
- * Sets a `duration` metadata field (in seconds) from ffprobe when probing
27
- * succeeds; metadata failures are swallowed since they're non-fatal.
28
- */
29
- export declare function uploadFile({ bucket, localPath, overwrite, remotePath, }: {
30
- bucket: UploadBucket;
31
- localPath: string;
32
- remotePath: string;
33
- overwrite?: boolean;
34
- }): Promise<UploadResult>;
@@ -1,49 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadFile = uploadFile;
4
- const path_1 = require("path");
5
- const ffmpeg_1 = require("./ffmpeg");
6
- const CONTENT_TYPE_BY_EXTENSION = {
7
- '.mp3': 'audio/mpeg',
8
- '.mp4': 'video/mp4',
9
- };
10
- function contentTypeFor(path) {
11
- const ext = (0, path_1.extname)(path).toLowerCase();
12
- const contentType = CONTENT_TYPE_BY_EXTENSION[ext];
13
- if (!contentType)
14
- throw new Error(`uploadFile: no content type mapped for extension '${ext}' (path: ${path}). ` +
15
- `Add it to CONTENT_TYPE_BY_EXTENSION in shared/functions/upload.ts.`);
16
- return contentType;
17
- }
18
- /**
19
- * Upload a local file to a Cloud Storage bucket. The content type is inferred
20
- * from the file extension via {@link CONTENT_TYPE_BY_EXTENSION}. Returns
21
- * `'skipped'` if the remote file already exists and `overwrite` is falsy,
22
- * `'uploaded'` otherwise. Throws if the underlying bucket operation fails.
23
- *
24
- * Sets a `duration` metadata field (in seconds) from ffprobe when probing
25
- * succeeds; metadata failures are swallowed since they're non-fatal.
26
- */
27
- async function uploadFile({ bucket, localPath, overwrite, remotePath, }) {
28
- const contentType = contentTypeFor(localPath);
29
- const [exists] = await bucket.file(remotePath).exists();
30
- if (exists && !overwrite)
31
- return 'skipped';
32
- await bucket.upload(localPath, {
33
- destination: remotePath,
34
- contentType,
35
- public: true,
36
- });
37
- try {
38
- const duration = await (0, ffmpeg_1.getDurationFromFile)(localPath);
39
- if (!isNaN(duration) && duration > 0) {
40
- await bucket.file(remotePath).setMetadata({
41
- metadata: { duration: duration.toString() },
42
- });
43
- }
44
- }
45
- catch {
46
- // Non-fatal — proceed even if setting duration metadata fails.
47
- }
48
- return 'uploaded';
49
- }