dsr/src/main.ts

211 lines
5.8 KiB
TypeScript
Raw Normal View History

2023-07-30 02:40:05 +02:00
import { BrowserWindow, app, dialog, ipcMain } from "electron";
import { statSync } from "fs";
2023-08-01 09:59:37 +02:00
import {
deleteFile,
deleteTwoPassFiles,
2023-08-01 09:59:37 +02:00
execute,
getNewFilename,
getVideoDuration,
printAndDevTool,
} from "./utils/misc";
import path = require("path");
2023-07-29 19:23:18 +02:00
import ffmpegPath = require("ffmpeg-static");
2023-07-30 13:44:47 +02:00
const moviesFilter = {
name: "Videos",
extensions: ["mp4", "mkv"],
};
2023-08-23 23:14:03 +02:00
const metadataAudio = `-metadata:s:a:0 title="System sounds and microphone" \
-metadata:s:a:1 title="System sounds" \
-metadata:s:a:2 title="Microphone"`;
2023-07-30 00:14:20 +02:00
/** Create a new window */
2023-07-28 02:32:18 +02:00
const createWindow = () => {
const win = new BrowserWindow({
2023-08-01 11:07:29 +02:00
width: 600,
height: 340,
2023-08-01 10:18:08 +02:00
icon: "./image/icon.ico",
2023-08-01 11:07:29 +02:00
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
2023-07-28 02:32:18 +02:00
});
2023-07-29 15:30:41 +02:00
win.loadFile(path.join(path.resolve(__dirname, ".."), "pages", "index.html"));
2023-07-30 02:01:16 +02:00
return win;
2023-07-28 02:32:18 +02:00
};
2023-07-29 15:30:41 +02:00
2023-07-30 02:40:05 +02:00
/* Ready to create the window */
2023-07-28 02:32:18 +02:00
app.whenReady().then(() => {
2023-07-30 02:01:16 +02:00
const win = createWindow();
2023-08-24 15:59:46 +02:00
/** Ask user files */
const askFiles = () => {
2023-07-30 02:01:16 +02:00
return dialog.showOpenDialogSync(win, {
filters: [moviesFilter],
2023-08-24 15:59:46 +02:00
properties: ["openFile", "dontAddToRecent", "multiSelections"],
2023-07-30 02:01:16 +02:00
});
};
/** Send confirmation to user */
const confirmation = async (message: string) => {
2023-08-01 11:10:35 +02:00
await dialog.showMessageBox(win, { message });
2023-07-30 02:01:16 +02:00
};
2023-08-24 15:59:46 +02:00
/** Get filename of a path */
const getFilename = (filepath: string) => path.parse(filepath).base;
/** Merge all audios track of a video into one
* In case video have only one track, silently pass */
2023-08-01 09:59:37 +02:00
const mergeAudio = async (file: string) => {
const copy = process.platform === "win32" ? "COPY" : "cp";
const copyArg = process.platform === "win32" ? "/Y" : "";
2023-08-01 09:59:37 +02:00
const tmpFile = getNewFilename(file, "TMP_");
let outFile;
2023-08-01 09:59:37 +02:00
// One track for the video
let nbTracks = 1;
2023-08-01 09:59:37 +02:00
// Merge 2 audio
2023-08-04 20:53:21 +02:00
// See: https://trac.ffmpeg.org/wiki/AudioChannelManipulation#a2stereostereo
2023-08-01 09:59:37 +02:00
await execute(
`"${ffmpegPath}" -y \
2023-08-24 15:56:19 +02:00
-i "${file}" \
-filter_complex "[0:a]amerge=inputs=2[a]" -ac 2 -map 0:v -map "[a]" \
-c:v copy \
"${tmpFile}"`
)
.catch(async (e) => {
if (
`${e}`.includes(
"Cannot find a matching stream for unlabeled input pad 1 on filter"
)
) {
// Only one audio in the file
outFile = getNewFilename(file, "(processed) ");
nbTracks += 1;
// Do a copy
await execute(`${copy} "${file}" "${outFile}" ${copyArg}`).catch(
(e) => printAndDevTool(win, e)
);
// We throw the error since we do not want to merge any audio
return Promise.resolve("skip");
} else {
// Error handling
printAndDevTool(win, e);
}
})
.then(async (val) => {
if (val == "skip") {
return;
}
outFile = getNewFilename(file, "(merged audio) ");
nbTracks += 3;
// Add merged audio as first position to original video and make it default
// About disposition: https://ffmpeg.org/ffmpeg.html#Main-options
// Also rename all tracks accordingly to what they are
await execute(
`"${ffmpegPath}" -y \
-i "${tmpFile}" -i "${file}" \
-map 0 -map 1:a -c:v copy \
-disposition:a 0 -disposition:a:0 default \
${metadataAudio} \
"${outFile}"`
).catch((e) => printAndDevTool(win, e));
// Delete the temporary video file
deleteFile(tmpFile);
});
2023-08-01 09:59:37 +02:00
const duration = getVideoDuration(outFile);
2023-08-01 09:59:37 +02:00
const stats = statSync(outFile);
return {
title: outFile,
size: stats.size / 1024 / 1024,
duration,
nbTracks,
};
2023-08-01 09:59:37 +02:00
};
/* Reduce size of a file */
const reduceSize = async (
file: string,
bitrate: number,
nbTracks: number
) => {
2023-08-01 09:59:37 +02:00
const audioBitrate = 400; // keep some room
2023-12-02 23:56:32 +01:00
let videoBitrate = bitrate - audioBitrate;
2023-08-01 09:59:37 +02:00
const finalFile = getNewFilename(file, "Compressed - ");
// Trash the output, depends on the platform
const nul = process.platform === "win32" ? "NUL" : "/dev/null";
// Mapping of tracks for FFMPEG
const mappingTracks = Array(nbTracks)
.fill("-map 0:")
.map(function (str, index) {
return str + index;
})
.join(" ");
2023-12-02 23:56:32 +01:00
let codec = "libx264";
let hwAcc = "";
const argv = process.argv;
if (argv.includes("/nvenc")) {
// Use NVenc
codec = "h264_nvenc";
hwAcc = "-hwaccel cuda";
// Increase video bitrate
videoBitrate = Math.floor(videoBitrate * 1.85);
}
2023-08-24 03:29:46 +02:00
// Compress the video
// Add metadata to audio's track
2023-08-01 09:59:37 +02:00
await execute(
2023-12-02 23:56:32 +01:00
`"${ffmpegPath}" -y ${hwAcc} \
2023-08-24 15:56:19 +02:00
-i "${file}" \
2023-12-02 23:56:32 +01:00
-c:v ${codec} -b:v ${videoBitrate}k -pass 1 -an -f mp4 \
2023-08-24 15:56:19 +02:00
${nul} \
&& \
2023-12-02 23:56:32 +01:00
"${ffmpegPath}" -y ${hwAcc} \
2023-08-24 15:56:19 +02:00
-i "${file}" \
2023-12-02 23:56:32 +01:00
-c:v ${codec} -b:v ${videoBitrate}k -pass 2 -c:a copy \
${mappingTracks} -f mp4 \
2023-08-24 15:56:19 +02:00
${metadataAudio} \
"${finalFile}"`
2023-08-01 09:59:37 +02:00
).catch((e) => printAndDevTool(win, e));
// Delete the old video file
deleteFile(file);
// Delete the 2 pass temporary files
deleteTwoPassFiles(file);
2023-08-01 09:59:37 +02:00
return finalFile;
};
2023-07-30 00:14:20 +02:00
/* Context bridge */
2023-07-29 20:20:55 +02:00
ipcMain.handle("argv", () => process.argv);
2023-07-30 00:14:20 +02:00
ipcMain.handle("allowedExtensions", () => moviesFilter);
2023-08-24 15:59:46 +02:00
ipcMain.handle("getFilename", (_, filepath: string) => getFilename(filepath));
ipcMain.handle("askFiles", () => askFiles());
2023-07-30 01:40:06 +02:00
ipcMain.handle("mergeAudio", (_, file: string) => mergeAudio(file));
ipcMain.handle(
"reduceSize",
(_, file: string, bitrate: number, nbTracks: number) =>
reduceSize(file, bitrate, nbTracks)
2023-07-30 13:44:47 +02:00
);
2023-08-01 11:10:35 +02:00
ipcMain.handle("exit", () => app.quit());
ipcMain.handle("confirmation", (_, text: string) => confirmation(text));
2023-07-28 02:32:18 +02:00
});