@editframe/vite-plugin 0.50.1 → 0.51.1
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/index.js +13 -77
- package/dist/index.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -8
- package/dist/forbidRelativePaths.js +0 -8
- package/dist/forbidRelativePaths.js.map +0 -1
- package/dist/jitTranscodeMiddleware.js +0 -421
- package/dist/jitTranscodeMiddleware.js.map +0 -1
- package/dist/middleware.js +0 -156
- package/dist/middleware.js.map +0 -1
- package/dist/sendTaskResult.js +0 -67
- package/dist/sendTaskResult.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { version } from "./version.js";
|
|
2
|
-
import { forbidRelativePaths } from "./forbidRelativePaths.js";
|
|
3
|
-
import { createJitTranscodeMiddleware } from "./jitTranscodeMiddleware.js";
|
|
4
|
-
import { createAssetsApiMiddleware, createLocalFilesApiMiddleware, handleClearCache } from "./middleware.js";
|
|
5
2
|
import { Client, createURLToken } from "@editframe/api";
|
|
6
3
|
import { cacheImage, findOrCreateCaptions, generateScrubTrack, generateTrack, generateTrackFragmentIndex, md5FilePath } from "@editframe/assets";
|
|
7
|
-
import
|
|
4
|
+
import { createEditframeRouter, createProdEfHandlers, injectApiHostScript } from "@editframe/dev-server";
|
|
8
5
|
//#region src/index.ts
|
|
9
6
|
const getEditframeClient = () => {
|
|
10
7
|
const token = process.env.EF_TOKEN;
|
|
@@ -22,90 +19,29 @@ const vitePluginEditframe = (options) => {
|
|
|
22
19
|
},
|
|
23
20
|
transformIndexHtml(html, ctx) {
|
|
24
21
|
if (!ctx.server) return html;
|
|
25
|
-
|
|
26
|
-
return html.replace(/(<head[^>]*>)/i, `$1${script}`);
|
|
22
|
+
return injectApiHostScript(html, `http://localhost:${devServerPort}`);
|
|
27
23
|
},
|
|
28
24
|
configureServer(server) {
|
|
29
25
|
server.httpServer?.on("listening", () => {
|
|
30
26
|
const addr = server.httpServer?.address();
|
|
31
27
|
if (addr && typeof addr === "object") devServerPort = addr.port;
|
|
32
28
|
});
|
|
33
|
-
|
|
34
|
-
...options,
|
|
35
|
-
handleRemoteUrls: true
|
|
36
|
-
}, {
|
|
37
|
-
generateTrack,
|
|
38
|
-
generateScrubTrack,
|
|
39
|
-
generateTrackFragmentIndex
|
|
40
|
-
}));
|
|
41
|
-
server.middlewares.use(createAssetsApiMiddleware(options, {
|
|
42
|
-
cacheImage,
|
|
43
|
-
findOrCreateCaptions
|
|
44
|
-
}));
|
|
45
|
-
server.middlewares.use(createLocalFilesApiMiddleware(options, {
|
|
29
|
+
const assetFns = {
|
|
46
30
|
generateTrack,
|
|
47
31
|
generateScrubTrack,
|
|
48
32
|
generateTrackFragmentIndex,
|
|
33
|
+
cacheImage,
|
|
34
|
+
findOrCreateCaptions,
|
|
49
35
|
md5FilePath
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
else return next();
|
|
55
|
-
log(`Handling ${req.url} at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
56
|
-
const cacheRoot = options.cacheRoot.replace("dist/", "src/");
|
|
57
|
-
const efPrefix = req.url.split("/")[1];
|
|
58
|
-
switch (efPrefix) {
|
|
59
|
-
case "@ef-clear-cache":
|
|
60
|
-
await handleClearCache(req, res, cacheRoot);
|
|
61
|
-
break;
|
|
62
|
-
case "@ef-sign-url": {
|
|
63
|
-
if (req.method !== "POST") {
|
|
64
|
-
res.writeHead(405, { Allow: "POST" });
|
|
65
|
-
res.end();
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
log("Signing URL token");
|
|
69
|
-
let body = "";
|
|
70
|
-
req.on("data", (chunk) => {
|
|
71
|
-
body += chunk.toString();
|
|
72
|
-
});
|
|
73
|
-
req.on("end", async () => {
|
|
74
|
-
try {
|
|
75
|
-
const payload = JSON.parse(body);
|
|
76
|
-
log("Token signing request payload:", payload);
|
|
77
|
-
const { url, params } = payload;
|
|
78
|
-
if (!url) {
|
|
79
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
80
|
-
res.end(JSON.stringify({ error: "URL is required" }));
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const client = getEditframeClient();
|
|
84
|
-
let fullUrl = url;
|
|
85
|
-
if (params) {
|
|
86
|
-
const urlObj = new URL(url);
|
|
87
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
88
|
-
urlObj.searchParams.set(key, String(value));
|
|
89
|
-
});
|
|
90
|
-
fullUrl = urlObj.toString();
|
|
91
|
-
}
|
|
92
|
-
log("Creating token for full URL:", fullUrl);
|
|
93
|
-
const token = await createURLToken(client, fullUrl);
|
|
94
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
95
|
-
res.end(JSON.stringify({ token }));
|
|
96
|
-
} catch (error) {
|
|
97
|
-
log(`Error signing URL token: ${error}`);
|
|
98
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
99
|
-
res.end(JSON.stringify({ error: "Failed to sign URL token" }));
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
default:
|
|
105
|
-
log(`Unknown asset type ${efPrefix}`);
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
36
|
+
};
|
|
37
|
+
const efHandlers = createProdEfHandlers({
|
|
38
|
+
createURLToken,
|
|
39
|
+
getClient: getEditframeClient
|
|
108
40
|
});
|
|
41
|
+
server.middlewares.use(createEditframeRouter({
|
|
42
|
+
...options,
|
|
43
|
+
handleRemoteUrls: true
|
|
44
|
+
}, assetFns, efHandlers));
|
|
109
45
|
}
|
|
110
46
|
};
|
|
111
47
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AAuBA,MAAM,2BAA2B;CAC/B,MAAM,QAAQ,QAAQ,IAAI;CAC1B,MAAM,SAAS,QAAQ,IAAI;AAC3B,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,4CAA4C;AAE9D,QAAO,IAAI,OAAO,OAAO,OAAO;;AAGlC,MAAa,uBAAuB,YAAwC;CAC1E,IAAI;AAEJ,QAAO;EACL,MAAM;EAEN,eAAe,gBAAqD;AAClE,kBAAe,WAAW,EAAE;AAC5B,kBAAe,OAAO,sBAAsB,KAAK,UAAU,QAAQ;;EAGrE,mBAAmB,MAAc,KAA2B;AAC1D,OAAI,CAAC,IAAI,OAAQ,QAAO;AACxB,UAAO,oBAAoB,MAAM,oBAAoB,gBAAgB;;EAGvE,gBAAgB,QAAQ;AACtB,UAAO,YAAY,GAAG,mBAAmB;IACvC,MAAM,OAAO,OAAO,YAAY,SAAS;AACzC,QAAI,QAAQ,OAAO,SAAS,SAC1B,iBAAgB,KAAK;KAEvB;GAEF,MAAM,WAAW;IACf;IACA;IACA;IACA;IACA;IACA;IACD;GAED,MAAM,aAAa,qBAAqB;IACtC;IACA,WAAW;IACZ,CAAC;AAEF,UAAO,YAAY,IACjB,sBAAsB;IAAE,GAAG;IAAS,kBAAkB;IAAM,EAAE,UAAU,WAAW,CACpF;;EAEJ"}
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.1",
|
|
4
4
|
"description": "Editframe vite plugin",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -20,16 +20,12 @@
|
|
|
20
20
|
"author": "",
|
|
21
21
|
"license": "SEE LICENSE IN LICENSE-FULL.md",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@editframe/api": "0.
|
|
24
|
-
"@editframe/assets": "0.
|
|
25
|
-
"
|
|
26
|
-
"debug": "^4.3.5",
|
|
27
|
-
"mime": "^4.0.3",
|
|
28
|
-
"odiff-bin": "^4.3.2",
|
|
23
|
+
"@editframe/api": "0.51.1",
|
|
24
|
+
"@editframe/assets": "0.51.1",
|
|
25
|
+
"@editframe/dev-server": "0.51.1",
|
|
29
26
|
"vite": "^8.0.0"
|
|
30
27
|
},
|
|
31
28
|
"devDependencies": {
|
|
32
|
-
"@types/connect": "^3.4.38",
|
|
33
29
|
"@types/dom-webcodecs": "^0.1.11",
|
|
34
30
|
"@types/node": "^22.0.0",
|
|
35
31
|
"typescript": "^5.9.3"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"forbidRelativePaths.js","names":[],"sources":["../src/forbidRelativePaths.ts"],"mappings":";AAEA,MAAa,uBAAuB,QAAyB;AAC3D,KAAI,IAAI,KAAK,SAAS,KAAK,CACzB,OAAM,IAAI,MAAM,+BAA+B"}
|
|
@@ -1,421 +0,0 @@
|
|
|
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 vite 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 vite 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 Express-compatible middleware function
|
|
243
|
-
*/
|
|
244
|
-
function createJitTranscodeMiddleware(options, assetFunctions) {
|
|
245
|
-
return async (req, res, next) => {
|
|
246
|
-
const log = debug("ef:vite-plugin: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 };
|
|
420
|
-
|
|
421
|
-
//# sourceMappingURL=jitTranscodeMiddleware.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jitTranscodeMiddleware.js","names":[],"sources":["../src/jitTranscodeMiddleware.ts"],"mappings":"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"}
|
package/dist/middleware.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { forbidRelativePaths } from "./forbidRelativePaths.js";
|
|
2
|
-
import { sendTaskResult } from "./sendTaskResult.js";
|
|
3
|
-
import debug from "debug";
|
|
4
|
-
import path, { join } from "node:path";
|
|
5
|
-
import { rm } from "node:fs/promises";
|
|
6
|
-
//#region src/middleware.ts
|
|
7
|
-
function createAssetsApiMiddleware(options, deps) {
|
|
8
|
-
const { cacheImage, findOrCreateCaptions } = deps;
|
|
9
|
-
return async (req, res, next) => {
|
|
10
|
-
const log = debug("ef:vite-plugin:assets");
|
|
11
|
-
const reqUrl = req.url || "";
|
|
12
|
-
if (!reqUrl.startsWith("/api/v1/assets/")) return next();
|
|
13
|
-
forbidRelativePaths(req);
|
|
14
|
-
const url = new URL(reqUrl, `http://${req.headers.host}`);
|
|
15
|
-
const urlPath = url.pathname;
|
|
16
|
-
const src = url.searchParams.get("src");
|
|
17
|
-
if (!src) {
|
|
18
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
19
|
-
res.end(JSON.stringify({ error: "src parameter is required" }));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const isRemote = src.startsWith("http://") || src.startsWith("https://");
|
|
23
|
-
const absolutePath = isRemote ? src : path.join(options.root, src).replace("dist/", "src/");
|
|
24
|
-
log(`Handling assets API: ${urlPath} src=${src}`);
|
|
25
|
-
try {
|
|
26
|
-
if (urlPath === "/api/v1/assets/image") {
|
|
27
|
-
if (isRemote) {
|
|
28
|
-
const response = await fetch(src);
|
|
29
|
-
if (!response.ok) {
|
|
30
|
-
res.writeHead(response.status);
|
|
31
|
-
res.end();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
35
|
-
const buffer = await response.arrayBuffer();
|
|
36
|
-
res.writeHead(200, { "Content-Type": contentType });
|
|
37
|
-
res.end(Buffer.from(buffer));
|
|
38
|
-
} else sendTaskResult(req, res, await cacheImage(options.cacheRoot, absolutePath));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (urlPath === "/api/v1/assets/captions") {
|
|
42
|
-
sendTaskResult(req, res, await findOrCreateCaptions(options.cacheRoot, absolutePath));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
46
|
-
res.end(JSON.stringify({ error: "Unknown assets endpoint" }));
|
|
47
|
-
} catch (error) {
|
|
48
|
-
log(`Error handling assets request: ${error}`);
|
|
49
|
-
if (error.code === "ENOENT") {
|
|
50
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
51
|
-
res.end("File not found");
|
|
52
|
-
} else {
|
|
53
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
54
|
-
res.end(JSON.stringify({ error: error.message }));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function createLocalFilesApiMiddleware(options, deps) {
|
|
60
|
-
const { generateTrack, generateScrubTrack, generateTrackFragmentIndex, md5FilePath } = deps;
|
|
61
|
-
return async (req, res, next) => {
|
|
62
|
-
const log = debug("ef:vite-plugin:files");
|
|
63
|
-
const reqUrl = req.url || "";
|
|
64
|
-
const url = new URL(reqUrl, `http://${req.headers.host}`);
|
|
65
|
-
const urlPath = url.pathname;
|
|
66
|
-
const src = url.searchParams.get("src");
|
|
67
|
-
if (!src || urlPath !== "/api/v1/files/index" && urlPath !== "/api/v1/files/md5" && urlPath !== "/api/v1/files/track") return next();
|
|
68
|
-
forbidRelativePaths(req);
|
|
69
|
-
const absolutePath = src.startsWith("http") ? src : path.join(options.root, src).replace("dist/", "src/");
|
|
70
|
-
log(`Handling local file API: ${urlPath} for ${absolutePath}`);
|
|
71
|
-
try {
|
|
72
|
-
if (urlPath === "/api/v1/files/index") {
|
|
73
|
-
log(`Serving track fragment index for ${absolutePath}`);
|
|
74
|
-
sendTaskResult(req, res, await generateTrackFragmentIndex(options.cacheRoot, absolutePath));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (urlPath === "/api/v1/files/md5") {
|
|
78
|
-
log(`Getting MD5 for ${absolutePath}`);
|
|
79
|
-
try {
|
|
80
|
-
const md5 = await md5FilePath(absolutePath);
|
|
81
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
82
|
-
res.end(JSON.stringify({ md5 }));
|
|
83
|
-
} catch (error) {
|
|
84
|
-
if (error.code === "ENOENT") {
|
|
85
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
86
|
-
res.end("File not found");
|
|
87
|
-
} else {
|
|
88
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
89
|
-
res.end(JSON.stringify({ error: error.message }));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (urlPath === "/api/v1/files/track") {
|
|
95
|
-
const trackIdStr = url.searchParams.get("trackId");
|
|
96
|
-
const segmentIdStr = url.searchParams.get("segmentId");
|
|
97
|
-
if (!trackIdStr) {
|
|
98
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
99
|
-
res.end(JSON.stringify({ error: "trackId parameter is required" }));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const trackId = parseInt(trackIdStr, 10);
|
|
103
|
-
if (trackId === -1) {
|
|
104
|
-
log(`Serving scrub track for ${absolutePath}`);
|
|
105
|
-
sendTaskResult(req, res, await generateScrubTrack(options.cacheRoot, absolutePath));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
log(`Serving track ${trackId} segment ${segmentIdStr || "all"} for ${absolutePath}`);
|
|
109
|
-
const trackUrl = `/@ef-track/${src}?trackId=${trackId}${segmentIdStr ? `&segmentId=${segmentIdStr}` : ""}`;
|
|
110
|
-
sendTaskResult(req, res, await generateTrack(options.cacheRoot, absolutePath, trackUrl));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
} catch (error) {
|
|
114
|
-
log(`Error handling local file request: ${error}`);
|
|
115
|
-
if (error.code === "ENOENT") {
|
|
116
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
117
|
-
res.end("File not found");
|
|
118
|
-
} else {
|
|
119
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
120
|
-
res.end(JSON.stringify({ error: error.message }));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
async function handleClearCache(req, res, cacheRoot) {
|
|
126
|
-
const log = debug("ef:vite-plugin");
|
|
127
|
-
if (req.method !== "DELETE") {
|
|
128
|
-
res.writeHead(405, { Allow: "DELETE" });
|
|
129
|
-
res.end();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
log(`Clearing cache for ${cacheRoot}`);
|
|
133
|
-
const cachePath = join(cacheRoot, ".cache");
|
|
134
|
-
const maxRetries = 3;
|
|
135
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) try {
|
|
136
|
-
await rm(cachePath, {
|
|
137
|
-
recursive: true,
|
|
138
|
-
force: true
|
|
139
|
-
});
|
|
140
|
-
break;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
if (error.code === "ENOENT") break;
|
|
143
|
-
if (error.code === "ENOTEMPTY" && attempt < maxRetries - 1) {
|
|
144
|
-
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
log(`Warning: Cache clear attempt ${attempt + 1} failed: ${error.message}`);
|
|
148
|
-
if (attempt === maxRetries - 1) log(`Cache clear failed after ${maxRetries} attempts, continuing anyway`);
|
|
149
|
-
}
|
|
150
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
151
|
-
res.end("Cache cleared");
|
|
152
|
-
}
|
|
153
|
-
//#endregion
|
|
154
|
-
export { createAssetsApiMiddleware, createLocalFilesApiMiddleware, handleClearCache };
|
|
155
|
-
|
|
156
|
-
//# sourceMappingURL=middleware.js.map
|
package/dist/middleware.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"mappings":"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"}
|
package/dist/sendTaskResult.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import debug from "debug";
|
|
2
|
-
import { createReadStream, statSync } from "node:fs";
|
|
3
|
-
import mime from "mime";
|
|
4
|
-
//#region src/sendTaskResult.ts
|
|
5
|
-
const sendTaskResult = (req, res, taskResult) => {
|
|
6
|
-
const { cachePath, md5Sum } = taskResult;
|
|
7
|
-
const filePath = cachePath;
|
|
8
|
-
const headers = { etag: md5Sum };
|
|
9
|
-
const log = debug("ef:sendfile");
|
|
10
|
-
const sendStartTime = Date.now();
|
|
11
|
-
try {
|
|
12
|
-
const stats = statSync(filePath);
|
|
13
|
-
log(`Sending file ${filePath} (size: ${stats.size} bytes)`);
|
|
14
|
-
if (req.headers.range) {
|
|
15
|
-
const [x, y] = req.headers.range.replace("bytes=", "").split("-");
|
|
16
|
-
let end = Number.parseInt(y ?? "0", 10) || stats.size - 1;
|
|
17
|
-
const start = Number.parseInt(x ?? "0", 10) || 0;
|
|
18
|
-
if (end >= stats.size) end = stats.size - 1;
|
|
19
|
-
if (start >= stats.size) {
|
|
20
|
-
log("Range start is greater than file size");
|
|
21
|
-
res.setHeader("Content-Range", `bytes */${stats.size}`);
|
|
22
|
-
res.statusCode = 416;
|
|
23
|
-
return res.end();
|
|
24
|
-
}
|
|
25
|
-
res.writeHead(206, {
|
|
26
|
-
...headers,
|
|
27
|
-
"Content-Type": mime.getType(filePath) || "text/plain",
|
|
28
|
-
"Cache-Control": "max-age=3600",
|
|
29
|
-
"Content-Range": `bytes ${start}-${end}/${stats.size}`,
|
|
30
|
-
"Content-Length": end - start + 1,
|
|
31
|
-
"Accept-Ranges": "bytes"
|
|
32
|
-
});
|
|
33
|
-
log(`Sending ${filePath} range ${start}-${end}/${stats.size}`);
|
|
34
|
-
const readStream = createReadStream(filePath, {
|
|
35
|
-
start,
|
|
36
|
-
end
|
|
37
|
-
});
|
|
38
|
-
readStream.on("end", () => {
|
|
39
|
-
log(`Range request completed in ${Date.now() - sendStartTime}ms`);
|
|
40
|
-
});
|
|
41
|
-
readStream.pipe(res);
|
|
42
|
-
} else {
|
|
43
|
-
res.writeHead(200, {
|
|
44
|
-
...headers,
|
|
45
|
-
"Content-Type": mime.getType(filePath) || "text/plain",
|
|
46
|
-
"Cache-Control": "max-age=3600",
|
|
47
|
-
"Content-Length": stats.size
|
|
48
|
-
});
|
|
49
|
-
log(`Sending full file ${filePath} (${stats.size} bytes)`);
|
|
50
|
-
const readStream = createReadStream(filePath);
|
|
51
|
-
readStream.on("end", () => {
|
|
52
|
-
log(`File send completed in ${Date.now() - sendStartTime}ms`);
|
|
53
|
-
});
|
|
54
|
-
readStream.pipe(res);
|
|
55
|
-
}
|
|
56
|
-
} catch (error) {
|
|
57
|
-
log(`Error sending file after ${Date.now() - sendStartTime}ms:`, error);
|
|
58
|
-
if (!res.headersSent) {
|
|
59
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
60
|
-
res.end(JSON.stringify({ error: error.message }));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
//#endregion
|
|
65
|
-
export { sendTaskResult };
|
|
66
|
-
|
|
67
|
-
//# sourceMappingURL=sendTaskResult.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sendTaskResult.js","names":[],"sources":["../src/sendTaskResult.ts"],"mappings":";;;;AAMA,MAAa,kBACX,KACA,KACA,eACG;CACH,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,WAAW;CACjB,MAAM,UAAU,EACd,MAAM,QACP;CACD,MAAM,MAAM,MAAM,cAAc;CAChC,MAAM,gBAAgB,KAAK,KAAK;AAChC,KAAI;EACF,MAAM,QAAQ,SAAS,SAAS;AAChC,MAAI,gBAAgB,SAAS,UAAU,MAAM,KAAK,SAAS;AAE3D,MAAI,IAAI,QAAQ,OAAO;GACrB,MAAM,CAAC,GAAG,KAAK,IAAI,QAAQ,MAAM,QAAQ,UAAU,GAAG,CAAC,MAAM,IAAI;GACjE,IAAI,MAAM,OAAO,SAAS,KAAK,KAAK,GAAG,IAAI,MAAM,OAAO;GACxD,MAAM,QAAQ,OAAO,SAAS,KAAK,KAAK,GAAG,IAAI;AAE/C,OAAI,OAAO,MAAM,KACf,OAAM,MAAM,OAAO;AAGrB,OAAI,SAAS,MAAM,MAAM;AACvB,QAAI,wCAAwC;AAC5C,QAAI,UAAU,iBAAiB,WAAW,MAAM,OAAO;AACvD,QAAI,aAAa;AACjB,WAAO,IAAI,KAAK;;AAGlB,OAAI,UAAU,KAAK;IACjB,GAAG;IACH,gBAAgB,KAAK,QAAQ,SAAS,IAAI;IAC1C,iBAAiB;IACjB,iBAAiB,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM;IAChD,kBAAkB,MAAM,QAAQ;IAChC,iBAAiB;IAClB,CAAC;AACF,OAAI,WAAW,SAAS,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM,OAAO;GAC9D,MAAM,aAAa,iBAAiB,UAAU;IAAE;IAAO;IAAK,CAAC;AAC7D,cAAW,GAAG,aAAa;AAEzB,QAAI,8BADY,KAAK,KAAK,GAAG,cACa,IAAI;KAC9C;AACF,cAAW,KAAK,IAAI;SACf;AACL,OAAI,UAAU,KAAK;IACjB,GAAG;IACH,gBAAgB,KAAK,QAAQ,SAAS,IAAI;IAC1C,iBAAiB;IACjB,kBAAkB,MAAM;IACzB,CAAC;AACF,OAAI,qBAAqB,SAAS,IAAI,MAAM,KAAK,SAAS;GAC1D,MAAM,aAAa,iBAAiB,SAAS;AAC7C,cAAW,GAAG,aAAa;AAEzB,QAAI,0BADY,KAAK,KAAK,GAAG,cACS,IAAI;KAC1C;AACF,cAAW,KAAK,IAAI;;UAEf,OAAO;AAEd,MAAI,4BADY,KAAK,KAAK,GAAG,cACW,MAAM,MAAM;AACpD,MAAI,CAAC,IAAI,aAAa;AACpB,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAQ,MAAgB,SAAS,CAAC,CAAC"}
|