Skip to content

Instantly share code, notes, and snippets.

@FelicitusNeko
Created August 13, 2022 20:09
Show Gist options
  • Select an option

  • Save FelicitusNeko/e221ab86b42532d3de17201e79faf36c to your computer and use it in GitHub Desktop.

Select an option

Save FelicitusNeko/e221ab86b42532d3de17201e79faf36c to your computer and use it in GitHub Desktop.
A script I just made to convert lossless OBS .MKV recordings to compressed .MP4 files, also outputting per-track .MP3 files. Requires ffmpeg on path, as well as TeraCopy to move converted files.
// Copyright (c) 2022 FelicitusNeko
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
const { readdirSync, writeFileSync, unlinkSync } = require("fs");
const { spawn } = require("child_process");
const { join, basename } = require("path");
const convertTarget = "C:\\Path\\To\\ConvertedFiles";
const completeTarget = "C:\\Path\\To\\RawFiles";
const pathTeracopy = "C:\\Program Files\\TeraCopy\\TeraCopy.exe"
const fileList = join(__dirname, "file.list");
const spawnFfmpeg = (file, output) => new Promise((f, r) => {
const fileIsArray = Array.isArray(file);
if (output == null) {
if (!fileIsArray) output = basename(file, ".mkv");
else r(new Exception("Output pattern must be specified if array of files is provided"));
}
const params = [
"-map", "0:v", "-map", "0:a:0", // Grab the video and primary audio track
"-c:v", "libx264",
"-preset", "veryslow",
"-tune", "animation",
"-crf", "20",
"-max_muxing_queue_size", "1024", // TODO: maybe this can be automatically adjusted as needed
"-c:a", "libmp3lame",
"-q:a", "2",
join(convertTarget, `${output}.mp4`),
"-map", "0:a:1", "-vn", // Grab the mic track
"-c:a", "libmp3lame",
"-q:a", "2",
join(convertTarget, `${output}-mic.mp3`),
"-map", "0:a:2", "-vn", // Grab the game audio track
"-c:a", "libmp3lame",
"-q:a", "2",
join(convertTarget, `${output}-game.mp3`),
"-map", "0:a:3", "-vn", // Grab the guest track
"-c:a", "libmp3lame",
"-q:a", "2",
join(convertTarget, `${output}-guest.mp3`)
];
if (fileIsArray) params.unshift(
"-protocol_whitelist", "pipe,file", // so that we can pipe in a concat list without creating it
"-f", "concat",
"-safe", "0", // because we're using full path defs, which ffmpeg doesn't like
"-i", "-" // read stdin
)
else params.unshift("-i", file);
var proc = spawn("ffmpeg.exe", params, {
stdio: ["pipe", "inherit", "inherit"]
});
if (fileIsArray) {
proc.stdin.write(file.map(i => `file '${join(__dirname, i)}'`).join("\n"));
proc.stdin.end();
}
proc.on("close", code => code == 0 ? f() : r(code));
});
const spawnTeracopy = () => new Promise((f, r) => {
const proc = spawn(pathTeracopy, [
"Move",
`*${fileList}`,
completeTarget,
], { stdio: "ignore" });
proc.on("close", code => code == 0 ? f() : r(code));
});
(async () => {
const files =
readdirSync(__dirname, { withFileTypes: true })
.filter(file => file.isFile() && file.name.endsWith(".mkv"))
.sort((a, b) => a.name.localeCompare(b.name));
const concats = {};
for (const file of files) {
const { name } = file, base = basename(name, ".mkv");
const pattern = /^(.*)\.(\d+)/.exec(base);
if (pattern != null) {
const [_, combineName, part] = pattern;
if (!concats[combineName]) concats[combineName] = [];
concats[combineName][Number.parseInt(part)] = name;
}
else await spawnFfmpeg(name);
}
for (const combineName in concats) {
const parts = concats[combineName].filter(i => i != null);
await spawnFfmpeg(parts, combineName);
}
writeFileSync(fileList, files.map(file => join(__dirname, file.name)).join("\n"));
await spawnTeracopy();
unlinkSync(fileList);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment