mirror of
https://github.com/imputnet/cobalt.git
synced 2025-01-04 06:06:07 +00:00
web/libav: emit progress events
This commit is contained in:
parent
f661e839b1
commit
28600e7e4c
|
@ -1,14 +1,16 @@
|
||||||
import mime from "mime";
|
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 { FileInfo, RenderParams } from "./types/libav";
|
import type { FFmpegProgressCallback, FFmpegProgressEvent, FFmpegProgressStatus, FileInfo, RenderParams } from "./types/libav";
|
||||||
|
|
||||||
export default class LibAVWrapper {
|
export default class LibAVWrapper {
|
||||||
libav: LibAVInstance | null;
|
libav: LibAVInstance | null;
|
||||||
concurrency: number;
|
concurrency: number;
|
||||||
|
onProgress?: FFmpegProgressCallback;
|
||||||
|
|
||||||
constructor() {
|
constructor(onProgress?: FFmpegProgressCallback) {
|
||||||
this.libav = null;
|
this.libav = null;
|
||||||
this.concurrency = Math.min(4, navigator.hardwareConcurrency);
|
this.concurrency = Math.min(4, navigator.hardwareConcurrency);
|
||||||
|
this.onProgress = onProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
@ -45,13 +47,16 @@ export default class LibAVWrapper {
|
||||||
|
|
||||||
// https://github.com/Yahweasel/libav.js/blob/7d359f69/docs/IO.md#block-writer-devices
|
// https://github.com/Yahweasel/libav.js/blob/7d359f69/docs/IO.md#block-writer-devices
|
||||||
await this.libav.mkwriterdev(outputName);
|
await this.libav.mkwriterdev(outputName);
|
||||||
|
await this.libav.mkwriterdev('progress.txt');
|
||||||
|
|
||||||
// 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 the original, preallocate its size for the output
|
||||||
let writtenData = new Uint8Array(blob.size), actualSize = 0;
|
let writtenData = new Uint8Array(blob.size), actualSize = 0;
|
||||||
|
|
||||||
this.libav.onwrite = (name, pos, data) => {
|
this.libav.onwrite = (name, pos, data) => {
|
||||||
if (name !== outputName) return;
|
if (name === 'progress.txt') {
|
||||||
|
return this.#emitProgress(data);
|
||||||
|
} else if (name !== outputName) return;
|
||||||
|
|
||||||
actualSize = Math.max(pos + data.length, actualSize);
|
actualSize = Math.max(pos + data.length, actualSize);
|
||||||
const newLen = Math.max(pos + data.length, writtenData.length);
|
const newLen = Math.max(pos + data.length, writtenData.length);
|
||||||
|
@ -65,6 +70,8 @@ export default class LibAVWrapper {
|
||||||
|
|
||||||
await this.libav.ffmpeg([
|
await this.libav.ffmpeg([
|
||||||
'-nostdin', '-y',
|
'-nostdin', '-y',
|
||||||
|
'-loglevel', 'error',
|
||||||
|
'-progress', 'progress.txt',
|
||||||
'-threads', this.concurrency.toString(),
|
'-threads', this.concurrency.toString(),
|
||||||
'-i', 'input',
|
'-i', 'input',
|
||||||
...args,
|
...args,
|
||||||
|
@ -72,6 +79,7 @@ export default class LibAVWrapper {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await this.libav.unlink(outputName);
|
await this.libav.unlink(outputName);
|
||||||
|
await this.libav.unlink('progress.txt');
|
||||||
await this.libav.unlinkreadaheadfile("input");
|
await this.libav.unlinkreadaheadfile("input");
|
||||||
|
|
||||||
// if we didn't need as much space as we allocated for some reason,
|
// if we didn't need as much space as we allocated for some reason,
|
||||||
|
@ -88,4 +96,45 @@ export default class LibAVWrapper {
|
||||||
if (renderBlob.size === 0) return;
|
if (renderBlob.size === 0) return;
|
||||||
return renderBlob;
|
return renderBlob;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#emitProgress(data: Uint8Array | Int8Array) {
|
||||||
|
const copy = new Uint8Array(data);
|
||||||
|
const text = new TextDecoder().decode(copy);
|
||||||
|
const entries = Object.fromEntries(
|
||||||
|
text.split('\n')
|
||||||
|
.filter(a => a)
|
||||||
|
.map(a => a.split('=', ))
|
||||||
|
);
|
||||||
|
|
||||||
|
const status: FFmpegProgressStatus = (() => {
|
||||||
|
const { progress } = entries;
|
||||||
|
|
||||||
|
if (progress === 'continue' || progress === 'end') {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
})();
|
||||||
|
|
||||||
|
const tryNumber = (str: string) => {
|
||||||
|
if (str) {
|
||||||
|
const num = Number(str);
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress: FFmpegProgressEvent = {
|
||||||
|
status,
|
||||||
|
frame: tryNumber(entries.frame),
|
||||||
|
fps: tryNumber(entries.fps),
|
||||||
|
total_size: tryNumber(entries.total_size),
|
||||||
|
dup_frames: tryNumber(entries.dup_frames),
|
||||||
|
drop_frames: tryNumber(entries.drop_frames),
|
||||||
|
speed: tryNumber(entries.speed?.trim()?.replace('x', '')),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onProgress(progress);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -12,3 +12,16 @@ export type RenderParams = {
|
||||||
args: string[],
|
args: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type FFmpegProgressStatus = "continue" | "end" | "unknown";
|
||||||
|
export type FFmpegProgressEvent = {
|
||||||
|
status: FFmpegProgressStatus,
|
||||||
|
frame?: number,
|
||||||
|
fps?: number,
|
||||||
|
total_size?: number,
|
||||||
|
dup_frames?: number,
|
||||||
|
drop_frames?: number,
|
||||||
|
speed?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FFmpegProgressCallback = (info: FFmpegProgressEvent) => void;
|
||||||
|
|
Loading…
Reference in a new issue