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.
@@ -1 +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"]}
1
+ {"version":3,"sources":["../../src/node/index.ts"],"names":["readFile","sharp","blob","file","mkdtemp","join","tmpdir","rm","ffmpegPath","writeFileFs","execa","fileTypeFromBuffer"],"mappings":";;;;;;;;;;;;;;;;AA8DA,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,kBAAkB,CAAA,EAAmB;AAC5C,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,GAAI,GAAG,CAAC,CAAC,CAAA;AAChE;AAEA,SAAS,aAAa,KAAA,EAAsD;AAC1E,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,IAAI,UAAA,CAAW,KAAK,CAAA;AACxF,EAAA,IAAI,KAAA,YAAiB,YAAY,OAAO,KAAA;AACxC,EAAA,OAAO,IAAI,WAAW,KAAK,CAAA;AAC7B;AAEA,eAAe,aAAa,KAAA,EAA8C;AACxE,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,aAAa,KAAK,CAAA;AAExD,EAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,KAAK,CAAA,EAAG;AAC/B,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,MAAM,EAAA,GAAK,MAAM,GAAA,CAAI,WAAA,EAAY;AACjC,IAAA,OAAO,IAAI,WAAW,EAAE,CAAA;AAAA,EAC1B;AAEA,EAAA,MAAM,GAAA,GAAM,MAAMA,iBAAA,CAAS,KAAK,CAAA;AAChC,EAAA,OAAO,IAAI,WAAW,GAAG,CAAA;AAC3B;AAEA,SAAS,QAAA,CAAS,OAAmB,IAAA,EAAoB;AACvD,EAAA,IAAI,OAAO,SAAS,UAAA,EAAY;AAC9B,IAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,EACxE;AACA,EAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA;AAC5C,EAAA,IAAA,CAAK,IAAI,KAAK,CAAA;AACd,EAAA,OAAO,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,EAAE,MAAM,CAAA;AAClC;AAEA,SAAS,aAAA,CAAc,KAAA,EAAmB,IAAA,EAAc,IAAA,EAAgC;AACtF,EAAA,IAAI,OAAO,IAAA,KAAS,UAAA,EAAY,OAAO,MAAA;AACvC,EAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA;AAC5C,EAAA,IAAA,CAAK,IAAI,KAAK,CAAA;AACd,EAAA,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,IAAA,EAAM,EAAE,MAAM,CAAA;AACxC;AAEA,SAAS,gBAAgB,QAAA,EAAmB;AAC1C,EAAA,OAAO,YAAY,QAAA,CAAS,IAAA,EAAK,CAAE,MAAA,GAAS,IAAI,QAAA,GAAW,YAAA;AAC7D;AAEA,SAAS,qBAAqB,QAAA,EAAmB;AAC/C,EAAA,OAAO,YAAY,QAAA,CAAS,IAAA,EAAK,CAAE,MAAA,GAAS,IAAI,QAAA,GAAW,YAAA;AAC7D;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,mBAAA,CACb,UAAA,EACA,KAAA,EACA,MAAA,EACA,SAAA,EACqB;AACrB,EAAA,MAAM,CAAA,GAAI,kBAAkB,SAAS,CAAA;AACrC,EAAA,MAAM,GAAA,GAAM,MAAMC,sBAAA,CAAM,UAAU,EAC/B,MAAA,CAAO;AAAA,IACN,KAAA;AAAA,IACA,MAAA;AAAA,IACA,GAAA,EAAK,MAAA;AAAA,IACL,kBAAA,EAAoB;AAAA,GACrB,EACA,IAAA,CAAK,EAAE,SAAS,CAAA,EAAG,EACnB,QAAA,EAAS;AACZ,EAAA,OAAO,IAAI,WAAW,GAAG,CAAA;AAC3B;AAKA,eAAsB,WAAA,CACpB,KAAA,EACA,OAAA,GAA8B,EAAC,EACH;AAC5B,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,QAAA,EAAU,GAAG,OAAA,EAAQ;AACvC,EAAA,MAAM,KAAA,GAAQ,MAAM,YAAA,CAAa,KAAK,CAAA;AAEtC,EAAA,MAAM,IAAA,GAAO,MAAMA,sBAAA,CAAM,KAAK,EAAE,QAAA,EAAS;AACzC,EAAA,MAAM,IAAA,GAAO,KAAK,KAAA,IAAS,CAAA;AAC3B,EAAA,MAAM,IAAA,GAAO,KAAK,MAAA,IAAU,CAAA;AAC5B,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAO,GAAI,iBAAA,CAAkB,MAAM,IAAA,EAAM,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,SAAS,CAAA;AAErF,EAAA,MAAM,IAAA,GAAO,YAAA;AAEb,EAAA,IAAI,CAAC,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,eAAe,CAAA,EAAG;AAC9C,IAAA,MAAM,WAAW,MAAM,mBAAA,CAAoB,OAAO,KAAA,EAAO,MAAA,EAAQ,KAAK,UAAU,CAAA;AAChF,IAAA,MAAMC,KAAAA,GAAO,QAAA,CAAS,QAAA,EAAU,IAAI,CAAA;AACpC,IAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,aAAA,CAAc,QAAA,EAAU,gBAAgB,IAAA,CAAK,QAAQ,CAAA,EAAG,IAAI,CAAA,GAAI,MAAA;AAC/F,IAAA,OAAO,EAAE,IAAA,EAAAD,KAAAA,EAAM,IAAA,EAAAC,KAAAA,EAAM,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAS,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA,EAAG,QAAQ,IAAA,EAAK;AAAA,EACtF;AAEA,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA;AACpC,EAAA,MAAM,OAAO,OAAA,CAAQ,IAAA,CAAK,IAAI,IAAA,CAAK,UAAA,EAAY,IAAI,CAAC,CAAA;AAEpD,EAAA,MAAM,aAAa,MAAM,mBAAA,CAAoB,KAAA,EAAO,KAAA,EAAO,QAAQ,IAAI,CAAA;AACvE,EAAA,IAAI,UAAA,CAAW,UAAA,IAAc,IAAA,CAAK,WAAA,EAAa;AAC7C,IAAA,MAAMD,KAAAA,GAAO,QAAA,CAAS,UAAA,EAAY,IAAI,CAAA;AACtC,IAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,aAAA,CAAc,UAAA,EAAY,gBAAgB,IAAA,CAAK,QAAQ,CAAA,EAAG,IAAI,CAAA,GAAI,MAAA;AACjG,IAAA,OAAO,EAAE,IAAA,EAAAD,KAAAA,EAAM,IAAA,EAAAC,KAAAA,EAAM,OAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAAA,EAClE;AAEA,EAAA,MAAM,aAAa,MAAM,mBAAA,CAAoB,KAAA,EAAO,KAAA,EAAO,QAAQ,IAAI,CAAA;AACvE,EAAA,IAAI,UAAA,CAAW,UAAA,GAAa,IAAA,CAAK,WAAA,EAAa;AAC5C,IAAA,MAAMD,KAAAA,GAAO,QAAA,CAAS,UAAA,EAAY,IAAI,CAAA;AACtC,IAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,UAAA,GAAa,aAAA,CAAc,UAAA,EAAY,gBAAgB,IAAA,CAAK,QAAQ,CAAA,EAAG,IAAI,CAAA,GAAI,MAAA;AACjG,IAAA,OAAO,EAAE,IAAA,EAAAD,KAAAA,EAAM,IAAA,EAAAC,KAAAA,EAAM,OAAO,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAAA,EAClE;AAEA,EAAA,IAAI,EAAA,GAAK,IAAA;AACT,EAAA,IAAI,EAAA,GAAK,IAAA;AACT,EAAA,IAAI,SAAA,GAAwB,UAAA;AAC5B,EAAA,IAAI,KAAA,GAAQ,IAAA;AAEZ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,IAAA,MAAM,GAAA,GAAA,CAAO,KAAK,EAAA,IAAM,CAAA;AACxB,IAAA,MAAM,MAAM,MAAM,mBAAA,CAAoB,KAAA,EAAO,KAAA,EAAO,QAAQ,GAAG,CAAA;AAC/D,IAAA,IAAI,GAAA,CAAI,UAAA,IAAc,IAAA,CAAK,WAAA,EAAa;AACtC,MAAA,SAAA,GAAY,GAAA;AACZ,MAAA,KAAA,GAAQ,GAAA;AACR,MAAA,EAAA,GAAK,GAAA;AAAA,IACP,CAAA,MAAO;AACL,MAAA,EAAA,GAAK,GAAA;AAAA,IACP;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,SAAA,EAAW,IAAI,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,GAAa,aAAA,CAAc,SAAA,EAAW,gBAAgB,IAAA,CAAK,QAAQ,CAAA,EAAG,IAAI,CAAA,GAAI,MAAA;AAChG,EAAA,OAAO,EAAE,MAAM,IAAA,EAAM,KAAA,EAAO,QAAQ,OAAA,EAAS,KAAA,EAAO,QAAQ,IAAA,EAAK;AACnE;AAgCA,IAAM,cAAA,GAAwF;AAAA,EAC5F,GAAA,EAAK,EAAA;AAAA,EACL,QAAA,EAAU,MAAA;AAAA,EACV,UAAA,EAAY;AACd,CAAA;AAEA,eAAe,YAAe,EAAA,EAA6C;AACzE,EAAA,MAAM,MAAM,MAAMC,gBAAA,CAAQC,UAAKC,SAAA,EAAO,EAAG,iBAAiB,CAAC,CAAA;AAC3D,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,GAAG,GAAG,CAAA;AAAA,EACrB,CAAA,SAAE;AACA,IAAA,MAAMC,YAAG,GAAA,EAAK,EAAE,WAAW,IAAA,EAAM,KAAA,EAAO,MAAM,CAAA;AAAA,EAChD;AACF;AAEA,SAAS,aAAA,GAAwB;AAC/B,EAAA,IAAI,CAACC,2BAAA,EAAY;AACf,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAOA,2BAAA;AACT;AAEA,SAAS,WAAW,IAAA,EAAwB;AAC1C,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,KAAO,KAAK,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAA,GAAM,CAAE,CAAA,CAAE,KAAK,GAAG,CAAA;AAChE;AAEA,SAAS,iBAAiB,GAAA,EAAsB;AAC9C,EAAA,IAAI,GAAA,YAAe,KAAA,IAAS,GAAA,CAAI,OAAA,SAAgB,GAAA,CAAI,OAAA;AACpD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,UAAU,GAAG,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAO,GAAG,CAAA;AAAA,EACnB;AACF;AAEA,SAAS,2BAA2B,OAAA,EAA0B;AAC5D,EAAA,MAAM,KAAA,GAAQ,QAAQ,WAAA,EAAY;AAClC,EAAA,OAAO,KAAA,CAAM,QAAA,CAAS,iBAAiB,CAAA,IAAK,KAAA,CAAM,SAAS,YAAY,CAAA,IAAK,KAAA,CAAM,QAAA,CAAS,kCAAkC,CAAA;AAC/H;AAEA,SAAS,cAAA,CACP,MAAA,EACA,OAAA,EACA,IAAA,EACA,KAAA,EACU;AACV,EAAA,MAAM,IAAA,GAAiB;AAAA,IACrB,cAAA;AAAA,IACA,IAAA;AAAA,IACA,GAAI,IAAA,CAAK,kBAAA,IAAsB,IAAA,CAAK,kBAAA,GAAqB,CAAA,GACrD,CAAC,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,kBAAkB,CAAC,IACtC,EAAC;AAAA,IACL,IAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA,KAAU,QAAQ,YAAA,GAAe,QAAA;AAAA,IACjC,MAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,IACf,WAAA;AAAA,IACA,IAAA,CAAK,QAAA;AAAA,IACL,GAAI,KAAA,KAAU,KAAA,GAAQ,CAAC,SAAA,EAAW,GAAG,IAAI,EAAC;AAAA,IAC1C,MAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO,IAAA;AACT;AAKA,eAAsB,WAAA,CACpB,KAAA,EACA,OAAA,GAA8B,EAAC,EACH;AAC5B,EAAA,MAAM,SAAS,aAAA,EAAc;AAC7B,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,cAAA,EAAgB,GAAG,OAAA,EAAQ;AAC7C,EAAA,MAAM,KAAA,GAAQ,MAAM,YAAA,CAAa,KAAK,CAAA;AAEtC,EAAA,MAAM,IAAA,GAAO,YAAA;AACb,EAAA,OAAO,MAAM,WAAA,CAAY,OAAO,GAAA,KAAQ;AACtC,IAAA,MAAM,MAAA,GAASH,SAAA,CAAK,GAAA,EAAK,OAAO,CAAA;AAChC,IAAA,MAAM,OAAA,GAAUA,SAAA,CAAK,GAAA,EAAK,aAAa,CAAA;AACvC,IAAA,MAAMI,kBAAA,CAAY,QAAQ,KAAK,CAAA;AAE/B,IAAA,MAAM,OAAA,GAAU,cAAA,CAAe,MAAA,EAAQ,OAAA,EAAS,MAAM,KAAK,CAAA;AAE3D,IAAA,IAAI;AACF,MAAA,MAAMC,WAAA,CAAM,QAAQ,OAAA,EAAS,EAAE,QAAQ,MAAA,EAAQ,MAAA,EAAQ,QAAQ,CAAA;AAAA,IACjE,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,SAAA,GAAY,iBAAiB,GAAG,CAAA;AACtC,MAAA,IAAI,CAAC,0BAAA,CAA2B,SAAS,CAAA,EAAG;AAC1C,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,YACE,qCAAA;AAAA,YACA,UAAU,MAAM,CAAA,CAAA;AAAA,YAChB,CAAA,KAAA,EAAQ,UAAA,CAAW,OAAO,CAAC,CAAA,CAAA;AAAA,YAC3B,SAAS,SAAS,CAAA;AAAA,WACpB,CAAE,KAAK,IAAI;AAAA,SACb;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,cAAA,CAAe,MAAA,EAAQ,OAAA,EAAS,MAAM,KAAK,CAAA;AAC3D,MAAA,IAAI;AACF,QAAA,MAAMA,WAAA,CAAM,QAAQ,OAAA,EAAS,EAAE,QAAQ,MAAA,EAAQ,MAAA,EAAQ,QAAQ,CAAA;AAAA,MACjE,SAAS,WAAA,EAAa;AACpB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,YACE,+CAAA;AAAA,YACA,UAAU,MAAM,CAAA,CAAA;AAAA,YAChB,CAAA,SAAA,EAAY,UAAA,CAAW,OAAO,CAAC,CAAA,CAAA;AAAA,YAC/B,aAAa,SAAS,CAAA,CAAA;AAAA,YACtB,CAAA,SAAA,EAAY,UAAA,CAAW,OAAO,CAAC,CAAA,CAAA;AAAA,YAC/B,CAAA,UAAA,EAAa,gBAAA,CAAiB,WAAW,CAAC,CAAA;AAAA,WAC5C,CAAE,KAAK,IAAI;AAAA,SACb;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,GAAA,GAAM,MAAMV,iBAAA,CAAS,OAAO,CAAA;AAClC,IAAA,MAAM,QAAA,GAAW,IAAI,UAAA,CAAW,GAAG,CAAA;AACnC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,QAAA,EAAU,IAAI,CAAA;AACpC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,GAAa,aAAA,CAAc,QAAA,EAAU,qBAAqB,IAAA,CAAK,QAAQ,CAAA,EAAG,IAAI,CAAA,GAAI,KAAA,CAAA;AACpG,IAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK;AAAA,EACpC,CAAC,CAAA;AACH;AAWA,eAAe,eAAe,KAAA,EAA6D;AACzF,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,MAAM,OAAA,GAAU,MAAM,WAAA,EAAY;AAClC,IAAA,IAAI,8CAAA,CAA+C,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,OAAA;AACzE,IAAA,IAAI,6CAAA,CAA8C,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,OAAA;AAAA,EAC1E;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAM,YAAA,CAAa,KAAK,CAAA;AACtC,EAAA,MAAM,EAAA,GAAK,MAAMW,2BAAA,CAAmB,KAAK,CAAA;AACzC,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,cAAA,CAAe,KAAK,CAAA;AACvC,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","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) {\r\n throw new Error(\r\n \"ffmpeg binary not found (ffmpeg-static did not resolve a path). Install optional platform dependencies or provide a runtime where ffmpeg-static can resolve.\"\r\n )\r\n }\r\n return ffmpegPath\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 getExecErrorText(err: unknown): string {\r\n if (err instanceof Error && err.message) return err.message\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 getLikelyMissingVp9Encoder(errText: string): boolean {\r\n const lower = errText.toLowerCase()\r\n return lower.includes(\"unknown encoder\") || lower.includes(\"libvpx-vp9\") || lower.includes(\"error initializing output stream\")\r\n}\r\n\r\nfunction buildVideoArgs(\r\n inPath: string,\r\n outPath: string,\r\n opts: Required<Pick<VideoToWebmOptions, \"crf\" | \"deadline\" | \"returnFile\">> & Pick<VideoToWebmOptions, \"maxDurationSeconds\">,\r\n codec: \"vp9\" | \"vp8\"\r\n): string[] {\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 codec === \"vp9\" ? \"libvpx-vp9\" : \"libvpx\",\r\n \"-b:v\",\r\n \"0\",\r\n \"-crf\",\r\n String(opts.crf),\r\n \"-deadline\",\r\n opts.deadline,\r\n ...(codec === \"vp9\" ? [\"-row-mt\", \"1\"] : []),\r\n \"-c:a\",\r\n \"libopus\",\r\n outPath,\r\n ]\r\n return args\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 vp9Args = buildVideoArgs(inPath, outPath, opts, \"vp9\")\r\n\r\n try {\r\n await execa(ffmpeg, vp9Args, { stderr: \"pipe\", stdout: \"pipe\" })\r\n } catch (err) {\r\n const execError = getExecErrorText(err)\r\n if (!getLikelyMissingVp9Encoder(execError)) {\r\n throw new Error(\r\n [\r\n \"videoToWebm failed with VP9 encode.\",\r\n `ffmpeg=${ffmpeg}`,\r\n `args=${formatArgs(vp9Args)}`,\r\n `error=${execError}`,\r\n ].join(\"\\n\")\r\n )\r\n }\r\n\r\n const vp8Args = buildVideoArgs(inPath, outPath, opts, \"vp8\")\r\n try {\r\n await execa(ffmpeg, vp8Args, { stderr: \"pipe\", stdout: \"pipe\" })\r\n } catch (fallbackErr) {\r\n throw new Error(\r\n [\r\n \"videoToWebm failed with VP9 and VP8 fallback.\",\r\n `ffmpeg=${ffmpeg}`,\r\n `vp9_args=${formatArgs(vp9Args)}`,\r\n `vp9_error=${execError}`,\r\n `vp8_args=${formatArgs(vp8Args)}`,\r\n `vp8_error=${getExecErrorText(fallbackErr)}`,\r\n ].join(\"\\n\")\r\n )\r\n }\r\n }\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"]}
@@ -136,9 +136,54 @@ async function withTempDir(fn) {
136
136
  }
