@editframe/dev-server 0.51.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/LICENSE-FULL.md +148 -0
- package/LICENSE.md +58 -0
- package/dist/efHandlers.d.ts +20 -0
- package/dist/efHandlers.js +272 -0
- package/dist/efHandlers.js.map +1 -0
- package/dist/forbidRelativePaths.d.ts +7 -0
- package/dist/forbidRelativePaths.js +8 -0
- package/dist/forbidRelativePaths.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/jitTranscodeMiddleware.d.ts +126 -0
- package/dist/jitTranscodeMiddleware.js +421 -0
- package/dist/jitTranscodeMiddleware.js.map +1 -0
- package/dist/middleware.d.ts +25 -0
- package/dist/middleware.js +156 -0
- package/dist/middleware.js.map +1 -0
- package/dist/router.d.ts +31 -0
- package/dist/router.js +80 -0
- package/dist/router.js.map +1 -0
- package/dist/sendTaskResult.d.ts +8 -0
- package/dist/sendTaskResult.js +67 -0
- package/dist/sendTaskResult.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.js +26 -0
- package/dist/server.js.map +1 -0
- package/package.json +47 -0
- package/tsdown.config.ts +9 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ServerResponse } from "node:http";
|
|
2
|
+
import { IncomingMessage as IncomingMessage$1, NextFunction } from "connect";
|
|
3
|
+
import { TrackFragmentIndex } from "@editframe/assets";
|
|
4
|
+
|
|
5
|
+
//#region src/jitTranscodeMiddleware.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Asset functions required by the JIT middleware.
|
|
8
|
+
* These are injected to support both package imports and direct imports.
|
|
9
|
+
*/
|
|
10
|
+
interface AssetFunctions {
|
|
11
|
+
generateTrack: (cacheRoot: string, absolutePath: string, trackUrl: string) => Promise<{
|
|
12
|
+
cachePath: string;
|
|
13
|
+
}>;
|
|
14
|
+
generateScrubTrack: (cacheRoot: string, absolutePath: string) => Promise<{
|
|
15
|
+
cachePath: string;
|
|
16
|
+
}>;
|
|
17
|
+
generateTrackFragmentIndex: (cacheRoot: string, absolutePath: string) => Promise<{
|
|
18
|
+
cachePath: string;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
interface JitMiddlewareOptions {
|
|
22
|
+
root: string;
|
|
23
|
+
cacheRoot: string;
|
|
24
|
+
/**
|
|
25
|
+
* When true, remote URLs (http/https) are handled locally via ffprobe/ffmpeg
|
|
26
|
+
* rather than passed to a downstream proxy (e.g. recordReplayProxyPlugin).
|
|
27
|
+
* Set to true in dev-projects; leave false (default) in test environments
|
|
28
|
+
* that use recordReplayProxyPlugin to proxy remote URLs to the cloud API.
|
|
29
|
+
*/
|
|
30
|
+
handleRemoteUrls?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Stream a specific byte range from a file.
|
|
34
|
+
* This is used for JIT segment serving where the server extracts the correct bytes.
|
|
35
|
+
*/
|
|
36
|
+
declare function sendByteRange(res: ServerResponse, filePath: string, offset: number, size: number, contentType?: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Stream multiple byte ranges from a file concatenated together.
|
|
39
|
+
* Used for creating playable .mp4 files by combining init segment + media segment.
|
|
40
|
+
*/
|
|
41
|
+
declare function sendMultipleByteRanges(res: ServerResponse, filePath: string, ranges: Array<{
|
|
42
|
+
offset: number;
|
|
43
|
+
size: number;
|
|
44
|
+
}>, contentType?: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a hostname refers to the local dev server.
|
|
47
|
+
* Handles various local hostname patterns including worktree domains.
|
|
48
|
+
*/
|
|
49
|
+
declare function isLocalHost(hostname: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a URL to either a remote URL (for ffprobe) or a local file path.
|
|
52
|
+
*
|
|
53
|
+
* - Remote URLs (different host): passed directly to ffprobe (it supports http/https)
|
|
54
|
+
* - Local URLs (localhost, *.localhost): resolved to local file path
|
|
55
|
+
*
|
|
56
|
+
* @param urlParam - The URL from the query parameter
|
|
57
|
+
* @param root - The plugin root directory
|
|
58
|
+
* @returns The path/URL to pass to ffprobe
|
|
59
|
+
*/
|
|
60
|
+
declare function resolveMediaPath(urlParam: string, root: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Convert fragment index segments to millisecond durations array.
|
|
63
|
+
*/
|
|
64
|
+
declare function getSegmentDurationsMs(track: TrackFragmentIndex): number[];
|
|
65
|
+
/**
|
|
66
|
+
* Generate a JIT manifest for a local file.
|
|
67
|
+
* Uses actual fragment index data for accurate segment information.
|
|
68
|
+
*/
|
|
69
|
+
declare function generateLocalJitManifest(absolutePath: string, sourceUrl: string, baseUrl: string, cacheRoot: string, assetFunctions: AssetFunctions): Promise<{
|
|
70
|
+
version: string;
|
|
71
|
+
type: string;
|
|
72
|
+
sourceUrl: string;
|
|
73
|
+
duration: number;
|
|
74
|
+
durationMs: number;
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
videoRenditions: {
|
|
77
|
+
id: string;
|
|
78
|
+
width: number;
|
|
79
|
+
height: number;
|
|
80
|
+
bitrate: number;
|
|
81
|
+
codec: string;
|
|
82
|
+
container: "video/webm" | "video/mp4";
|
|
83
|
+
mimeType: string;
|
|
84
|
+
segmentDuration: number;
|
|
85
|
+
segmentDurationMs: number;
|
|
86
|
+
segmentDurationsMs: number[];
|
|
87
|
+
startTimeOffsetMs: number | undefined;
|
|
88
|
+
frameRate: number;
|
|
89
|
+
profile: string;
|
|
90
|
+
level: string;
|
|
91
|
+
}[];
|
|
92
|
+
audioRenditions: {
|
|
93
|
+
id: string;
|
|
94
|
+
channels: number;
|
|
95
|
+
sampleRate: number;
|
|
96
|
+
bitrate: number;
|
|
97
|
+
codec: string;
|
|
98
|
+
container: string;
|
|
99
|
+
mimeType: string;
|
|
100
|
+
segmentDuration: number;
|
|
101
|
+
segmentDurationMs: number;
|
|
102
|
+
segmentDurationsMs: number[];
|
|
103
|
+
language: string;
|
|
104
|
+
}[];
|
|
105
|
+
endpoints: {
|
|
106
|
+
initSegment: string;
|
|
107
|
+
mediaSegment: string;
|
|
108
|
+
};
|
|
109
|
+
jitInfo: {
|
|
110
|
+
parallelTranscodingSupported: boolean;
|
|
111
|
+
expectedTranscodeLatency: number;
|
|
112
|
+
segmentCount: number;
|
|
113
|
+
scrubSegmentCount: number;
|
|
114
|
+
};
|
|
115
|
+
}>;
|
|
116
|
+
/**
|
|
117
|
+
* Create the JIT transcode middleware for /api/v1/transcode/* routes.
|
|
118
|
+
*
|
|
119
|
+
* @param options - The middleware options (root, cacheRoot)
|
|
120
|
+
* @param assetFunctions - The asset functions to use (allows dependency injection)
|
|
121
|
+
* @returns Connect-compatible middleware function
|
|
122
|
+
*/
|
|
123
|
+
declare function createJitTranscodeMiddleware(options: JitMiddlewareOptions, assetFunctions: AssetFunctions): (req: IncomingMessage$1, res: ServerResponse, next: NextFunction) => Promise<void>;
|
|
124
|
+
//#endregion
|
|
125
|
+
export { AssetFunctions, JitMiddlewareOptions, type TrackFragmentIndex, createJitTranscodeMiddleware, generateLocalJitManifest, getSegmentDurationsMs, isLocalHost, resolveMediaPath, sendByteRange, sendMultipleByteRanges };
|
|
126
|
+
//# sourceMappingURL=jitTranscodeMiddleware.d.ts.map
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { forbidRelativePaths } from "./forbidRelativePaths.js";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { createReadStream, statSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import mime from "mime";
|
|
6
|
+
//#region src/jitTranscodeMiddleware.ts
|
|
7
|
+
/** Resolve HTTP Content-Type for a segment extension. */
|
|
8
|
+
function resolveSegmentContentType(ext) {
|
|
9
|
+
switch (ext) {
|
|
10
|
+
case "webm": return "video/webm";
|
|
11
|
+
case "m4s": return "video/iso.segment";
|
|
12
|
+
case "mp4": return "video/mp4";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Manifest format descriptor for a video codec profile. */
|
|
16
|
+
function resolveManifestFormat(isVp9) {
|
|
17
|
+
if (isVp9) return {
|
|
18
|
+
container: "video/webm",
|
|
19
|
+
segmentExt: "webm",
|
|
20
|
+
mimeType: (codec) => `video/webm; codecs="${codec}"`
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
container: "video/mp4",
|
|
24
|
+
segmentExt: "m4s",
|
|
25
|
+
mimeType: (codec) => `video/mp4; codecs="${codec}"`
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Stream a specific byte range from a file.
|
|
30
|
+
* This is used for JIT segment serving where the server extracts the correct bytes.
|
|
31
|
+
*/
|
|
32
|
+
function sendByteRange(res, filePath, offset, size, contentType) {
|
|
33
|
+
const log = debug("ef:sendByteRange");
|
|
34
|
+
const stats = statSync(filePath);
|
|
35
|
+
const end = offset + size - 1;
|
|
36
|
+
if (end >= stats.size) {
|
|
37
|
+
log(`Requested range ${offset}-${end} exceeds file size ${stats.size}`);
|
|
38
|
+
res.writeHead(416, { "Content-Range": `bytes */${stats.size}` });
|
|
39
|
+
res.end();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
log(`Streaming bytes ${offset}-${end} (${size} bytes) from ${filePath}`);
|
|
43
|
+
res.writeHead(200, {
|
|
44
|
+
"Content-Type": contentType || mime.getType(filePath) || "video/mp4",
|
|
45
|
+
"Content-Length": size,
|
|
46
|
+
"Cache-Control": "public, max-age=3600"
|
|
47
|
+
});
|
|
48
|
+
createReadStream(filePath, {
|
|
49
|
+
start: offset,
|
|
50
|
+
end
|
|
51
|
+
}).pipe(res);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Stream multiple byte ranges from a file concatenated together.
|
|
55
|
+
* Used for creating playable .mp4 files by combining init segment + media segment.
|
|
56
|
+
*/
|
|
57
|
+
function sendMultipleByteRanges(res, filePath, ranges, contentType) {
|
|
58
|
+
const log = debug("ef:sendMultipleByteRanges");
|
|
59
|
+
const stats = statSync(filePath);
|
|
60
|
+
for (const range of ranges) {
|
|
61
|
+
const end = range.offset + range.size - 1;
|
|
62
|
+
if (end >= stats.size) {
|
|
63
|
+
log(`Requested range ${range.offset}-${end} exceeds file size ${stats.size}`);
|
|
64
|
+
res.writeHead(416, { "Content-Range": `bytes */${stats.size}` });
|
|
65
|
+
res.end();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const totalSize = ranges.reduce((sum, r) => sum + r.size, 0);
|
|
70
|
+
log(`Streaming ${ranges.length} ranges (${totalSize} total bytes) from ${filePath}`);
|
|
71
|
+
res.writeHead(200, {
|
|
72
|
+
"Content-Type": contentType || "video/mp4",
|
|
73
|
+
"Content-Length": totalSize,
|
|
74
|
+
"Cache-Control": "public, max-age=3600"
|
|
75
|
+
});
|
|
76
|
+
let rangeIndex = 0;
|
|
77
|
+
const streamNextRange = () => {
|
|
78
|
+
if (rangeIndex >= ranges.length) {
|
|
79
|
+
res.end();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const range = ranges[rangeIndex];
|
|
83
|
+
const end = range.offset + range.size - 1;
|
|
84
|
+
const readStream = createReadStream(filePath, {
|
|
85
|
+
start: range.offset,
|
|
86
|
+
end
|
|
87
|
+
});
|
|
88
|
+
readStream.on("end", () => {
|
|
89
|
+
rangeIndex++;
|
|
90
|
+
streamNextRange();
|
|
91
|
+
});
|
|
92
|
+
readStream.on("error", (err) => {
|
|
93
|
+
log(`Error streaming range ${rangeIndex}: ${err}`);
|
|
94
|
+
res.destroy();
|
|
95
|
+
});
|
|
96
|
+
readStream.pipe(res, { end: false });
|
|
97
|
+
};
|
|
98
|
+
streamNextRange();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if a hostname refers to the local dev server.
|
|
102
|
+
* Handles various local hostname patterns including worktree domains.
|
|
103
|
+
*/
|
|
104
|
+
function isLocalHost(hostname) {
|
|
105
|
+
const localPatterns = [
|
|
106
|
+
"localhost",
|
|
107
|
+
"127.0.0.1",
|
|
108
|
+
"0.0.0.0",
|
|
109
|
+
".localhost"
|
|
110
|
+
];
|
|
111
|
+
const lowerHost = hostname.toLowerCase();
|
|
112
|
+
return localPatterns.some((pattern) => pattern.startsWith(".") ? lowerHost.endsWith(pattern) || lowerHost === pattern.slice(1) : lowerHost === pattern || lowerHost.startsWith(pattern + ":"));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve a URL to either a remote URL (for ffprobe) or a local file path.
|
|
116
|
+
*
|
|
117
|
+
* - Remote URLs (different host): passed directly to ffprobe (it supports http/https)
|
|
118
|
+
* - Local URLs (localhost, *.localhost): resolved to local file path
|
|
119
|
+
*
|
|
120
|
+
* @param urlParam - The URL from the query parameter
|
|
121
|
+
* @param root - The plugin root directory
|
|
122
|
+
* @returns The path/URL to pass to ffprobe
|
|
123
|
+
*/
|
|
124
|
+
function resolveMediaPath(urlParam, root) {
|
|
125
|
+
try {
|
|
126
|
+
const url = new URL(urlParam);
|
|
127
|
+
const hostname = url.hostname;
|
|
128
|
+
if (!isLocalHost(hostname)) return urlParam;
|
|
129
|
+
let filePath = decodeURIComponent(url.pathname);
|
|
130
|
+
if (filePath.startsWith("/")) filePath = filePath.slice(1);
|
|
131
|
+
if (filePath.startsWith("src/")) filePath = filePath.slice(4);
|
|
132
|
+
return path.join(root, filePath);
|
|
133
|
+
} catch {
|
|
134
|
+
let filePath = urlParam;
|
|
135
|
+
if (filePath.startsWith("src/")) filePath = filePath.slice(4);
|
|
136
|
+
return path.join(root, filePath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Convert fragment index segments to millisecond durations array.
|
|
141
|
+
*/
|
|
142
|
+
function getSegmentDurationsMs(track) {
|
|
143
|
+
return track.segments.map((segment) => segment.duration / track.timescale * 1e3);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Generate a JIT manifest for a local file.
|
|
147
|
+
* Uses actual fragment index data for accurate segment information.
|
|
148
|
+
*/
|
|
149
|
+
async function generateLocalJitManifest(absolutePath, sourceUrl, baseUrl, cacheRoot, assetFunctions) {
|
|
150
|
+
const log = debug("ef:generateLocalJitManifest");
|
|
151
|
+
log(`Generating fragment index for ${absolutePath}`);
|
|
152
|
+
const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(cacheRoot, absolutePath);
|
|
153
|
+
const fragmentIndex = JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(fragmentIndexResult.cachePath, "utf-8")));
|
|
154
|
+
const videoTrack = Object.values(fragmentIndex).find((t) => t.type === "video");
|
|
155
|
+
const audioTrack = Object.values(fragmentIndex).find((t) => t.type === "audio");
|
|
156
|
+
const scrubTrack = fragmentIndex[-1];
|
|
157
|
+
const hasVideo = videoTrack?.type === "video";
|
|
158
|
+
const hasAudio = audioTrack?.type === "audio";
|
|
159
|
+
let durationMs = 0;
|
|
160
|
+
if (hasVideo && videoTrack) durationMs = Math.max(durationMs, videoTrack.duration / videoTrack.timescale * 1e3);
|
|
161
|
+
if (hasAudio && audioTrack) durationMs = Math.max(durationMs, audioTrack.duration / audioTrack.timescale * 1e3);
|
|
162
|
+
const durationSeconds = durationMs / 1e3;
|
|
163
|
+
const width = hasVideo && videoTrack && "width" in videoTrack ? videoTrack.width : 1920;
|
|
164
|
+
const height = hasVideo && videoTrack && "height" in videoTrack ? videoTrack.height : 1080;
|
|
165
|
+
const codec = hasVideo && videoTrack ? videoTrack.codec : "avc1.640029";
|
|
166
|
+
const fmt = resolveManifestFormat(hasVideo && videoTrack && "isAlpha" in videoTrack ? !!videoTrack.isAlpha : false);
|
|
167
|
+
const videoSegmentDurationsMs = hasVideo && videoTrack ? getSegmentDurationsMs(videoTrack) : [];
|
|
168
|
+
const scrubSegmentDurationsMs = scrubTrack ? getSegmentDurationsMs(scrubTrack) : [];
|
|
169
|
+
const audioSegmentDurationsMs = hasAudio && audioTrack ? getSegmentDurationsMs(audioTrack) : [];
|
|
170
|
+
const avgVideoSegmentDurationMs = videoSegmentDurationsMs.length > 0 ? videoSegmentDurationsMs.reduce((a, b) => a + b, 0) / videoSegmentDurationsMs.length : 2e3;
|
|
171
|
+
const avgScrubSegmentDurationMs = scrubSegmentDurationsMs.length > 0 ? scrubSegmentDurationsMs.reduce((a, b) => a + b, 0) / scrubSegmentDurationsMs.length : 3e4;
|
|
172
|
+
const avgAudioSegmentDurationMs = audioSegmentDurationsMs.length > 0 ? audioSegmentDurationsMs.reduce((a, b) => a + b, 0) / audioSegmentDurationsMs.length : 2e3;
|
|
173
|
+
log(`Video: ${videoSegmentDurationsMs.length} segments, Audio: ${audioSegmentDurationsMs.length} segments, Scrub: ${scrubSegmentDurationsMs.length} segments`);
|
|
174
|
+
return {
|
|
175
|
+
version: "1.0",
|
|
176
|
+
type: "com.editframe/local-jit-manifest",
|
|
177
|
+
sourceUrl,
|
|
178
|
+
duration: durationSeconds,
|
|
179
|
+
durationMs,
|
|
180
|
+
baseUrl,
|
|
181
|
+
videoRenditions: hasVideo ? [{
|
|
182
|
+
id: "high",
|
|
183
|
+
width,
|
|
184
|
+
height,
|
|
185
|
+
bitrate: 5e6,
|
|
186
|
+
codec,
|
|
187
|
+
container: fmt.container,
|
|
188
|
+
mimeType: fmt.mimeType(codec),
|
|
189
|
+
segmentDuration: avgVideoSegmentDurationMs / 1e3,
|
|
190
|
+
segmentDurationMs: avgVideoSegmentDurationMs,
|
|
191
|
+
segmentDurationsMs: videoSegmentDurationsMs,
|
|
192
|
+
startTimeOffsetMs: videoTrack.startTimeOffsetMs,
|
|
193
|
+
frameRate: 30,
|
|
194
|
+
profile: "High",
|
|
195
|
+
level: "4.1"
|
|
196
|
+
}, ...scrubTrack ? [{
|
|
197
|
+
id: "scrub",
|
|
198
|
+
width: 320,
|
|
199
|
+
height: Math.round(320 * (height ?? 1080) / (width ?? 1920)),
|
|
200
|
+
bitrate: 1e5,
|
|
201
|
+
codec: scrubTrack.codec,
|
|
202
|
+
container: fmt.container,
|
|
203
|
+
mimeType: fmt.mimeType(scrubTrack.codec),
|
|
204
|
+
segmentDuration: avgScrubSegmentDurationMs / 1e3,
|
|
205
|
+
segmentDurationMs: avgScrubSegmentDurationMs,
|
|
206
|
+
segmentDurationsMs: scrubSegmentDurationsMs,
|
|
207
|
+
startTimeOffsetMs: scrubTrack.startTimeOffsetMs,
|
|
208
|
+
frameRate: 15,
|
|
209
|
+
profile: "High",
|
|
210
|
+
level: "4.1"
|
|
211
|
+
}] : []] : [],
|
|
212
|
+
audioRenditions: hasAudio && audioTrack ? [{
|
|
213
|
+
id: "audio",
|
|
214
|
+
channels: "channel_count" in audioTrack ? audioTrack.channel_count : 2,
|
|
215
|
+
sampleRate: "sample_rate" in audioTrack ? audioTrack.sample_rate : 48e3,
|
|
216
|
+
bitrate: 128e3,
|
|
217
|
+
codec: audioTrack.codec,
|
|
218
|
+
container: "audio/mp4",
|
|
219
|
+
mimeType: `audio/mp4; codecs="${audioTrack.codec}"`,
|
|
220
|
+
segmentDuration: avgAudioSegmentDurationMs / 1e3,
|
|
221
|
+
segmentDurationMs: avgAudioSegmentDurationMs,
|
|
222
|
+
segmentDurationsMs: audioSegmentDurationsMs,
|
|
223
|
+
language: "en"
|
|
224
|
+
}] : [],
|
|
225
|
+
endpoints: {
|
|
226
|
+
initSegment: `${baseUrl}/api/v1/transcode/{rendition}/init.${fmt.segmentExt}?url=${encodeURIComponent(sourceUrl)}`,
|
|
227
|
+
mediaSegment: `${baseUrl}/api/v1/transcode/{rendition}/{segmentId}.${fmt.segmentExt}?url=${encodeURIComponent(sourceUrl)}`
|
|
228
|
+
},
|
|
229
|
+
jitInfo: {
|
|
230
|
+
parallelTranscodingSupported: true,
|
|
231
|
+
expectedTranscodeLatency: 100,
|
|
232
|
+
segmentCount: videoSegmentDurationsMs.length,
|
|
233
|
+
scrubSegmentCount: scrubSegmentDurationsMs.length
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Create the JIT transcode middleware for /api/v1/transcode/* routes.
|
|
239
|
+
*
|
|
240
|
+
* @param options - The middleware options (root, cacheRoot)
|
|
241
|
+
* @param assetFunctions - The asset functions to use (allows dependency injection)
|
|
242
|
+
* @returns Connect-compatible middleware function
|
|
243
|
+
*/
|
|
244
|
+
function createJitTranscodeMiddleware(options, assetFunctions) {
|
|
245
|
+
return async (req, res, next) => {
|
|
246
|
+
const log = debug("ef:dev-server:jit");
|
|
247
|
+
if (!req.url?.startsWith("/api/v1/transcode/")) return next();
|
|
248
|
+
forbidRelativePaths(req);
|
|
249
|
+
log(`Handling JIT transcode request: ${req.url}`);
|
|
250
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
251
|
+
const sourceUrl = url.searchParams.get("url");
|
|
252
|
+
if (!sourceUrl) {
|
|
253
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
254
|
+
res.end(JSON.stringify({ error: "url parameter is required" }));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const mediaPath = resolveMediaPath(sourceUrl, options.root);
|
|
258
|
+
if (!options.handleRemoteUrls && (mediaPath.startsWith("http://") || mediaPath.startsWith("https://"))) return next();
|
|
259
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
260
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
261
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
262
|
+
if (req.method === "OPTIONS") {
|
|
263
|
+
res.writeHead(204);
|
|
264
|
+
res.end();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const pathMatch = url.pathname.match(/^\/api\/v1\/transcode\/(?:([^/]+)\/)?(.+)$/);
|
|
269
|
+
if (!pathMatch) {
|
|
270
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
271
|
+
res.end(JSON.stringify({ error: "Invalid transcode endpoint" }));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const [, rendition, endpoint] = pathMatch;
|
|
275
|
+
if (endpoint === "manifest.json") {
|
|
276
|
+
log(`Generating manifest for ${mediaPath}`);
|
|
277
|
+
const baseUrl = `${req.headers["x-forwarded-proto"] || url.protocol.replace(":", "")}://${url.host}`;
|
|
278
|
+
try {
|
|
279
|
+
const manifest = await generateLocalJitManifest(mediaPath, sourceUrl, baseUrl, options.cacheRoot, assetFunctions);
|
|
280
|
+
res.writeHead(200, {
|
|
281
|
+
"Content-Type": "application/json",
|
|
282
|
+
"Cache-Control": "public, max-age=300"
|
|
283
|
+
});
|
|
284
|
+
res.end(JSON.stringify(manifest, null, 2));
|
|
285
|
+
} catch (error) {
|
|
286
|
+
log(`Error generating manifest: ${error}`);
|
|
287
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
288
|
+
res.end(JSON.stringify({
|
|
289
|
+
error: "Failed to generate manifest",
|
|
290
|
+
details: error instanceof Error ? error.message : String(error)
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);
|
|
296
|
+
const fragmentIndex = JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(fragmentIndexResult.cachePath, "utf-8")));
|
|
297
|
+
const getTrackId = (renditionId) => {
|
|
298
|
+
if (renditionId === "scrub") return -1;
|
|
299
|
+
if (renditionId === "audio") {
|
|
300
|
+
for (const [trackIdStr, trackInfo] of Object.entries(fragmentIndex)) if (trackInfo.type === "audio") return Number.parseInt(trackIdStr, 10);
|
|
301
|
+
return 2;
|
|
302
|
+
}
|
|
303
|
+
for (const [trackIdStr, trackInfo] of Object.entries(fragmentIndex)) if (trackInfo.type === "video") return Number.parseInt(trackIdStr, 10);
|
|
304
|
+
return 1;
|
|
305
|
+
};
|
|
306
|
+
const initMatch = endpoint?.match(/^init\.(mp4|m4s|webm)$/);
|
|
307
|
+
if (initMatch && rendition) {
|
|
308
|
+
const extension = initMatch[1];
|
|
309
|
+
const contentType = resolveSegmentContentType(extension);
|
|
310
|
+
log(`Serving init segment (${extension}) for ${mediaPath}, rendition: ${rendition}`);
|
|
311
|
+
try {
|
|
312
|
+
const trackId = getTrackId(rendition);
|
|
313
|
+
let trackTaskResult;
|
|
314
|
+
if (trackId === -1) trackTaskResult = await assetFunctions.generateScrubTrack(options.cacheRoot, mediaPath);
|
|
315
|
+
else {
|
|
316
|
+
const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;
|
|
317
|
+
trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);
|
|
318
|
+
}
|
|
319
|
+
const track = fragmentIndex[trackId];
|
|
320
|
+
if (!track) {
|
|
321
|
+
const validTracks = Object.keys(fragmentIndex).join(", ");
|
|
322
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
323
|
+
res.end(JSON.stringify({ error: `Track ${trackId} not found (valid tracks: ${validTracks})` }));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const { offset, size } = track.initSegment;
|
|
327
|
+
log(`Init segment: offset=${offset}, size=${size}`);
|
|
328
|
+
sendByteRange(res, trackTaskResult.cachePath, offset, size, contentType);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
log(`Error serving init segment: ${error}`);
|
|
331
|
+
if (error.code === "ENOENT") {
|
|
332
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
333
|
+
res.end("File not found");
|
|
334
|
+
} else {
|
|
335
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
336
|
+
res.end(JSON.stringify({
|
|
337
|
+
error: "Failed to generate init segment",
|
|
338
|
+
details: error instanceof Error ? error.message : String(error)
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const segmentMatch = endpoint?.match(/^(\d+)\.(mp4|m4s|webm)$/);
|
|
345
|
+
if (segmentMatch?.[1] && segmentMatch?.[2] && rendition) {
|
|
346
|
+
const segmentId = Number.parseInt(segmentMatch[1], 10);
|
|
347
|
+
const extension = segmentMatch[2];
|
|
348
|
+
const includeInit = extension === "mp4";
|
|
349
|
+
log(`Serving media segment ${segmentId}.${extension} for ${mediaPath}, rendition: ${rendition}, includeInit: ${includeInit}`);
|
|
350
|
+
try {
|
|
351
|
+
const trackId = getTrackId(rendition);
|
|
352
|
+
let trackTaskResult;
|
|
353
|
+
if (trackId === -1) trackTaskResult = await assetFunctions.generateScrubTrack(options.cacheRoot, mediaPath);
|
|
354
|
+
else {
|
|
355
|
+
const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;
|
|
356
|
+
trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);
|
|
357
|
+
}
|
|
358
|
+
const track = fragmentIndex[trackId];
|
|
359
|
+
if (!track) {
|
|
360
|
+
const validTracks = Object.keys(fragmentIndex).join(", ");
|
|
361
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
362
|
+
res.end(JSON.stringify({ error: `Track ${trackId} not found (valid tracks: ${validTracks})` }));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const segmentIndex = segmentId - 1;
|
|
366
|
+
const segment = track.segments[segmentIndex];
|
|
367
|
+
if (!segment) {
|
|
368
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
369
|
+
res.end(JSON.stringify({
|
|
370
|
+
error: `Segment ${segmentId} not found`,
|
|
371
|
+
availableSegments: track.segments.length
|
|
372
|
+
}));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const contentType = resolveSegmentContentType(extension);
|
|
376
|
+
if (includeInit) {
|
|
377
|
+
const initSegment = track.initSegment;
|
|
378
|
+
log(`Media segment ${segmentId}.mp4: init(offset=${initSegment.offset}, size=${initSegment.size}) + segment(offset=${segment.offset}, size=${segment.size})`);
|
|
379
|
+
sendMultipleByteRanges(res, trackTaskResult.cachePath, [{
|
|
380
|
+
offset: initSegment.offset,
|
|
381
|
+
size: initSegment.size
|
|
382
|
+
}, {
|
|
383
|
+
offset: segment.offset,
|
|
384
|
+
size: segment.size
|
|
385
|
+
}], contentType);
|
|
386
|
+
} else {
|
|
387
|
+
const { offset, size } = segment;
|
|
388
|
+
log(`Media segment ${segmentId}.m4s: offset=${offset}, size=${size}`);
|
|
389
|
+
sendByteRange(res, trackTaskResult.cachePath, offset, size, contentType);
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
log(`Error serving media segment: ${error}`);
|
|
393
|
+
if (error.code === "ENOENT") {
|
|
394
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
395
|
+
res.end("File not found");
|
|
396
|
+
} else {
|
|
397
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
398
|
+
res.end(JSON.stringify({
|
|
399
|
+
error: "Failed to generate media segment",
|
|
400
|
+
details: error instanceof Error ? error.message : String(error)
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
407
|
+
res.end(JSON.stringify({ error: "Unknown transcode endpoint" }));
|
|
408
|
+
} catch (error) {
|
|
409
|
+
log(`Unexpected error: ${error}`);
|
|
410
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
411
|
+
res.end(JSON.stringify({
|
|
412
|
+
error: "Internal server error",
|
|
413
|
+
details: error instanceof Error ? error.message : String(error)
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
export { createJitTranscodeMiddleware, generateLocalJitManifest, getSegmentDurationsMs, isLocalHost, resolveMediaPath, sendByteRange, sendMultipleByteRanges };
|
|
420
|
+
|
|
421
|
+
//# sourceMappingURL=jitTranscodeMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jitTranscodeMiddleware.js","names":[],"sources":["../src/jitTranscodeMiddleware.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ServerResponse } from "node:http";
|
|
2
|
+
import { IncomingMessage as IncomingMessage$1, NextFunction } from "connect";
|
|
3
|
+
|
|
4
|
+
//#region src/middleware.d.ts
|
|
5
|
+
type Middleware = (req: IncomingMessage$1, res: ServerResponse, next: NextFunction) => void;
|
|
6
|
+
interface PluginOptions {
|
|
7
|
+
root: string;
|
|
8
|
+
cacheRoot: string;
|
|
9
|
+
}
|
|
10
|
+
interface AssetsDeps {
|
|
11
|
+
cacheImage: (cacheRoot: string, src: string) => Promise<any>;
|
|
12
|
+
findOrCreateCaptions: (cacheRoot: string, src: string) => Promise<any>;
|
|
13
|
+
}
|
|
14
|
+
interface FilesDeps {
|
|
15
|
+
generateTrack: (cacheRoot: string, src: string, trackUrl: string) => Promise<any>;
|
|
16
|
+
generateScrubTrack: (cacheRoot: string, src: string) => Promise<any>;
|
|
17
|
+
generateTrackFragmentIndex: (cacheRoot: string, src: string) => Promise<any>;
|
|
18
|
+
md5FilePath: (src: string) => Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
declare function createAssetsApiMiddleware(options: PluginOptions, deps: AssetsDeps): Middleware;
|
|
21
|
+
declare function createLocalFilesApiMiddleware(options: PluginOptions, deps: FilesDeps): Middleware;
|
|
22
|
+
declare function handleClearCache(req: IncomingMessage$1, res: ServerResponse, cacheRoot: string): Promise<void>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createAssetsApiMiddleware, createLocalFilesApiMiddleware, handleClearCache };
|
|
25
|
+
//# sourceMappingURL=middleware.d.ts.map
|