weblet-convert 0.0.3 → 0.0.5

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/dist/index.js CHANGED
@@ -44,17 +44,68 @@ function defaultVideoName(input, fileName) {
44
44
  var ffmpegSingleton = null;
45
45
  var ffmpegLoadPromise = null;
46
46
  var ffmpegJobQueue = Promise.resolve();
47
- async function getFfmpeg() {
47
+ var ffmpegConfigKey = "{}";
48
+ function normalizeBrowserFfmpegLoadOptions(input) {
49
+ if (!input) return void 0;
50
+ const baseURL = input.baseURL?.trim();
51
+ const normalize = (v) => v?.trim() || void 0;
52
+ const make = (suffix) => {
53
+ if (!baseURL) return void 0;
54
+ return `${baseURL.replace(/\/+$/, "")}/${suffix}`;
55
+ };
56
+ return {
57
+ baseURL,
58
+ coreURL: normalize(input.coreURL) ?? make("ffmpeg-core.js"),
59
+ wasmURL: normalize(input.wasmURL) ?? make("ffmpeg-core.wasm"),
60
+ workerURL: normalize(input.workerURL) ?? make("ffmpeg-core.worker.js")
61
+ };
62
+ }
63
+ function getFfmpegConfigKey(input) {
64
+ return JSON.stringify(normalizeBrowserFfmpegLoadOptions(input) ?? {});
65
+ }
66
+ async function getFfmpeg(loadOptions) {
67
+ const nextKey = getFfmpegConfigKey(loadOptions);
68
+ if (nextKey !== ffmpegConfigKey) {
69
+ await resetFfmpegRuntime();
70
+ ffmpegConfigKey = nextKey;
71
+ }
48
72
  if (ffmpegSingleton) return ffmpegSingleton;
49
73
  if (ffmpegLoadPromise) return ffmpegLoadPromise;
50
74
  const ff = new FFmpeg();
75
+ const normalized = normalizeBrowserFfmpegLoadOptions(loadOptions);
76
+ const loadParams = normalized && (normalized.coreURL || normalized.wasmURL || normalized.workerURL) ? {
77
+ ...normalized.coreURL ? { coreURL: normalized.coreURL } : {},
78
+ ...normalized.wasmURL ? { wasmURL: normalized.wasmURL } : {},
79
+ ...normalized.workerURL ? { workerURL: normalized.workerURL } : {}
80
+ } : void 0;
51
81
  ffmpegLoadPromise = (async () => {
52
- await ff.load();
53
- ffmpegSingleton = ff;
54
- return ff;
82
+ try {
83
+ await ff.load(loadParams);
84
+ ffmpegSingleton = ff;
85
+ return ff;
86
+ } catch (err) {
87
+ ffmpegLoadPromise = null;
88
+ const detail = [
89
+ "Failed to initialize ffmpeg.wasm.",
90
+ `error=${toErrorMessage(err)}`,
91
+ "hint=Ensure ffmpeg core/worker assets are reachable by your bundler/runtime and not blocked by CSP/CORS."
92
+ ].join("\n");
93
+ throw new Error(detail);
94
+ }
55
95
  })();
56
96
  return ffmpegLoadPromise;
57
97
  }
98
+ async function resetFfmpegRuntime() {
99
+ const current = ffmpegSingleton;
100
+ ffmpegSingleton = null;
101
+ ffmpegLoadPromise = null;
102
+ if (current && typeof current.terminate === "function") {
103
+ try {
104
+ await current.terminate();
105
+ } catch {
106
+ }
107
+ }
108
+ }
58
109
  async function runWithFfmpegLock(job) {
59
110
  const previous = ffmpegJobQueue;
60
111
  let release;
@@ -71,15 +122,110 @@ async function runWithFfmpegLock(job) {
71
122
  function createFsSafeId() {
72
123
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
73
124
  }
125
+ function toErrorMessage(err) {
126
+ if (err instanceof Error && err.message) return err.message;
127
+ if (typeof err === "string") return err;
128
+ try {
129
+ return JSON.stringify(err);
130
+ } catch {
131
+ return String(err);
132
+ }
133
+ }
134
+ function formatArgs(args) {
135
+ return args.map((v) => /\s/.test(v) ? `"${v}"` : v).join(" ");
136
+ }
137
+ function isRecoverableWasmError(err) {
138
+ const msg = toErrorMessage(err).toLowerCase();
139
+ return msg.includes("memory access out of bounds") || msg.includes("runtimeerror") || msg.includes("aborted") || msg.includes("wasm");
140
+ }
74
141
  var VIDEO_DEFAULTS = {
75
142
  crf: 32,
76
143
  deadline: "good",
77
- returnFile: false
144
+ returnFile: false,
145
+ maxInputBytes: 64 * 1024 * 1024
78
146
  };
79
- async function videoToWebm(input, options = {}) {
80
- const opts = { ...VIDEO_DEFAULTS, ...options };
81
- const blob = await inputToBlob(input);
82
- const ffmpeg = await getFfmpeg();
147
+ function assertVideoInputGuardrails(blob, opts) {
148
+ if (opts.maxInputBytes > 0 && blob.size > opts.maxInputBytes) {
149
+ throw new Error(
150
+ [
151
+ "videoToWebm guardrail: input too large for browser ffmpeg.wasm.",
152
+ `input_bytes=${blob.size}`,
153
+ `max_input_bytes=${opts.maxInputBytes}`,
154
+ "hint=Lower source resolution/bitrate, trim video, or increase maxInputBytes if your environment has enough memory."
155
+ ].join("\n")
156
+ );
157
+ }
158
+ }
159
+ function buildVideoDiagnostic(stage, err) {
160
+ const raw = toErrorMessage(err);
161
+ const lower = raw.toLowerCase();
162
+ const recoverable = isRecoverableWasmError(err);
163
+ if (lower.includes("failed to fetch")) {
164
+ return {
165
+ cause: "input-fetch-failed",
166
+ stage,
167
+ rawError: raw,
168
+ recoverable: false,
169
+ hints: ["Verify URL reachability and CORS policy for browser fetch."]
170
+ };
171
+ }
172
+ if (lower.includes("input too large") || lower.includes("max_input_bytes")) {
173
+ return {
174
+ cause: "input-too-large",
175
+ stage,
176
+ rawError: raw,
177
+ recoverable: false,
178
+ hints: ["Increase maxInputBytes or reduce source size/resolution/duration."]
179
+ };
180
+ }
181
+ if (lower.includes("failed to initialize ffmpeg.wasm")) {
182
+ return {
183
+ cause: "ffmpeg-init-failed",
184
+ stage,
185
+ rawError: raw,
186
+ recoverable,
187
+ hints: [
188
+ "Provide video.ffmpeg.baseURL or explicit coreURL/wasmURL/workerURL.",
189
+ "Ensure CSP/CORS allows ffmpeg worker + wasm assets."
190
+ ]
191
+ };
192
+ }
193
+ if (lower.includes("memory access out of bounds") || lower.includes("runtimeerror") || lower.includes("aborted")) {
194
+ return {
195
+ cause: "ffmpeg-wasm-runtime-crash",
196
+ stage,
197
+ rawError: raw,
198
+ recoverable: true,
199
+ hints: ["Reduce workload: trim duration, lower resolution/bitrate, or split video."]
200
+ };
201
+ }
202
+ if (lower.includes("videotowebm failed")) {
203
+ return {
204
+ cause: "ffmpeg-transcode-failed",
205
+ stage,
206
+ rawError: raw,
207
+ recoverable,
208
+ hints: ["Inspect ffmpeg_logs in rawError for codec/container-specific details."]
209
+ };
210
+ }
211
+ if (lower.includes("unsupported input type")) {
212
+ return {
213
+ cause: "unsupported-input",
214
+ stage,
215
+ rawError: raw,
216
+ recoverable: false,
217
+ hints: ["Use a valid image/* or video/* input and ensure proper MIME/magic bytes."]
218
+ };
219
+ }
220
+ return {
221
+ cause: "unknown",
222
+ stage,
223
+ rawError: raw,
224
+ recoverable,
225
+ hints: ["Use videoToWebmDebug() output to trace failing stage and share rawError."]
226
+ };
227
+ }
228
+ async function runVideoTranscodeJob(ffmpeg, blob, opts) {
83
229
  const jobId = createFsSafeId();
84
230
  const inName = `input-${jobId}`;
85
231
  const outName = `output-${jobId}.webm`;
@@ -107,18 +253,114 @@ async function videoToWebm(input, options = {}) {
107
253
  "libopus",
108
254
  outName
109
255
  ];
256
+ const logs = [];
257
+ const onLog = ({ message }) => {
258
+ if (typeof message !== "string" || message.length === 0) return;
259
+ logs.push(message);
260
+ if (logs.length > 60) logs.shift();
261
+ };
110
262
  try {
263
+ ffmpeg.on("log", onLog);
111
264
  await ffmpeg.exec(args);
112
265
  const outData = await ffmpeg.readFile(outName);
113
266
  return outData instanceof Uint8Array ? outData : new Uint8Array(outData);
267
+ } catch (err) {
268
+ const detail = [
269
+ `videoToWebm failed.`,
270
+ `input=${inName}`,
271
+ `output=${outName}`,
272
+ `args=${formatArgs(args)}`,
273
+ `error=${toErrorMessage(err)}`,
274
+ logs.length ? `ffmpeg_logs:
275
+ ${logs.join("\n")}` : "ffmpeg_logs: <none>"
276
+ ].join("\n");
277
+ throw new Error(detail);
114
278
  } finally {
279
+ ffmpeg.off("log", onLog);
115
280
  await Promise.allSettled([ffmpeg.deleteFile(inName), ffmpeg.deleteFile(outName)]);
116
281
  }
117
282
  });
118
- const outBlob = new Blob([outBytes], { type: "video/webm" });
283
+ return new Blob([outBytes], { type: "video/webm" });
284
+ }
285
+ async function videoToWebm(input, options = {}) {
286
+ const opts = { ...VIDEO_DEFAULTS, ...options };
287
+ const blob = await inputToBlob(input);
288
+ assertVideoInputGuardrails(blob, opts);
289
+ let outBlob;
290
+ try {
291
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
292
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
293
+ } catch (err) {
294
+ if (!isRecoverableWasmError(err)) throw err;
295
+ await resetFfmpegRuntime();
296
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
297
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
298
+ }
119
299
  const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : void 0;
120
300
  return { blob: outBlob, file, isWebm: true };
121
301
  }
302
+ async function videoToWebmDebug(input, options = {}, onEvent) {
303
+ const events = [];
304
+ let lastStage = "input";
305
+ const emit = (event) => {
306
+ lastStage = event.stage;
307
+ events.push(event);
308
+ onEvent?.(event);
309
+ };
310
+ try {
311
+ emit({ stage: "input", message: "Reading input as Blob." });
312
+ const opts = { ...VIDEO_DEFAULTS, ...options };
313
+ const blob = await inputToBlob(input);
314
+ emit({
315
+ stage: "guardrail",
316
+ message: "Checking browser ffmpeg.wasm memory guardrail.",
317
+ detail: `input_bytes=${blob.size};max_input_bytes=${opts.maxInputBytes}`
318
+ });
319
+ assertVideoInputGuardrails(blob, opts);
320
+ let outBlob;
321
+ try {
322
+ emit({ stage: "init", message: "Initializing ffmpeg.wasm runtime." });
323
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
324
+ emit({ stage: "transcode", message: "Running video transcode job (VP9 + Opus)." });
325
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
326
+ } catch (err) {
327
+ if (!isRecoverableWasmError(err)) throw err;
328
+ emit({
329
+ stage: "retry-reset",
330
+ message: "Recoverable wasm error detected, resetting runtime.",
331
+ detail: toErrorMessage(err)
332
+ });
333
+ await resetFfmpegRuntime();
334
+ emit({ stage: "retry-init", message: "Reinitializing ffmpeg.wasm runtime." });
335
+ const ffmpeg = await getFfmpeg(opts.ffmpeg);
336
+ emit({ stage: "retry-transcode", message: "Retrying video transcode job." });
337
+ outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts);
338
+ }
339
+ const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : void 0;
340
+ emit({
341
+ stage: "done",
342
+ message: "Video converted to WebM.",
343
+ detail: `output_bytes=${outBlob.size};mime=${outBlob.type}`
344
+ });
345
+ return {
346
+ ok: true,
347
+ blob: outBlob,
348
+ file,
349
+ isWebm: true,
350
+ events,
351
+ diagnostic: { cause: "none", stage: "done", recoverable: false, hints: [] }
352
+ };
353
+ } catch (err) {
354
+ const error = err instanceof Error ? err : new Error(toErrorMessage(err));
355
+ const diagnostic = buildVideoDiagnostic(lastStage, error);
356
+ emit({
357
+ stage: "done",
358
+ message: "Video conversion failed.",
359
+ detail: `cause=${diagnostic.cause};${error.message}`
360
+ });
361
+ return { ok: false, error, events, diagnostic };
362
+ }
363
+ }
122
364
  async function detectKindBrowser(input) {
123
365
  if (typeof input !== "string") {
124
366
  const t = (input.type || "").toLowerCase();
@@ -269,6 +511,6 @@ async function imageToWebp(input, options = {}) {
269
511
  }
270
512
  }
271
513
 
272
- export { convert, imageToWebp, videoToWebm };
514
+ export { convert, imageToWebp, videoToWebm, videoToWebmDebug };
273
515
  //# sourceMappingURL=index.js.map
274
516
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/browser/index.ts"],"names":["canvas","ctx","fileName","file"],"mappings":";;;;;AA0DA,IAAM,QAAA,GAKF;AAAA,EACF,QAAA,EAAU,IAAA;AAAA,EACV,SAAA,EAAW,IAAA;AAAA,EACX,UAAA,EAAY,IAAA;AAAA,EACZ,UAAA,EAAY,IAAA;AAAA,EACZ,KAAA,EAAO,IAAA;AAAA,EACP,UAAA,EAAY;AACd,CAAA;AAEA,SAAS,QAAQ,CAAA,EAAW;AAC1B,EAAA,OAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,CAAA;AACnC;AAEA,SAAS,WAAW,IAAA,EAAc;AAChC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAA;AACzC,EAAA,OAAO,CAAA,EAAG,QAAQ,OAAO,CAAA,KAAA,CAAA;AAC3B;AAEA,SAAS,aAAa,KAAA,EAAyB;AAC7C,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,YAAA;AACtC,EAAA,IAAI,iBAAiB,IAAA,IAAQ,KAAA,CAAM,MAAM,OAAO,UAAA,CAAW,MAAM,IAAI,CAAA;AACrE,EAAA,OAAO,YAAA;AACT;AAEA,eAAe,YAAY,KAAA,EAAwC;AACjE,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,UAAU,CAAA,CAAE,CAAA;AAC/E,IAAA,OAAO,MAAM,IAAI,IAAA,EAAK;AAAA,EACxB;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,WAAW,IAAA,EAAc;AAChC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAA;AACzC,EAAA,OAAO,CAAA,EAAG,QAAQ,OAAO,CAAA,KAAA,CAAA;AAC3B;AAEA,SAAS,gBAAA,CAAiB,OAA6B,QAAA,EAAmB;AACxE,EAAA,IAAI,YAAY,QAAA,CAAS,IAAA,EAAK,CAAE,MAAA,GAAS,GAAG,OAAO,QAAA;AACnD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,YAAA;AACtC,EAAA,IAAI,iBAAiB,IAAA,IAAQ,KAAA,CAAM,MAAM,OAAO,UAAA,CAAW,MAAM,IAAI,CAAA;AACrE,EAAA,OAAO,YAAA;AACT;AAEA,IAAI,eAAA,GAAiC,IAAA;AACrC,IAAI,iBAAA,GAA4C,IAAA;AAChD,IAAI,cAAA,GAAgC,QAAQ,OAAA,EAAQ;AAEpD,eAAe,SAAA,GAA6B;AAC1C,EAAA,IAAI,iBAAiB,OAAO,eAAA;AAC5B,EAAA,IAAI,mBAAmB,OAAO,iBAAA;AAC9B,EAAA,MAAM,EAAA,GAAK,IAAI,MAAA,EAAO;AACtB,EAAA,iBAAA,GAAA,CAAqB,YAAY;AAC/B,IAAA,MAAM,GAAG,IAAA,EAAK;AACd,IAAA,eAAA,GAAkB,EAAA;AAClB,IAAA,OAAO,EAAA;AAAA,EACT,CAAA,GAAG;AACH,EAAA,OAAO,iBAAA;AACT;AAEA,eAAe,kBAAqB,GAAA,EAAmC;AACrE,EAAA,MAAM,QAAA,GAAW,cAAA;AACjB,EAAA,IAAI,OAAA;AACJ,EAAA,cAAA,GAAiB,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AAC9C,IAAA,OAAA,GAAU,OAAA;AAAA,EACZ,CAAC,CAAA;AACD,EAAA,MAAM,QAAA;AACN,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EACnB,CAAA,SAAE;AACA,IAAA,OAAA,EAAQ;AAAA,EACV;AACF;AAEA,SAAS,cAAA,GAAyB;AAChC,EAAA,OAAO,GAAG,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAC9E;AAkBA,IAAM,cAAA,GAAwF;AAAA,EAC5F,GAAA,EAAK,EAAA;AAAA,EACL,QAAA,EAAU,MAAA;AAAA,EACV,UAAA,EAAY;AACd,CAAA;AAEA,eAAsB,WAAA,CACpB,KAAA,EACA,OAAA,GAA8B,EAAC,EACH;AAC5B,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,cAAA,EAAgB,GAAG,OAAA,EAAQ;AAC7C,EAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,KAAK,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,MAAM,SAAA,EAAU;AAC/B,EAAA,MAAM,QAAQ,cAAA,EAAe;AAC7B,EAAA,MAAM,MAAA,GAAS,SAAS,KAAK,CAAA,CAAA;AAC7B,EAAA,MAAM,OAAA,GAAU,UAAU,KAAK,CAAA,KAAA,CAAA;AAE/B,EAAA,MAAM,QAAA,GAAW,MAAM,iBAAA,CAAkB,YAAY;AACnD,IAAA,MAAM,OAAO,SAAA,CAAU,MAAA,EAAQ,MAAM,SAAA,CAAU,IAAI,CAAC,CAAA;AAEpD,IAAA,MAAM,IAAA,GAAiB;AAAA,MACrB,GAAI,IAAA,CAAK,kBAAA,IAAsB,IAAA,CAAK,kBAAA,GAAqB,CAAA,GAAI,CAAC,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,kBAAkB,CAAC,IAAI,EAAC;AAAA,MACxG,IAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,YAAA;AAAA,MACA,MAAA;AAAA,MACA,GAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,MACf,WAAA;AAAA,MACA,IAAA,CAAK,QAAA;AAAA,MACL,SAAA;AAAA,MACA,GAAA;AAAA,MACA,MAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,CAAO,KAAK,IAAI,CAAA;AACtB,MAAA,MAAM,OAAA,GAAU,MAAM,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA;AAC7C,MAAA,OAAO,OAAA,YAAmB,UAAA,GAAa,OAAA,GAAU,IAAI,WAAW,OAAc,CAAA;AAAA,IAChF,CAAA,SAAE;AACA,MAAA,MAAM,OAAA,CAAQ,UAAA,CAAW,CAAC,MAAA,CAAO,UAAA,CAAW,MAAM,CAAA,EAAG,MAAA,CAAO,UAAA,CAAW,OAAO,CAAC,CAAC,CAAA;AAAA,IAClF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,GAAU,IAAI,IAAA,CAAK,CAAC,QAAQ,CAAA,EAAG,EAAE,IAAA,EAAM,YAAA,EAAc,CAAA;AAC3D,EAAA,MAAM,OAAO,IAAA,CAAK,UAAA,GAAa,IAAI,IAAA,CAAK,CAAC,OAAO,CAAA,EAAG,gBAAA,CAAiB,KAAA,EAAO,IAAA,CAAK,QAAQ,CAAA,EAAG,EAAE,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAAI,MAAA;AACrH,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,QAAQ,IAAA,EAAK;AAC7C;AAWA,eAAe,kBAAkB,KAAA,EAA6D;AAC5F,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,MAAM,CAAA,GAAA,CAAK,KAAA,CAAM,IAAA,IAAQ,EAAA,EAAI,WAAA,EAAY;AACzC,IAAA,IAAI,CAAA,CAAE,UAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,OAAA;AACnC,IAAA,IAAI,CAAA,CAAE,UAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,OAAA;AAAA,EACrC;AAEA,EAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,KAAK,CAAA;AACpC,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,WAAA,EAAa,CAAA;AAClE,EAAA,MAAM,EAAA,GAAK,MAAM,kBAAA,CAAmB,GAAG,CAAA;AACvC,EAAA,IAAI,CAAC,IAAI,OAAO,SAAA;AAChB,EAAA,IAAI,EAAA,CAAG,IAAA,CAAK,UAAA,CAAW,QAAQ,GAAG,OAAO,OAAA;AACzC,EAAA,IAAI,EAAA,CAAG,IAAA,CAAK,UAAA,CAAW,QAAQ,GAAG,OAAO,OAAA;AACzC,EAAA,OAAO,SAAA;AACT;AAEA,eAAsB,OAAA,CAAQ,KAAA,EAAqB,OAAA,GAA0B,EAAC,EAA2B;AACvG,EAAA,MAAM,IAAA,GAAO,MAAM,iBAAA,CAAkB,KAAK,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,KAAA;AAEzC,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,KAAA,EAAO,EAAE,GAAG,OAAA,CAAQ,KAAA,EAAO,UAAA,EAAY,QAAA,EAAU,OAAA,CAAQ,QAAA,EAAU,CAAA;AACjG,IAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,GAAG,GAAA,EAAI;AAAA,EACjC;AAEA,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,KAAA,EAAO,EAAE,GAAG,OAAA,CAAQ,KAAA,EAAO,UAAA,EAAY,QAAA,EAAU,OAAA,CAAQ,QAAA,EAAU,CAAA;AACjG,IAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,GAAG,GAAA,EAAI;AAAA,EACjC;AAEA,EAAA,MAAM,IAAI,MAAM,+DAA+D,CAAA;AACjF;AAEA,SAAS,iBAAA,CACP,IAAA,EACA,IAAA,EACA,IAAA,EACA,IAAA,EACmC;AACnC,EAAA,IAAI,IAAA,IAAQ,KAAK,IAAA,IAAQ,CAAA,SAAU,EAAE,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAC/D,EAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,GAAO,IAAA,EAAM,OAAO,IAAI,CAAA;AAClD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,GAAO,KAAK,CAAC,CAAA;AAAA,IAC3C,MAAA,EAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,GAAO,KAAK,CAAC;AAAA,GAC9C;AACF;AAEA,eAAe,YAAY,IAAA,EAA6E;AACtG,EAAA,IAAI,OAAO,sBAAsB,UAAA,EAAY;AAC3C,IAAA,MAAM,SAAS,MAAM,iBAAA,CAAkB,MAAM,EAAE,gBAAA,EAAkB,cAAqB,CAAA;AACtF,IAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,EAAQ,OAAO,MAAA,EAAO;AAAA,EAC9D;AAEA,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,IAAI,OAAA,CAA0B,CAAC,SAAS,MAAA,KAAW;AACnE,MAAA,MAAM,EAAA,GAAK,IAAI,KAAA,EAAM;AACrB,MAAA,EAAA,CAAG,MAAA,GAAS,MAAM,OAAA,CAAQ,EAAE,CAAA;AAC5B,MAAA,EAAA,CAAG,UAAU,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,wBAAwB,CAAC,CAAA;AAC7D,MAAA,EAAA,CAAG,GAAA,GAAM,GAAA;AAAA,IACX,CAAC,CAAA;AAED,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,IAAA,MAAA,CAAO,KAAA,GAAQ,GAAA,CAAI,YAAA,IAAgB,GAAA,CAAI,KAAA;AACvC,IAAA,MAAA,CAAO,MAAA,GAAS,GAAA,CAAI,aAAA,IAAiB,GAAA,CAAI,MAAA;AACzC,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,IAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAC3D,IAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,CAAA,EAAG,CAAC,CAAA;AAEvB,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,SAAA,CAAU,WAAW,CAAA;AAC5C,IAAA,MAAM,UAAU,MAAA,CAAO,MAAM,KAAA,CAAM,OAAO,GAAG,IAAA,EAAK;AAClD,IAAA,MAAM,MAAA,GAAS,MAAM,iBAAA,CAAkB,OAAO,CAAA;AAC9C,IAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,EAAQ,OAAO,MAAA,EAAO;AAAA,EAC9D,CAAA,SAAE;AACA,IAAA,GAAA,CAAI,gBAAgB,GAAG,CAAA;AAAA,EACzB;AACF;AAEA,eAAe,oBAAA,CACb,MAAA,EACA,KAAA,EACA,MAAA,EACA,OAAA,EACsB;AACtB,EAAA,MAAM,CAAA,GAAI,QAAQ,OAAO,CAAA;AAEzB,EAAA,MAAM,YAAA,GAAe,OAAO,eAAA,KAAoB,WAAA;AAChD,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,MAAMA,OAAAA,GAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AAChD,IAAA,MAAMC,IAAAA,GAAMD,OAAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,IAAA,IAAI,CAACC,MAAK,OAAO,IAAA;AACjB,IAAAA,KAAI,SAAA,CAAU,MAAA,EAAQ,CAAA,EAAG,CAAA,EAAG,OAAO,MAAM,CAAA;AACzC,IAAA,IAAI;AACF,MAAA,OAAO,MAAMD,QAAO,aAAA,CAAc,EAAE,MAAM,YAAA,EAAc,OAAA,EAAS,GAAG,CAAA;AAAA,IACtE,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,EAAA,MAAA,CAAO,KAAA,GAAQ,KAAA;AACf,EAAA,MAAA,CAAO,MAAA,GAAS,MAAA;AAChB,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,GAAA,CAAI,SAAA,CAAU,MAAA,EAAQ,CAAA,EAAG,CAAA,EAAG,OAAO,MAAM,CAAA;AAEzC,EAAA,MAAM,IAAA,GAAO,MAAM,IAAI,OAAA,CAAqB,CAAC,OAAA,KAAY;AACvD,IAAA,MAAA,CAAO,OAAO,CAAC,CAAA,KAAM,QAAQ,CAAC,CAAA,EAAG,cAAc,CAAC,CAAA;AAAA,EAClD,CAAC,CAAA;AACD,EAAA,OAAO,IAAA;AACT;AAQA,eAAsB,WAAA,CACpB,KAAA,EACA,OAAA,GAA8B,EAAC,EACH;AAC5B,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,QAAA,EAAU,GAAG,OAAA,EAAQ;AACvC,EAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,KAAK,CAAA;AAEpC,EAAA,MAAM,EAAE,QAAQ,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAK,GAAI,MAAM,WAAA,CAAY,IAAI,CAAA;AACpE,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAO,GAAI,iBAAA,CAAkB,MAAM,IAAA,EAAM,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,SAAS,CAAA;AAErF,IAAA,IAAI,CAAC,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,eAAe,CAAA,EAAG;AAC9C,MAAA,MAAM,MAAM,MAAM,oBAAA,CAAqB,QAAQ,KAAA,EAAO,MAAA,EAAQ,KAAK,UAAU,CAAA;AAC7E,MAAA,IAAI,CAAC,GAAA,EAAK;AACR,QAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,CAAA,EAAG,QAAQ,KAAA,EAAM;AACrF,QAAA,OAAO,EAAE,MAAM,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAM,OAAA,EAAS,CAAA,EAAG,MAAA,EAAQ,KAAA,EAAM;AAAA,MACtE;AAEA,MAAA,MAAME,SAAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,KAAK,CAAA;AACpD,MAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,IAAI,KAAK,CAAC,GAAG,CAAA,EAAGD,SAAAA,EAAU,EAAE,IAAA,EAAM,GAAA,CAAI,IAAA,EAAM,CAAA,GAAI,KAAA,CAAA;AAC/E,MAAA,OAAO,EAAE,IAAA,EAAM,GAAA,EAAK,IAAA,EAAAC,KAAAA,EAAM,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,CAAK,UAAA,EAAY,MAAA,EAAQ,IAAA,EAAK;AAAA,IAClF;AAEA,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA;AACpC,IAAA,MAAM,OAAO,OAAA,CAAQ,IAAA,CAAK,IAAI,IAAA,CAAK,UAAA,EAAY,IAAI,CAAC,CAAA;AAEpD,IAAA,MAAM,QAAQ,MAAM,oBAAA,CAAqB,MAAA,EAAQ,KAAA,EAAO,QAAQ,IAAI,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,CAAA,EAAG,QAAQ,KAAA,EAAM;AACrF,MAAA,OAAO,EAAE,MAAM,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAM,OAAA,EAAS,CAAA,EAAG,MAAA,EAAQ,KAAA,EAAM;AAAA,IACtE;AACA,IAAA,IAAI,KAAA,CAAM,IAAA,IAAQ,IAAA,CAAK,WAAA,EAAa;AAClC,MAAA,MAAMD,SAAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,KAAK,CAAA;AACpD,MAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,IAAI,KAAK,CAAC,KAAK,CAAA,EAAGD,SAAAA,EAAU,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,CAAA,GAAI,KAAA,CAAA;AACnF,MAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAAC,KAAAA,EAAM,OAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAAA,IACzE;AAEA,IAAA,MAAM,QAAQ,MAAM,oBAAA,CAAqB,MAAA,EAAQ,KAAA,EAAO,QAAQ,IAAI,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAMD,SAAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,KAAK,CAAA;AACpD,MAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,IAAI,KAAK,CAAC,KAAK,CAAA,EAAGD,SAAAA,EAAU,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,CAAA,GAAI,KAAA,CAAA;AACnF,MAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAAC,KAAAA,EAAM,OAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAAA,IACzE;AACA,IAAA,IAAI,KAAA,CAAM,IAAA,GAAO,IAAA,CAAK,WAAA,EAAa;AACjC,MAAA,MAAMD,SAAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,KAAK,CAAA;AACpD,MAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,IAAI,KAAK,CAAC,KAAK,CAAA,EAAGD,SAAAA,EAAU,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,CAAA,GAAI,KAAA,CAAA;AACnF,MAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAAC,KAAAA,EAAM,OAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAAA,IACzE;AAEA,IAAA,IAAI,EAAA,GAAK,IAAA;AACT,IAAA,IAAI,EAAA,GAAK,IAAA;AACT,IAAA,IAAI,QAAA,GAAiB,KAAA;AACrB,IAAA,IAAI,KAAA,GAAQ,IAAA;AAEZ,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,MAAA,MAAM,GAAA,GAAA,CAAO,KAAK,EAAA,IAAM,CAAA;AACxB,MAAA,MAAM,MAAM,MAAM,oBAAA,CAAqB,MAAA,EAAQ,KAAA,EAAO,QAAQ,GAAG,CAAA;AACjE,MAAA,IAAI,CAAC,GAAA,EAAK;AAEV,MAAA,IAAI,GAAA,CAAI,IAAA,IAAQ,IAAA,CAAK,WAAA,EAAa;AAChC,QAAA,QAAA,GAAW,GAAA;AACX,QAAA,KAAA,GAAQ,GAAA;AACR,QAAA,EAAA,GAAK,GAAA;AAAA,MACP,CAAA,MAAO;AACL,QAAA,EAAA,GAAK,GAAA;AAAA,MACP;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,KAAK,CAAA;AACpD,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,GAAa,IAAI,KAAK,CAAC,QAAQ,CAAA,EAAG,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,CAAS,IAAA,EAAM,CAAA,GAAI,KAAA,CAAA;AACzF,IAAA,OAAO,EAAE,MAAM,QAAA,EAAU,IAAA,EAAM,OAAO,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAK;AAAA,EAC7E,CAAA,SAAE;AACA,IAAA,MAAA,CAAO,KAAA,EAAM;AAAA,EACf;AACF","file":"index.js","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":["canvas","ctx","fileName","file"],"mappings":"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","file":"index.js","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 * 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<Pick<VideoToWebmOptions, \"crf\" | \"deadline\" | \"returnFile\" | \"maxInputBytes\">> = {\r\n crf: 32,\r\n deadline: \"good\",\r\n returnFile: false,\r\n maxInputBytes: 64 * 1024 * 1024,\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<Pick<VideoToWebmOptions, \"crf\" | \"deadline\" | \"returnFile\" | \"maxInputBytes\">> &\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\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 outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts)\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 outBlob = await runVideoTranscodeJob(ffmpeg, blob, opts)\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"]}
@@ -143,9 +143,54 @@ async function withTempDir(fn) {
143
143
  }
144
144
  }
