86 lines
2.4 KiB
JavaScript
86 lines
2.4 KiB
JavaScript
import { ERROR_TERMINATED } from "./ffmpeg/package/dist/esm/errors.js";
|
|
|
|
export class VideoCompressor {
|
|
constructor(progressBar) {
|
|
this.progressBar = progressBar;
|
|
this.ffmpeg = new FFmpegWASM.FFmpeg();
|
|
this.inProgress = false;
|
|
}
|
|
|
|
async compress(file, filesizeBytes, duration) {
|
|
this.progressBar.setProgress(0);
|
|
this.inProgress = true;
|
|
|
|
const progressCallback = event => this.progressBar.setProgress(event.progress);
|
|
this.ffmpeg.on("progress", progressCallback);
|
|
|
|
const filesizeKbit = filesizeBytes * 0.008;
|
|
|
|
// Calculate target bitrate to create a video file with desired file size (https://trac.ffmpeg.org/wiki/Encode/H.264#twopass)
|
|
let bitrate = filesizeKbit / duration;
|
|
bitrate -= 128; // Subtract audio bitrate
|
|
bitrate -= (bitrate / 100) * 5; // Subtract 5% to compensate for overhead
|
|
bitrate = Math.floor(bitrate);
|
|
|
|
if (bitrate <= 0) {
|
|
this.inProgress = false;
|
|
throw new Error("Selected file size is too low for this video");
|
|
}
|
|
|
|
console.debug("Target bitrate:", bitrate, "Video length:", duration);
|
|
|
|
// Run compression
|
|
try {
|
|
await this.ffmpeg.load({ coreURL: "/assets/scripts/core/package/dist/umd/ffmpeg-core.js" });
|
|
|
|
await this.ffmpeg.writeFile(file.name, await this.readFromBlob(file));
|
|
|
|
await this.ffmpeg.exec(["-i", file.name, "-preset", "ultrafast", "-c:v", "libx264", "-b:v", bitrate + "k", "-c:a", "copy", "-b:a", "128k", "compressed.mp4"]);
|
|
} catch (e) {
|
|
// Ignore termination error
|
|
if (e.message === ERROR_TERMINATED.message) {
|
|
this.inProgress = false;
|
|
return null;
|
|
}
|
|
|
|
throw e;
|
|
}
|
|
|
|
const video = await this.ffmpeg.readFile("compressed.mp4");
|
|
const url = URL.createObjectURL(new Blob([video.buffer], { type: "video/mp4" }));
|
|
|
|
// Clean up
|
|
this.ffmpeg.off("progress", progressCallback);
|
|
this.inProgress = false;
|
|
|
|
return url;
|
|
}
|
|
|
|
stop() {
|
|
this.ffmpeg.terminate();
|
|
this.inProgress = false;
|
|
}
|
|
|
|
// https://github.com/ffmpegwasm/ffmpeg.wasm/blob/main/packages/util/src/index.ts
|
|
readFromBlob(blob) {
|
|
return new Promise((resolve, reject) => {
|
|
const fileReader = new FileReader();
|
|
|
|
fileReader.onload = () => {
|
|
const { result } = fileReader;
|
|
if (result instanceof ArrayBuffer) {
|
|
resolve(new Uint8Array(result));
|
|
} else {
|
|
resolve(new Uint8Array());
|
|
}
|
|
};
|
|
|
|
fileReader.onerror = (event) => {
|
|
reject(Error(`File could not be read! Code=${event?.target?.error?.code || -1}`));
|
|
};
|
|
|
|
fileReader.readAsArrayBuffer(blob);
|
|
});
|
|
}
|
|
}
|