weblet-convert 0.0.4 → 0.0.6

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/README.md CHANGED
@@ -96,12 +96,50 @@ Notes:
96
96
 
97
97
  Transcodes videos to WebM.
98
98
 
99
+ `video` options now support ffmpeg.wasm asset configuration in browser runtimes:
100
+
101
+ - `ffmpeg.baseURL` (auto-derives core/wasm/worker URLs)
102
+ - or explicit `ffmpeg.coreURL`, `ffmpeg.wasmURL`, `ffmpeg.workerURL`
103
+ - `enableLowMemoryFallback` (default `true`) retries with reduced memory profile
104
+ - `fallbackMaxWidth` (default `960`) output width cap used by fallback
105
+
106
+ ### `videoToWebmDebug(input, options?, onEvent?)` (Ultimate debug mode)
107
+
108
+ Browser-only debug helper for diagnosing ffmpeg.wasm failures.
109
+
110
+ - Returns `{ ok: true, ...result, events, diagnostic }` on success
111
+ - Returns `{ ok: false, error, events, diagnostic }` on failure
112
+ - Emits stage events like `init`, `transcode`, `retry-reset`, and `done`
113
+ - `diagnostic` includes:
114
+ - `cause` (classified root-cause code, e.g. `ffmpeg-init-failed`, `input-too-large`)
115
+ - `stage` (where it failed)
116
+ - `rawError`
117
+ - `recoverable`
118
+ - `hints` (actionable fixes)
119
+
120
+ Example:
121
+
122
+ ```ts
123
+ import { videoToWebmDebug } from "@weblet/convert"
124
+
125
+ const debug = await videoToWebmDebug(file, { maxInputBytes: 256 * 1024 * 1024 }, (e) => {
126
+ console.log(`[${e.stage}] ${e.message}`, e.detail ?? "")
127
+ })
128
+
129
+ if (!debug.ok) {
130
+ console.error("WebM conversion failed:", debug.error.message)
131
+ console.error("Cause:", debug.diagnostic.cause)
132
+ console.error("Hints:", debug.diagnostic.hints.join(" | "))
133
+ }
134
+ ```
135
+
99
136
  ## Notes and limitations
100
137
 
101
138
  - **Browser WebP support varies**: the browser build relies on Canvas/OffscreenCanvas WebP encoding support. When encoding isn’t available, the function can return the original input and set `isWebp: false`.
102
139
  - **Metadata**: EXIF/IPTC metadata is not preserved.
103
140
  - **Not a perfect “max bytes” guarantee**: `targetBytes` is a best-effort search within the provided quality range.
104
- - **Browser video memory guardrail**: `videoToWebm()` now enforces a default `maxInputBytes` limit (64 MiB) before ffmpeg.wasm processing to avoid hard crashes on constrained devices. You can override this via `video: { maxInputBytes: ... }`.
141
+ - **Browser video input limit**: by default there is **no input-size limit** (`maxInputBytes: 0`).
142
+ If you want a safeguard in your app, set `video: { maxInputBytes: ... }` explicitly.
105
143
 
106
144
  ## Troubleshooting video conversion
107
145
 
@@ -115,7 +153,7 @@ Transcodes videos to WebM.
115
153
 
116
154
  ## Import paths
117
155
 
118
- - **Browser**: `import { convert, imageToWebp, videoToWebm } from "@weblet/convert"`
156
+ - **Browser**: `import { convert, imageToWebp, videoToWebm, videoToWebmDebug } from "@weblet/convert"`
119
157
  - **Node**: `import { convert, imageToWebp, videoToWebm } from "weblet-convert/node"`
120
158
 
121
159
  ## Build (contributors)
