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.
@@ -0,0 +1,221 @@
1
+ 'use strict';
2
+
3
+ var promises = require('fs/promises');
4
+ var os = require('os');
5
+ var path = require('path');
6
+ var execa = require('execa');
7
+ var ffmpegPath = require('ffmpeg-static');
8
+ var fileType = require('file-type');
9
+ var sharp = require('sharp');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var ffmpegPath__default = /*#__PURE__*/_interopDefault(ffmpegPath);
14
+ var sharp__default = /*#__PURE__*/_interopDefault(sharp);
15
+
16
+ // src/node/index.ts
17
+ var DEFAULTS = {
18
+ maxWidth: 2048,
19
+ maxHeight: 2048,
20
+ maxQuality: 0.82,
21
+ minQuality: 0.45,
22
+ force: true,
23
+ returnFile: false
24
+ };
25
+ function clamp01(n) {
26
+ return Math.max(0, Math.min(1, n));
27
+ }
28
+ function quality01ToSharpQ(q) {
29
+ return Math.max(1, Math.min(100, Math.round(clamp01(q) * 100)));
30
+ }
31
+ function toUint8Array(input) {
32
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(input)) return new Uint8Array(input);
33
+ if (input instanceof Uint8Array) return input;
34
+ return new Uint8Array(input);
35
+ }
36
+ async function inputToBytes(input) {
37
+ if (typeof input !== "string") return toUint8Array(input);
38
+ if (/^https?:\/\//i.test(input)) {
39
+ const res = await fetch(input);
40
+ if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
41
+ const ab = await res.arrayBuffer();
42
+ return new Uint8Array(ab);
43
+ }
44
+ const buf = await promises.readFile(input);
45
+ return new Uint8Array(buf);
46
+ }
47
+ function makeBlob(bytes, type) {
48
+ if (typeof Blob !== "function") {
49
+ throw new Error("Global Blob is not available (requires Node.js 18+).");
50
+ }
51
+ const copy = new Uint8Array(bytes.byteLength);
52
+ copy.set(bytes);
53
+ return new Blob([copy], { type });
54
+ }
55
+ function maybeMakeFile(bytes, name, type) {
56
+ if (typeof File !== "function") return void 0;
57
+ const copy = new Uint8Array(bytes.byteLength);
58
+ copy.set(bytes);
59
+ return new File([copy], name, { type });
60
+ }
61
+ function defaultFileName(fileName) {
62
+ return fileName && fileName.trim().length > 0 ? fileName : "image.webp";
63
+ }
64
+ function defaultVideoFileName(fileName) {
65
+ return fileName && fileName.trim().length > 0 ? fileName : "video.webm";
66
+ }
67
+ function computeTargetSize(srcW, srcH, maxW, maxH) {
68
+ if (srcW <= 0 || srcH <= 0) return { width: srcW, height: srcH };
69
+ const scale = Math.min(1, maxW / srcW, maxH / srcH);
70
+ return {
71
+ width: Math.max(1, Math.round(srcW * scale)),
72
+ height: Math.max(1, Math.round(srcH * scale))
73
+ };
74
+ }
75
+ async function encodeWithSharpWebp(inputBytes, width, height, quality01) {
76
+ const q = quality01ToSharpQ(quality01);
77
+ const out = await sharp__default.default(inputBytes).resize({
78
+ width,
79
+ height,
80
+ fit: "fill",
81
+ withoutEnlargement: true
82
+ }).webp({ quality: q }).toBuffer();
83
+ return new Uint8Array(out);
84
+ }
85
+ async function imageToWebp(input, options = {}) {
86
+ const opts = { ...DEFAULTS, ...options };
87
+ const bytes = await inputToBytes(input);
88
+ const meta = await sharp__default.default(bytes).metadata();
89
+ const srcW = meta.width ?? 0;
90
+ const srcH = meta.height ?? 0;
91
+ const { width, height } = computeTargetSize(srcW, srcH, opts.maxWidth, opts.maxHeight);
92
+ const type = "image/webp";
93
+ if (!opts.targetBytes || opts.targetBytes <= 0) {
94
+ const outBytes = await encodeWithSharpWebp(bytes, width, height, opts.maxQuality);
95
+ const blob2 = makeBlob(outBytes, type);
96
+ const file2 = opts.returnFile ? maybeMakeFile(outBytes, defaultFileName(opts.fileName), type) : void 0;
97
+ return { blob: blob2, file: file2, width, height, quality: clamp01(opts.maxQuality), isWebp: true };
98
+ }
99
+ const maxQ = clamp01(opts.maxQuality);
100
+ const minQ = clamp01(Math.min(opts.minQuality, maxQ));
101
+ const atMaxBytes = await encodeWithSharpWebp(bytes, width, height, maxQ);
102
+ if (atMaxBytes.byteLength <= opts.targetBytes) {
103
+ const blob2 = makeBlob(atMaxBytes, type);
104
+ const file2 = opts.returnFile ? maybeMakeFile(atMaxBytes, defaultFileName(opts.fileName), type) : void 0;
105
+ return { blob: blob2, file: file2, width, height, quality: maxQ, isWebp: true };
106
+ }
107
+ const atMinBytes = await encodeWithSharpWebp(bytes, width, height, minQ);
108
+ if (atMinBytes.byteLength > opts.targetBytes) {
109
+ const blob2 = makeBlob(atMinBytes, type);
110
+ const file2 = opts.returnFile ? maybeMakeFile(atMinBytes, defaultFileName(opts.fileName), type) : void 0;
111
+ return { blob: blob2, file: file2, width, height, quality: minQ, isWebp: true };
112
+ }
113
+ let lo = minQ;
114
+ let hi = maxQ;
115
+ let bestBytes = atMinBytes;
116
+ let bestQ = minQ;
117
+ for (let i = 0; i < 7; i++) {
118
+ const mid = (lo + hi) / 2;
119
+ const enc = await encodeWithSharpWebp(bytes, width, height, mid);
120
+ if (enc.byteLength <= opts.targetBytes) {
121
+ bestBytes = enc;
122
+ bestQ = mid;
123
+ lo = mid;
124
+ } else {
125
+ hi = mid;
126
+ }
127
+ }
128
+ const blob = makeBlob(bestBytes, type);
129
+ const file = opts.returnFile ? maybeMakeFile(bestBytes, defaultFileName(opts.fileName), type) : void 0;
130
+ return { blob, file, width, height, quality: bestQ, isWebp: true };
131
+ }
132
+ var VIDEO_DEFAULTS = {
133
+ crf: 32,
134
+ deadline: "good",
135
+ returnFile: false
136
+ };
137
+ async function withTempDir(fn) {
138
+ const dir = await promises.mkdtemp(path.join(os.tmpdir(), "weblet-convert-"));
139
+ try {
140
+ return await fn(dir);
141
+ } finally {
142
+ await promises.rm(dir, { recursive: true, force: true });
143
+ }
144
+ }
145
+ function getFfmpegPath() {
146
+ if (!ffmpegPath__default.default) throw new Error("ffmpeg binary not found (ffmpeg-static did not resolve a path).");
147
+ return ffmpegPath__default.default;
148
+ }
149
+ async function videoToWebm(input, options = {}) {
150
+ const ffmpeg = getFfmpegPath();
151
+ const opts = { ...VIDEO_DEFAULTS, ...options };
152
+ const bytes = await inputToBytes(input);
153
+ const type = "video/webm";
154
+ return await withTempDir(async (dir) => {
155
+ const inPath = path.join(dir, "input");
156
+ const outPath = path.join(dir, "output.webm");
157
+ 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" });
183
+ const out = await promises.readFile(outPath);
184
+ const outBytes = new Uint8Array(out);
185
+ const blob = makeBlob(outBytes, type);
186
+ const file = opts.returnFile ? maybeMakeFile(outBytes, defaultVideoFileName(opts.fileName), type) : void 0;
187
+ return { blob, file, isWebm: true };
188
+ });
189
+ }
190
+ async function detectKindNode(input) {
191
+ if (typeof input === "string") {
192
+ const lowered = input.toLowerCase();
193
+ if (/\.(png|jpe?g|gif|webp|avif|bmp|tiff?)($|\?)/i.test(lowered)) return "image";
194
+ if (/\.(mp4|mov|m4v|mkv|webm|avi|wmv|flv)($|\?)/i.test(lowered)) return "video";
195
+ }
196
+ const bytes = await inputToBytes(input);
197
+ const ft = await fileType.fileTypeFromBuffer(bytes);
198
+ if (!ft) return "unknown";
199
+ if (ft.mime.startsWith("image/")) return "image";
200
+ if (ft.mime.startsWith("video/")) return "video";
201
+ return "unknown";
202
+ }
203
+ async function convert(input, options = {}) {
204
+ const kind = await detectKindNode(input);
205
+ const returnFile = options.returnFile ?? false;
206
+ if (kind === "image") {
207
+ const res = await imageToWebp(input, { ...options.image, returnFile, fileName: options.fileName });
208
+ return { kind: "image", ...res };
209
+ }
210
+ if (kind === "video") {
211
+ const res = await videoToWebm(input, { ...options.video, returnFile, fileName: options.fileName });
212
+ return { kind: "video", ...res };
213
+ }
214
+ throw new Error("Unsupported input type. Expected an image/* or video/* asset.");
215
+ }
216
+
217
+ exports.convert = convert;
218
+ exports.imageToWebp = imageToWebp;
219
+ exports.videoToWebm = videoToWebm;
220
+ //# sourceMappingURL=index.cjs.map
221
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/node/index.ts"],"names":["readFile","sharp","blob","file","mkdtemp","join","tmpdir","rm","ffmpegPath","writeFileFs","execa","fileTypeFromBuffer"],"mappings":"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","file":"index.cjs","sourcesContent":["import { readFile } from \"node:fs/promises\"\r\nimport { mkdtemp, rm, writeFile as writeFileFs } from \"node:fs/promises\"\r\nimport { tmpdir } from \"node:os\"\r\nimport { join } from \"node:path\"\r\nimport { execa } from \"execa\"\r\nimport ffmpegPath from \"ffmpeg-static\"\r\nimport { fileTypeFromBuffer } from \"file-type\"\r\nimport sharp from \"sharp\"\r\n\r\nimport type { ConvertResult } from \"../shared/types.js\"\r\n\r\nexport type ImageToWebpInput = Buffer | Uint8Array | ArrayBuffer | 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 */\r\n fileName?: string\r\n\r\n /**\r\n * Return a File instead of a Blob (only if global File exists).\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 quality01ToSharpQ(q: number): number {\r\n return Math.max(1, Math.min(100, Math.round(clamp01(q) * 100)))\r\n}\r\n\r\nfunction toUint8Array(input: Exclude<ImageToWebpInput, string>): Uint8Array {\r\n if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(input)) return new Uint8Array(input)\r\n if (input instanceof Uint8Array) return input\r\n return new Uint8Array(input)\r\n}\r\n\r\nasync function inputToBytes(input: ImageToWebpInput): Promise<Uint8Array> {\r\n if (typeof input !== \"string\") return toUint8Array(input)\r\n\r\n if (/^https?:\\/\\//i.test(input)) {\r\n const res = await fetch(input)\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`)\r\n const ab = await res.arrayBuffer()\r\n return new Uint8Array(ab)\r\n }\r\n\r\n const buf = await readFile(input)\r\n return new Uint8Array(buf)\r\n}\r\n\r\nfunction makeBlob(bytes: Uint8Array, type: string): Blob {\r\n if (typeof Blob !== \"function\") {\r\n throw new Error(\"Global Blob is not available (requires Node.js 18+).\")\r\n }\r\n const copy = new Uint8Array(bytes.byteLength)\r\n copy.set(bytes)\r\n return new Blob([copy], { type })\r\n}\r\n\r\nfunction maybeMakeFile(bytes: Uint8Array, name: string, type: string): File | undefined {\r\n if (typeof File !== \"function\") return undefined\r\n const copy = new Uint8Array(bytes.byteLength)\r\n copy.set(bytes)\r\n return new File([copy], name, { type })\r\n}\r\n\r\nfunction defaultFileName(fileName?: string) {\r\n return fileName && fileName.trim().length > 0 ? fileName : \"image.webp\"\r\n}\r\n\r\nfunction defaultVideoFileName(fileName?: string) {\r\n return fileName && fileName.trim().length > 0 ? fileName : \"video.webm\"\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 encodeWithSharpWebp(\r\n inputBytes: Uint8Array,\r\n width: number,\r\n height: number,\r\n quality01: number\r\n): Promise<Uint8Array> {\r\n const q = quality01ToSharpQ(quality01)\r\n const out = await sharp(inputBytes)\r\n .resize({\r\n width,\r\n height,\r\n fit: \"fill\",\r\n withoutEnlargement: true,\r\n })\r\n .webp({ quality: q })\r\n .toBuffer()\r\n return new Uint8Array(out)\r\n}\r\n\r\n/**\r\n * Node implementation (sharp) of image->WebP conversion.\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 bytes = await inputToBytes(input)\r\n\r\n const meta = await sharp(bytes).metadata()\r\n const srcW = meta.width ?? 0\r\n const srcH = meta.height ?? 0\r\n const { width, height } = computeTargetSize(srcW, srcH, opts.maxWidth, opts.maxHeight)\r\n\r\n const type = \"image/webp\"\r\n\r\n if (!opts.targetBytes || opts.targetBytes <= 0) {\r\n const outBytes = await encodeWithSharpWebp(bytes, width, height, opts.maxQuality)\r\n const blob = makeBlob(outBytes, type)\r\n const file = opts.returnFile ? maybeMakeFile(outBytes, defaultFileName(opts.fileName), type) : undefined\r\n return { blob, file, width, height, quality: clamp01(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 atMaxBytes = await encodeWithSharpWebp(bytes, width, height, maxQ)\r\n if (atMaxBytes.byteLength <= opts.targetBytes) {\r\n const blob = makeBlob(atMaxBytes, type)\r\n const file = opts.returnFile ? maybeMakeFile(atMaxBytes, defaultFileName(opts.fileName), type) : undefined\r\n return { blob, file, width, height, quality: maxQ, isWebp: true }\r\n }\r\n\r\n const atMinBytes = await encodeWithSharpWebp(bytes, width, height, minQ)\r\n if (atMinBytes.byteLength > opts.targetBytes) {\r\n const blob = makeBlob(atMinBytes, type)\r\n const file = opts.returnFile ? maybeMakeFile(atMinBytes, defaultFileName(opts.fileName), type) : undefined\r\n return { blob, file, width, height, quality: minQ, isWebp: true }\r\n }\r\n\r\n let lo = minQ\r\n let hi = maxQ\r\n let bestBytes: Uint8Array = atMinBytes\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 encodeWithSharpWebp(bytes, width, height, mid)\r\n if (enc.byteLength <= opts.targetBytes) {\r\n bestBytes = 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 blob = makeBlob(bestBytes, type)\r\n const file = opts.returnFile ? maybeMakeFile(bestBytes, defaultFileName(opts.fileName), type) : undefined\r\n return { blob, file, width, height, quality: bestQ, isWebp: true }\r\n}\r\n\r\nexport type VideoToWebmInput = Buffer | Uint8Array | ArrayBuffer | string\r\n\r\nexport type VideoToWebmOptions = {\r\n /**\r\n * Constant Rate Factor for VP9 (lower is higher quality).\r\n * Typical range: 18..40. Default 32.\r\n */\r\n crf?: number\r\n /**\r\n * VP9 deadline/preset: \"good\" is a reasonable default.\r\n */\r\n deadline?: \"good\" | \"best\" | \"realtime\"\r\n /**\r\n * Best-effort guardrail to bound work: stops after N seconds.\r\n */\r\n maxDurationSeconds?: number\r\n\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 /**\r\n * True if output is `video/webm`.\r\n */\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\nasync function withTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T> {\r\n const dir = await mkdtemp(join(tmpdir(), \"weblet-convert-\"))\r\n try {\r\n return await fn(dir)\r\n } finally {\r\n await rm(dir, { recursive: true, force: true })\r\n }\r\n}\r\n\r\nfunction getFfmpegPath(): string {\r\n if (!ffmpegPath) throw new Error(\"ffmpeg binary not found (ffmpeg-static did not resolve a path).\")\r\n return ffmpegPath\r\n}\r\n\r\n/**\r\n * Node implementation (ffmpeg) of video->WebM conversion.\r\n */\r\nexport async function videoToWebm(\r\n input: VideoToWebmInput,\r\n options: VideoToWebmOptions = {}\r\n): Promise<VideoToWebmResult> {\r\n const ffmpeg = getFfmpegPath()\r\n const opts = { ...VIDEO_DEFAULTS, ...options }\r\n const bytes = await inputToBytes(input)\r\n\r\n const type = \"video/webm\"\r\n return await withTempDir(async (dir) => {\r\n const inPath = join(dir, \"input\")\r\n const outPath = join(dir, \"output.webm\")\r\n await writeFileFs(inPath, bytes)\r\n\r\n const args: string[] = [\r\n \"-hide_banner\",\r\n \"-y\",\r\n ...(opts.maxDurationSeconds && opts.maxDurationSeconds > 0\r\n ? [\"-t\", String(opts.maxDurationSeconds)]\r\n : []),\r\n \"-i\",\r\n inPath,\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 outPath,\r\n ]\r\n\r\n await execa(ffmpeg, args, { stderr: \"pipe\", stdout: \"pipe\" })\r\n\r\n const out = await readFile(outPath)\r\n const outBytes = new Uint8Array(out)\r\n const blob = makeBlob(outBytes, type)\r\n const file = opts.returnFile ? maybeMakeFile(outBytes, defaultVideoFileName(opts.fileName), type) : undefined\r\n return { blob, file, isWebm: true }\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 detectKindNode(input: ConvertInput): Promise<\"image\" | \"video\" | \"unknown\"> {\r\n if (typeof input === \"string\") {\r\n const lowered = input.toLowerCase()\r\n if (/\\.(png|jpe?g|gif|webp|avif|bmp|tiff?)($|\\?)/i.test(lowered)) return \"image\"\r\n if (/\\.(mp4|mov|m4v|mkv|webm|avi|wmv|flv)($|\\?)/i.test(lowered)) return \"video\"\r\n }\r\n\r\n const bytes = await inputToBytes(input)\r\n const ft = await fileTypeFromBuffer(bytes)\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 detectKindNode(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\n"]}
@@ -0,0 +1,106 @@
1
+ type ConvertKind = "image" | "video";
2
+ type ConvertResultCommon = {
3
+ kind: ConvertKind;
4
+ blob: Blob;
5
+ file?: File;
6
+ };
7
+ type ConvertImageResult = ConvertResultCommon & {
8
+ kind: "image";
9
+ width: number;
10
+ height: number;
11
+ quality: number;
12
+ isWebp: boolean;
13
+ };
14
+ type ConvertVideoResult = ConvertResultCommon & {
15
+ kind: "video";
16
+ isWebm: boolean;
17
+ };
18
+ type ConvertResult = ConvertImageResult | ConvertVideoResult;
19
+
20
+ type ImageToWebpInput = Buffer | Uint8Array | ArrayBuffer | string;
21
+ type ImageToWebpOptions = {
22
+ /**
23
+ * Hard cap for output dimensions, preserving aspect ratio.
24
+ * If the input is larger, it will be downscaled.
25
+ */
26
+ maxWidth?: number;
27
+ maxHeight?: number;
28
+ /**
29
+ * Target maximum output size in bytes.
30
+ * If provided, the encoder will try to reach <= targetBytes by reducing quality.
31
+ */
32
+ targetBytes?: number;
33
+ /**
34
+ * Quality search range (0..1). Higher is better quality, larger file.
35
+ */
36
+ maxQuality?: number;
37
+ minQuality?: number;
38
+ /**
39
+ * If true, always return WebP even if it can't reach targetBytes.
40
+ * If false, will return the original input when WebP isn't supported.
41
+ */
42
+ force?: boolean;
43
+ /**
44
+ * Optional output filename (only used when returning a File).
45
+ */
46
+ fileName?: string;
47
+ /**
48
+ * Return a File instead of a Blob (only if global File exists).
49
+ */
50
+ returnFile?: boolean;
51
+ };
52
+ type ImageToWebpResult = {
53
+ blob: Blob;
54
+ file?: File;
55
+ width: number;
56
+ height: number;
57
+ quality: number;
58
+ /**
59
+ * True if output is `image/webp`.
60
+ */
61
+ isWebp: boolean;
62
+ };
63
+ /**
64
+ * Node implementation (sharp) of image->WebP conversion.
65
+ */
66
+ declare function imageToWebp(input: ImageToWebpInput, options?: ImageToWebpOptions): Promise<ImageToWebpResult>;
67
+ type VideoToWebmInput = Buffer | Uint8Array | ArrayBuffer | string;
68
+ type VideoToWebmOptions = {
69
+ /**
70
+ * Constant Rate Factor for VP9 (lower is higher quality).
71
+ * Typical range: 18..40. Default 32.
72
+ */
73
+ crf?: number;
74
+ /**
75
+ * VP9 deadline/preset: "good" is a reasonable default.
76
+ */
77
+ deadline?: "good" | "best" | "realtime";
78
+ /**
79
+ * Best-effort guardrail to bound work: stops after N seconds.
80
+ */
81
+ maxDurationSeconds?: number;
82
+ fileName?: string;
83
+ returnFile?: boolean;
84
+ };
85
+ type VideoToWebmResult = {
86
+ blob: Blob;
87
+ file?: File;
88
+ /**
89
+ * True if output is `video/webm`.
90
+ */
91
+ isWebm: boolean;
92
+ };
93
+ /**
94
+ * Node implementation (ffmpeg) of video->WebM conversion.
95
+ */
96
+ declare function videoToWebm(input: VideoToWebmInput, options?: VideoToWebmOptions): Promise<VideoToWebmResult>;
97
+ type ConvertInput = ImageToWebpInput;
98
+ type ConvertOptions = {
99
+ image?: ImageToWebpOptions;
100
+ video?: VideoToWebmOptions;
101
+ returnFile?: boolean;
102
+ fileName?: string;
103
+ };
104
+ declare function convert(input: ConvertInput, options?: ConvertOptions): Promise<ConvertResult>;
105
+
106
+ export { type ConvertInput, type ConvertOptions, type ImageToWebpInput, type ImageToWebpOptions, type ImageToWebpResult, type VideoToWebmInput, type VideoToWebmOptions, type VideoToWebmResult, convert, imageToWebp, videoToWebm };
@@ -0,0 +1,106 @@
1
+ type ConvertKind = "image" | "video";
2
+ type ConvertResultCommon = {
3
+ kind: ConvertKind;
4
+ blob: Blob;
5
+ file?: File;
6
+ };
7
+ type ConvertImageResult = ConvertResultCommon & {
8
+ kind: "image";
9
+ width: number;
10
+ height: number;
11
+ quality: number;
12
+ isWebp: boolean;
13
+ };
14
+ type ConvertVideoResult = ConvertResultCommon & {
15
+ kind: "video";
16
+ isWebm: boolean;
17
+ };
18
+ type ConvertResult = ConvertImageResult | ConvertVideoResult;
19
+
20
+ type ImageToWebpInput = Buffer | Uint8Array | ArrayBuffer | string;
21
+ type ImageToWebpOptions = {
22
+ /**
23
+ * Hard cap for output dimensions, preserving aspect ratio.
24
+ * If the input is larger, it will be downscaled.
25
+ */
26
+ maxWidth?: number;
27
+ maxHeight?: number;
28
+ /**
29
+ * Target maximum output size in bytes.
30
+ * If provided, the encoder will try to reach <= targetBytes by reducing quality.
31
+ */
32
+ targetBytes?: number;
33
+ /**
34
+ * Quality search range (0..1). Higher is better quality, larger file.
35
+ */
36
+ maxQuality?: number;
37
+ minQuality?: number;
38
+ /**
39
+ * If true, always return WebP even if it can't reach targetBytes.
40
+ * If false, will return the original input when WebP isn't supported.
41
+ */
42
+ force?: boolean;
43
+ /**
44
+ * Optional output filename (only used when returning a File).
45
+ */
46
+ fileName?: string;
47
+ /**
48
+ * Return a File instead of a Blob (only if global File exists).
49
+ */
50
+ returnFile?: boolean;
51
+ };
52
+ type ImageToWebpResult = {
53
+ blob: Blob;
54
+ file?: File;
55
+ width: number;
56
+ height: number;
57
+ quality: number;
58
+ /**
59
+ * True if output is `image/webp`.
60
+ */
61
+ isWebp: boolean;
62
+ };
63
+ /**
64
+ * Node implementation (sharp) of image->WebP conversion.
65
+ */
66
+ declare function imageToWebp(input: ImageToWebpInput, options?: ImageToWebpOptions): Promise<ImageToWebpResult>;
67
+ type VideoToWebmInput = Buffer | Uint8Array | ArrayBuffer | string;
68
+ type VideoToWebmOptions = {
69
+ /**
70
+ * Constant Rate Factor for VP9 (lower is higher quality).
71
+ * Typical range: 18..40. Default 32.
72
+ */
73
+ crf?: number;
74
+ /**
75
+ * VP9 deadline/preset: "good" is a reasonable default.
76
+ */
77
+ deadline?: "good" | "best" | "realtime";
78
+ /**
79
+ * Best-effort guardrail to bound work: stops after N seconds.
80
+ */
81
+ maxDurationSeconds?: number;
82
+ fileName?: string;
83
+ returnFile?: boolean;
84
+ };
85
+ type VideoToWebmResult = {
86
+ blob: Blob;
87
+ file?: File;
88
+ /**
89
+ * True if output is `video/webm`.
90
+ */
91
+ isWebm: boolean;
92
+ };
93
+ /**
94
+ * Node implementation (ffmpeg) of video->WebM conversion.
95
+ */
96
+ declare function videoToWebm(input: VideoToWebmInput, options?: VideoToWebmOptions): Promise<VideoToWebmResult>;
97
+ type ConvertInput = ImageToWebpInput;
98
+ type ConvertOptions = {
99
+ image?: ImageToWebpOptions;
100
+ video?: VideoToWebmOptions;
101
+ returnFile?: boolean;
102
+ fileName?: string;
103
+ };
104
+ declare function convert(input: ConvertInput, options?: ConvertOptions): Promise<ConvertResult>;
105
+
106
+ export { type ConvertInput, type ConvertOptions, type ImageToWebpInput, type ImageToWebpOptions, type ImageToWebpResult, type VideoToWebmInput, type VideoToWebmOptions, type VideoToWebmResult, convert, imageToWebp, videoToWebm };