-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolve.ts
More file actions
121 lines (104 loc) · 3.34 KB
/
Copy pathresolve.ts
File metadata and controls
121 lines (104 loc) · 3.34 KB
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
/*
* This file exists purely to avoid reliance on ffmpeg/ffprobe being in the system path
* not a big fan of path based resolution
*/
import { spawn } from "node:child_process";
import { access } from "node:fs/promises";
import path from "node:path";
const cache: Partial<Record<"ffmpeg" | "ffprobe", string>> = {};
function candidatePaths(bin: "ffmpeg" | "ffprobe"): string[] {
switch (process.platform) {
case "win32":
return [
`C:\\ffmpeg\\bin\\${bin}.exe`,
`C:\\Program Files\\ffmpeg\\bin\\${bin}.exe`,
`C:\\Program Files (x86)\\ffmpeg\\bin\\${bin}.exe`,
];
case "darwin":
return [
`/opt/homebrew/bin/${bin}`,
`/usr/local/bin/${bin}`,
`/usr/bin/${bin}`,
];
default:
return [
`/usr/bin/${bin}`,
`/usr/local/bin/${bin}`,
`/bin/${bin}`,
`/snap/bin/${bin}`,
`/app/bin/${bin}`,
];
}
}
async function validateBinary(binPath: string, binName: string, timeoutMs = 5000): Promise<void> {
return new Promise<void>((resolve, reject) => {
const p = spawn(binPath, ["-version"], { shell: false });
let out = "";
let timeExceeded = false;
const timeout = setTimeout(() => {
timeExceeded = true;
p.kill();
reject(new Error(`${binPath} validation timed out after ${timeoutMs}ms`));
}, timeoutMs);
p.stdout.on("data", d => (out += d));
p.on("close", code => {
clearTimeout(timeout);
if (timeExceeded) return;
if (code !== 0 || !out.toLowerCase().includes(binName)) {
reject(new Error(`${binPath} is not a valid ${binName} binary`));
} else {
resolve();
}
});
p.on("error", err => {
clearTimeout(timeout);
if (!timeExceeded) reject(err);
});
});
}
async function fileExists(filePath: string): Promise<boolean> {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
export function pullBinary(bin: "ffmpeg" | "ffprobe") {
const binPath = cache[bin];
if (!binPath) throw new Error("requested binary has not been resolved");
return binPath;
}
export async function resolveBinary(
userPath: string | undefined,
bin: "ffmpeg" | "ffprobe",
): Promise<string> {
if (cache[bin]) return cache[bin];
const searchedPaths: string[] = [];
if (userPath) {
if (!path.isAbsolute(userPath)) {
throw new Error(`${bin} path must be absolute`);
}
if (!(await fileExists(userPath))) {
throw new Error(`${bin} not found at ${userPath}`);
}
await validateBinary(userPath, bin);
cache[bin] = userPath;
return userPath;
}
for (const p of candidatePaths(bin)) {
searchedPaths.push(p);
if (await fileExists(p)) {
try {
await validateBinary(p, bin);
cache[bin] = p;
return p;
} catch (err) {
continue;
}
}
}
throw new Error(
`${bin} not found, either needs installation or define a custom path`
);
}