137
137
  }
138
138
  function getFfmpegPath() {
139
- if (!ffmpegPath) throw new Error("ffmpeg binary not found (ffmpeg-static did not resolve a path).");
139
+ if (!ffmpegPath) {
140
+ throw new Error(
141
+ "ffmpeg binary not found (ffmpeg-static did not resolve a path). Install optional platform dependencies or provide a runtime where ffmpeg-static can resolve."
142
+ );
143
+ }
140
144
  return ffmpegPath;
141
145
  }
146
+ function formatArgs(args) {
147
+ return args.map((v) => /\s/.test(v) ? `"${v}"` : v).join(" ");
148
+ }
149
+ function getExecErrorText(err) {
150
+ if (err instanceof Error && err.message) return err.message;
151
+ try {
152
+ return JSON.stringify(err);
153
+ } catch {
154
+ return String(err);
155
+ }
156
+ }
157
+ function getLikelyMissingVp9Encoder(errText) {
158
+ const lower = errText.toLowerCase();
159
+ return lower.includes("unknown encoder") || lower.includes("libvpx-vp9") || lower.includes("error initializing output stream");
160
+ }
161
+ function buildVideoArgs(inPath, outPath, opts, codec) {
162
+ const args = [
163
+ "-hide_banner",
164
+ "-y",
165
+ ...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
166
+ "-i",
167
+ inPath,
168
+ "-map",
169
+ "0:v:0",
170
+ "-map",
171
+ "0:a?",
172
+ "-c:v",
173
+ codec === "vp9" ? "libvpx-vp9" : "libvpx",
174
+ "-b:v",
175
+ "0",
176
+ "-crf",
177
+ String(opts.crf),
178
+ "-deadline",
179
+ opts.deadline,
180
+ ...codec === "vp9" ? ["-row-mt", "1"] : [],
181
+ "-c:a",
182
+ "libopus",
183
+ outPath
184
+ ];
185
+ return args;
186
+ }
142
187
  async function videoToWebm(input, options = {}) {
143
188
  const ffmpeg = getFfmpegPath();
144
189
  const opts = { ...VIDEO_DEFAULTS, ...options };
@@ -148,31 +193,37 @@ async function videoToWebm(input, options = {}) {
148
193
  const inPath = join(dir, "input");
149
194
  const outPath = join(dir, "output.webm");
150
195
  await writeFile(inPath, bytes);
151
- const args = [
152
- "-hide_banner",
153
- "-y",
154
- ...opts.maxDurationSeconds && opts.maxDurationSeconds > 0 ? ["-t", String(opts.maxDurationSeconds)] : [],
155
- "-i",
156
- inPath,
157
- "-map",
158
- "0:v:0",
159
- "-map",
160
- "0:a?",
161
- "-c:v",
162
- "libvpx-vp9",
163
- "-b:v",
164
- "0",
165
- "-crf",
166
- String(opts.crf),
167
- "-deadline",
168
- opts.deadline,
169
- "-row-mt",
170
- "1",
171
- "-c:a",
172
- "libopus",
173
- outPath
174
- ];
175
- await execa(ffmpeg, args, { stderr: "pipe", stdout: "pipe" });
196
+ const vp9Args = buildVideoArgs(inPath, outPath, opts, "vp9");
197
+ try {
198
+ await execa(ffmpeg, vp9Args, { stderr: "pipe", stdout: "pipe" });
199
+ } catch (err) {
200
+ const execError = getExecErrorText(err);
201
+ if (!getLikelyMissingVp9Encoder(execError)) {
202
+ throw new Error(
203
+ [
204
+ "videoToWebm failed with VP9 encode.",
205
+ `ffmpeg=${ffmpeg}`,
206
+ `args=${formatArgs(vp9Args)}`,
207
+ `error=${execError}`
208
+ ].join("\n")
209
+ );
210
+ }
211
+ const vp8Args = buildVideoArgs(inPath, outPath, opts, "vp8");
212
+ try {
213
+ await execa(ffmpeg, vp8Args, { stderr: "pipe", stdout: "pipe" });
214
+ } catch (fallbackErr) {
215
+ throw new Error(
216
+ [
217
+ "videoToWebm failed with VP9 and VP8 fallback.",
218
+ `ffmpeg=${ffmpeg}`,
219
+ `vp9_args=${formatArgs(vp9Args)}`,
220
+ `vp9_error=${execError}`,
221
+ `vp8_args=${formatArgs(vp8Args)}`,
222
+ `vp8_error=${getExecErrorText(fallbackErr)}`
223
+ ].join("\n")
224
+ );
225
+ }
226
+ }
176
227
  const out = await readFile(outPath);
177
228
  const outBytes = new Uint8Array(out);
178
229
  const blob = makeBlob(outBytes, type);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/node/index.ts"],"names":["blob","file","writeFileFs"],"mappings":"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","file":"index.js","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"]}
1
+ {"version":3,"sources":["../../src/node/index.ts"],"names":["blob","file","writeFileFs"],"mappings":"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","file":"index.js","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) {\r\n throw new Error(\r\n \"ffmpeg binary not found (ffmpeg-static did not resolve a path). Install optional platform dependencies or provide a runtime where ffmpeg-static can resolve.\"\r\n )\r\n }\r\n return ffmpegPath\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 getExecErrorText(err: unknown): string {\r\n if (err instanceof Error && err.message) return err.message\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 getLikelyMissingVp9Encoder(errText: string): boolean {\r\n const lower = errText.toLowerCase()\r\n return lower.includes(\"unknown encoder\") || lower.includes(\"libvpx-vp9\") || lower.includes(\"error initializing output stream\")\r\n}\r\n\r\nfunction buildVideoArgs(\r\n inPath: string,\r\n outPath: string,\r\n opts: Required<Pick<VideoToWebmOptions, \"crf\" | \"deadline\" | \"returnFile\">> & Pick<VideoToWebmOptions, \"maxDurationSeconds\">,\r\n codec: \"vp9\" | \"vp8\"\r\n): string[] {\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 codec === \"vp9\" ? \"libvpx-vp9\" : \"libvpx\",\r\n \"-b:v\",\r\n \"0\",\r\n \"-crf\",\r\n String(opts.crf),\r\n \"-deadline\",\r\n opts.deadline,\r\n ...(codec === \"vp9\" ? [\"-row-mt\", \"1\"] : []),\r\n \"-c:a\",\r\n \"libopus\",\r\n outPath,\r\n ]\r\n return args\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 vp9Args = buildVideoArgs(inPath, outPath, opts, \"vp9\")\r\n\r\n try {\r\n await execa(ffmpeg, vp9Args, { stderr: \"pipe\", stdout: \"pipe\" })\r\n } catch (err) {\r\n const execError = getExecErrorText(err)\r\n if (!getLikelyMissingVp9Encoder(execError)) {\r\n throw new Error(\r\n [\r\n \"videoToWebm failed with VP9 encode.\",\r\n `ffmpeg=${ffmpeg}`,\r\n `args=${formatArgs(vp9Args)}`,\r\n `error=${execError}`,\r\n ].join(\"\\n\")\r\n )\r\n }\r\n\r\n const vp8Args = buildVideoArgs(inPath, outPath, opts, \"vp8\")\r\n try {\r\n await execa(ffmpeg, vp8Args, { stderr: \"pipe\", stdout: \"pipe\" })\r\n } catch (fallbackErr) {\r\n throw new Error(\r\n [\r\n \"videoToWebm failed with VP9 and VP8 fallback.\",\r\n `ffmpeg=${ffmpeg}`,\r\n `vp9_args=${formatArgs(vp9Args)}`,\r\n `vp9_error=${execError}`,\r\n `vp8_args=${formatArgs(vp8Args)}`,\r\n `vp8_error=${getExecErrorText(fallbackErr)}`,\r\n ].join(\"\\n\")\r\n )\r\n }\r\n }\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weblet-convert",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Convert images to WebP and videos to WebM in browser and Node.js.",
5
5
  "license": "MIT",
6
6
  "repository": {