Feat: refactor ffmpeg config (#296)

* bundle ffmpeg wasm in package

* remove ffmpeg discover & add ffmpeg-static

* fix ffprobe-static version

* fix metadata generate

* fix auto transcribe if pending

* remove console.log
This commit is contained in:
an-lee
2024-02-11 17:21:39 +08:00
committed by GitHub
parent bc22a5e2b4
commit 57d6efa547
21 changed files with 131 additions and 703 deletions

View File

@@ -1,38 +1,22 @@
import { ipcMain } from "electron";
import ffmpegPath from "ffmpeg-static";
import ffprobePath from "@andrkrn/ffprobe-static";
import Ffmpeg from "fluent-ffmpeg";
import settings from "@main/settings";
import log from "electron-log/main";
import path from "path";
import fs from "fs-extra";
import AdmZip from "adm-zip";
import downloader from "@main/downloader";
import storage from "@main/storage";
import readdirp from "readdirp";
import { t } from "i18next";
import uniq from "lodash/uniq";
const logger = log.scope("ffmpeg");
export default class FfmpegWrapper {
public ffmpeg: Ffmpeg.FfmpegCommand;
public config: any;
constructor(config?: {
ffmpegPath: string;
ffprobePath: string;
commandExists?: boolean;
}) {
this.config = config || settings.ffmpegConfig();
if (this.config.commandExists) {
logger.info("Using system ffmpeg");
this.ffmpeg = Ffmpeg();
} else {
logger.info("Using downloaded ffmpeg");
const ff = Ffmpeg();
ff.setFfmpegPath(this.config.ffmpegPath);
ff.setFfprobePath(this.config.ffprobePath);
this.ffmpeg = ff;
}
constructor() {
const ff = Ffmpeg();
logger.debug("Using ffmpeg path:", ffmpegPath);
logger.debug("Using ffprobe path:", ffprobePath);
ff.setFfmpegPath(ffmpegPath);
ff.setFfprobePath(ffprobePath);
this.ffmpeg = ff;
}
checkCommand(): Promise<boolean> {
@@ -191,274 +175,10 @@ export default class FfmpegWrapper {
return this.convertToWav(input, output);
}
}
export class FfmpegDownloader {
public async download(webContents?: Electron.WebContents) {
if (process.platform === "win32") {
return await this.downloadForWin32(webContents);
} else if (process.platform === "darwin") {
return await this.downloadForDarwin(webContents);
} else {
throw new Error(
`You are using ${process.platform}, please install ffmpeg manually`
);
}
}
async downloadForDarwinArm64(webContents?: Electron.WebContents) {
const DARWIN_FFMPEG_ARM64_URL = storage.getUrl(
"ffmpeg-apple-arm64-build-6.0.zip"
);
fs.ensureDirSync(path.join(settings.libraryPath(), "ffmpeg"));
const ffmpegZipPath = await downloader.download(DARWIN_FFMPEG_ARM64_URL, {
webContents,
});
const ffmepgZip = new AdmZip(ffmpegZipPath);
ffmepgZip.extractEntryTo(
"ffmpeg/ffmpeg",
path.join(settings.libraryPath(), "ffmpeg"),
false,
true
);
ffmepgZip.extractEntryTo(
"ffmpeg/ffprobe",
path.join(settings.libraryPath(), "ffmpeg"),
false,
true
);
fs.chmodSync(path.join(settings.libraryPath(), "ffmpeg", "ffmpeg"), 0o775);
fs.chmodSync(path.join(settings.libraryPath(), "ffmpeg", "ffprobe"), 0o775);
}
async downloadForDarwin(webContents?: Electron.WebContents) {
if (process.arch === "arm64") {
return this.downloadForDarwinArm64(webContents);
}
const DARWIN_FFMPEG_URL = "https://evermeet.cx/ffmpeg/getrelease/zip";
const DARWIN_FFPROBE_URL =
"https://evermeet.cx/ffmpeg/getrelease/ffprobe/zip";
fs.ensureDirSync(path.join(settings.libraryPath(), "ffmpeg"));
const ffmpegZipPath = await downloader.download(DARWIN_FFMPEG_URL, {
webContents,
});
const ffmepgZip = new AdmZip(ffmpegZipPath);
ffmepgZip.extractEntryTo(
"ffmpeg",
path.join(settings.libraryPath(), "ffmpeg"),
false,
true
);
fs.chmodSync(path.join(settings.libraryPath(), "ffmpeg", "ffmpeg"), 0o775);
const ffprobeZipPath = await downloader.download(DARWIN_FFPROBE_URL, {
webContents,
});
const ffprobeZip = new AdmZip(ffprobeZipPath);
ffprobeZip.extractEntryTo(
"ffprobe",
path.join(settings.libraryPath(), "ffmpeg"),
false,
true
);
fs.chmodSync(path.join(settings.libraryPath(), "ffmpeg", "ffprobe"), 0o775);
return settings.ffmpegConfig();
}
async downloadForWin32(webContents?: Electron.WebContents) {
const WINDOWS_DOWNLOAD_URL =
"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip";
const zipPath = await downloader.download(WINDOWS_DOWNLOAD_URL, {
webContents,
});
fs.ensureDirSync(path.join(settings.libraryPath(), "ffmpeg"));
const zip = new AdmZip(zipPath);
zip.extractEntryTo(
`${path.basename(zipPath, ".zip")}/bin/ffmpeg.exe`,
path.join(settings.libraryPath(), "ffmpeg"),
false,
true
);
zip.extractEntryTo(
`${path.basename(zipPath, ".zip")}/bin/ffprobe.exe`,
path.join(settings.libraryPath(), "ffmpeg"),
false,
true
);
return settings.ffmpegConfig();
}
unzip(zipPath: string) {
if (!fs.existsSync(zipPath)) {
throw new Error(`File ${zipPath} does not exist`);
}
const dir = path.dirname(zipPath);
const zip = new AdmZip(zipPath);
zip.extractAllTo(dir, true);
const unzipPath = zipPath.replace(".zip", "");
return unzipPath;
}
registerIpcHandlers() {
ipcMain.handle("ffmpeg-config", async (_event) => {
return settings.ffmpegConfig();
});
ipcMain.handle("ffmpeg-set-config", async (_event, config) => {
settings.setSync("ffmpeg.ffmpegPath", config.ffmpegPath);
settings.setSync("ffmpeg.ffprobePath", config.ffrobePath);
});
ipcMain.handle("ffmpeg-download", async (event) => {
try {
return await this.download(event.sender);
} catch (err) {
logger.error(err);
event.sender.send("on-notification", {
type: "error",
message: `FFmpeg download failed: ${err.message}`,
});
}
});
ipcMain.handle("ffmpeg-check-command", async (event) => {
const ffmpeg = new FfmpegWrapper();
const valid = await ffmpeg.checkCommand();
if (valid) {
event.sender.send("on-notification", {
type: "success",
message: t("ffmpegCommandIsWorking"),
});
} else {
logger.error("FFmpeg command not valid", ffmpeg.config);
event.sender.send("on-notification", {
type: "warning",
message: t("ffmpegCommandIsNotWorking"),
});
}
return valid;
});
ipcMain.handle("ffmpeg-discover-command", async (event) => {
try {
return await discoverFfmpeg();
} catch (err) {
logger.error(err);
event.sender.send("on-notification", {
type: "error",
message: `FFmpeg discover failed: ${err.message}`,
});
}
ipcMain.handle("ffmpeg-check-command", async (_event) => {
return await this.checkCommand();
});
}
}
export const discoverFfmpeg = async () => {
const platform = process.platform;
let ffmpegPath: string;
let ffprobePath: string;
const libraryFfmpegPath = path.join(settings.libraryPath(), "ffmpeg");
let scanDirs = [...COMMAND_SCAN_DIR[platform], libraryFfmpegPath];
const currentFfmpegPath = settings.ffmpegConfig().ffmpegPath as string;
const currentFfprobePath = settings.ffmpegConfig().ffprobePath as string;
if (currentFfmpegPath) {
scanDirs.push(path.dirname(currentFfmpegPath));
}
if (currentFfprobePath) {
scanDirs.push(path.dirname(currentFfprobePath));
}
scanDirs = uniq(scanDirs);
await Promise.all(
scanDirs.map(async (dir: string) => {
if (!fs.existsSync(dir)) return;
dir = path.resolve(dir);
log.info("FFmpeg scanning: " + dir);
const fileStream = readdirp(dir, {
depth: 3,
});
for await (const entry of fileStream) {
const appName = entry.basename
.replace(".app", "")
.replace(".exe", "")
.toLowerCase();
if (appName === "ffmpeg") {
logger.info("Found ffmpeg: ", entry.fullPath);
ffmpegPath = entry.fullPath;
}
if (appName === "ffprobe") {
logger.info("Found ffprobe: ", entry.fullPath);
ffprobePath = entry.fullPath;
}
if (ffmpegPath && ffprobePath) break;
}
})
);
let valid = false;
if (ffmpegPath && ffprobePath) {
const ffmepg = new FfmpegWrapper({ ffmpegPath, ffprobePath });
valid = await ffmepg.checkCommand();
}
if (valid) {
settings.setSync("ffmpeg", {
ffmpegPath,
ffprobePath,
});
} else {
ffmpegPath = undefined;
ffprobePath = undefined;
settings.setSync("ffmpeg", null);
}
return {
ffmpegPath,
ffprobePath,
scanDirs,
};
};
export const COMMAND_SCAN_DIR: { [key: string]: string[] } = {
darwin: [
"/usr/bin",
"/usr/local/bin",
"/Applications",
process.env.HOME + "/Applications",
"/opt/homebrew/bin",
],
linux: ["/usr/bin", "/usr/local/bin", "/snap/bin"],
win32: [
process.env.SystemDrive + "\\Program Files\\",
process.env.SystemDrive + "\\Program Files (x86)\\",
process.env.LOCALAPPDATA + "\\Apps\\2.0\\",
],
};