weblet-convert 0.0.2
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/LICENSE +22 -0
- package/README.md +121 -0
- package/dist/browser/index.cjs +254 -0
- package/dist/browser/index.cjs.map +1 -0
- package/dist/browser/index.d.cts +94 -0
- package/dist/browser/index.d.ts +94 -0
- package/dist/browser/index.js +250 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.cjs +254 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.cjs +221 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +106 -0
- package/dist/node/index.d.ts +106 -0
- package/dist/node/index.js +212 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
|
2
|
+
import { fetchFile } from '@ffmpeg/util';
|
|
3
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
4
|
+
|
|
5
|
+
// src/browser/index.ts
|
|
6
|
+
var DEFAULTS = {
|
|
7
|
+
maxWidth: 2048,
|
|
8
|
+
maxHeight: 2048,
|
|
9
|
+
maxQuality: 0.82,
|
|
10
|
+
minQuality: 0.45,
|
|
11
|
+
force: true,
|
|
12
|
+
returnFile: false
|
|
13
|
+
};
|
|
14
|
+
function clamp01(n) {
|
|
15
|
+
return Math.max(0, Math.min(1, n));
|
|
16
|
+
}
|
|
17
|
+
function toWebpName(name) {
|
|
18
|
+
const base = name.replace(/\.[^/.]+$/, "");
|
|
19
|
+
return `${base || "image"}.webp`;
|
|
20
|
+
}
|
|
21
|
+
function getInputName(input) {
|
|
22
|
+
if (typeof input === "string") return "image.webp";
|
|
23
|
+
if (input instanceof File && input.name) return toWebpName(input.name);
|
|
24
|
+
return "image.webp";
|
|
25
|
+
}
|
|
26
|
+
async function inputToBlob(input) {
|
|
27
|
+
if (typeof input === "string") {
|
|
28
|
+
const res = await fetch(input);
|
|
29
|
+
if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
|
|
30
|
+
return await res.blob();
|
|
31
|
+
}
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
34
|
+
function toWebmName(name) {
|
|
35
|
+
const base = name.replace(/\.[^/.]+$/, "");
|
|
36
|
+
return `${base || "video"}.webm`;
|
|
37
|
+
}
|
|
38
|
+
function defaultVideoName(input, fileName) {
|
|
39
|
+
if (fileName && fileName.trim().length > 0) return fileName;
|
|
40
|
+
if (typeof input === "string") return "video.webm";
|
|
41
|
+
if (input instanceof File && input.name) return toWebmName(input.name);
|
|
42
|
+
return "video.webm";
|
|
43
|
+
}
|
|
44
|
+
var ffmpegSingleton = null;
|
|
45
|
+
var ffmpegLoadPromise = null;
|
|
46
|
+
async function getFfmpeg() {
|
|
47
|
+
if (ffmpegSingleton) return ffmpegSingleton;
|
|
48
|
+
if (ffmpegLoadPromise) return ffmpegLoadPromise;
|
|
49
|
+
const ff = new FFmpeg();
|
|
50
|
+
ffmpegLoadPromise = (async () => {
|
|
51
|
+
await ff.load();
|
|
52
|
+
ffmpegSingleton = ff;
|
|
53
|
+
return ff;
|
|
54
|
+
})();
|
|
55
|
+
return ffmpegLoadPromise;
|
|
56
|
+
}
|
|
57
|
+
var VIDEO_DEFAULTS = {
|
|
58
|
+
crf: 32,
|
|
59
|
+
deadline: "good",
|
|
60
|
+
returnFile: false
|
|
61
|
+
};
|
|
62
|
+
async function videoToWebm(input, options = {}) {
|
|
63
|
+
const opts = { ...VIDEO_DEFAULTS, ...options };
|
|
64
|
+
const blob = await inputToBlob(input);
|
|
65
|
+
const ffmpeg = await getFfmpeg();
|
|
66
|
+
const inName = "input";
|
|
67
|
+
const outName = "output.webm";
|
|
68
|
+
await ffmpeg.writeFile(inName, await fetchFile(blob));
|
|
69
|
+
const args = [
|
|
70
|
+
...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
|
|
71
|
+
"-i",
|
|
72
|
+
inName,
|
|
73
|
+
"-map",
|
|
74
|
+
"0:v:0",
|
|
75
|
+
"-map",
|
|
76
|
+
"0:a?",
|
|
77
|
+
"-c:v",
|
|
78
|
+
"libvpx-vp9",
|
|
79
|
+
"-b:v",
|
|
80
|
+
"0",
|
|
81
|
+
"-crf",
|
|
82
|
+
String(opts.crf),
|
|
83
|
+
"-deadline",
|
|
84
|
+
opts.deadline,
|
|
85
|
+
"-row-mt",
|
|
86
|
+
"1",
|
|
87
|
+
"-c:a",
|
|
88
|
+
"libopus",
|
|
89
|
+
outName
|
|
90
|
+
];
|
|
91
|
+
await ffmpeg.exec(args);
|
|
92
|
+
const outData = await ffmpeg.readFile(outName);
|
|
93
|
+
const outBytes = outData instanceof Uint8Array ? outData : new Uint8Array(outData);
|
|
94
|
+
const outBlob = new Blob([outBytes], { type: "video/webm" });
|
|
95
|
+
const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : void 0;
|
|
96
|
+
return { blob: outBlob, file, isWebm: true };
|
|
97
|
+
}
|
|
98
|
+
async function detectKindBrowser(input) {
|
|
99
|
+
if (typeof input !== "string") {
|
|
100
|
+
const t = (input.type || "").toLowerCase();
|
|
101
|
+
if (t.startsWith("image/")) return "image";
|
|
102
|
+
if (t.startsWith("video/")) return "video";
|
|
103
|
+
}
|
|
104
|
+
const blob = await inputToBlob(input);
|
|
105
|
+
const buf = new Uint8Array(await blob.slice(0, 4100).arrayBuffer());
|
|
106
|
+
const ft = await fileTypeFromBuffer(buf);
|
|
107
|
+
if (!ft) return "unknown";
|
|
108
|
+
if (ft.mime.startsWith("image/")) return "image";
|
|
109
|
+
if (ft.mime.startsWith("video/")) return "video";
|
|
110
|
+
return "unknown";
|
|
111
|
+
}
|
|
112
|
+
async function convert(input, options = {}) {
|
|
113
|
+
const kind = await detectKindBrowser(input);
|
|
114
|
+
const returnFile = options.returnFile ?? false;
|
|
115
|
+
if (kind === "image") {
|
|
116
|
+
const res = await imageToWebp(input, { ...options.image, returnFile, fileName: options.fileName });
|
|
117
|
+
return { kind: "image", ...res };
|
|
118
|
+
}
|
|
119
|
+
if (kind === "video") {
|
|
120
|
+
const res = await videoToWebm(input, { ...options.video, returnFile, fileName: options.fileName });
|
|
121
|
+
return { kind: "video", ...res };
|
|
122
|
+
}
|
|
123
|
+
throw new Error("Unsupported input type. Expected an image/* or video/* asset.");
|
|
124
|
+
}
|
|
125
|
+
function computeTargetSize(srcW, srcH, maxW, maxH) {
|
|
126
|
+
if (srcW <= 0 || srcH <= 0) return { width: srcW, height: srcH };
|
|
127
|
+
const scale = Math.min(1, maxW / srcW, maxH / srcH);
|
|
128
|
+
return {
|
|
129
|
+
width: Math.max(1, Math.round(srcW * scale)),
|
|
130
|
+
height: Math.max(1, Math.round(srcH * scale))
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function decodeImage(blob) {
|
|
134
|
+
if (typeof createImageBitmap === "function") {
|
|
135
|
+
const bitmap = await createImageBitmap(blob, { imageOrientation: "from-image" });
|
|
136
|
+
return { bitmap, width: bitmap.width, height: bitmap.height };
|
|
137
|
+
}
|
|
138
|
+
const url = URL.createObjectURL(blob);
|
|
139
|
+
try {
|
|
140
|
+
const img = await new Promise((resolve, reject) => {
|
|
141
|
+
const el = new Image();
|
|
142
|
+
el.onload = () => resolve(el);
|
|
143
|
+
el.onerror = () => reject(new Error("Failed to decode image"));
|
|
144
|
+
el.src = url;
|
|
145
|
+
});
|
|
146
|
+
const canvas = document.createElement("canvas");
|
|
147
|
+
canvas.width = img.naturalWidth || img.width;
|
|
148
|
+
canvas.height = img.naturalHeight || img.height;
|
|
149
|
+
const ctx = canvas.getContext("2d");
|
|
150
|
+
if (!ctx) throw new Error("Canvas 2D context not available");
|
|
151
|
+
ctx.drawImage(img, 0, 0);
|
|
152
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
153
|
+
const pngBlob = await (await fetch(dataUrl)).blob();
|
|
154
|
+
const bitmap = await createImageBitmap(pngBlob);
|
|
155
|
+
return { bitmap, width: bitmap.width, height: bitmap.height };
|
|
156
|
+
} finally {
|
|
157
|
+
URL.revokeObjectURL(url);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function encodeWebpFromBitmap(bitmap, width, height, quality) {
|
|
161
|
+
const q = clamp01(quality);
|
|
162
|
+
const hasOffscreen = typeof OffscreenCanvas !== "undefined";
|
|
163
|
+
if (hasOffscreen) {
|
|
164
|
+
const canvas2 = new OffscreenCanvas(width, height);
|
|
165
|
+
const ctx2 = canvas2.getContext("2d");
|
|
166
|
+
if (!ctx2) return null;
|
|
167
|
+
ctx2.drawImage(bitmap, 0, 0, width, height);
|
|
168
|
+
try {
|
|
169
|
+
return await canvas2.convertToBlob({ type: "image/webp", quality: q });
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const canvas = document.createElement("canvas");
|
|
175
|
+
canvas.width = width;
|
|
176
|
+
canvas.height = height;
|
|
177
|
+
const ctx = canvas.getContext("2d");
|
|
178
|
+
if (!ctx) return null;
|
|
179
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
180
|
+
const blob = await new Promise((resolve) => {
|
|
181
|
+
canvas.toBlob((b) => resolve(b), "image/webp", q);
|
|
182
|
+
});
|
|
183
|
+
return blob;
|
|
184
|
+
}
|
|
185
|
+
async function imageToWebp(input, options = {}) {
|
|
186
|
+
const opts = { ...DEFAULTS, ...options };
|
|
187
|
+
const blob = await inputToBlob(input);
|
|
188
|
+
const { bitmap, width: srcW, height: srcH } = await decodeImage(blob);
|
|
189
|
+
try {
|
|
190
|
+
const { width, height } = computeTargetSize(srcW, srcH, opts.maxWidth, opts.maxHeight);
|
|
191
|
+
if (!opts.targetBytes || opts.targetBytes <= 0) {
|
|
192
|
+
const out = await encodeWebpFromBitmap(bitmap, width, height, opts.maxQuality);
|
|
193
|
+
if (!out) {
|
|
194
|
+
if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
195
|
+
return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
196
|
+
}
|
|
197
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
198
|
+
const file2 = opts.returnFile ? new File([out], fileName2, { type: out.type }) : void 0;
|
|
199
|
+
return { blob: out, file: file2, width, height, quality: opts.maxQuality, isWebp: true };
|
|
200
|
+
}
|
|
201
|
+
const maxQ = clamp01(opts.maxQuality);
|
|
202
|
+
const minQ = clamp01(Math.min(opts.minQuality, maxQ));
|
|
203
|
+
const atMax = await encodeWebpFromBitmap(bitmap, width, height, maxQ);
|
|
204
|
+
if (!atMax) {
|
|
205
|
+
if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
206
|
+
return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
207
|
+
}
|
|
208
|
+
if (atMax.size <= opts.targetBytes) {
|
|
209
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
210
|
+
const file2 = opts.returnFile ? new File([atMax], fileName2, { type: atMax.type }) : void 0;
|
|
211
|
+
return { blob: atMax, file: file2, width, height, quality: maxQ, isWebp: true };
|
|
212
|
+
}
|
|
213
|
+
const atMin = await encodeWebpFromBitmap(bitmap, width, height, minQ);
|
|
214
|
+
if (!atMin) {
|
|
215
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
216
|
+
const file2 = opts.returnFile ? new File([atMax], fileName2, { type: atMax.type }) : void 0;
|
|
217
|
+
return { blob: atMax, file: file2, width, height, quality: maxQ, isWebp: true };
|
|
218
|
+
}
|
|
219
|
+
if (atMin.size > opts.targetBytes) {
|
|
220
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
221
|
+
const file2 = opts.returnFile ? new File([atMin], fileName2, { type: atMin.type }) : void 0;
|
|
222
|
+
return { blob: atMin, file: file2, width, height, quality: minQ, isWebp: true };
|
|
223
|
+
}
|
|
224
|
+
let lo = minQ;
|
|
225
|
+
let hi = maxQ;
|
|
226
|
+
let bestBlob = atMin;
|
|
227
|
+
let bestQ = minQ;
|
|
228
|
+
for (let i = 0; i < 7; i++) {
|
|
229
|
+
const mid = (lo + hi) / 2;
|
|
230
|
+
const enc = await encodeWebpFromBitmap(bitmap, width, height, mid);
|
|
231
|
+
if (!enc) break;
|
|
232
|
+
if (enc.size <= opts.targetBytes) {
|
|
233
|
+
bestBlob = enc;
|
|
234
|
+
bestQ = mid;
|
|
235
|
+
lo = mid;
|
|
236
|
+
} else {
|
|
237
|
+
hi = mid;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const fileName = opts.fileName ?? getInputName(input);
|
|
241
|
+
const file = opts.returnFile ? new File([bestBlob], fileName, { type: bestBlob.type }) : void 0;
|
|
242
|
+
return { blob: bestBlob, file, width, height, quality: bestQ, isWebp: true };
|
|
243
|
+
} finally {
|
|
244
|
+
bitmap.close();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { convert, imageToWebp, videoToWebm };
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
|
250
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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\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\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\r\n const inName = \"input\"\r\n const outName = \"output.webm\"\r\n\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 await ffmpeg.exec(args)\r\n const outData = await ffmpeg.readFile(outName)\r\n const outBytes = outData instanceof Uint8Array ? outData : new Uint8Array(outData as any)\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"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ffmpeg = require('@ffmpeg/ffmpeg');
|
|
4
|
+
var util = require('@ffmpeg/util');
|
|
5
|
+
var fileType = require('file-type');
|
|
6
|
+
|
|
7
|
+
// src/browser/index.ts
|
|
8
|
+
var DEFAULTS = {
|
|
9
|
+
maxWidth: 2048,
|
|
10
|
+
maxHeight: 2048,
|
|
11
|
+
maxQuality: 0.82,
|
|
12
|
+
minQuality: 0.45,
|
|
13
|
+
force: true,
|
|
14
|
+
returnFile: false
|
|
15
|
+
};
|
|
16
|
+
function clamp01(n) {
|
|
17
|
+
return Math.max(0, Math.min(1, n));
|
|
18
|
+
}
|
|
19
|
+
function toWebpName(name) {
|
|
20
|
+
const base = name.replace(/\.[^/.]+$/, "");
|
|
21
|
+
return `${base || "image"}.webp`;
|
|
22
|
+
}
|
|
23
|
+
function getInputName(input) {
|
|
24
|
+
if (typeof input === "string") return "image.webp";
|
|
25
|
+
if (input instanceof File && input.name) return toWebpName(input.name);
|
|
26
|
+
return "image.webp";
|
|
27
|
+
}
|
|
28
|
+
async function inputToBlob(input) {
|
|
29
|
+
if (typeof input === "string") {
|
|
30
|
+
const res = await fetch(input);
|
|
31
|
+
if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
|
|
32
|
+
return await res.blob();
|
|
33
|
+
}
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
36
|
+
function toWebmName(name) {
|
|
37
|
+
const base = name.replace(/\.[^/.]+$/, "");
|
|
38
|
+
return `${base || "video"}.webm`;
|
|
39
|
+
}
|
|
40
|
+
function defaultVideoName(input, fileName) {
|
|
41
|
+
if (fileName && fileName.trim().length > 0) return fileName;
|
|
42
|
+
if (typeof input === "string") return "video.webm";
|
|
43
|
+
if (input instanceof File && input.name) return toWebmName(input.name);
|
|
44
|
+
return "video.webm";
|
|
45
|
+
}
|
|
46
|
+
var ffmpegSingleton = null;
|
|
47
|
+
var ffmpegLoadPromise = null;
|
|
48
|
+
async function getFfmpeg() {
|
|
49
|
+
if (ffmpegSingleton) return ffmpegSingleton;
|
|
50
|
+
if (ffmpegLoadPromise) return ffmpegLoadPromise;
|
|
51
|
+
const ff = new ffmpeg.FFmpeg();
|
|
52
|
+
ffmpegLoadPromise = (async () => {
|
|
53
|
+
await ff.load();
|
|
54
|
+
ffmpegSingleton = ff;
|
|
55
|
+
return ff;
|
|
56
|
+
})();
|
|
57
|
+
return ffmpegLoadPromise;
|
|
58
|
+
}
|
|
59
|
+
var VIDEO_DEFAULTS = {
|
|
60
|
+
crf: 32,
|
|
61
|
+
deadline: "good",
|
|
62
|
+
returnFile: false
|
|
63
|
+
};
|
|
64
|
+
async function videoToWebm(input, options = {}) {
|
|
65
|
+
const opts = { ...VIDEO_DEFAULTS, ...options };
|
|
66
|
+
const blob = await inputToBlob(input);
|
|
67
|
+
const ffmpeg = await getFfmpeg();
|
|
68
|
+
const inName = "input";
|
|
69
|
+
const outName = "output.webm";
|
|
70
|
+
await ffmpeg.writeFile(inName, await util.fetchFile(blob));
|
|
71
|
+
const args = [
|
|
72
|
+
...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
|
|
73
|
+
"-i",
|
|
74
|
+
inName,
|
|
75
|
+
"-map",
|
|
76
|
+
"0:v:0",
|
|
77
|
+
"-map",
|
|
78
|
+
"0:a?",
|
|
79
|
+
"-c:v",
|
|
80
|
+
"libvpx-vp9",
|
|
81
|
+
"-b:v",
|
|
82
|
+
"0",
|
|
83
|
+
"-crf",
|
|
84
|
+
String(opts.crf),
|
|
85
|
+
"-deadline",
|
|
86
|
+
opts.deadline,
|
|
87
|
+
"-row-mt",
|
|
88
|
+
"1",
|
|
89
|
+
"-c:a",
|
|
90
|
+
"libopus",
|
|
91
|
+
outName
|
|
92
|
+
];
|
|
93
|
+
await ffmpeg.exec(args);
|
|
94
|
+
const outData = await ffmpeg.readFile(outName);
|
|
95
|
+
const outBytes = outData instanceof Uint8Array ? outData : new Uint8Array(outData);
|
|
96
|
+
const outBlob = new Blob([outBytes], { type: "video/webm" });
|
|
97
|
+
const file = opts.returnFile ? new File([outBlob], defaultVideoName(input, opts.fileName), { type: outBlob.type }) : void 0;
|
|
98
|
+
return { blob: outBlob, file, isWebm: true };
|
|
99
|
+
}
|
|
100
|
+
async function detectKindBrowser(input) {
|
|
101
|
+
if (typeof input !== "string") {
|
|
102
|
+
const t = (input.type || "").toLowerCase();
|
|
103
|
+
if (t.startsWith("image/")) return "image";
|
|
104
|
+
if (t.startsWith("video/")) return "video";
|
|
105
|
+
}
|
|
106
|
+
const blob = await inputToBlob(input);
|
|
107
|
+
const buf = new Uint8Array(await blob.slice(0, 4100).arrayBuffer());
|
|
108
|
+
const ft = await fileType.fileTypeFromBuffer(buf);
|
|
109
|
+
if (!ft) return "unknown";
|
|
110
|
+
if (ft.mime.startsWith("image/")) return "image";
|
|
111
|
+
if (ft.mime.startsWith("video/")) return "video";
|
|
112
|
+
return "unknown";
|
|
113
|
+
}
|
|
114
|
+
async function convert(input, options = {}) {
|
|
115
|
+
const kind = await detectKindBrowser(input);
|
|
116
|
+
const returnFile = options.returnFile ?? false;
|
|
117
|
+
if (kind === "image") {
|
|
118
|
+
const res = await imageToWebp(input, { ...options.image, returnFile, fileName: options.fileName });
|
|
119
|
+
return { kind: "image", ...res };
|
|
120
|
+
}
|
|
121
|
+
if (kind === "video") {
|
|
122
|
+
const res = await videoToWebm(input, { ...options.video, returnFile, fileName: options.fileName });
|
|
123
|
+
return { kind: "video", ...res };
|
|
124
|
+
}
|
|
125
|
+
throw new Error("Unsupported input type. Expected an image/* or video/* asset.");
|
|
126
|
+
}
|
|
127
|
+
function computeTargetSize(srcW, srcH, maxW, maxH) {
|
|
128
|
+
if (srcW <= 0 || srcH <= 0) return { width: srcW, height: srcH };
|
|
129
|
+
const scale = Math.min(1, maxW / srcW, maxH / srcH);
|
|
130
|
+
return {
|
|
131
|
+
width: Math.max(1, Math.round(srcW * scale)),
|
|
132
|
+
height: Math.max(1, Math.round(srcH * scale))
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function decodeImage(blob) {
|
|
136
|
+
if (typeof createImageBitmap === "function") {
|
|
137
|
+
const bitmap = await createImageBitmap(blob, { imageOrientation: "from-image" });
|
|
138
|
+
return { bitmap, width: bitmap.width, height: bitmap.height };
|
|
139
|
+
}
|
|
140
|
+
const url = URL.createObjectURL(blob);
|
|
141
|
+
try {
|
|
142
|
+
const img = await new Promise((resolve, reject) => {
|
|
143
|
+
const el = new Image();
|
|
144
|
+
el.onload = () => resolve(el);
|
|
145
|
+
el.onerror = () => reject(new Error("Failed to decode image"));
|
|
146
|
+
el.src = url;
|
|
147
|
+
});
|
|
148
|
+
const canvas = document.createElement("canvas");
|
|
149
|
+
canvas.width = img.naturalWidth || img.width;
|
|
150
|
+
canvas.height = img.naturalHeight || img.height;
|
|
151
|
+
const ctx = canvas.getContext("2d");
|
|
152
|
+
if (!ctx) throw new Error("Canvas 2D context not available");
|
|
153
|
+
ctx.drawImage(img, 0, 0);
|
|
154
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
155
|
+
const pngBlob = await (await fetch(dataUrl)).blob();
|
|
156
|
+
const bitmap = await createImageBitmap(pngBlob);
|
|
157
|
+
return { bitmap, width: bitmap.width, height: bitmap.height };
|
|
158
|
+
} finally {
|
|
159
|
+
URL.revokeObjectURL(url);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function encodeWebpFromBitmap(bitmap, width, height, quality) {
|
|
163
|
+
const q = clamp01(quality);
|
|
164
|
+
const hasOffscreen = typeof OffscreenCanvas !== "undefined";
|
|
165
|
+
if (hasOffscreen) {
|
|
166
|
+
const canvas2 = new OffscreenCanvas(width, height);
|
|
167
|
+
const ctx2 = canvas2.getContext("2d");
|
|
168
|
+
if (!ctx2) return null;
|
|
169
|
+
ctx2.drawImage(bitmap, 0, 0, width, height);
|
|
170
|
+
try {
|
|
171
|
+
return await canvas2.convertToBlob({ type: "image/webp", quality: q });
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const canvas = document.createElement("canvas");
|
|
177
|
+
canvas.width = width;
|
|
178
|
+
canvas.height = height;
|
|
179
|
+
const ctx = canvas.getContext("2d");
|
|
180
|
+
if (!ctx) return null;
|
|
181
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
182
|
+
const blob = await new Promise((resolve) => {
|
|
183
|
+
canvas.toBlob((b) => resolve(b), "image/webp", q);
|
|
184
|
+
});
|
|
185
|
+
return blob;
|
|
186
|
+
}
|
|
187
|
+
async function imageToWebp(input, options = {}) {
|
|
188
|
+
const opts = { ...DEFAULTS, ...options };
|
|
189
|
+
const blob = await inputToBlob(input);
|
|
190
|
+
const { bitmap, width: srcW, height: srcH } = await decodeImage(blob);
|
|
191
|
+
try {
|
|
192
|
+
const { width, height } = computeTargetSize(srcW, srcH, opts.maxWidth, opts.maxHeight);
|
|
193
|
+
if (!opts.targetBytes || opts.targetBytes <= 0) {
|
|
194
|
+
const out = await encodeWebpFromBitmap(bitmap, width, height, opts.maxQuality);
|
|
195
|
+
if (!out) {
|
|
196
|
+
if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
197
|
+
return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
198
|
+
}
|
|
199
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
200
|
+
const file2 = opts.returnFile ? new File([out], fileName2, { type: out.type }) : void 0;
|
|
201
|
+
return { blob: out, file: file2, width, height, quality: opts.maxQuality, isWebp: true };
|
|
202
|
+
}
|
|
203
|
+
const maxQ = clamp01(opts.maxQuality);
|
|
204
|
+
const minQ = clamp01(Math.min(opts.minQuality, maxQ));
|
|
205
|
+
const atMax = await encodeWebpFromBitmap(bitmap, width, height, maxQ);
|
|
206
|
+
if (!atMax) {
|
|
207
|
+
if (!opts.force) return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
208
|
+
return { blob, width: srcW, height: srcH, quality: 1, isWebp: false };
|
|
209
|
+
}
|
|
210
|
+
if (atMax.size <= opts.targetBytes) {
|
|
211
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
212
|
+
const file2 = opts.returnFile ? new File([atMax], fileName2, { type: atMax.type }) : void 0;
|
|
213
|
+
return { blob: atMax, file: file2, width, height, quality: maxQ, isWebp: true };
|
|
214
|
+
}
|
|
215
|
+
const atMin = await encodeWebpFromBitmap(bitmap, width, height, minQ);
|
|
216
|
+
if (!atMin) {
|
|
217
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
218
|
+
const file2 = opts.returnFile ? new File([atMax], fileName2, { type: atMax.type }) : void 0;
|
|
219
|
+
return { blob: atMax, file: file2, width, height, quality: maxQ, isWebp: true };
|
|
220
|
+
}
|
|
221
|
+
if (atMin.size > opts.targetBytes) {
|
|
222
|
+
const fileName2 = opts.fileName ?? getInputName(input);
|
|
223
|
+
const file2 = opts.returnFile ? new File([atMin], fileName2, { type: atMin.type }) : void 0;
|
|
224
|
+
return { blob: atMin, file: file2, width, height, quality: minQ, isWebp: true };
|
|
225
|
+
}
|
|
226
|
+
let lo = minQ;
|
|
227
|
+
let hi = maxQ;
|
|
228
|
+
let bestBlob = atMin;
|
|
229
|
+
let bestQ = minQ;
|
|
230
|
+
for (let i = 0; i < 7; i++) {
|
|
231
|
+
const mid = (lo + hi) / 2;
|
|
232
|
+
const enc = await encodeWebpFromBitmap(bitmap, width, height, mid);
|
|
233
|
+
if (!enc) break;
|
|
234
|
+
if (enc.size <= opts.targetBytes) {
|
|
235
|
+
bestBlob = enc;
|
|
236
|
+
bestQ = mid;
|
|
237
|
+
lo = mid;
|
|
238
|
+
} else {
|
|
239
|
+
hi = mid;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const fileName = opts.fileName ?? getInputName(input);
|
|
243
|
+
const file = opts.returnFile ? new File([bestBlob], fileName, { type: bestBlob.type }) : void 0;
|
|
244
|
+
return { blob: bestBlob, file, width, height, quality: bestQ, isWebp: true };
|
|
245
|
+
} finally {
|
|
246
|
+
bitmap.close();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
exports.convert = convert;
|
|
251
|
+
exports.imageToWebp = imageToWebp;
|
|
252
|
+
exports.videoToWebm = videoToWebm;
|
|
253
|
+
//# sourceMappingURL=index.cjs.map
|
|
254
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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\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\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\r\n const inName = \"input\"\r\n const outName = \"output.webm\"\r\n\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 await ffmpeg.exec(args)\r\n const outData = await ffmpeg.readFile(outName)\r\n const outBytes = outData instanceof Uint8Array ? outData : new Uint8Array(outData as any)\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"]}
|