145
145
  function getFfmpegPath() {
146
- if (!ffmpegPath__default.default) throw new Error("ffmpeg binary not found (ffmpeg-static did not resolve a path).");
146
+ if (!ffmpegPath__default.default) {
147
+ throw new Error(
148
+ "ffmpeg binary not found (ffmpeg-static did not resolve a path). Install optional platform dependencies or provide a runtime where ffmpeg-static can resolve."
149
+ );
150
+ }
147
151
  return ffmpegPath__default.default;
148
152
  }
153
+ function formatArgs(args) {
154
+ return args.map((v) => /\s/.test(v) ? `"${v}"` : v).join(" ");
155
+ }
156
+ function getExecErrorText(err) {
157
+ if (err instanceof Error && err.message) return err.message;
158
+ try {
159
+ return JSON.stringify(err);
160
+ } catch {
161
+ return String(err);
162
+ }
163
+ }
164
+ function getLikelyMissingVp9Encoder(errText) {
165
+ const lower = errText.toLowerCase();
166
+ return lower.includes("unknown encoder") || lower.includes("libvpx-vp9") || lower.includes("error initializing output stream");
167
+ }
168
+ function buildVideoArgs(inPath, outPath, opts, codec) {
169
+ const args = [
170
+ "-hide_banner",
171
+ "-y",
172
+ ...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
173
+ "-i",
174
+ inPath,
175
+ "-map",
176
+ "0:v:0",
177
+ "-map",
178
+ "0:a?",
179
+ "-c:v",
180
+ codec === "vp9" ? "libvpx-vp9" : "libvpx",
181
+ "-b:v",
182
+ "0",
183
+ "-crf",
184
+ String(opts.crf),
185
+ "-deadline",
186
+ opts.deadline,
187
+ ...codec === "vp9" ? ["-row-mt", "1"] : [],
188
+ "-c:a",
189
+ "libopus",
190
+ outPath
191
+ ];
192
+ return args;
193
+ }
149
194
  async function videoToWebm(input, options = {}) {
150
195
  const ffmpeg = getFfmpegPath();
151
196
  const opts = { ...VIDEO_DEFAULTS, ...options };
@@ -155,31 +200,37 @@ async function videoToWebm(input, options = {}) {
155
200
  const inPath = path.join(dir, "input");
156
201
  const outPath = path.join(dir, "output.webm");
157
202
  await promises.writeFile(inPath, bytes);
158
- const args = [
159
- "-hide_banner",
160
- "-y",
161
- ...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
162
- "-i",
163
- inPath,
164
- "-map",
165
- "0:v:0",
166
- "-map",
167
- "0:a?",
168
- "-c:v",
169
- "libvpx-vp9",
170
- "-b:v",
171
- "0",
172
- "-crf",
173
- String(opts.crf),
174
- "-deadline",
175
- opts.deadline,
176
- "-row-mt",
177
- "1",
178
- "-c:a",
179
- "libopus",
180
- outPath
181
- ];
182
- await execa.execa(ffmpeg, args, { stderr: "pipe", stdout: "pipe" });
203
+ const vp9Args = buildVideoArgs(inPath, outPath, opts, "vp9");
204
+ try {
205
+ await execa.execa(ffmpeg, vp9Args, { stderr: "pipe", stdout: "pipe" });
206
+ } catch (err) {
207
+ const execError = getExecErrorText(err);
208
+ if (!getLikelyMissingVp9Encoder(execError)) {
209
+ throw new Error(
210
+ [
211
+ "videoToWebm failed with VP9 encode.",
212
+ `ffmpeg=${ffmpeg}`,
213
+ `args=${formatArgs(vp9Args)}`,
214
+ `error=${execError}`
215
+ ].join("\n")
216
+ );
217
+ }
218
+ const vp8Args = buildVideoArgs(inPath, outPath, opts, "vp8");
219
+ try {
220
+ await execa.execa(ffmpeg, vp8Args, { stderr: "pipe", stdout: "pipe" });
221
+ } catch (fallbackErr) {
222
+ throw new Error(
223
+ [
224
+ "videoToWebm failed with VP9 and VP8 fallback.",
225
+ `ffmpeg=${ffmpeg}`,
226
+ `vp9_args=${formatArgs(vp9Args)}`,
227
+ `vp9_error=${execError}`,
228
+ `vp8_args=${formatArgs(vp8Args)}`,
229
+ `vp8_error=${getExecErrorText(fallbackErr)}`
230
+ ].join("\n")
231
+ );
232
+ }
233
+ }
183
234
  const out = await promises.readFile(outPath);
184
235
  const outBytes = new Uint8Array(out);
185
236
  const blob = makeBlob(outBytes, type);