@hyperframes/engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +92 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +77 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/services/audioMixer.d.ts +11 -0
- package/dist/services/audioMixer.d.ts.map +1 -0
- package/dist/services/audioMixer.js +306 -0
- package/dist/services/audioMixer.js.map +1 -0
- package/dist/services/audioMixer.types.d.ts +27 -0
- package/dist/services/audioMixer.types.d.ts.map +1 -0
- package/dist/services/audioMixer.types.js +2 -0
- package/dist/services/audioMixer.types.js.map +1 -0
- package/dist/services/browserManager.d.ts +29 -0
- package/dist/services/browserManager.d.ts.map +1 -0
- package/dist/services/browserManager.js +175 -0
- package/dist/services/browserManager.js.map +1 -0
- package/dist/services/chunkEncoder.d.ts +32 -0
- package/dist/services/chunkEncoder.d.ts.map +1 -0
- package/dist/services/chunkEncoder.js +347 -0
- package/dist/services/chunkEncoder.js.map +1 -0
- package/dist/services/chunkEncoder.test.d.ts +2 -0
- package/dist/services/chunkEncoder.test.d.ts.map +1 -0
- package/dist/services/chunkEncoder.test.js +24 -0
- package/dist/services/chunkEncoder.test.js.map +1 -0
- package/dist/services/chunkEncoder.types.d.ts +26 -0
- package/dist/services/chunkEncoder.types.d.ts.map +1 -0
- package/dist/services/chunkEncoder.types.js +2 -0
- package/dist/services/chunkEncoder.types.js.map +1 -0
- package/dist/services/fileServer.d.ts +25 -0
- package/dist/services/fileServer.d.ts.map +1 -0
- package/dist/services/fileServer.js +138 -0
- package/dist/services/fileServer.js.map +1 -0
- package/dist/services/frameCapture.d.ts +52 -0
- package/dist/services/frameCapture.d.ts.map +1 -0
- package/dist/services/frameCapture.js +317 -0
- package/dist/services/frameCapture.js.map +1 -0
- package/dist/services/parallelCoordinator.d.ts +40 -0
- package/dist/services/parallelCoordinator.d.ts.map +1 -0
- package/dist/services/parallelCoordinator.js +192 -0
- package/dist/services/parallelCoordinator.js.map +1 -0
- package/dist/services/parallelCoordinator.test.d.ts +2 -0
- package/dist/services/parallelCoordinator.test.d.ts.map +1 -0
- package/dist/services/parallelCoordinator.test.js +38 -0
- package/dist/services/parallelCoordinator.test.js.map +1 -0
- package/dist/services/screenshotService.d.ts +28 -0
- package/dist/services/screenshotService.d.ts.map +1 -0
- package/dist/services/screenshotService.js +190 -0
- package/dist/services/screenshotService.js.map +1 -0
- package/dist/services/streamingEncoder.d.ts +49 -0
- package/dist/services/streamingEncoder.d.ts.map +1 -0
- package/dist/services/streamingEncoder.js +227 -0
- package/dist/services/streamingEncoder.js.map +1 -0
- package/dist/services/videoFrameExtractor.d.ts +65 -0
- package/dist/services/videoFrameExtractor.d.ts.map +1 -0
- package/dist/services/videoFrameExtractor.js +302 -0
- package/dist/services/videoFrameExtractor.js.map +1 -0
- package/dist/services/videoFrameInjector.d.ts +17 -0
- package/dist/services/videoFrameInjector.d.ts.map +1 -0
- package/dist/services/videoFrameInjector.js +92 -0
- package/dist/services/videoFrameInjector.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/ffprobe.d.ts +18 -0
- package/dist/utils/ffprobe.d.ts.map +1 -0
- package/dist/utils/ffprobe.js +137 -0
- package/dist/utils/ffprobe.js.map +1 -0
- package/dist/utils/gpuEncoder.d.ts +11 -0
- package/dist/utils/gpuEncoder.d.ts.map +1 -0
- package/dist/utils/gpuEncoder.js +55 -0
- package/dist/utils/gpuEncoder.js.map +1 -0
- package/dist/utils/parityContract.d.ts +3 -0
- package/dist/utils/parityContract.d.ts.map +1 -0
- package/dist/utils/parityContract.js +35 -0
- package/dist/utils/parityContract.js.map +1 -0
- package/dist/utils/runFfmpeg.d.ts +19 -0
- package/dist/utils/runFfmpeg.d.ts.map +1 -0
- package/dist/utils/runFfmpeg.js +62 -0
- package/dist/utils/runFfmpeg.js.map +1 -0
- package/dist/utils/urlDownloader.d.ts +3 -0
- package/dist/utils/urlDownloader.d.ts.map +1 -0
- package/dist/utils/urlDownloader.js +68 -0
- package/dist/utils/urlDownloader.js.map +1 -0
- package/package.json +30 -0
- package/src/cdp-headless-experimental.d.ts +54 -0
- package/src/config.test.ts +95 -0
- package/src/config.ts +176 -0
- package/src/index.ts +154 -0
- package/src/services/audioMixer.ts +392 -0
- package/src/services/audioMixer.types.ts +28 -0
- package/src/services/browserManager.ts +219 -0
- package/src/services/chunkEncoder.test.ts +27 -0
- package/src/services/chunkEncoder.ts +399 -0
- package/src/services/chunkEncoder.types.ts +27 -0
- package/src/services/fileServer.ts +175 -0
- package/src/services/frameCapture.ts +452 -0
- package/src/services/parallelCoordinator.test.ts +44 -0
- package/src/services/parallelCoordinator.ts +293 -0
- package/src/services/screenshotService.ts +222 -0
- package/src/services/streamingEncoder.ts +308 -0
- package/src/services/videoFrameExtractor.ts +395 -0
- package/src/services/videoFrameInjector.ts +113 -0
- package/src/types.ts +91 -0
- package/src/utils/ffprobe.ts +192 -0
- package/src/utils/gpuEncoder.ts +58 -0
- package/src/utils/parityContract.ts +35 -0
- package/src/utils/runFfmpeg.ts +81 -0
- package/src/utils/urlDownloader.ts +78 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
const videoMetadataCache = new Map();
|
|
3
|
+
const audioMetadataCache = new Map();
|
|
4
|
+
function parseFrameRate(frameRateStr) {
|
|
5
|
+
if (!frameRateStr)
|
|
6
|
+
return 0;
|
|
7
|
+
const parts = frameRateStr.split("/");
|
|
8
|
+
if (parts.length === 2) {
|
|
9
|
+
const num = parseFloat(parts[0] ?? "");
|
|
10
|
+
const den = parseFloat(parts[1] ?? "");
|
|
11
|
+
if (den !== 0)
|
|
12
|
+
return Math.round((num / den) * 100) / 100;
|
|
13
|
+
}
|
|
14
|
+
return parseFloat(frameRateStr) || 0;
|
|
15
|
+
}
|
|
16
|
+
export async function extractVideoMetadata(filePath) {
|
|
17
|
+
const cached = videoMetadataCache.get(filePath);
|
|
18
|
+
if (cached) {
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
const probePromise = new Promise((resolve, reject) => {
|
|
22
|
+
const args = ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath];
|
|
23
|
+
const ffprobe = spawn("ffprobe", args);
|
|
24
|
+
let stdout = "";
|
|
25
|
+
let stderr = "";
|
|
26
|
+
ffprobe.stdout.on("data", (data) => {
|
|
27
|
+
stdout += data.toString();
|
|
28
|
+
});
|
|
29
|
+
ffprobe.stderr.on("data", (data) => {
|
|
30
|
+
stderr += data.toString();
|
|
31
|
+
});
|
|
32
|
+
ffprobe.on("close", (code) => {
|
|
33
|
+
if (code !== 0) {
|
|
34
|
+
reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const output = JSON.parse(stdout);
|
|
39
|
+
const videoStream = output.streams.find((s) => s.codec_type === "video");
|
|
40
|
+
if (!videoStream) {
|
|
41
|
+
reject(new Error("[FFmpeg] No video stream found"));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const hasAudio = output.streams.some((s) => s.codec_type === "audio");
|
|
45
|
+
const fps = parseFrameRate(videoStream.avg_frame_rate) || parseFrameRate(videoStream.r_frame_rate);
|
|
46
|
+
const durationSeconds = output.format.duration ? parseFloat(output.format.duration) : 0;
|
|
47
|
+
const metadata = {
|
|
48
|
+
durationSeconds,
|
|
49
|
+
width: videoStream.width || 0,
|
|
50
|
+
height: videoStream.height || 0,
|
|
51
|
+
fps,
|
|
52
|
+
videoCodec: videoStream.codec_name || "unknown",
|
|
53
|
+
hasAudio,
|
|
54
|
+
};
|
|
55
|
+
resolve(metadata);
|
|
56
|
+
}
|
|
57
|
+
catch (parseError) {
|
|
58
|
+
reject(new Error(`[FFmpeg] Failed to parse ffprobe output: ${parseError instanceof Error ? parseError.message : parseError}`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
ffprobe.on("error", (err) => {
|
|
62
|
+
if (err.code === "ENOENT") {
|
|
63
|
+
reject(new Error("[FFmpeg] ffprobe not found. Please install FFmpeg."));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
reject(err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
videoMetadataCache.set(filePath, probePromise);
|
|
71
|
+
probePromise.catch(() => {
|
|
72
|
+
if (videoMetadataCache.get(filePath) === probePromise) {
|
|
73
|
+
videoMetadataCache.delete(filePath);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return probePromise;
|
|
77
|
+
}
|
|
78
|
+
export async function extractAudioMetadata(filePath) {
|
|
79
|
+
const cached = audioMetadataCache.get(filePath);
|
|
80
|
+
if (cached) {
|
|
81
|
+
return cached;
|
|
82
|
+
}
|
|
83
|
+
const probePromise = new Promise((resolve, reject) => {
|
|
84
|
+
const args = ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath];
|
|
85
|
+
const ffprobe = spawn("ffprobe", args);
|
|
86
|
+
let stdout = "";
|
|
87
|
+
let stderr = "";
|
|
88
|
+
ffprobe.stdout.on("data", (data) => {
|
|
89
|
+
stdout += data.toString();
|
|
90
|
+
});
|
|
91
|
+
ffprobe.stderr.on("data", (data) => {
|
|
92
|
+
stderr += data.toString();
|
|
93
|
+
});
|
|
94
|
+
ffprobe.on("close", (code) => {
|
|
95
|
+
if (code !== 0) {
|
|
96
|
+
reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const output = JSON.parse(stdout);
|
|
101
|
+
const audioStream = output.streams.find((s) => s.codec_type === "audio");
|
|
102
|
+
if (!audioStream) {
|
|
103
|
+
reject(new Error("[FFmpeg] No audio stream found"));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const durationSeconds = output.format.duration ? parseFloat(output.format.duration) : 0;
|
|
107
|
+
const metadata = {
|
|
108
|
+
durationSeconds,
|
|
109
|
+
sampleRate: audioStream.sample_rate ? parseInt(audioStream.sample_rate) : 44100,
|
|
110
|
+
channels: audioStream.channels || 2,
|
|
111
|
+
audioCodec: audioStream.codec_name || "unknown",
|
|
112
|
+
bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : undefined,
|
|
113
|
+
};
|
|
114
|
+
resolve(metadata);
|
|
115
|
+
}
|
|
116
|
+
catch (parseError) {
|
|
117
|
+
reject(new Error(`[FFmpeg] Failed to parse ffprobe output: ${parseError instanceof Error ? parseError.message : parseError}`));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
ffprobe.on("error", (err) => {
|
|
121
|
+
if (err.code === "ENOENT") {
|
|
122
|
+
reject(new Error("[FFmpeg] ffprobe not found. Please install FFmpeg."));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
reject(err);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
audioMetadataCache.set(filePath, probePromise);
|
|
130
|
+
probePromise.catch(() => {
|
|
131
|
+
if (audioMetadataCache.get(filePath) === probePromise) {
|
|
132
|
+
audioMetadataCache.delete(filePath);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return probePromise;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=ffprobe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffprobe.js","sourceRoot":"","sources":["../../src/utils/ffprobe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAkC,CAAC;AACrE,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAkC,CAAC;AAwCrE,SAAS,cAAc,CAAC,YAAgC;IACtD,IAAI,CAAC,YAAY;QAAE,OAAO,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvC,IAAI,GAAG,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IAC5D,CAAC;IACD,OAAO,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IACzD,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClE,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;QAEjG,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC3B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,qCAAqC,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC1E,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAkB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACjD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;gBACzE,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;oBACpD,OAAO;gBACT,CAAC;gBAED,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;gBACtE,MAAM,GAAG,GAAG,cAAc,CAAC,WAAW,CAAC,cAAc,CAAC,IAAI,cAAc,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;gBACnG,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAExF,MAAM,QAAQ,GAAkB;oBAC9B,eAAe;oBACf,KAAK,EAAE,WAAW,CAAC,KAAK,IAAI,CAAC;oBAC7B,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,CAAC;oBAC/B,GAAG;oBACH,UAAU,EAAE,WAAW,CAAC,UAAU,IAAI,SAAS;oBAC/C,QAAQ;iBACT,CAAC;gBACF,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,UAAmB,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,KAAK,CAAC,4CAA4C,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACjI,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1B,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,MAAM,CAAC,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC,CAAC;YAC1E,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC/C,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE;QACtB,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,YAAY,EAAE,CAAC;YACtD,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IACzD,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClE,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;QAEjG,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC3B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,qCAAqC,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC1E,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAkB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACjD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;gBACzE,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;oBACpD,OAAO;gBACT,CAAC;gBAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAExF,MAAM,QAAQ,GAAkB;oBAC9B,eAAe;oBACf,UAAU,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK;oBAC/E,QAAQ,EAAE,WAAW,CAAC,QAAQ,IAAI,CAAC;oBACnC,UAAU,EAAE,WAAW,CAAC,UAAU,IAAI,SAAS;oBAC/C,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;iBAC/E,CAAC;gBACF,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,UAAmB,EAAE,CAAC;gBAC7B,MAAM,CACJ,IAAI,KAAK,CACP,4CAA4C,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,CAC5G,CACF,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1B,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,MAAM,CAAC,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC,CAAC;YAC1E,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC/C,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE;QACtB,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,YAAY,EAAE,CAAC;YACtD,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Encoder Detection
|
|
3
|
+
*
|
|
4
|
+
* Shared GPU encoder detection and naming utilities used by both
|
|
5
|
+
* chunkEncoder and streamingEncoder services.
|
|
6
|
+
*/
|
|
7
|
+
export type GpuEncoder = "nvenc" | "videotoolbox" | "vaapi" | "qsv" | null;
|
|
8
|
+
export declare function detectGpuEncoder(): Promise<GpuEncoder>;
|
|
9
|
+
export declare function getCachedGpuEncoder(): Promise<GpuEncoder>;
|
|
10
|
+
export declare function getGpuEncoderName(encoder: GpuEncoder, codec: "h264" | "h265"): string;
|
|
11
|
+
//# sourceMappingURL=gpuEncoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpuEncoder.d.ts","sourceRoot":"","sources":["../../src/utils/gpuEncoder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,cAAc,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC;AAE3E,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAqB5D;AAID,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,UAAU,CAAC,CAK/D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAcrF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Encoder Detection
|
|
3
|
+
*
|
|
4
|
+
* Shared GPU encoder detection and naming utilities used by both
|
|
5
|
+
* chunkEncoder and streamingEncoder services.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
export async function detectGpuEncoder() {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const ffmpeg = spawn("ffmpeg", ["-encoders"], {
|
|
11
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
12
|
+
});
|
|
13
|
+
let stdout = "";
|
|
14
|
+
ffmpeg.stdout.on("data", (data) => {
|
|
15
|
+
stdout += data.toString();
|
|
16
|
+
});
|
|
17
|
+
ffmpeg.on("close", () => {
|
|
18
|
+
if (stdout.includes("h264_nvenc"))
|
|
19
|
+
resolve("nvenc");
|
|
20
|
+
else if (stdout.includes("h264_videotoolbox"))
|
|
21
|
+
resolve("videotoolbox");
|
|
22
|
+
else if (stdout.includes("h264_vaapi"))
|
|
23
|
+
resolve("vaapi");
|
|
24
|
+
else if (stdout.includes("h264_qsv"))
|
|
25
|
+
resolve("qsv");
|
|
26
|
+
else
|
|
27
|
+
resolve(null);
|
|
28
|
+
});
|
|
29
|
+
ffmpeg.on("error", () => resolve(null));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
let cachedGpuEncoder = undefined;
|
|
33
|
+
export async function getCachedGpuEncoder() {
|
|
34
|
+
if (cachedGpuEncoder === undefined) {
|
|
35
|
+
cachedGpuEncoder = await detectGpuEncoder();
|
|
36
|
+
}
|
|
37
|
+
return cachedGpuEncoder;
|
|
38
|
+
}
|
|
39
|
+
export function getGpuEncoderName(encoder, codec) {
|
|
40
|
+
if (!encoder)
|
|
41
|
+
return codec === "h264" ? "libx264" : "libx265";
|
|
42
|
+
switch (encoder) {
|
|
43
|
+
case "nvenc":
|
|
44
|
+
return codec === "h264" ? "h264_nvenc" : "hevc_nvenc";
|
|
45
|
+
case "videotoolbox":
|
|
46
|
+
return codec === "h264" ? "h264_videotoolbox" : "hevc_videotoolbox";
|
|
47
|
+
case "vaapi":
|
|
48
|
+
return codec === "h264" ? "h264_vaapi" : "hevc_vaapi";
|
|
49
|
+
case "qsv":
|
|
50
|
+
return codec === "h264" ? "h264_qsv" : "hevc_qsv";
|
|
51
|
+
default:
|
|
52
|
+
return codec === "h264" ? "libx264" : "libx265";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=gpuEncoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpuEncoder.js","sourceRoot":"","sources":["../../src/utils/gpuEncoder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAItC,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE;YAC5C,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;iBAC/C,IAAI,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC;gBAAE,OAAO,CAAC,cAAc,CAAC,CAAC;iBAClE,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;iBACpD,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;;gBAChD,OAAO,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,gBAAgB,GAA2B,SAAS,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,gBAAgB,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC9C,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAmB,EAAE,KAAsB;IAC3E,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO;YACV,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;QACxD,KAAK,cAAc;YACjB,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,mBAAmB,CAAC;QACtE,KAAK,OAAO;YACV,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;QACxD,KAAK,KAAK;YACR,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;QACpD;YACE,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const MEDIA_VISUAL_STYLE_PROPERTIES: readonly ["width", "height", "top", "left", "right", "bottom", "inset", "object-fit", "object-position", "z-index", "opacity", "visibility", "filter", "mix-blend-mode", "backdrop-filter", "border-radius", "overflow", "clip-path", "mask", "mask-image", "mask-size", "mask-position", "mask-repeat", "transform", "transform-origin", "box-sizing"];
|
|
2
|
+
export declare function quantizeTimeToFrame(timeSeconds: number, fps: number): number;
|
|
3
|
+
//# sourceMappingURL=parityContract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parityContract.d.ts","sourceRoot":"","sources":["../../src/utils/parityContract.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,yVA2BhC,CAAC;AAEX,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAK5E"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const MEDIA_VISUAL_STYLE_PROPERTIES = [
|
|
2
|
+
"width",
|
|
3
|
+
"height",
|
|
4
|
+
"top",
|
|
5
|
+
"left",
|
|
6
|
+
"right",
|
|
7
|
+
"bottom",
|
|
8
|
+
"inset",
|
|
9
|
+
"object-fit",
|
|
10
|
+
"object-position",
|
|
11
|
+
"z-index",
|
|
12
|
+
"opacity",
|
|
13
|
+
"visibility",
|
|
14
|
+
"filter",
|
|
15
|
+
"mix-blend-mode",
|
|
16
|
+
"backdrop-filter",
|
|
17
|
+
"border-radius",
|
|
18
|
+
"overflow",
|
|
19
|
+
"clip-path",
|
|
20
|
+
"mask",
|
|
21
|
+
"mask-image",
|
|
22
|
+
"mask-size",
|
|
23
|
+
"mask-position",
|
|
24
|
+
"mask-repeat",
|
|
25
|
+
"transform",
|
|
26
|
+
"transform-origin",
|
|
27
|
+
"box-sizing",
|
|
28
|
+
];
|
|
29
|
+
export function quantizeTimeToFrame(timeSeconds, fps) {
|
|
30
|
+
const safeFps = Number.isFinite(fps) && fps > 0 ? fps : 30;
|
|
31
|
+
const safeTime = Number.isFinite(timeSeconds) && timeSeconds > 0 ? timeSeconds : 0;
|
|
32
|
+
const frameIndex = Math.floor(safeTime * safeFps + 1e-9);
|
|
33
|
+
return frameIndex / safeFps;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=parityContract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parityContract.js","sourceRoot":"","sources":["../../src/utils/parityContract.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,iBAAiB;IACjB,SAAS;IACT,SAAS;IACT,YAAY;IACZ,QAAQ;IACR,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,UAAU;IACV,WAAW;IACX,MAAM;IACN,YAAY;IACZ,WAAW;IACX,eAAe;IACf,aAAa;IACb,WAAW;IACX,kBAAkB;IAClB,YAAY;CACJ,CAAC;AAEX,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,GAAW;IAClE,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IACzD,OAAO,UAAU,GAAG,OAAO,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared FFmpeg process runner.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the repeated spawn-stderr-timeout-abort-close-error pattern
|
|
5
|
+
* that appears across audioMixer and chunkEncoder into a single helper.
|
|
6
|
+
*/
|
|
7
|
+
export interface RunFfmpegOptions {
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
onStderr?: (line: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface RunFfmpegResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
exitCode: number | null;
|
|
15
|
+
stderr: string;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunFfmpegResult>;
|
|
19
|
+
//# sourceMappingURL=runFfmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runFfmpeg.d.ts","sourceRoot":"","sources":["../../src/utils/runFfmpeg.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwDjG"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared FFmpeg process runner.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the repeated spawn-stderr-timeout-abort-close-error pattern
|
|
5
|
+
* that appears across audioMixer and chunkEncoder into a single helper.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
const DEFAULT_TIMEOUT = 300_000;
|
|
9
|
+
export async function runFfmpeg(args, opts) {
|
|
10
|
+
const startMs = Date.now();
|
|
11
|
+
const signal = opts?.signal;
|
|
12
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
|
|
13
|
+
const onStderr = opts?.onStderr;
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const ffmpeg = spawn("ffmpeg", args);
|
|
16
|
+
let stderr = "";
|
|
17
|
+
const onAbort = () => {
|
|
18
|
+
ffmpeg.kill("SIGTERM");
|
|
19
|
+
};
|
|
20
|
+
if (signal) {
|
|
21
|
+
if (signal.aborted) {
|
|
22
|
+
ffmpeg.kill("SIGTERM");
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
ffmpeg.kill("SIGTERM");
|
|
30
|
+
}, timeout);
|
|
31
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
32
|
+
const chunk = data.toString();
|
|
33
|
+
stderr += chunk;
|
|
34
|
+
if (onStderr) {
|
|
35
|
+
onStderr(chunk);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
ffmpeg.on("close", (code) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
if (signal)
|
|
41
|
+
signal.removeEventListener("abort", onAbort);
|
|
42
|
+
resolve({
|
|
43
|
+
success: !signal?.aborted && code === 0,
|
|
44
|
+
exitCode: code,
|
|
45
|
+
stderr,
|
|
46
|
+
durationMs: Date.now() - startMs,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
ffmpeg.on("error", (err) => {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
if (signal)
|
|
52
|
+
signal.removeEventListener("abort", onAbort);
|
|
53
|
+
resolve({
|
|
54
|
+
success: false,
|
|
55
|
+
exitCode: null,
|
|
56
|
+
stderr: err.message,
|
|
57
|
+
durationMs: Date.now() - startMs,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=runFfmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runFfmpeg.js","sourceRoot":"","sources":["../../src/utils/runFfmpeg.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAetC,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAc,EAAE,IAAuB;IACrE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC;IAC5B,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,eAAe,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC;IAEhC,OAAO,IAAI,OAAO,CAAkB,CAAC,OAAO,EAAE,EAAE;QAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzB,CAAC,CAAC;QAEF,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzB,CAAC,EAAE,OAAO,CAAC,CAAC;QAEZ,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC;YAChB,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,MAAM;gBAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzD,OAAO,CAAC;gBACN,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI,KAAK,CAAC;gBACvC,QAAQ,EAAE,IAAI;gBACd,MAAM;gBACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;aACjC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,MAAM;gBAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzD,OAAO,CAAC;gBACN,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE,GAAG,CAAC,OAAO;gBACnB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;aACjC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urlDownloader.d.ts","sourceRoot":"","sources":["../../src/utils/urlDownloader.ts"],"names":[],"mappings":"AAgBA,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAyD9G;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createWriteStream, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { join, extname } from "path";
|
|
4
|
+
import { Readable } from "stream";
|
|
5
|
+
import { finished } from "stream/promises";
|
|
6
|
+
const downloadPathCache = new Map();
|
|
7
|
+
const inFlightDownloads = new Map();
|
|
8
|
+
function getFilenameFromUrl(url) {
|
|
9
|
+
const hash = createHash("md5").update(url).digest("hex").slice(0, 12);
|
|
10
|
+
const urlObj = new URL(url);
|
|
11
|
+
const ext = extname(urlObj.pathname) || ".mp4";
|
|
12
|
+
return `download_${hash}${ext}`;
|
|
13
|
+
}
|
|
14
|
+
export async function downloadToTemp(url, destDir, timeoutMs = 300000) {
|
|
15
|
+
const cachedPath = downloadPathCache.get(url);
|
|
16
|
+
if (cachedPath && existsSync(cachedPath)) {
|
|
17
|
+
return cachedPath;
|
|
18
|
+
}
|
|
19
|
+
const inFlight = inFlightDownloads.get(url);
|
|
20
|
+
if (inFlight) {
|
|
21
|
+
return inFlight;
|
|
22
|
+
}
|
|
23
|
+
if (!existsSync(destDir)) {
|
|
24
|
+
mkdirSync(destDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
const filename = getFilenameFromUrl(url);
|
|
27
|
+
const localPath = join(destDir, filename);
|
|
28
|
+
if (existsSync(localPath)) {
|
|
29
|
+
downloadPathCache.set(url, localPath);
|
|
30
|
+
return localPath;
|
|
31
|
+
}
|
|
32
|
+
const downloadPromise = (async () => {
|
|
33
|
+
try {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
36
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
37
|
+
clearTimeout(timeoutId);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
if (!response.body) {
|
|
42
|
+
throw new Error("Response body is empty");
|
|
43
|
+
}
|
|
44
|
+
const fileStream = createWriteStream(localPath);
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
const readableStream = Readable.fromWeb(response.body);
|
|
47
|
+
await finished(readableStream.pipe(fileStream));
|
|
48
|
+
downloadPathCache.set(url, localPath);
|
|
49
|
+
return localPath;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
if (message.includes("aborted")) {
|
|
54
|
+
throw new Error(`[URLDownloader] Download timeout after ${timeoutMs / 1000}s: ${url}`);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`[URLDownloader] Download failed: ${message}`);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
inFlightDownloads.delete(url);
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
inFlightDownloads.set(url, downloadPromise);
|
|
63
|
+
return downloadPromise;
|
|
64
|
+
}
|
|
65
|
+
export function isHttpUrl(path) {
|
|
66
|
+
return path.startsWith("http://") || path.startsWith("https://");
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=urlDownloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urlDownloader.js","sourceRoot":"","sources":["../../src/utils/urlDownloader.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyperframes/engine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@hono/node-server": "^1.13.0",
|
|
17
|
+
"@hyperframes/core": "workspace:*",
|
|
18
|
+
"hono": "^4.6.0",
|
|
19
|
+
"linkedom": "^0.18.12",
|
|
20
|
+
"puppeteer": "^24.0.0",
|
|
21
|
+
"puppeteer-core": "^24.39.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.10.1",
|
|
25
|
+
"typescript": "^5.7.2"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type augmentation for Chrome's HeadlessExperimental CDP domain.
|
|
3
|
+
*
|
|
4
|
+
* Puppeteer's CDPSession.send() is typed against devtools-protocol's
|
|
5
|
+
* ProtocolMapping.Commands, which does not include HeadlessExperimental.
|
|
6
|
+
* This module augmentation adds proper types so we can call these methods
|
|
7
|
+
* without unsafe `as any` casts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Parameters for HeadlessExperimental.beginFrame */
|
|
11
|
+
interface HeadlessExperimentalBeginFrameRequest {
|
|
12
|
+
/** Timestamp in milliseconds since epoch for the frame */
|
|
13
|
+
frameTimeTicks: number;
|
|
14
|
+
/** Interval in milliseconds between frames */
|
|
15
|
+
interval: number;
|
|
16
|
+
/** If true, do not produce display updates (warmup mode) */
|
|
17
|
+
noDisplayUpdates?: boolean;
|
|
18
|
+
/** Optional screenshot configuration */
|
|
19
|
+
screenshot?: {
|
|
20
|
+
format: "jpeg" | "png";
|
|
21
|
+
quality?: number;
|
|
22
|
+
optimizeForSpeed?: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Response from HeadlessExperimental.beginFrame */
|
|
27
|
+
interface HeadlessExperimentalBeginFrameResponse {
|
|
28
|
+
/** Whether the compositor reported visual damage */
|
|
29
|
+
hasDamage: boolean;
|
|
30
|
+
/** Base64-encoded screenshot data (present only when screenshot was requested and hasDamage is true) */
|
|
31
|
+
screenshotData?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {};
|
|
35
|
+
|
|
36
|
+
declare module "devtools-protocol/types/protocol-mapping.js" {
|
|
37
|
+
// Merge into the existing ProtocolMapping namespace
|
|
38
|
+
export namespace ProtocolMapping {
|
|
39
|
+
interface Commands {
|
|
40
|
+
"HeadlessExperimental.enable": {
|
|
41
|
+
paramsType: [];
|
|
42
|
+
returnType: void;
|
|
43
|
+
};
|
|
44
|
+
"HeadlessExperimental.disable": {
|
|
45
|
+
paramsType: [];
|
|
46
|
+
returnType: void;
|
|
47
|
+
};
|
|
48
|
+
"HeadlessExperimental.beginFrame": {
|
|
49
|
+
paramsType: [HeadlessExperimentalBeginFrameRequest];
|
|
50
|
+
returnType: HeadlessExperimentalBeginFrameResponse;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { resolveConfig, DEFAULT_CONFIG } from "./config.js";
|
|
3
|
+
|
|
4
|
+
describe("resolveConfig", () => {
|
|
5
|
+
const savedEnv = new Map<string, string | undefined>();
|
|
6
|
+
|
|
7
|
+
function setEnv(key: string, value: string) {
|
|
8
|
+
savedEnv.set(key, process.env[key]);
|
|
9
|
+
process.env[key] = value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
savedEnv.clear();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
for (const [key, value] of savedEnv) {
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
delete process.env[key];
|
|
20
|
+
} else {
|
|
21
|
+
process.env[key] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns defaults when no overrides or env vars are set", () => {
|
|
27
|
+
const config = resolveConfig();
|
|
28
|
+
expect(config.fps).toBe(30);
|
|
29
|
+
expect(config.quality).toBe("standard");
|
|
30
|
+
expect(config.format).toBe("jpeg");
|
|
31
|
+
expect(config.jpegQuality).toBe(80);
|
|
32
|
+
expect(config.debug).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("applies explicit overrides over defaults", () => {
|
|
36
|
+
const config = resolveConfig({ fps: 60, debug: true });
|
|
37
|
+
expect(config.fps).toBe(60);
|
|
38
|
+
expect(config.debug).toBe(true);
|
|
39
|
+
// Non-overridden fields remain at defaults
|
|
40
|
+
expect(config.quality).toBe("standard");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("reads numeric env vars with PRODUCER_ prefix", () => {
|
|
44
|
+
setEnv("PRODUCER_MAX_WORKERS", "4");
|
|
45
|
+
setEnv("PRODUCER_CORES_PER_WORKER", "3");
|
|
46
|
+
|
|
47
|
+
const config = resolveConfig();
|
|
48
|
+
expect(config.concurrency).toBe(4);
|
|
49
|
+
expect(config.coresPerWorker).toBe(3);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("reads boolean env vars (true/false strings)", () => {
|
|
53
|
+
setEnv("PRODUCER_DISABLE_GPU", "true");
|
|
54
|
+
setEnv("PRODUCER_ENABLE_BROWSER_POOL", "true");
|
|
55
|
+
|
|
56
|
+
const config = resolveConfig();
|
|
57
|
+
expect(config.disableGpu).toBe(true);
|
|
58
|
+
expect(config.enableBrowserPool).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("treats non-'true' boolean env vars as false", () => {
|
|
62
|
+
setEnv("PRODUCER_DISABLE_GPU", "yes");
|
|
63
|
+
|
|
64
|
+
const config = resolveConfig();
|
|
65
|
+
expect(config.disableGpu).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("explicit overrides take precedence over env vars", () => {
|
|
69
|
+
setEnv("PRODUCER_CORES_PER_WORKER", "5");
|
|
70
|
+
|
|
71
|
+
const config = resolveConfig({ coresPerWorker: 8 });
|
|
72
|
+
expect(config.coresPerWorker).toBe(8);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("falls back to defaults for invalid numeric env vars", () => {
|
|
76
|
+
setEnv("PRODUCER_CORES_PER_WORKER", "not-a-number");
|
|
77
|
+
|
|
78
|
+
const config = resolveConfig();
|
|
79
|
+
expect(config.coresPerWorker).toBe(DEFAULT_CONFIG.coresPerWorker);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("clamps chunkSizeFrames to minimum of 120", () => {
|
|
83
|
+
setEnv("PRODUCER_CHUNK_SIZE_FRAMES", "50");
|
|
84
|
+
|
|
85
|
+
const config = resolveConfig();
|
|
86
|
+
expect(config.chunkSizeFrames).toBe(120);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("clamps frameDataUriCacheLimit to minimum of 32", () => {
|
|
90
|
+
setEnv("PRODUCER_FRAME_DATA_URI_CACHE_LIMIT", "10");
|
|
91
|
+
|
|
92
|
+
const config = resolveConfig();
|
|
93
|
+
expect(config.frameDataUriCacheLimit).toBe(32);
|
|
94
|
+
});
|
|
95
|
+
});
|