2024-08-12 18:28:38 +02:00
|
|
|
import mime from "mime";
|
|
|
|
import LibAV, { type LibAV as LibAVInstance } from "@imput/libav.js-remux-cli";
|
2024-08-12 22:03:07 +02:00
|
|
|
import type { FileInfo, RenderParams } from "./types/libav";
|
2024-08-12 18:28:38 +02:00
|
|
|
|
|
|
|
export default class LibAVWrapper {
|
2024-08-12 22:03:30 +02:00
|
|
|
libav: LibAVInstance | null;
|
2024-08-12 18:28:38 +02:00
|
|
|
concurrency: number;
|
|
|
|
|
|
|
|
constructor() {
|
2024-08-12 22:03:30 +02:00
|
|
|
this.libav = null;
|
2024-08-12 18:28:38 +02:00
|
|
|
this.concurrency = Math.min(4, navigator.hardwareConcurrency);
|
|
|
|
}
|
|
|
|
|
|
|
|
async init() {
|
|
|
|
if (!this.libav) {
|
|
|
|
this.libav = await LibAV.LibAV({
|
2024-08-12 19:06:45 +02:00
|
|
|
yesthreads: true,
|
2024-08-12 22:36:24 +02:00
|
|
|
base: '/_libav'
|
|
|
|
});
|
2024-08-12 18:28:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-12 19:06:45 +02:00
|
|
|
async render({ blob, output, args }: RenderParams) {
|
2024-08-12 18:28:38 +02:00
|
|
|
if (!this.libav) throw new Error("LibAV wasn't initialized");
|
|
|
|
|
2024-08-12 19:06:45 +02:00
|
|
|
const inputKind = blob.type.split("/")[0];
|
|
|
|
const inputExtension = mime.getExtension(blob.type);
|
2024-08-12 18:28:38 +02:00
|
|
|
|
|
|
|
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}`;
|
|
|
|
|
2024-08-12 19:06:45 +02:00
|
|
|
await this.libav.mkreadaheadfile("input", blob);
|
2024-08-12 18:28:38 +02:00
|
|
|
|
|
|
|
// https://github.com/Yahweasel/libav.js/blob/7d359f69/docs/IO.md#block-writer-devices
|
|
|
|
await this.libav.mkwriterdev(outputName);
|
2024-08-12 20:06:59 +02:00
|
|
|
|
|
|
|
// since we expect the output file to be roughly the same size
|
|
|
|
// as the original, preallocate its size for the output
|
|
|
|
let writtenData = new Uint8Array(blob.size), actualSize = 0;
|
2024-08-12 18:28:38 +02:00
|
|
|
|
|
|
|
this.libav.onwrite = (name, pos, data) => {
|
2024-08-12 19:06:45 +02:00
|
|
|
if (name !== outputName) return;
|
|
|
|
|
2024-08-12 20:06:59 +02:00
|
|
|
actualSize = Math.max(pos + data.length, actualSize);
|
|
|
|
const newLen = Math.max(pos + data.length, writtenData.length);
|
2024-08-12 18:28:38 +02:00
|
|
|
if (newLen > writtenData.length) {
|
|
|
|
const newData = new Uint8Array(newLen);
|
|
|
|
newData.set(writtenData);
|
|
|
|
writtenData = newData;
|
|
|
|
}
|
|
|
|
writtenData.set(data, pos);
|
|
|
|
};
|
|
|
|
|
2024-08-12 20:06:59 +02:00
|
|
|
// if we didn't need as much space as we allocated for some reason,
|
|
|
|
// shrink the buffer so that we don't inflate the file with zeros
|
|
|
|
if (writtenData.length > actualSize) {
|
|
|
|
writtenData = writtenData.slice(0, actualSize);
|
|
|
|
}
|
|
|
|
|
2024-08-12 18:28:38 +02:00
|
|
|
await this.libav.ffmpeg([
|
2024-08-12 19:06:45 +02:00
|
|
|
'-nostdin', '-y',
|
2024-08-12 18:28:38 +02:00
|
|
|
'-threads', this.concurrency.toString(),
|
|
|
|
'-i', 'input',
|
|
|
|
...args,
|
|
|
|
outputName
|
|
|
|
]);
|
|
|
|
|
2024-08-12 19:06:45 +02:00
|
|
|
await this.libav.unlink(outputName);
|
2024-08-12 22:36:24 +02:00
|
|
|
await this.libav.unlinkreadaheadfile("input");
|
2024-08-12 18:28:38 +02:00
|
|
|
|
|
|
|
const renderBlob = new Blob(
|
2024-08-12 19:06:45 +02:00
|
|
|
[ writtenData ],
|
2024-08-12 18:28:38 +02:00
|
|
|
{ type: output.type }
|
|
|
|
);
|
|
|
|
|
|
|
|
if (renderBlob.size === 0) return;
|
|
|
|
return renderBlob;
|
|
|
|
}
|
2024-08-12 19:06:45 +02:00
|
|
|
}
|