video-compressor/public/assets/scripts/VideoCompressor.js

95 lines
2.6 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.setStatus("Initializing...");
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 resultFile = new File([video.buffer], this.generateFileName(file.name), { type: "video/mp4" });
// Clean up
this.ffmpeg.off("progress", progressCallback);
this.inProgress = false;
return resultFile;
}
stop() {
this.ffmpeg.terminate();
this.inProgress = false;
}
generateFileName(filename) {
let name = filename.replace(/\.\w+$/, "");
if (/\s/.test(name)) name += " compressed";
else name += "_compressed";
return name + ".mp4";
}
// 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);
});
}
}