@@ -46,17 +46,68 @@ function defaultVideoName(input, fileName) {
46
46
  var ffmpegSingleton = null;
47
47
  var ffmpegLoadPromise = null;
48
48
  var ffmpegJobQueue = Promise.resolve();
49
- async function getFfmpeg() {
49
+ var ffmpegConfigKey = "{}";
50
+ function normalizeBrowserFfmpegLoadOptions(input) {
51
+ if (!input) return void 0;
52
+ const baseURL = input.baseURL?.trim();
53
+ const normalize = (v) => v?.trim() || void 0;
54
+ const make = (suffix) => {
55
+ if (!baseURL) return void 0;
56
+ return `${baseURL.replace(/\/+$/, "")}/${suffix}`;
57
+ };
58
+ return {
59
+ baseURL,
60
+ coreURL: normalize(input.coreURL) ?? make("ffmpeg-core.js"),
61
+ wasmURL: normalize(input.wasmURL) ?? make("ffmpeg-core.wasm"),
62
+ workerURL: normalize(input.workerURL) ?? make("ffmpeg-core.worker.js")
63
+ };
64
+ }
65
+ function getFfmpegConfigKey(input) {
66
+ return JSON.stringify(normalizeBrowserFfmpegLoadOptions(input) ?? {});
67
+ }
68
+ async function getFfmpeg(loadOptions) {
69
+ const nextKey = getFfmpegConfigKey(loadOptions);
70
+ if (nextKey !== ffmpegConfigKey) {
71
+ await resetFfmpegRuntime();
72
+ ffmpegConfigKey = nextKey;
73
+ }
50
74
  if (ffmpegSingleton) return ffmpegSingleton;
51
75
  if (ffmpegLoadPromise) return ffmpegLoadPromise;
52
76
  const ff = new ffmpeg.FFmpeg();
77
+ const normalized = normalizeBrowserFfmpegLoadOptions(loadOptions);
78
+ const loadParams = normalized && (normalized.coreURL || normalized.wasmURL || normalized.workerURL) ? {
79
+ ...normalized.coreURL ? { coreURL: normalized.coreURL } : {},
80
+ ...normalized.wasmURL ? { wasmURL: normalized.wasmURL } : {},
81
+ ...normalized.workerURL ? { workerURL: normalized.workerURL } : {}
82
+ } : void 0;
53
83
  ffmpegLoadPromise = (async () => {
54
- await ff.load();
55
- ffmpegSingleton = ff;
56
- return ff;
84
+ try {
85
+ await ff.load(loadParams);
86
+ ffmpegSingleton = ff;
87
+ return ff;
88
+ } catch (err) {
89
+ ffmpegLoadPromise = null;
90
+ const detail = [
91
+ "Failed to initialize ffmpeg.wasm.",
92
+ `error=${toErrorMessage(err)}`,
93
+ "hint=Ensure ffmpeg core/worker assets are reachable by your bundler/runtime and not blocked by CSP/CORS."
94
+ ].join("\n");
95
+ throw new Error(detail);
96
+ }
57
97
  })();
58
98
  return ffmpegLoadPromise;
59
99
  }
100
+ async function resetFfmpegRuntime() {
101
+ const current = ffmpegSingleton;
102
+ ffmpegSingleton = null;
103
+ ffmpegLoadPromise = null;
104
+ if (current && typeof current.terminate === "function") {
105
+ try {
106
+ await current.terminate();
107
+ } catch {
108
+ }
109
+ }
110
+ }
60
111
  async function runWithFfmpegLock(job) {
61
112
  const previous = ffmpegJobQueue;
62
113
  let release;
@@ -73,15 +124,112 @@ async function runWithFfmpegLock(job) {
73
124
  function createFsSafeId() {
74
125
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
75
126
  }
127
+ function toErrorMessage(err) {
128
+ if (err instanceof Error && err.message) return err.message;
129
+ if (typeof err === "string") return err;
130
+ try {
131
+ return JSON.stringify(err);
132
+ } catch {
133
+ return String(err);
134
+ }
135
+ }
136
+ function formatArgs(args) {
137
+ return args.map((v) => /\s/.test(v) ? `"${v}"` : v).join(" ");
138
+ }
139
+ function isRecoverableWasmError(err) {
140
+ const msg = toErrorMessage(err).toLowerCase();
141
+ return msg.includes("memory access out of bounds") || msg.includes("runtimeerror") || msg.includes("aborted") || msg.includes("wasm");
142
+ }
76
143
  var VIDEO_DEFAULTS = {
77
144
  crf: 32,
78
145
  deadline: "good",
79
- returnFile: false
146
+ returnFile: false,
147
+ maxInputBytes: 0,
148
+ enableLowMemoryFallback: true,
149
+ fallbackMaxWidth: 960
80
150
  };
81
- async function videoToWebm(input, options = {}) {
82
- const opts = { ...VIDEO_DEFAULTS, ...options };
83
- const blob = await inputToBlob(input);
84
- const ffmpeg = await getFfmpeg();
151
+ function assertVideoInputGuardrails(blob, opts) {
152
+ if (opts.maxInputBytes > 0 && blob.size > opts.maxInputBytes) {
153
+ throw new Error(
154
+ [
155
+ "videoToWebm guardrail: input too large for browser ffmpeg.wasm.",
156
+ `input_bytes=${blob.size}`,
157
+ `max_input_bytes=${opts.maxInputBytes}`,
158
+ "hint=Lower source resolution/bitrate, trim video, or increase maxInputBytes if your environment has enough memory."
159
+ ].join("\n")
160
+ );
161
+ }
162
+ }
163
+ function buildVideoDiagnostic(stage, err) {
164
+ const raw = toErrorMessage(err);
165
+ const lower = raw.toLowerCase();
166
+ const recoverable = isRecoverableWasmError(err);
167
+ if (lower.includes("failed to fetch")) {
168
+ return {
169
+ cause: "input-fetch-failed",
170
+ stage,
171
+ rawError: raw,
172
+ recoverable: false,
173
+ hints: ["Verify URL reachability and CORS policy for browser fetch."]
174
+ };
175
+ }
176
+ if (lower.includes("input too large") || lower.includes("max_input_bytes")) {
177
+ return {
178
+ cause: "input-too-large",
179
+ stage,
180
+ rawError: raw,
181
+ recoverable: false,
182
+ hints: ["Increase maxInputBytes or reduce source size/resolution/duration."]
183
+ };
184
+ }
185
+ if (lower.includes("failed to initialize ffmpeg.wasm")) {
186
+ return {
187
+ cause: "ffmpeg-init-failed",
188
+ stage,
189
+ rawError: raw,
190
+ recoverable,
191
+ hints: [
192
+ "Provide video.ffmpeg.baseURL or explicit coreURL/wasmURL/workerURL.",
193
+ "Ensure CSP/CORS allows ffmpeg worker + wasm assets."
194
+ ]
195
+ };
196
+ }
197
+ if (lower.includes("memory access out of bounds") || lower.includes("runtimeerror") || lower.includes("aborted")) {
198
+ return {
199
+ cause: "ffmpeg-wasm-runtime-crash",
200
+ stage,
201
+ rawError: raw,
202
+ recoverable: true,
203
+ hints: ["Reduce workload: trim duration, lower resolution/bitrate, or split video."]
204
+ };
205
+ }
206
+ if (lower.includes("videotowebm failed")) {
207
+ return {
208
+ cause: "ffmpeg-transcode-failed",
209
+ stage,
210
+ rawError: raw,
211
+ recoverable,
212
+ hints: ["Inspect ffmpeg_logs in rawError for codec/container-specific details."]
213
+ };
214
+ }
215
+ if (lower.includes("unsupported input type")) {
216
+ return {
217
+ cause: "unsupported-input",
218
+ stage,
219
+ rawError: raw,
220
+ recoverable: false,
221
+ hints: ["Use a valid image/* or video/* input and ensure proper MIME/magic bytes."]
222
+ };
223
+ }
224
+ return {
225
+ cause: "unknown",
226
+ stage,
227
+ rawError: raw,
228
+ recoverable,
229
+ hints: ["Use videoToWebmDebug() output to trace failing stage and share rawError."]
230
+ };
231
+ }
232
+ async function runVideoTranscodeJob(ffmpeg, blob, opts) {
85
233
  const jobId = createFsSafeId();
86
234
  const inName = `input-${jobId}`;
87
235
  const outName = `output-${jobId}.webm`;
@@ -109,18 +257,195 @@ async function videoToWebm(input, options = {}) {
109
257
  "libopus",
110
258
  outName
111
259
  ];
260
+ const logs = [];
261
+ const onLog = ({ message }) => {
262
+ if (typeof message !== "string" || message.length === 0) return;
263
+ logs.push(message);
264
+ if (logs.length > 60) logs.shift();
265
+ };
112
266
  try {
267
+ ffmpeg.on("log", onLog);
113
268
  await ffmpeg.exec(args);
114
269
  const outData = await ffmpeg.readFile(outName);
115
270
  return outData instanceof Uint8Array ? outData : new Uint8Array(outData);
271
+ } catch (err) {
272
+ const detail = [
273
+ `videoToWebm failed.`,
274
+ `input=${inName}`,
275
+ `output=${outName}`,
276
+ `args=${formatArgs(args)}`,
277
+ `error=${toErrorMessage(err)}`,
278
+ logs.length ? `ffmpeg_logs:
279
+ ${logs.join("\n")}` : "ffmpeg_logs: <none>"
280
+ ].join("\n");
281
+ throw new Error(detail);
116
282
  } finally {
283
+ ffmpeg.off("log", onLog);
117
284
  await Promise.allSettled([ffmpeg.deleteFile(inName), ffmpeg.deleteFile(outName)]);
118
285
  }
119
286
  });
120
- const outBlob = new Blob([outBytes], { type: "video/webm" });
287
+ return new Blob([outBytes], { type: "video/webm" });
288
+ }
289
+ async function runVideoTranscodeLowMemoryJob(ffmpeg, blob, opts) {
290
+ const jobId = createFsSafeId();
291
+ const inName = `input-${jobId}`;
292
+ const outName = `output-${jobId}.webm`;
293
+ const safeWidth = Math.max(320, Math.round(opts.fallbackMaxWidth));
294
+ const outBytes = await runWithFfmpegLock(async () => {
295
+ await ffmpeg.writeFile(inName, await util.fetchFile(blob));
296
+ const args = [
297
+ ...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
298
+ "-i",
299
+ inName,
300
+ "-map",
301
+ "0:v:0",
302
+ "-map",
303
+ "0:a?",
304
+ "-vf",
305
+ `scale='min(${safeWidth},iw)':-2:flags=bilinear`,
306
+ "-c:v",
307
+ "libvpx-vp9",
308
+ "-b:v",
309
+ "0",
310
+ "-crf",
311
+ String(Math.max(34, opts.crf)),
312
+ "-deadline",
313
+ "realtime",
314
+ "-cpu-used",
315
+ "8",
316
+ "-row-mt",
317
+ "1",
318
+ "-c:a",
319
+ "libopus",
320
+ outName
321
+ ];
322
+ const logs = [];
323
+ const onLog = ({ message }) => {
324
+ if (typeof message !== "string" || message.length === 0) return;
325
+ logs.push(message);
326
+ if (logs.length > 60) logs.shift();
327
+ };
328
+ try {
329
+ ffmpeg.on("log", onLog);
330
+ await ffmpeg.exec(args);
331
+ const outData = await ffmpeg.readFile(outName);
332
+ return outData instanceof Uint8Array ? outData : new Uint8Array(outData);
333
+ } catch (err) {
334
+ const detail = [
335
+ `videoToWebm low-memory fallback failed.`,
336
+ `input=${inName}`,
337
+ `output=${outName}`,
338
+ `args=${formatArgs(args)}`,
339
+ `error=${toErrorMessage(err)}`,
340
+ logs.length ? `ffmpeg_logs:
341
+ ${logs.join("\n")}` : "ffmpeg_logs: <none>"
342
+ ].join("\n");
343
+ throw new Error(detail);
344
+ } finally {
345
+ ffmpeg.off("log", onLog);
346
+ await Promise.allSettled([ffmpeg.deleteFile(inName), ffmpeg.deleteFile(outName)]);
347
+ }
348
+ });
349
+ return new Blob([outBytes], { type: "video/webm" });
350
+ }
351
+ async function videoToWebm(input, options = {}) {
352
+ const opts = { ...VIDEO_DEFAULTS, ...options };
353
+ const blob = await inputToBlob(input);
354
+ assertVideoInputGuardrails(blob, opts);
355
+ let outBlob;
356
+ try {
357
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
358
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
359
+ } catch (err) {
360
+ if (!isRecoverableWasmError(err)) throw err;
361
+ await resetFfmpegRuntime();
362
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
363
+ try {
364
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
365
+ } catch (retryErr) {
366
+ if (!isRecoverableWasmError(retryErr) || !opts.enableLowMemoryFallback) throw retryErr;
367
+ await resetFfmpegRuntime();
368
+ const ffmpegFallback = await getFfmpeg(opts.ffmpeg);
369
+ outBlob = await runVideoTranscodeLowMemoryJob(ffmpegFallback, blob, opts);
370
+ }
371
+ }
121
372
  const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : void 0;
122
373
  return { blob: outBlob, file, isWebm: true };
123
374
  }
375
+ async function videoToWebmDebug(input, options = {}, onEvent) {
376
+ const events = [];
377
+ let lastStage = "input";
378
+ const emit = (event) => {
379
+ lastStage = event.stage;
380
+ events.push(event);
381
+ onEvent?.(event);
382
+ };
383
+ try {
384
+ emit({ stage: "input", message: "Reading input as Blob." });
385
+ const opts = { ...VIDEO_DEFAULTS, ...options };
386
+ const blob = await inputToBlob(input);
387
+ emit({
388
+ stage: "guardrail",
389
+ message: "Checking browser ffmpeg.wasm memory guardrail.",
390
+ detail: `input_bytes=${blob.size};max_input_bytes=${opts.maxInputBytes}`
391
+ });
392
+ assertVideoInputGuardrails(blob, opts);
393
+ let outBlob;
394
+ try {
395
+ emit({ stage: "init", message: "Initializing ffmpeg.wasm runtime." });
396
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
397
+ emit({ stage: "transcode", message: "Running video transcode job (VP9 + Opus)." });
398
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
399
+ } catch (err) {
400
+ if (!isRecoverableWasmError(err)) throw err;
401
+ emit({
402
+ stage: "retry-reset",
403
+ message: "Recoverable wasm error detected, resetting runtime.",
404
+ detail: toErrorMessage(err)
405
+ });
406
+ await resetFfmpegRuntime();
407
+ emit({ stage: "retry-init", message: "Reinitializing ffmpeg.wasm runtime." });
408
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
409
+ emit({ stage: "retry-transcode", message: "Retrying video transcode job." });
410
+ try {
411
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
412
+ } catch (retryErr) {
413
+ if (!isRecoverableWasmError(retryErr) || !opts.enableLowMemoryFallback) throw retryErr;
414
+ emit({
415
+ stage: "retry-transcode",
416
+ message: "Running low-memory fallback transcode profile.",
417
+ detail: `fallback_max_width=${opts.fallbackMaxWidth}`
418
+ });
419
+ await resetFfmpegRuntime();
420
+ const ffmpegFallback = await getFfmpeg(opts.ffmpeg);
421
+ outBlob = await runVideoTranscodeLowMemoryJob(ffmpegFallback, blob, opts);
422
+ }
423
+ }
424
+ const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : void 0;
425
+ emit({
426
+ stage: "done",
427
+ message: "Video converted to WebM.",
428
+ detail: `output_bytes=${outBlob.size};mime=${outBlob.type}`
429
+ });
430
+ return {
431
+ ok: true,
432
+ blob: outBlob,
433
+ file,
434
+ isWebm: true,
435
+ events,
436
+ diagnostic: { cause: "none", stage: "done", recoverable: false, hints: [] }
437
+ };
438
+ } catch (err) {
439
+ const error = err instanceof Error ? err : new Error(toErrorMessage(err));
440
+ const diagnostic = buildVideoDiagnostic(lastStage, error);
441
+ emit({
442
+ stage: "done",
443
+ message: "Video conversion failed.",
444
+ detail: `cause=${diagnostic.cause};${error.message}`
445
+ });
446
+ return { ok: false, error, events, diagnostic };
447
+ }
448
+ }
124
449
  async function detectKindBrowser(input) {
125
450
  if (typeof input !== "string") {
126
451
  const t = (input.type || "").toLowerCase();
@@ -274,5 +599,6 @@ async function imageToWebp(input, options = {}) {
274
599
  exports.convert = convert;
275
600
  exports.imageToWebp = imageToWebp;
276
601
  exports.videoToWebm = videoToWebm;
602
+ exports.videoToWebmDebug = videoToWebmDebug;
277
603
  //# sourceMappingURL=index.cjs.map
278
604
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/browser/index.ts"],"names":["FFmpeg","fetchFile","fileTypeFromBuffer","canvas","ctx","fileName","file"],"mappings":"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","file":"index.cjs","sourcesContent":["import { FFmpeg } from \"@ffmpeg/ffmpeg\"\r\nimport { fetchFile } from \"@ffmpeg/util\"\r\nimport { fileTypeFromBuffer } from \"file-type\"\r\n\r\nimport type { ConvertResult } from \"../shared/types.js\"\r\n\r\nexport type ImageToWebpInput = Blob | File | string\r\n\r\nexport type ImageToWebpOptions = {\r\n /**\r\n * Hard cap for output dimensions, preserving aspect ratio.\r\n * If the input is larger, it will be downscaled.\r\n */\r\n maxWidth?: number\r\n maxHeight?: number\r\n\r\n /**\r\n * Target maximum output size in bytes.\r\n * If provided, the encoder will try to reach <= targetBytes by reducing quality.\r\n */\r\n targetBytes?: number\r\n\r\n /**\r\n * Quality search range (0..1). Higher is better quality, larger file.\r\n */\r\n maxQuality?: number\r\n minQuality?: number\r\n\r\n /**\r\n * If true, always return WebP even if it can't reach targetBytes.\r\n * If false, will return the original input when WebP isn't supported.\r\n */\r\n force?: boolean\r\n\r\n /**\r\n * Optional output filename (only used when returning a File).\r\n * If omitted and input is a File, the name is derived from it.\r\n */\r\n fileName?: string\r\n\r\n /**\r\n * Return a File instead of a Blob.\r\n */\r\n returnFile?: boolean\r\n}\r\n\r\nexport type ImageToWebpResult = {\r\n blob: Blob\r\n file?: File\r\n width: number\r\n height: number\r\n quality: number\r\n /**\r\n * True if output is `image/webp`.\r\n */\r\n isWebp: boolean\r\n}\r\n\r\nconst DEFAULTS: Required<\r\n Pick<\r\n ImageToWebpOptions,\r\n \"maxWidth\" | \"maxHeight\" | \"maxQuality\" | \"minQuality\" | \"force\" | \"returnFile\"\r\n >\r\n> = {\r\n maxWidth: 2048,\r\n maxHeight: 2048,\r\n maxQuality: 0.82,\r\n minQuality: 0.45,\r\n force: true,\r\n returnFile: false,\r\n}\r\n\r\nfunction clamp01(n: number) {\r\n return Math.max(0, Math.min(1, n))\r\n}\r\n\r\nfunction toWebpName(name: string) {\r\n const base = name.replace(/\\.[^/.]+$/, \"\")\r\n return `${base || \"image\"}.webp`\r\n}\r\n\r\nfunction getInputName(input: ImageToWebpInput) {\r\n if (typeof input === \"string\") return \"image.webp\"\r\n if (input instanceof File && input.name) return toWebpName(input.name)\r\n return \"image.webp\"\r\n}\r\n\r\nasync function inputToBlob(input: ImageToWebpInput): Promise<Blob> {\r\n if (typeof input === \"string\") {\r\n const res = await fetch(input)\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`)\r\n return await res.blob()\r\n }\r\n return input\r\n}\r\n\r\nfunction toWebmName(name: string) {\r\n const base = name.replace(/\\.[^/.]+$/, \"\")\r\n return `${base || \"video\"}.webm`\r\n}\r\n\r\nfunction defaultVideoName(input: Blob | File | string, fileName?: string) {\r\n if (fileName && fileName.trim().length > 0) return fileName\r\n if (typeof input === \"string\") return \"video.webm\"\r\n if (input instanceof File && input.name) return toWebmName(input.name)\r\n return \"video.webm\"\r\n}\r\n\r\nlet ffmpegSingleton: FFmpeg | null = null\r\nlet ffmpegLoadPromise: Promise<FFmpeg> | null = null\r\nlet ffmpegJobQueue: Promise<void> = Promise.resolve()\r\n\r\nasync function getFfmpeg(): Promise<FFmpeg> {\r\n if (ffmpegSingleton) return ffmpegSingleton\r\n if (ffmpegLoadPromise) return ffmpegLoadPromise\r\n const ff = new FFmpeg()\r\n ffmpegLoadPromise = (async () => {\r\n await ff.load()\r\n ffmpegSingleton = ff\r\n return ff\r\n })()\r\n return ffmpegLoadPromise\r\n}\r\n\r\nasync function runWithFfmpegLock<T>(job: () => Promise<T>): Promise<T> {\r\n const previous = ffmpegJobQueue\r\n let release!: () => void\r\n ffmpegJobQueue = new Promise<void>((resolve) => {\r\n release = resolve\r\n })\r\n await previous\r\n try {\r\n return await job()\r\n } finally {\r\n release()\r\n }\r\n}\r\n\r\nfunction createFsSafeId(): string {\r\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\r\n}\r\n\r\nexport type VideoToWebmInput = Blob | File | string\r\n\r\nexport type VideoToWebmOptions = {\r\n crf?: number\r\n deadline?: \"good\" | \"best\" | \"realtime\"\r\n maxDurationSeconds?: number\r\n fileName?: string\r\n returnFile?: boolean\r\n}\r\n\r\nexport type VideoToWebmResult = {\r\n blob: Blob\r\n file?: File\r\n isWebm: boolean\r\n}\r\n\r\nconst VIDEO_DEFAULTS: Required<Pick<VideoToWebmOptions, \"crf\" | \"deadline\" | \"returnFile\">> = {\r\n crf: 32,\r\n deadline: \"good\",\r\n returnFile: false,\r\n}\r\n\r\nexport async function videoToWebm(\r\n input: VideoToWebmInput,\r\n options: VideoToWebmOptions = {}\r\n): Promise<VideoToWebmResult> {\r\n const opts = { ...VIDEO_DEFAULTS, ...options }\r\n const blob = await inputToBlob(input)\r\n const ffmpeg = await getFfmpeg()\r\n const jobId = createFsSafeId()\r\n const inName = `input-${jobId}`\r\n const outName = `output-${jobId}.webm`\r\n\r\n const outBytes = await runWithFfmpegLock(async () => {\r\n await ffmpeg.writeFile(inName, await fetchFile(blob))\r\n\r\n const args: string[] = [\r\n ...(opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? [\"-t\", String(opts.maxDurationSeconds)] : []),\r\n \"-i\",\r\n inName,\r\n \"-map\",\r\n \"0:v:0\",\r\n \"-map\",\r\n \"0:a?\",\r\n \"-c:v\",\r\n \"libvpx-vp9\",\r\n \"-b:v\",\r\n \"0\",\r\n \"-crf\",\r\n String(opts.crf),\r\n \"-deadline\",\r\n opts.deadline,\r\n \"-row-mt\",\r\n \"1\",\r\n \"-c:a\",\r\n \"libopus\",\r\n outName,\r\n ]\r\n\r\n try {\r\n await ffmpeg.exec(args)\r\n const outData = await ffmpeg.readFile(outName)\r\n return outData instanceof Uint8Array ? outData : new Uint8Array(outData as any)\r\n } finally {\r\n await Promise.allSettled([ffmpeg.deleteFile(inName), ffmpeg.deleteFile(outName)])\r\n }\r\n })\r\n\r\n const outBlob = new Blob([outBytes], { type: \"video/webm\" })\r\n const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : undefined\r\n return { blob: outBlob, file, isWebm: true }\r\n}\r\n\r\nexport type ConvertInput = ImageToWebpInput\r\n\r\nexport type ConvertOptions = {\r\n image?: ImageToWebpOptions\r\n video?: VideoToWebmOptions\r\n returnFile?: boolean\r\n fileName?: string\r\n}\r\n\r\nasync function detectKindBrowser(input: ConvertInput): Promise<\"image\" | \"video\" | \"unknown\"> {\r\n if (typeof input !== \"string\") {\r\n const t = (input.type || \"\").toLowerCase()\r\n if (t.startsWith(\"image/\")) return \"image\"\r\n if (t.startsWith(\"video/\")) return \"video\"\r\n }\r\n\r\n const blob = await inputToBlob(input)\r\n const buf = new Uint8Array(await blob.slice(0, 4100).arrayBuffer())\r\n const ft = await fileTypeFromBuffer(buf)\r\n if (!ft) return \"unknown\"\r\n if (ft.mime.startsWith(\"image/\")) return \"image\"\r\n if (ft.mime.startsWith(\"video/\")) return \"video\"\r\n return \"unknown\"\r\n}\r\n\r\nexport async function convert(input: ConvertInput, options: ConvertOptions = {}): Promise<ConvertResult> {\r\n const kind = await detectKindBrowser(input)\r\n const returnFile = options.returnFile ?? false\r\n\r\n if (kind === \"image\") {\r\n const res = await imageToWebp(input, { ...options.image, returnFile, fileName: options.fileName })\r\n return { kind: \"image\", ...res }\r\n }\r\n\r\n if (kind === \"video\") {\r\n const res = await videoToWebm(input, { ...options.video, returnFile, fileName: options.fileName })\r\n return { kind: \"video\", ...res }\r\n }\r\n\r\n throw new Error(\"Unsupported input type. Expected an image/* or video/* asset.\")\r\n}\r\n\r\nfunction computeTargetSize(\r\n srcW: number,\r\n srcH: number,\r\n maxW: number,\r\n maxH: number\r\n): { width: number; height: number } {\r\n if (srcW <= 0 || srcH <= 0) return { width: srcW, height: srcH }\r\n const scale = Math.min(1, maxW / srcW, maxH / srcH)\r\n return {\r\n width: Math.max(1, Math.round(srcW * scale)),\r\n height: Math.max(1, Math.round(srcH * scale)),\r\n }\r\n}\r\n\r\nasync function decodeImage(blob: Blob): Promise<{ bitmap: ImageBitmap; width: number; height: number }> {\r\n if (typeof createImageBitmap === \"function\") {\r\n const bitmap = await createImageBitmap(blob, { imageOrientation: \"from-image\" as any })\r\n return { bitmap, width: bitmap.width, height: bitmap.height }\r\n }\r\n\r\n const url = URL.createObjectURL(blob)\r\n try {\r\n const img = await new Promise<HTMLImageElement>((resolve, reject) => {\r\n const el = new Image()\r\n el.onload = () => resolve(el)\r\n el.onerror = () => reject(new Error(\"Failed to decode image\"))\r\n el.src = url\r\n })\r\n\r\n const canvas = document.createElement(\"canvas\")\r\n canvas.width = img.naturalWidth || img.width\r\n canvas.height = img.naturalHeight || img.height\r\n const ctx = canvas.getContext(\"2d\")\r\n if (!ctx) throw new Error(\"Canvas 2D context not available\")\r\n ctx.drawImage(img, 0, 0)\r\n\r\n const dataUrl = canvas.toDataURL(\"image/png\")\r\n const pngBlob = await (await fetch(dataUrl)).blob()\r\n const bitmap = await createImageBitmap(pngBlob)\r\n return { bitmap, width: bitmap.width, height: bitmap.height }\r\n } finally {\r\n URL.revokeObjectURL(url)\r\n }\r\n}\r\n\r\nasync function encodeWebpFromBitmap(\r\n bitmap: ImageBitmap,\r\n width: number,\r\n height: number,\r\n quality: number\r\n): Promise<Blob | null> {\r\n const q = clamp01(quality)\r\n\r\n const hasOffscreen = typeof OffscreenCanvas !== \"undefined\"\r\n if (hasOffscreen) {\r\n const canvas = new OffscreenCanvas(width, height)\r\n const ctx = canvas.getContext(\"2d\")\r\n if (!ctx) return null\r\n ctx.drawImage(bitmap, 0, 0, width, height)\r\n try {\r\n return await canvas.convertToBlob({ type: \"image/webp\", quality: q })\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n const canvas = document.createElement(\"canvas\")\r\n canvas.width = width\r\n canvas.height = height\r\n const ctx = canvas.getContext(\"2d\")\r\n if (!ctx) return null\r\n ctx.drawImage(bitmap, 0, 0, width, height)\r\n\r\n const blob = await new Promise<Blob | null>((resolve) => {\r\n canvas.toBlob((b) => resolve(b), \"image/webp\", q)\r\n })\r\n return blob\r\n}\r\n\r\n/**\r\n * Convert an image (png/jpg/etc) to WebP and shrink size while keeping similar quality.\r\n *\r\n * - Downscales to `maxWidth`/`maxHeight` (keeps aspect ratio).\r\n * - Encodes as WebP with a quality search to try meeting `targetBytes` (if provided).\r\n */\r\nexport async function imageToWebp(\r\n input: ImageToWebpInput,\r\n options: ImageToWebpOptions = {}\r\n): Promise<ImageToWebpResult> {\r\n const opts = { ...DEFAULTS, ...options }\r\n const blob = await inputToBlob(input)\r\n\r\n const { bitmap, width: srcW, height: srcH } = await decodeImage(blob)\r\n try {\r\n const { width, height } = computeTargetSize(srcW, srcH, opts.maxWidth, opts.maxHeight)\r\n\r\n if (!opts.targetBytes || opts.targetBytes <= 0) {\r\n const out = await encodeWebpFromBitmap(bitmap, width, height, opts.maxQuality)\r\n if (!out) {\r\n if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n }\r\n\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([out], fileName, { type: out.type }) : undefined\r\n return { blob: out, file, width, height, quality: opts.maxQuality, isWebp: true }\r\n }\r\n\r\n const maxQ = clamp01(opts.maxQuality)\r\n const minQ = clamp01(Math.min(opts.minQuality, maxQ))\r\n\r\n const atMax = await encodeWebpFromBitmap(bitmap, width, height, maxQ)\r\n if (!atMax) {\r\n if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n }\r\n if (atMax.size <= opts.targetBytes) {\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([atMax], fileName, { type: atMax.type }) : undefined\r\n return { blob: atMax, file, width, height, quality: maxQ, isWebp: true }\r\n }\r\n\r\n const atMin = await encodeWebpFromBitmap(bitmap, width, height, minQ)\r\n if (!atMin) {\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([atMax], fileName, { type: atMax.type }) : undefined\r\n return { blob: atMax, file, width, height, quality: maxQ, isWebp: true }\r\n }\r\n if (atMin.size > opts.targetBytes) {\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([atMin], fileName, { type: atMin.type }) : undefined\r\n return { blob: atMin, file, width, height, quality: minQ, isWebp: true }\r\n }\r\n\r\n let lo = minQ\r\n let hi = maxQ\r\n let bestBlob: Blob = atMin\r\n let bestQ = minQ\r\n\r\n for (let i = 0; i < 7; i++) {\r\n const mid = (lo + hi) / 2\r\n const enc = await encodeWebpFromBitmap(bitmap, width, height, mid)\r\n if (!enc) break\r\n\r\n if (enc.size <= opts.targetBytes) {\r\n bestBlob = enc\r\n bestQ = mid\r\n lo = mid\r\n } else {\r\n hi = mid\r\n }\r\n }\r\n\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([bestBlob], fileName, { type: bestBlob.type }) : undefined\r\n return { blob: bestBlob, file, width, height, quality: bestQ, isWebp: true }\r\n } finally {\r\n bitmap.close()\r\n }\r\n}\r\n\r\n"]}
1
+ {"version":3,"sources":["../../src/browser/index.ts"],"names":["FFmpeg","fetchFile","fileTypeFromBuffer","canvas","ctx","fileName","file"],"mappings":"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","file":"index.cjs","sourcesContent":["import { FFmpeg } from \"@ffmpeg/ffmpeg\"\r\nimport { fetchFile } from \"@ffmpeg/util\"\r\nimport { fileTypeFromBuffer } from \"file-type\"\r\n\r\nimport type { ConvertResult } from \"../shared/types.js\"\r\n\r\nexport type ImageToWebpInput = Blob | File | string\r\n\r\nexport type ImageToWebpOptions = {\r\n /**\r\n * Hard cap for output dimensions, preserving aspect ratio.\r\n * If the input is larger, it will be downscaled.\r\n */\r\n maxWidth?: number\r\n maxHeight?: number\r\n\r\n /**\r\n * Target maximum output size in bytes.\r\n * If provided, the encoder will try to reach <= targetBytes by reducing quality.\r\n */\r\n targetBytes?: number\r\n\r\n /**\r\n * Quality search range (0..1). Higher is better quality, larger file.\r\n */\r\n maxQuality?: number\r\n minQuality?: number\r\n\r\n /**\r\n * If true, always return WebP even if it can't reach targetBytes.\r\n * If false, will return the original input when WebP isn't supported.\r\n */\r\n force?: boolean\r\n\r\n /**\r\n * Optional output filename (only used when returning a File).\r\n * If omitted and input is a File, the name is derived from it.\r\n */\r\n fileName?: string\r\n\r\n /**\r\n * Return a File instead of a Blob.\r\n */\r\n returnFile?: boolean\r\n}\r\n\r\nexport type ImageToWebpResult = {\r\n blob: Blob\r\n file?: File\r\n width: number\r\n height: number\r\n quality: number\r\n /**\r\n * True if output is `image/webp`.\r\n */\r\n isWebp: boolean\r\n}\r\n\r\nconst DEFAULTS: Required<\r\n Pick<\r\n ImageToWebpOptions,\r\n \"maxWidth\" | \"maxHeight\" | \"maxQuality\" | \"minQuality\" | \"force\" | \"returnFile\"\r\n >\r\n> = {\r\n maxWidth: 2048,\r\n maxHeight: 2048,\r\n maxQuality: 0.82,\r\n minQuality: 0.45,\r\n force: true,\r\n returnFile: false,\r\n}\r\n\r\nfunction clamp01(n: number) {\r\n return Math.max(0, Math.min(1, n))\r\n}\r\n\r\nfunction toWebpName(name: string) {\r\n const base = name.replace(/\\.[^/.]+$/, \"\")\r\n return `${base || \"image\"}.webp`\r\n}\r\n\r\nfunction getInputName(input: ImageToWebpInput) {\r\n if (typeof input === \"string\") return \"image.webp\"\r\n if (input instanceof File && input.name) return toWebpName(input.name)\r\n return \"image.webp\"\r\n}\r\n\r\nasync function inputToBlob(input: ImageToWebpInput): Promise<Blob> {\r\n if (typeof input === \"string\") {\r\n const res = await fetch(input)\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`)\r\n return await res.blob()\r\n }\r\n return input\r\n}\r\n\r\nfunction toWebmName(name: string) {\r\n const base = name.replace(/\\.[^/.]+$/, \"\")\r\n return `${base || \"video\"}.webm`\r\n}\r\n\r\nfunction defaultVideoName(input: Blob | File | string, fileName?: string) {\r\n if (fileName && fileName.trim().length > 0) return fileName\r\n if (typeof input === \"string\") return \"video.webm\"\r\n if (input instanceof File && input.name) return toWebmName(input.name)\r\n return \"video.webm\"\r\n}\r\n\r\nlet ffmpegSingleton: FFmpeg | null = null\r\nlet ffmpegLoadPromise: Promise<FFmpeg> | null = null\r\nlet ffmpegJobQueue: Promise<void> = Promise.resolve()\r\nlet ffmpegConfigKey = \"{}\"\r\n\r\nfunction normalizeBrowserFfmpegLoadOptions(input?: BrowserFfmpegLoadOptions): BrowserFfmpegLoadOptions | undefined {\r\n if (!input) return undefined\r\n const baseURL = input.baseURL?.trim()\r\n const normalize = (v?: string) => v?.trim() || undefined\r\n const make = (suffix: string) => {\r\n if (!baseURL) return undefined\r\n return `${baseURL.replace(/\\/+$/, \"\")}/${suffix}`\r\n }\r\n return {\r\n baseURL,\r\n coreURL: normalize(input.coreURL) ?? make(\"ffmpeg-core.js\"),\r\n wasmURL: normalize(input.wasmURL) ?? make(\"ffmpeg-core.wasm\"),\r\n workerURL: normalize(input.workerURL) ?? make(\"ffmpeg-core.worker.js\"),\r\n }\r\n}\r\n\r\nfunction getFfmpegConfigKey(input?: BrowserFfmpegLoadOptions): string {\r\n return JSON.stringify(normalizeBrowserFfmpegLoadOptions(input) ?? {})\r\n}\r\n\r\nasync function getFfmpeg(loadOptions?: BrowserFfmpegLoadOptions): Promise<FFmpeg> {\r\n const nextKey = getFfmpegConfigKey(loadOptions)\r\n if (nextKey !== ffmpegConfigKey) {\r\n await resetFfmpegRuntime()\r\n ffmpegConfigKey = nextKey\r\n }\r\n if (ffmpegSingleton) return ffmpegSingleton\r\n if (ffmpegLoadPromise) return ffmpegLoadPromise\r\n const ff = new FFmpeg()\r\n const normalized = normalizeBrowserFfmpegLoadOptions(loadOptions)\r\n const loadParams =\r\n normalized && (normalized.coreURL || normalized.wasmURL || normalized.workerURL)\r\n ? {\r\n ...(normalized.coreURL ? { coreURL: normalized.coreURL } : {}),\r\n ...(normalized.wasmURL ? { wasmURL: normalized.wasmURL } : {}),\r\n ...(normalized.workerURL ? { workerURL: normalized.workerURL } : {}),\r\n }\r\n : undefined\r\n ffmpegLoadPromise = (async () => {\r\n try {\r\n await ff.load(loadParams as any)\r\n ffmpegSingleton = ff\r\n return ff\r\n } catch (err) {\r\n ffmpegLoadPromise = null\r\n const detail = [\r\n \"Failed to initialize ffmpeg.wasm.\",\r\n `error=${toErrorMessage(err)}`,\r\n \"hint=Ensure ffmpeg core/worker assets are reachable by your bundler/runtime and not blocked by CSP/CORS.\",\r\n ].join(\"\\n\")\r\n throw new Error(detail)\r\n }\r\n })()\r\n return ffmpegLoadPromise\r\n}\r\n\r\nasync function resetFfmpegRuntime(): Promise<void> {\r\n const current = ffmpegSingleton\r\n ffmpegSingleton = null\r\n ffmpegLoadPromise = null\r\n if (current && typeof (current as any).terminate === \"function\") {\r\n try {\r\n await (current as any).terminate()\r\n } catch {\r\n // Best-effort reset only.\r\n }\r\n }\r\n}\r\n\r\nasync function runWithFfmpegLock<T>(job: () => Promise<T>): Promise<T> {\r\n const previous = ffmpegJobQueue\r\n let release!: () => void\r\n ffmpegJobQueue = new Promise<void>((resolve) => {\r\n release = resolve\r\n })\r\n await previous\r\n try {\r\n return await job()\r\n } finally {\r\n release()\r\n }\r\n}\r\n\r\nfunction createFsSafeId(): string {\r\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\r\n}\r\n\r\nfunction toErrorMessage(err: unknown): string {\r\n if (err instanceof Error && err.message) return err.message\r\n if (typeof err === \"string\") return err\r\n try {\r\n return JSON.stringify(err)\r\n } catch {\r\n return String(err)\r\n }\r\n}\r\n\r\nfunction formatArgs(args: string[]): string {\r\n return args.map((v) => (/\\s/.test(v) ? `\"${v}\"` : v)).join(\" \")\r\n}\r\n\r\nfunction isRecoverableWasmError(err: unknown): boolean {\r\n const msg = toErrorMessage(err).toLowerCase()\r\n return (\r\n msg.includes(\"memory access out of bounds\") ||\r\n msg.includes(\"runtimeerror\") ||\r\n msg.includes(\"aborted\") ||\r\n msg.includes(\"wasm\")\r\n )\r\n}\r\n\r\nexport type VideoToWebmInput = Blob | File | string\r\n\r\nexport type BrowserFfmpegLoadOptions = {\r\n /**\r\n * Base URL where ffmpeg.wasm assets live.\r\n * Missing specific URLs are derived from this.\r\n */\r\n baseURL?: string\r\n coreURL?: string\r\n wasmURL?: string\r\n workerURL?: string\r\n}\r\n\r\nexport type VideoToWebmOptions = {\r\n crf?: number\r\n deadline?: \"good\" | \"best\" | \"realtime\"\r\n maxDurationSeconds?: number\r\n /**\r\n * Guardrail for browser wasm memory pressure.\r\n * Throws before transcoding when input exceeds this limit.\r\n */\r\n maxInputBytes?: number\r\n /**\r\n * On recoverable wasm crashes, retry with lower-memory ffmpeg args.\r\n */\r\n enableLowMemoryFallback?: boolean\r\n /**\r\n * Max output width used by low-memory fallback.\r\n */\r\n fallbackMaxWidth?: number\r\n /**\r\n * Optional browser ffmpeg.wasm asset URL configuration.\r\n */\r\n ffmpeg?: BrowserFfmpegLoadOptions\r\n fileName?: string\r\n returnFile?: boolean\r\n}\r\n\r\nexport type VideoToWebmResult = {\r\n blob: Blob\r\n file?: File\r\n isWebm: boolean\r\n}\r\n\r\nexport type VideoToWebmDebugStage =\r\n | \"input\"\r\n | \"guardrail\"\r\n | \"init\"\r\n | \"transcode\"\r\n | \"retry-reset\"\r\n | \"retry-init\"\r\n | \"retry-transcode\"\r\n | \"done\"\r\n\r\nexport type VideoToWebmDebugEvent = {\r\n stage: VideoToWebmDebugStage\r\n message: string\r\n detail?: string\r\n}\r\n\r\nexport type VideoToWebmDebugCause =\r\n | \"none\"\r\n | \"input-fetch-failed\"\r\n | \"input-too-large\"\r\n | \"ffmpeg-init-failed\"\r\n | \"ffmpeg-transcode-failed\"\r\n | \"ffmpeg-wasm-runtime-crash\"\r\n | \"unsupported-input\"\r\n | \"unknown\"\r\n\r\nexport type VideoToWebmDiagnostic = {\r\n cause: VideoToWebmDebugCause\r\n stage: VideoToWebmDebugStage\r\n rawError?: string\r\n recoverable: boolean\r\n hints: string[]\r\n}\r\n\r\nexport type VideoToWebmDebugResult =\r\n | ({ ok: true } & VideoToWebmResult & { events: VideoToWebmDebugEvent[]; diagnostic: VideoToWebmDiagnostic })\r\n | { ok: false; error: Error; events: VideoToWebmDebugEvent[]; diagnostic: VideoToWebmDiagnostic }\r\n\r\nconst VIDEO_DEFAULTS: Required<\r\n Pick<\r\n VideoToWebmOptions,\r\n \"crf\" | \"deadline\" | \"returnFile\" | \"maxInputBytes\" | \"enableLowMemoryFallback\" | \"fallbackMaxWidth\"\r\n >\r\n> = {\r\n crf: 32,\r\n deadline: \"good\",\r\n returnFile: false,\r\n maxInputBytes: 0,\r\n enableLowMemoryFallback: true,\r\n fallbackMaxWidth: 960,\r\n}\r\n\r\nfunction assertVideoInputGuardrails(blob: Blob, opts: Required<Pick<VideoToWebmOptions, \"maxInputBytes\">>) {\r\n if (opts.maxInputBytes > 0 && blob.size > opts.maxInputBytes) {\r\n throw new Error(\r\n [\r\n \"videoToWebm guardrail: input too large for browser ffmpeg.wasm.\",\r\n `input_bytes=${blob.size}`,\r\n `max_input_bytes=${opts.maxInputBytes}`,\r\n \"hint=Lower source resolution/bitrate, trim video, or increase maxInputBytes if your environment has enough memory.\",\r\n ].join(\"\\n\")\r\n )\r\n }\r\n}\r\n\r\ntype NormalizedVideoOptions = Required<\r\n Pick<\r\n VideoToWebmOptions,\r\n \"crf\" | \"deadline\" | \"returnFile\" | \"maxInputBytes\" | \"enableLowMemoryFallback\" | \"fallbackMaxWidth\"\r\n >\r\n> &\r\n Pick<VideoToWebmOptions, \"maxDurationSeconds\" | \"fileName\" | \"ffmpeg\">\r\n\r\nfunction buildVideoDiagnostic(stage: VideoToWebmDebugStage, err: unknown): VideoToWebmDiagnostic {\r\n const raw = toErrorMessage(err)\r\n const lower = raw.toLowerCase()\r\n const recoverable = isRecoverableWasmError(err)\r\n\r\n if (lower.includes(\"failed to fetch\")) {\r\n return {\r\n cause: \"input-fetch-failed\",\r\n stage,\r\n rawError: raw,\r\n recoverable: false,\r\n hints: [\"Verify URL reachability and CORS policy for browser fetch.\"],\r\n }\r\n }\r\n\r\n if (lower.includes(\"input too large\") || lower.includes(\"max_input_bytes\")) {\r\n return {\r\n cause: \"input-too-large\",\r\n stage,\r\n rawError: raw,\r\n recoverable: false,\r\n hints: [\"Increase maxInputBytes or reduce source size/resolution/duration.\"],\r\n }\r\n }\r\n\r\n if (lower.includes(\"failed to initialize ffmpeg.wasm\")) {\r\n return {\r\n cause: \"ffmpeg-init-failed\",\r\n stage,\r\n rawError: raw,\r\n recoverable,\r\n hints: [\r\n \"Provide video.ffmpeg.baseURL or explicit coreURL/wasmURL/workerURL.\",\r\n \"Ensure CSP/CORS allows ffmpeg worker + wasm assets.\",\r\n ],\r\n }\r\n }\r\n\r\n if (lower.includes(\"memory access out of bounds\") || lower.includes(\"runtimeerror\") || lower.includes(\"aborted\")) {\r\n return {\r\n cause: \"ffmpeg-wasm-runtime-crash\",\r\n stage,\r\n rawError: raw,\r\n recoverable: true,\r\n hints: [\"Reduce workload: trim duration, lower resolution/bitrate, or split video.\"],\r\n }\r\n }\r\n\r\n if (lower.includes(\"videotowebm failed\")) {\r\n return {\r\n cause: \"ffmpeg-transcode-failed\",\r\n stage,\r\n rawError: raw,\r\n recoverable,\r\n hints: [\"Inspect ffmpeg_logs in rawError for codec/container-specific details.\"],\r\n }\r\n }\r\n\r\n if (lower.includes(\"unsupported input type\")) {\r\n return {\r\n cause: \"unsupported-input\",\r\n stage,\r\n rawError: raw,\r\n recoverable: false,\r\n hints: [\"Use a valid image/* or video/* input and ensure proper MIME/magic bytes.\"],\r\n }\r\n }\r\n\r\n return {\r\n cause: \"unknown\",\r\n stage,\r\n rawError: raw,\r\n recoverable,\r\n hints: [\"Use videoToWebmDebug() output to trace failing stage and share rawError.\"],\r\n }\r\n}\r\n\r\nasync function runVideoTranscodeJob(ffmpeg: FFmpeg, blob: Blob, opts: NormalizedVideoOptions) {\r\n const jobId = createFsSafeId()\r\n const inName = `input-${jobId}`\r\n const outName = `output-${jobId}.webm`\r\n\r\n const outBytes = await runWithFfmpegLock(async () => {\r\n await ffmpeg.writeFile(inName, await fetchFile(blob))\r\n\r\n const args: string[] = [\r\n ...(opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? [\"-t\", String(opts.maxDurationSeconds)] : []),\r\n \"-i\",\r\n inName,\r\n \"-map\",\r\n \"0:v:0\",\r\n \"-map\",\r\n \"0:a?\",\r\n \"-c:v\",\r\n \"libvpx-vp9\",\r\n \"-b:v\",\r\n \"0\",\r\n \"-crf\",\r\n String(opts.crf),\r\n \"-deadline\",\r\n opts.deadline,\r\n \"-row-mt\",\r\n \"1\",\r\n \"-c:a\",\r\n \"libopus\",\r\n outName,\r\n ]\r\n\r\n const logs: string[] = []\r\n const onLog = ({ message }: { message: string }) => {\r\n if (typeof message !== \"string\" || message.length === 0) return\r\n logs.push(message)\r\n if (logs.length > 60) logs.shift()\r\n }\r\n\r\n try {\r\n ffmpeg.on(\"log\", onLog)\r\n await ffmpeg.exec(args)\r\n const outData = await ffmpeg.readFile(outName)\r\n return outData instanceof Uint8Array ? outData : new Uint8Array(outData as any)\r\n } catch (err) {\r\n const detail = [\r\n `videoToWebm failed.`,\r\n `input=${inName}`,\r\n `output=${outName}`,\r\n `args=${formatArgs(args)}`,\r\n `error=${toErrorMessage(err)}`,\r\n logs.length ? `ffmpeg_logs:\\n${logs.join(\"\\n\")}` : \"ffmpeg_logs: <none>\",\r\n ].join(\"\\n\")\r\n throw new Error(detail)\r\n } finally {\r\n ffmpeg.off(\"log\", onLog)\r\n await Promise.allSettled([ffmpeg.deleteFile(inName), ffmpeg.deleteFile(outName)])\r\n }\r\n })\r\n\r\n return new Blob([outBytes], { type: \"video/webm\" })\r\n}\r\n\r\nasync function runVideoTranscodeLowMemoryJob(ffmpeg: FFmpeg, blob: Blob, opts: NormalizedVideoOptions) {\r\n const jobId = createFsSafeId()\r\n const inName = `input-${jobId}`\r\n const outName = `output-${jobId}.webm`\r\n const safeWidth = Math.max(320, Math.round(opts.fallbackMaxWidth))\r\n\r\n const outBytes = await runWithFfmpegLock(async () => {\r\n await ffmpeg.writeFile(inName, await fetchFile(blob))\r\n\r\n const args: string[] = [\r\n ...(opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? [\"-t\", String(opts.maxDurationSeconds)] : []),\r\n \"-i\",\r\n inName,\r\n \"-map\",\r\n \"0:v:0\",\r\n \"-map\",\r\n \"0:a?\",\r\n \"-vf\",\r\n `scale='min(${safeWidth},iw)':-2:flags=bilinear`,\r\n \"-c:v\",\r\n \"libvpx-vp9\",\r\n \"-b:v\",\r\n \"0\",\r\n \"-crf\",\r\n String(Math.max(34, opts.crf)),\r\n \"-deadline\",\r\n \"realtime\",\r\n \"-cpu-used\",\r\n \"8\",\r\n \"-row-mt\",\r\n \"1\",\r\n \"-c:a\",\r\n \"libopus\",\r\n outName,\r\n ]\r\n\r\n const logs: string[] = []\r\n const onLog = ({ message }: { message: string }) => {\r\n if (typeof message !== \"string\" || message.length === 0) return\r\n logs.push(message)\r\n if (logs.length > 60) logs.shift()\r\n }\r\n\r\n try {\r\n ffmpeg.on(\"log\", onLog)\r\n await ffmpeg.exec(args)\r\n const outData = await ffmpeg.readFile(outName)\r\n return outData instanceof Uint8Array ? outData : new Uint8Array(outData as any)\r\n } catch (err) {\r\n const detail = [\r\n `videoToWebm low-memory fallback failed.`,\r\n `input=${inName}`,\r\n `output=${outName}`,\r\n `args=${formatArgs(args)}`,\r\n `error=${toErrorMessage(err)}`,\r\n logs.length ? `ffmpeg_logs:\\n${logs.join(\"\\n\")}` : \"ffmpeg_logs: <none>\",\r\n ].join(\"\\n\")\r\n throw new Error(detail)\r\n } finally {\r\n ffmpeg.off(\"log\", onLog)\r\n await Promise.allSettled([ffmpeg.deleteFile(inName), ffmpeg.deleteFile(outName)])\r\n }\r\n })\r\n\r\n return new Blob([outBytes], { type: \"video/webm\" })\r\n}\r\n\r\nexport async function videoToWebm(\r\n input: VideoToWebmInput,\r\n options: VideoToWebmOptions = {}\r\n): Promise<VideoToWebmResult> {\r\n const opts = { ...VIDEO_DEFAULTS, ...options }\r\n const blob = await inputToBlob(input)\r\n assertVideoInputGuardrails(blob, opts)\r\n\r\n let outBlob: Blob\r\n try {\r\n const ffmpeg = await getFfmpeg(opts.ffmpeg)\r\n outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts)\r\n } catch (err) {\r\n if (!isRecoverableWasmError(err)) throw err\r\n await resetFfmpegRuntime()\r\n const ffmpeg = await getFfmpeg(opts.ffmpeg)\r\n try {\r\n outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts)\r\n } catch (retryErr) {\r\n if (!isRecoverableWasmError(retryErr) || !opts.enableLowMemoryFallback) throw retryErr\r\n await resetFfmpegRuntime()\r\n const ffmpegFallback = await getFfmpeg(opts.ffmpeg)\r\n outBlob = await runVideoTranscodeLowMemoryJob(ffmpegFallback, blob, opts)\r\n }\r\n }\r\n\r\n const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : undefined\r\n return { blob: outBlob, file, isWebm: true }\r\n}\r\n\r\nexport async function videoToWebmDebug(\r\n input: VideoToWebmInput,\r\n options: VideoToWebmOptions = {},\r\n onEvent?: (event: VideoToWebmDebugEvent) => void\r\n): Promise<VideoToWebmDebugResult> {\r\n const events: VideoToWebmDebugEvent[] = []\r\n let lastStage: VideoToWebmDebugStage = \"input\"\r\n const emit = (event: VideoToWebmDebugEvent) => {\r\n lastStage = event.stage\r\n events.push(event)\r\n onEvent?.(event)\r\n }\r\n\r\n try {\r\n emit({ stage: \"input\", message: \"Reading input as Blob.\" })\r\n const opts = { ...VIDEO_DEFAULTS, ...options }\r\n const blob = await inputToBlob(input)\r\n\r\n emit({\r\n stage: \"guardrail\",\r\n message: \"Checking browser ffmpeg.wasm memory guardrail.\",\r\n detail: `input_bytes=${blob.size};max_input_bytes=${opts.maxInputBytes}`,\r\n })\r\n assertVideoInputGuardrails(blob, opts)\r\n\r\n let outBlob: Blob\r\n try {\r\n emit({ stage: \"init\", message: \"Initializing ffmpeg.wasm runtime.\" })\r\n const ffmpeg = await getFfmpeg(opts.ffmpeg)\r\n emit({ stage: \"transcode\", message: \"Running video transcode job (VP9 + Opus).\" })\r\n outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts)\r\n } catch (err) {\r\n if (!isRecoverableWasmError(err)) throw err\r\n emit({\r\n stage: \"retry-reset\",\r\n message: \"Recoverable wasm error detected, resetting runtime.\",\r\n detail: toErrorMessage(err),\r\n })\r\n await resetFfmpegRuntime()\r\n emit({ stage: \"retry-init\", message: \"Reinitializing ffmpeg.wasm runtime.\" })\r\n const ffmpeg = await getFfmpeg(opts.ffmpeg)\r\n emit({ stage: \"retry-transcode\", message: \"Retrying video transcode job.\" })\r\n try {\r\n outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts)\r\n } catch (retryErr) {\r\n if (!isRecoverableWasmError(retryErr) || !opts.enableLowMemoryFallback) throw retryErr\r\n emit({\r\n stage: \"retry-transcode\",\r\n message: \"Running low-memory fallback transcode profile.\",\r\n detail: `fallback_max_width=${opts.fallbackMaxWidth}`,\r\n })\r\n await resetFfmpegRuntime()\r\n const ffmpegFallback = await getFfmpeg(opts.ffmpeg)\r\n outBlob = await runVideoTranscodeLowMemoryJob(ffmpegFallback, blob, opts)\r\n }\r\n }\r\n\r\n const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : undefined\r\n emit({\r\n stage: \"done\",\r\n message: \"Video converted to WebM.\",\r\n detail: `output_bytes=${outBlob.size};mime=${outBlob.type}`,\r\n })\r\n return {\r\n ok: true,\r\n blob: outBlob,\r\n file,\r\n isWebm: true,\r\n events,\r\n diagnostic: { cause: \"none\", stage: \"done\", recoverable: false, hints: [] },\r\n }\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error(toErrorMessage(err))\r\n const diagnostic = buildVideoDiagnostic(lastStage, error)\r\n emit({\r\n stage: \"done\",\r\n message: \"Video conversion failed.\",\r\n detail: `cause=${diagnostic.cause};${error.message}`,\r\n })\r\n return { ok: false, error, events, diagnostic }\r\n }\r\n}\r\n\r\nexport type ConvertInput = ImageToWebpInput\r\n\r\nexport type ConvertOptions = {\r\n image?: ImageToWebpOptions\r\n video?: VideoToWebmOptions\r\n returnFile?: boolean\r\n fileName?: string\r\n}\r\n\r\nasync function detectKindBrowser(input: ConvertInput): Promise<\"image\" | \"video\" | \"unknown\"> {\r\n if (typeof input !== \"string\") {\r\n const t = (input.type || \"\").toLowerCase()\r\n if (t.startsWith(\"image/\")) return \"image\"\r\n if (t.startsWith(\"video/\")) return \"video\"\r\n }\r\n\r\n const blob = await inputToBlob(input)\r\n const buf = new Uint8Array(await blob.slice(0, 4100).arrayBuffer())\r\n const ft = await fileTypeFromBuffer(buf)\r\n if (!ft) return \"unknown\"\r\n if (ft.mime.startsWith(\"image/\")) return \"image\"\r\n if (ft.mime.startsWith(\"video/\")) return \"video\"\r\n return \"unknown\"\r\n}\r\n\r\nexport async function convert(input: ConvertInput, options: ConvertOptions = {}): Promise<ConvertResult> {\r\n const kind = await detectKindBrowser(input)\r\n const returnFile = options.returnFile ?? false\r\n\r\n if (kind === \"image\") {\r\n const res = await imageToWebp(input, { ...options.image, returnFile, fileName: options.fileName })\r\n return { kind: \"image\", ...res }\r\n }\r\n\r\n if (kind === \"video\") {\r\n const res = await videoToWebm(input, { ...options.video, returnFile, fileName: options.fileName })\r\n return { kind: \"video\", ...res }\r\n }\r\n\r\n throw new Error(\"Unsupported input type. Expected an image/* or video/* asset.\")\r\n}\r\n\r\nfunction computeTargetSize(\r\n srcW: number,\r\n srcH: number,\r\n maxW: number,\r\n maxH: number\r\n): { width: number; height: number } {\r\n if (srcW <= 0 || srcH <= 0) return { width: srcW, height: srcH }\r\n const scale = Math.min(1, maxW / srcW, maxH / srcH)\r\n return {\r\n width: Math.max(1, Math.round(srcW * scale)),\r\n height: Math.max(1, Math.round(srcH * scale)),\r\n }\r\n}\r\n\r\nasync function decodeImage(blob: Blob): Promise<{ bitmap: ImageBitmap; width: number; height: number }> {\r\n if (typeof createImageBitmap === \"function\") {\r\n const bitmap = await createImageBitmap(blob, { imageOrientation: \"from-image\" as any })\r\n return { bitmap, width: bitmap.width, height: bitmap.height }\r\n }\r\n\r\n const url = URL.createObjectURL(blob)\r\n try {\r\n const img = await new Promise<HTMLImageElement>((resolve, reject) => {\r\n const el = new Image()\r\n el.onload = () => resolve(el)\r\n el.onerror = () => reject(new Error(\"Failed to decode image\"))\r\n el.src = url\r\n })\r\n\r\n const canvas = document.createElement(\"canvas\")\r\n canvas.width = img.naturalWidth || img.width\r\n canvas.height = img.naturalHeight || img.height\r\n const ctx = canvas.getContext(\"2d\")\r\n if (!ctx) throw new Error(\"Canvas 2D context not available\")\r\n ctx.drawImage(img, 0, 0)\r\n\r\n const dataUrl = canvas.toDataURL(\"image/png\")\r\n const pngBlob = await (await fetch(dataUrl)).blob()\r\n const bitmap = await createImageBitmap(pngBlob)\r\n return { bitmap, width: bitmap.width, height: bitmap.height }\r\n } finally {\r\n URL.revokeObjectURL(url)\r\n }\r\n}\r\n\r\nasync function encodeWebpFromBitmap(\r\n bitmap: ImageBitmap,\r\n width: number,\r\n height: number,\r\n quality: number\r\n): Promise<Blob | null> {\r\n const q = clamp01(quality)\r\n\r\n const hasOffscreen = typeof OffscreenCanvas !== \"undefined\"\r\n if (hasOffscreen) {\r\n const canvas = new OffscreenCanvas(width, height)\r\n const ctx = canvas.getContext(\"2d\")\r\n if (!ctx) return null\r\n ctx.drawImage(bitmap, 0, 0, width, height)\r\n try {\r\n return await canvas.convertToBlob({ type: \"image/webp\", quality: q })\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n const canvas = document.createElement(\"canvas\")\r\n canvas.width = width\r\n canvas.height = height\r\n const ctx = canvas.getContext(\"2d\")\r\n if (!ctx) return null\r\n ctx.drawImage(bitmap, 0, 0, width, height)\r\n\r\n const blob = await new Promise<Blob | null>((resolve) => {\r\n canvas.toBlob((b) => resolve(b), \"image/webp\", q)\r\n })\r\n return blob\r\n}\r\n\r\n/**\r\n * Convert an image (png/jpg/etc) to WebP and shrink size while keeping similar quality.\r\n *\r\n * - Downscales to `maxWidth`/`maxHeight` (keeps aspect ratio).\r\n * - Encodes as WebP with a quality search to try meeting `targetBytes` (if provided).\r\n */\r\nexport async function imageToWebp(\r\n input: ImageToWebpInput,\r\n options: ImageToWebpOptions = {}\r\n): Promise<ImageToWebpResult> {\r\n const opts = { ...DEFAULTS, ...options }\r\n const blob = await inputToBlob(input)\r\n\r\n const { bitmap, width: srcW, height: srcH } = await decodeImage(blob)\r\n try {\r\n const { width, height } = computeTargetSize(srcW, srcH, opts.maxWidth, opts.maxHeight)\r\n\r\n if (!opts.targetBytes || opts.targetBytes <= 0) {\r\n const out = await encodeWebpFromBitmap(bitmap, width, height, opts.maxQuality)\r\n if (!out) {\r\n if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n }\r\n\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([out], fileName, { type: out.type }) : undefined\r\n return { blob: out, file, width, height, quality: opts.maxQuality, isWebp: true }\r\n }\r\n\r\n const maxQ = clamp01(opts.maxQuality)\r\n const minQ = clamp01(Math.min(opts.minQuality, maxQ))\r\n\r\n const atMax = await encodeWebpFromBitmap(bitmap, width, height, maxQ)\r\n if (!atMax) {\r\n if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n return { blob, width: srcW, height: srcH, quality: 1, isWebp: false }\r\n }\r\n if (atMax.size <= opts.targetBytes) {\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([atMax], fileName, { type: atMax.type }) : undefined\r\n return { blob: atMax, file, width, height, quality: maxQ, isWebp: true }\r\n }\r\n\r\n const atMin = await encodeWebpFromBitmap(bitmap, width, height, minQ)\r\n if (!atMin) {\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([atMax], fileName, { type: atMax.type }) : undefined\r\n return { blob: atMax, file, width, height, quality: maxQ, isWebp: true }\r\n }\r\n if (atMin.size > opts.targetBytes) {\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([atMin], fileName, { type: atMin.type }) : undefined\r\n return { blob: atMin, file, width, height, quality: minQ, isWebp: true }\r\n }\r\n\r\n let lo = minQ\r\n let hi = maxQ\r\n let bestBlob: Blob = atMin\r\n let bestQ = minQ\r\n\r\n for (let i = 0; i < 7; i++) {\r\n const mid = (lo + hi) / 2\r\n const enc = await encodeWebpFromBitmap(bitmap, width, height, mid)\r\n if (!enc) break\r\n\r\n if (enc.size <= opts.targetBytes) {\r\n bestBlob = enc\r\n bestQ = mid\r\n lo = mid\r\n } else {\r\n hi = mid\r\n }\r\n }\r\n\r\n const fileName = opts.fileName ?? getInputName(input)\r\n const file = opts.returnFile ? new File([bestBlob], fileName, { type: bestBlob.type }) : undefined\r\n return { blob: bestBlob, file, width, height, quality: bestQ, isWebp: true }\r\n } finally {\r\n bitmap.close()\r\n }\r\n}\r\n\r\n"]}