Skip to content

Instantly share code, notes, and snippets.

@neckro
Last active July 23, 2025 23:19
Show Gist options
  • Select an option

  • Save neckro/d03340d85e2d4e53e15b4af4f9c7e821 to your computer and use it in GitHub Desktop.

Select an option

Save neckro/d03340d85e2d4e53e15b4af4f9c7e821 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S bun run
import fs from 'node:fs';
const indexRegexp = new RegExp(/^\d+$/);
const timeRegexp = new RegExp(/^(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)$/);
const file_a = Bun.argv[2];
const file_b = Bun.argv[3];
interface SubEntry {
begin: string;
end: string;
text: string;
}
function parseSub (file: string): SubEntry[] {
const out: SubEntry[] = [];
let begin: string = '';
let end: string = '';
let text: string[] = [];
const input = fs.readFileSync(file).toString().replace(/\r\n/g, '\n').split('\n');
for (let i = 0; i < input.length; i++) {
if (indexRegexp.test(input[i]) && timeRegexp.test(input[i+1])) {
const item = {
begin,
end,
text: text.join('\n'),
};
if (text.length > 0) {
out.push(item);
}
i += 1;
let matches = input[i].match(timeRegexp);
if (matches == null) {
throw "Woops, that's not supposed to happen";
} else {
begin = matches[1];
end = matches[2];
text = [];
}
} else {
text.push(input[i]);
}
}
if (text.length > 0) {
out.push({
begin,
end,
text: text.join('\n'),
});
}
return out;
}
function LT (a: string, b: string): boolean {
a = a.replace(/\D/g, '');
b = b.replace(/\D/g, '');
return (+a < +b);
}
function LTE (a: string, b: string): boolean {
a = a.replace(/\D/g, '');
b = b.replace(/\D/g, '');
return (+a <= +b);
}
function duration (begin: string, end: string): number {
// in ms
begin = begin.replace(/\D/g, '');
end = end.replace(/\D/g, '');
return (+end - +begin);
}
const print = (() => {
let outIndex: number = 1;
const minTime = 250;
return function (e: SubEntry): void {
if (duration(e.begin, e.end) < minTime) {
// too short
return;
}
console.log(outIndex++);
console.log(`${e.begin} --> ${e.end}`);
console.log(e.text.replace(/\n\n+/g, "\n\n").trim() + "\n");
}
})();
function merge(a: SubEntry[], b: SubEntry[]): void {
let ai = 0, bi = 0;
let time: string = "0";
const amax = a.length - 1;
const bmax = b.length - 1;
while (1) {
if (ai > amax && bi > bmax) {
// done
break;
}
if (ai > amax && bi <= bmax) {
print (b[bi]);
bi++;
continue;
}
if (bi > bmax && ai <= amax) {
print (a[ai]);
ai++;
continue;
}
if (LT(a[ai].begin, b[bi].begin)) {
// a begins first
if (LTE(a[ai].end, time)) {
// already ended
ai++;
continue;
}
if (LT(a[ai].begin, time)) {
// truncate begin time
a[ai].begin = time;
}
if (LT(a[ai].end, b[bi].begin)) {
// a ends before b begins
print (a[ai]);
ai++;
continue;
} else {
// b begins before a ends, truncate a and keep it
print ({
...a[ai],
end: b[bi].begin,
});
print (b[bi]);
time = b[bi].end;
bi++;
continue;
}
} else {
// b begins first, so takes priority
print (b[bi]);
time = b[bi].end;
bi++;
continue;
}
}
}
const subs_a = parseSub(file_a);
const subs_b = parseSub(file_b);
merge(subs_a, subs_b);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment