-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrunner.ts
129 lines (119 loc) · 4.87 KB
/
runner.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {path, log} from "./deps.ts";
import Job, {Edit} from "./job.ts";
export default class Runner {
private readonly decoder: TextDecoder = new TextDecoder();
private readonly encoder: TextEncoder = new TextEncoder();
private readonly mkvpropedit: string;
private readonly mkvextract: string;
/**
* Create a new Runner using the provided instances of `mkvpropedit` and
* `mkvextract`.
*
* @param mkvpropedit Path to the `mkvpropedit` executable
* @param mkvextract Path to the `mkvextract` executable
*/
constructor(mkvpropedit: string, mkvextract: string) {
this.mkvpropedit = mkvpropedit;
this.mkvextract = mkvextract;
}
/**
* Directly run a job. Loops through the batches in the job and runs
* Runner#edits and Runner#chapters as appropriate.
*
* @param job The job to run
*/
async job(job: Job): Promise<number> {
const promises = [];
for (const batch of job.batches) {
if (!batch.edits && !batch.chapters) continue;
const folder = path.join(path.dirname(job.file), batch.watch.folder || '.');
for await (const entry of Deno.readDir(folder))
if (entry.name.match(batch.watch.files)) {
const file = path.join(folder, entry.name);
if (batch.edits)
promises.push(this.edits(file, batch.edits));
if (batch.chapters)
promises.push(this.chapters(file, batch.chapters));
}
}
return Promise.all(promises).then((mods: number[]) => mods.reduce((a, b) => a + b));
}
/**
* Apply `mkvpropedit` edits to an MKV file. These edits are passed in a
* `{edit: "key", set: "value"}` format, such that the behavior is
* equivalent to:
* ```
* mkvpropedit <file> --edit key --set value
* ```
* Multiple edits are passed within the same command.
*
* @param file The file to edit
* @param edits The edits to be performed
*/
async edits(file: string, edits: Edit[]): Promise<number> {
if (!edits || edits.length == 0)
return 0;
log.info(`Running edits ${JSON.stringify(edits)} on ${file}`);
const cmd = [this.mkvpropedit, file];
for (const edit of edits) {
cmd.push("--edit", edit.edit);
for (const [key, value] of edit.set.entries())
cmd.push("--set", `${key}=${value}`);
}
log.debug(cmd.join(' '));
return await Deno.run({cmd: cmd, stdout: "inherit", stderr: "inherit"})
.status()
.then(s => s.success ? 1 : 0);
}
/**
* Sets the provided MKV's chapters to the provided values, as long as the
* actual number of chapters matches the number provided. Returns the
* number of times the file was modified.
*
* @param file The file whose chapters will be updated
* @param chapters The new chapters to write
*/
async chapters(file: string, chapters: string[]): Promise<number> {
if (!chapters)
return 0;
log.info(`Updating ${chapters.length} chapters for ${file}`);
const markers = await this.getChapterMarkers(file);
log.debug(`Found chapter markers at ${markers.join(', ')}`);
if (chapters.length !== markers.length) {
log.warning(`Expected ${chapters.length} chapters, found ${markers.length} for ${file}`);
return 0;
}
let chapterData = "";
for (const i in markers) {
const id = ((i + 1) as string).padStart(2, "0");
chapterData += "CHAPTER" + id + "=" + markers[i] + "\n";
chapterData += "CHAPTER" + id + "NAME=" + chapters[i] + "\n";
}
const chapterFile = file + "-chapters";
Deno.writeFileSync(chapterFile, this.encoder.encode(chapterData));
const cmd = [this.mkvpropedit, file, "-c", chapterFile];
log.debug(cmd.join(' '));
return await Deno.run({cmd: cmd, stdout: "inherit", stderr: "inherit"})
.status()
.then(s => s.success ? 1 : 0);
}
/**
* Gets the chapter markers from an MKV file using `mkvextract`. Converts
* the output into an array of timecodes.
*
* @param file The file from which to extract chapter markers
* @private Internally used by Runner#chapters
*/
private async getChapterMarkers(file: string) {
const cmd = [this.mkvextract, "chapters", file, "-s"];
log.debug(cmd.join(' '));
const output = await Deno.run({cmd: cmd, stdout: "piped", stderr: "inherit"}).output();
return this.decoder
.decode(output)
.split("\n")
.filter((_e, i) => i % 2 == 0)
.map(s => s.trim())
.filter(s => s.length > 0)
.map(s => s.split('=')[1]);
}
}