From 57054c24b25beb7cff4c6fa6c1dca3c420bf9abd Mon Sep 17 00:00:00 2001 From: wukko <me@wukko.me> Date: Mon, 12 Aug 2024 22:28:38 +0600 Subject: [PATCH] web: draft libav functionality --- pnpm-lock.yaml | 8 ++++ web/src/lib/libav.ts | 89 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 web/src/lib/libav.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5351924f..58fdabb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,9 @@ importers: '@imput/ffmpeg.wasm': specifier: ^0.12.11 version: 0.12.11 + '@imput/libav.js-remux-cli': + specifier: ^5.4.6 + version: 5.4.6 '@imput/version-info': specifier: workspace:^ version: link:../packages/version-info @@ -533,6 +536,9 @@ packages: resolution: {integrity: sha512-/krk6BPy7TdDN73KHj/g0TVNDNLvvtlTv2T8di4GwrbuIcqfv5wCa2iMR2II/Vhf7zFYJvC2m5kDYHTtDQWS5Q==} engines: {node: '>=18.x'} + '@imput/libav.js-remux-cli@5.4.6': + resolution: {integrity: sha512-lO83tppljyCyJAks2cfA0YULnPstAYE/5LQVoFAFu+CSwUcgZqB9OmBNHufMj3GnTXzYE35OORSuYHYZEyiC5w==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2496,6 +2502,8 @@ snapshots: dependencies: '@imput/ffmpeg-types': 0.12.3 + '@imput/libav.js-remux-cli@5.4.6': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 diff --git a/web/src/lib/libav.ts b/web/src/lib/libav.ts new file mode 100644 index 00000000..266ac75c --- /dev/null +++ b/web/src/lib/libav.ts @@ -0,0 +1,89 @@ +import mime from "mime"; +import LibAV, { type LibAV as LibAVInstance } from "@imput/libav.js-remux-cli"; + +type InputFileKind = "video" | "audio"; + +type FileInfo = { + type?: string | null, + kind: InputFileKind, + extension: string, +} + +type RenderParams = { + file: File, + output?: FileInfo, + args: string[], +} + +export default class LibAVWrapper { + libav!: LibAVInstance | null; + concurrency: number; + + constructor() { + this.concurrency = Math.min(4, navigator.hardwareConcurrency); + } + + async init() { + if (!this.libav) { + this.libav = await LibAV.LibAV({ + yesthreads: true + }) + } + } + + async renderFile({ file, output, args }: RenderParams) { + if (!this.libav) throw new Error("LibAV wasn't initialized"); + + const inputKind = file.type.split("/")[0]; + const inputExtension = mime.getExtension(file.type); + + if (inputKind !== "video" && inputKind !== "audio") return; + if (!inputExtension) return; + + const input: FileInfo = { + kind: inputKind, + extension: inputExtension, + } + + if (!output) output = input; + + output.type = mime.getType(output.extension); + if (!output.type) return; + + const outputName = `output.${output.extension}`; + + const buffer = new Blob([await file.arrayBuffer()]); + await this.libav.mkreadaheadfile("input", buffer); + + // https://github.com/Yahweasel/libav.js/blob/7d359f69/docs/IO.md#block-writer-devices + await this.libav.mkwriterdev(outputName); + let writtenData = new Uint8Array(0); + + this.libav.onwrite = (name, pos, data) => { + const newLen = Math.max(writtenData.length, pos + data.length); + if (newLen > writtenData.length) { + const newData = new Uint8Array(newLen); + newData.set(writtenData); + writtenData = newData; + } + writtenData.set(data, pos); + }; + + await this.libav.ffmpeg([ + '-threads', this.concurrency.toString(), + '-i', 'input', + ...args, + outputName + ]); + + await this.libav.unlinkmkreadaheadfile("input"); + + const renderBlob = new Blob( + [writtenData], + { type: output.type } + ); + + if (renderBlob.size === 0) return; + return renderBlob; + } +}