@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 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 debug from "debug";
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
- const script = `<script>window.__EDITFRAME__ = Object.assign({}, window.__EDITFRAME__, { apiHost: "http://localhost:${devServerPort}" });<\/script>`;
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
- server.middlewares.use(createJitTranscodeMiddleware({
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
- server.middlewares.use(async (req, res, next) => {
52
- const log = debug("ef:vite-plugin");
53
- if (req.url?.startsWith("/@ef")) forbidRelativePaths(req);
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":"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"}
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
@@ -1,5 +1,5 @@
1
1
  //#region src/version.ts
2
- const version = "0.50.1";
2
+ const version = "0.51.1";
3
3
  //#endregion
4
4
  export { version };
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/vite-plugin",
3
- "version": "0.50.1",
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.50.1",
24
- "@editframe/assets": "0.50.1",
25
- "connect": "^3.7.0",
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,8 +0,0 @@
1
- //#region src/forbidRelativePaths.ts
2
- const forbidRelativePaths = (req) => {
3
- if (req.url?.includes("..")) throw new Error("Relative paths are forbidden");
4
- };
5
- //#endregion
6
- export { forbidRelativePaths };
7
-
8
- //# sourceMappingURL=forbidRelativePaths.js.map
@@ -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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"}
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"mappings":"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"}
@@ -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"}