mirror of
https://github.com/wukko/cobalt.git
synced 2025-03-23 09:39:21 +01:00
web/libav: accept several inputs, refactor
This commit is contained in:
parent
fd1a7530ed
commit
00106e9379
3 changed files with 34 additions and 57 deletions
|
@ -1,7 +1,7 @@
|
||||||
import mime from "mime";
|
|
||||||
import LibAV, { type LibAV as LibAVInstance } from "@imput/libav.js-remux-cli";
|
import LibAV, { type LibAV as LibAVInstance } from "@imput/libav.js-remux-cli";
|
||||||
import type { FFmpegProgressCallback, FFmpegProgressEvent, FFmpegProgressStatus, FileInfo, RenderParams } from "./types/libav";
|
|
||||||
import type { FfprobeData } from "fluent-ffmpeg";
|
import type { FfprobeData } from "fluent-ffmpeg";
|
||||||
|
import type { FFmpegProgressCallback, FFmpegProgressEvent, FFmpegProgressStatus, RenderParams } from "$lib/types/libav";
|
||||||
|
|
||||||
export default class LibAVWrapper {
|
export default class LibAVWrapper {
|
||||||
libav: Promise<LibAVInstance> | null;
|
libav: Promise<LibAVInstance> | null;
|
||||||
|
@ -56,60 +56,35 @@ export default class LibAVWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getExtensionFromType(blob: Blob | File) {
|
async render({ files, output, args }: RenderParams) {
|
||||||
const canonicalExtension = blob instanceof File && blob.name.split('.').pop()?.toLowerCase();
|
|
||||||
const extensions = mime.getAllExtensions(blob.type);
|
|
||||||
|
|
||||||
if (canonicalExtension && extensions?.has(canonicalExtension))
|
|
||||||
return canonicalExtension;
|
|
||||||
|
|
||||||
const overrides = ['mp3', 'mov', 'opus'];
|
|
||||||
|
|
||||||
if (!extensions)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (const override of overrides)
|
|
||||||
if (extensions?.has(override))
|
|
||||||
return override;
|
|
||||||
|
|
||||||
return [...extensions][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async render({ blob, output, args }: RenderParams) {
|
|
||||||
if (!this.libav) throw new Error("LibAV wasn't initialized");
|
if (!this.libav) throw new Error("LibAV wasn't initialized");
|
||||||
const libav = await this.libav;
|
const libav = await this.libav;
|
||||||
const inputKind = blob.type.split("/")[0];
|
|
||||||
const inputExtension = LibAVWrapper.getExtensionFromType(blob);
|
|
||||||
|
|
||||||
if (inputKind !== "video" && inputKind !== "audio") return;
|
if (!(output.extension && output.type)) {
|
||||||
if (!inputExtension) return;
|
throw new Error("output's extension or type is missing");
|
||||||
|
|
||||||
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 outputName = `output.${output.extension}`;
|
||||||
|
const ffInputs = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await libav.mkreadaheadfile("input", blob);
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
await libav.mkreadaheadfile(`input${i}`, files[i]);
|
||||||
|
ffInputs.push('-i', `input${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/Yahweasel/libav.js/blob/7d359f69/docs/IO.md#block-writer-devices
|
|
||||||
await libav.mkwriterdev(outputName);
|
await libav.mkwriterdev(outputName);
|
||||||
await libav.mkwriterdev('progress.txt');
|
await libav.mkwriterdev('progress.txt');
|
||||||
|
|
||||||
|
const totalInputSize = files.reduce((a, b) => a + b.size, 0);
|
||||||
|
|
||||||
const MB = 1024 * 1024;
|
const MB = 1024 * 1024;
|
||||||
const chunks: Uint8Array[] = [];
|
const chunks: Uint8Array[] = [];
|
||||||
const chunkSize = Math.min(512 * MB, blob.size);
|
const chunkSize = Math.min(512 * MB, totalInputSize);
|
||||||
|
|
||||||
// since we expect the output file to be roughly the same size
|
// since we expect the output file to be roughly the same size
|
||||||
// as the original, preallocate its size for the output
|
// as inputs, preallocate its size for the output
|
||||||
for (let toAllocate = blob.size; toAllocate > 0; toAllocate -= chunkSize) {
|
for (let toAllocate = totalInputSize; toAllocate > 0; toAllocate -= chunkSize) {
|
||||||
chunks.push(new Uint8Array(chunkSize));
|
chunks.push(new Uint8Array(chunkSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +125,7 @@ export default class LibAVWrapper {
|
||||||
'-loglevel', 'error',
|
'-loglevel', 'error',
|
||||||
'-progress', 'progress.txt',
|
'-progress', 'progress.txt',
|
||||||
'-threads', this.concurrency.toString(),
|
'-threads', this.concurrency.toString(),
|
||||||
'-i', 'input',
|
...ffInputs,
|
||||||
...args,
|
...args,
|
||||||
outputName
|
outputName
|
||||||
]);
|
]);
|
||||||
|
@ -183,7 +158,11 @@ export default class LibAVWrapper {
|
||||||
try {
|
try {
|
||||||
await libav.unlink(outputName);
|
await libav.unlink(outputName);
|
||||||
await libav.unlink('progress.txt');
|
await libav.unlink('progress.txt');
|
||||||
await libav.unlinkreadaheadfile("input");
|
|
||||||
|
await Promise.allSettled(
|
||||||
|
files.map((_, i) =>
|
||||||
|
libav.unlinkreadaheadfile(`input${i}`)
|
||||||
|
));
|
||||||
} catch { /* catch & ignore */ }
|
} catch { /* catch & ignore */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,7 +175,7 @@ export default class LibAVWrapper {
|
||||||
const entries = Object.fromEntries(
|
const entries = Object.fromEntries(
|
||||||
text.split('\n')
|
text.split('\n')
|
||||||
.filter(a => a)
|
.filter(a => a)
|
||||||
.map(a => a.split('=', ))
|
.map(a => a.split('='))
|
||||||
);
|
);
|
||||||
|
|
||||||
const status: FFmpegProgressStatus = (() => {
|
const status: FFmpegProgressStatus = (() => {
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
export type InputFileKind = "video" | "audio";
|
|
||||||
|
|
||||||
export type FileInfo = {
|
export type FileInfo = {
|
||||||
type?: string | null,
|
type?: string,
|
||||||
kind: InputFileKind,
|
extension?: string,
|
||||||
extension: string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderParams = {
|
export type RenderParams = {
|
||||||
blob: Blob | File,
|
files: File[],
|
||||||
output?: FileInfo,
|
output: FileInfo,
|
||||||
args: string[],
|
args: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type FFmpegProgressStatus = "continue" | "end" | "unknown";
|
export type FFmpegProgressStatus = "continue" | "end" | "unknown";
|
||||||
export type FFmpegProgressEvent = {
|
export type FFmpegProgressEvent = {
|
||||||
status: FFmpegProgressStatus,
|
status: FFmpegProgressStatus,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import mime from "mime";
|
|
||||||
import LibAVWrapper from "$lib/libav";
|
import LibAVWrapper from "$lib/libav";
|
||||||
|
|
||||||
const error = (code: string) => {
|
const error = (code: string) => {
|
||||||
|
@ -54,15 +53,18 @@ const remux = async (file: File) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (file instanceof File && !file.type) {
|
if (!file.type) {
|
||||||
file = new File([file], file.name, {
|
// TODO: better & more appropriate error code
|
||||||
type: mime.getType(file.name) ?? undefined,
|
error("remux.corrupted");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const render = await ff
|
const render = await ff
|
||||||
.render({
|
.render({
|
||||||
blob: file,
|
files: [file],
|
||||||
|
output: {
|
||||||
|
type: file.type,
|
||||||
|
extension: file.name.split(".").pop(),
|
||||||
|
},
|
||||||
args: ["-c", "copy", "-map", "0"],
|
args: ["-c", "copy", "-map", "0"],
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue