@editframe/assets 0.37.3-beta → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Probe.cjs +494 -0
- package/dist/Probe.cjs.map +1 -0
- package/dist/Probe.d.cts +1135 -0
- package/dist/Probe.d.ts +26 -26
- package/dist/Probe.js +2 -0
- package/dist/Probe.js.map +1 -1
- package/dist/VideoRenderOptions.cjs +42 -0
- package/dist/VideoRenderOptions.cjs.map +1 -0
- package/dist/VideoRenderOptions.d.cts +198 -0
- package/dist/VideoRenderOptions.d.ts +48 -48
- package/dist/_virtual/rolldown_runtime.cjs +25 -0
- package/dist/generateFragmentIndex.cjs +420 -0
- package/dist/generateFragmentIndex.cjs.map +1 -0
- package/dist/generateFragmentIndex.d.cts +8 -0
- package/dist/generateFragmentIndex.js.map +1 -1
- package/dist/generateSingleTrack.cjs +82 -0
- package/dist/generateSingleTrack.cjs.map +1 -0
- package/dist/idempotentTask.cjs +152 -0
- package/dist/idempotentTask.cjs.map +1 -0
- package/dist/idempotentTask.d.cts +11 -0
- package/dist/idempotentTask.js.map +1 -1
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +11 -0
- package/dist/md5.cjs +64 -0
- package/dist/md5.cjs.map +1 -0
- package/dist/md5.d.cts +11 -0
- package/dist/md5.js +4 -2
- package/dist/md5.js.map +1 -1
- package/dist/tasks/cacheImage.cjs +28 -0
- package/dist/tasks/cacheImage.cjs.map +1 -0
- package/dist/tasks/cacheImage.d.cts +7 -0
- package/dist/tasks/findOrCreateCaptions.cjs +58 -0
- package/dist/tasks/findOrCreateCaptions.cjs.map +1 -0
- package/dist/tasks/findOrCreateCaptions.d.cts +8 -0
- package/dist/tasks/findOrCreateCaptions.js.map +1 -1
- package/dist/tasks/generateScrubTrack.cjs +107 -0
- package/dist/tasks/generateScrubTrack.cjs.map +1 -0
- package/dist/tasks/generateScrubTrack.d.cts +12 -0
- package/dist/tasks/generateScrubTrack.js.map +1 -1
- package/dist/tasks/generateTrack.cjs +34 -0
- package/dist/tasks/generateTrack.cjs.map +1 -0
- package/dist/tasks/generateTrack.d.cts +8 -0
- package/dist/tasks/generateTrackFragmentIndex.cjs +69 -0
- package/dist/tasks/generateTrackFragmentIndex.cjs.map +1 -0
- package/dist/tasks/generateTrackFragmentIndex.d.cts +9 -0
- package/dist/truncateDecimal.cjs +10 -0
- package/dist/truncateDecimal.cjs.map +1 -0
- package/package.json +19 -6
- package/tsdown.config.ts +1 -0
- package/types.json +1 -1
package/dist/md5.cjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs);
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises);
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
8
|
+
let node_crypto = require("node:crypto");
|
|
9
|
+
node_crypto = require_rolldown_runtime.__toESM(node_crypto);
|
|
10
|
+
|
|
11
|
+
//#region src/md5.ts
|
|
12
|
+
async function md5Directory(directory, spinner) {
|
|
13
|
+
const shouldEndSpinner = !spinner;
|
|
14
|
+
if (!spinner) {
|
|
15
|
+
const { default: ora } = await import("ora");
|
|
16
|
+
spinner = ora("⚡️ Calculating MD5").start();
|
|
17
|
+
}
|
|
18
|
+
spinner.suffixText = directory;
|
|
19
|
+
const files = await (0, node_fs_promises.readdir)(directory, { withFileTypes: true });
|
|
20
|
+
const hashes = await Promise.all(files.map(async (file) => {
|
|
21
|
+
const filePath = (0, node_path.join)(directory, file.name);
|
|
22
|
+
if (file.isDirectory()) return md5Directory(filePath, spinner);
|
|
23
|
+
spinner.suffixText = filePath;
|
|
24
|
+
return md5FilePath(filePath);
|
|
25
|
+
}));
|
|
26
|
+
const hash = node_crypto.default.createHash("md5");
|
|
27
|
+
for (const fileHash of hashes) hash.update(fileHash);
|
|
28
|
+
if (shouldEndSpinner) {
|
|
29
|
+
spinner.succeed("MD5 calculated");
|
|
30
|
+
spinner.suffixText = directory;
|
|
31
|
+
}
|
|
32
|
+
return addDashesToUUID(hash.digest("hex"));
|
|
33
|
+
}
|
|
34
|
+
async function md5FilePath(filePath) {
|
|
35
|
+
return md5ReadStream((0, node_fs.createReadStream)(filePath));
|
|
36
|
+
}
|
|
37
|
+
function md5ReadStream(readStream) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const hash = node_crypto.default.createHash("md5");
|
|
40
|
+
readStream.on("data", (data) => {
|
|
41
|
+
hash.update(data);
|
|
42
|
+
});
|
|
43
|
+
readStream.on("error", reject);
|
|
44
|
+
readStream.on("end", () => {
|
|
45
|
+
resolve(addDashesToUUID(hash.digest("hex")));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function md5Buffer(buffer) {
|
|
50
|
+
const hash = node_crypto.default.createHash("md5");
|
|
51
|
+
hash.update(buffer);
|
|
52
|
+
return addDashesToUUID(hash.digest("hex"));
|
|
53
|
+
}
|
|
54
|
+
function addDashesToUUID(uuidWithoutDashes) {
|
|
55
|
+
if (uuidWithoutDashes.length !== 32) throw new Error("Invalid UUID without dashes. Expected 32 characters.");
|
|
56
|
+
return uuidWithoutDashes.slice(0, 8) + "-" + uuidWithoutDashes.slice(8, 12) + "-" + uuidWithoutDashes.slice(12, 16) + "-" + uuidWithoutDashes.slice(16, 20) + "-" + uuidWithoutDashes.slice(20, 32);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
exports.md5Buffer = md5Buffer;
|
|
61
|
+
exports.md5Directory = md5Directory;
|
|
62
|
+
exports.md5FilePath = md5FilePath;
|
|
63
|
+
exports.md5ReadStream = md5ReadStream;
|
|
64
|
+
//# sourceMappingURL=md5.cjs.map
|
package/dist/md5.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md5.cjs","names":["crypto"],"sources":["../src/md5.ts"],"sourcesContent":["import { type ReadStream, createReadStream } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport crypto from \"node:crypto\";\nimport type { Ora } from \"ora\";\n\n// Recursively calculate the MD5 hash of all files in a directory\nexport async function md5Directory(directory: string, spinner?: Ora) {\n const shouldEndSpinner = !spinner;\n if (!spinner) {\n const { default: ora } = await import(\"ora\");\n spinner = ora(\"⚡️ Calculating MD5\").start();\n }\n spinner.suffixText = directory;\n const files = await readdir(directory, { withFileTypes: true });\n const hashes = await Promise.all(\n files.map(async (file) => {\n const filePath = join(directory, file.name);\n if (file.isDirectory()) {\n return md5Directory(filePath, spinner);\n }\n spinner.suffixText = filePath;\n return md5FilePath(filePath);\n }),\n );\n\n const hash = crypto.createHash(\"md5\");\n for (const fileHash of hashes) {\n hash.update(fileHash);\n }\n\n if (shouldEndSpinner) {\n spinner.succeed(\"MD5 calculated\");\n spinner.suffixText = directory;\n }\n return addDashesToUUID(hash.digest(\"hex\"));\n}\n\nexport async function md5FilePath(filePath: string) {\n const readStream = createReadStream(filePath);\n return md5ReadStream(readStream);\n}\n\nexport function md5ReadStream(readStream: ReadStream) {\n return new Promise<string>((resolve, reject) => {\n const hash = crypto.createHash(\"md5\");\n readStream.on(\"data\", (data) => {\n hash.update(data);\n });\n readStream.on(\"error\", reject);\n readStream.on(\"end\", () => {\n resolve(addDashesToUUID(hash.digest(\"hex\")));\n });\n });\n}\n\nexport function md5Buffer(buffer: Buffer) {\n const hash = crypto.createHash(\"md5\");\n hash.update(buffer);\n return addDashesToUUID(hash.digest(\"hex\"));\n}\n\nfunction addDashesToUUID(uuidWithoutDashes: string) {\n if (uuidWithoutDashes.length !== 32) {\n throw new Error(\"Invalid UUID without dashes. Expected 32 characters.\");\n }\n\n return (\n // biome-ignore lint/style/useTemplate: using a template makes a long line\n uuidWithoutDashes.slice(0, 8) +\n \"-\" +\n uuidWithoutDashes.slice(8, 12) +\n \"-\" +\n uuidWithoutDashes.slice(12, 16) +\n \"-\" +\n uuidWithoutDashes.slice(16, 20) +\n \"-\" +\n uuidWithoutDashes.slice(20, 32)\n );\n}\n"],"mappings":";;;;;;;;;;;AAOA,eAAsB,aAAa,WAAmB,SAAe;CACnE,MAAM,mBAAmB,CAAC;AAC1B,KAAI,CAAC,SAAS;EACZ,MAAM,EAAE,SAAS,QAAQ,MAAM,OAAO;AACtC,YAAU,IAAI,qBAAqB,CAAC,OAAO;;AAE7C,SAAQ,aAAa;CACrB,MAAM,QAAQ,oCAAc,WAAW,EAAE,eAAe,MAAM,CAAC;CAC/D,MAAM,SAAS,MAAM,QAAQ,IAC3B,MAAM,IAAI,OAAO,SAAS;EACxB,MAAM,+BAAgB,WAAW,KAAK,KAAK;AAC3C,MAAI,KAAK,aAAa,CACpB,QAAO,aAAa,UAAU,QAAQ;AAExC,UAAQ,aAAa;AACrB,SAAO,YAAY,SAAS;GAC5B,CACH;CAED,MAAM,OAAOA,oBAAO,WAAW,MAAM;AACrC,MAAK,MAAM,YAAY,OACrB,MAAK,OAAO,SAAS;AAGvB,KAAI,kBAAkB;AACpB,UAAQ,QAAQ,iBAAiB;AACjC,UAAQ,aAAa;;AAEvB,QAAO,gBAAgB,KAAK,OAAO,MAAM,CAAC;;AAG5C,eAAsB,YAAY,UAAkB;AAElD,QAAO,4CAD6B,SAAS,CACb;;AAGlC,SAAgB,cAAc,YAAwB;AACpD,QAAO,IAAI,SAAiB,SAAS,WAAW;EAC9C,MAAM,OAAOA,oBAAO,WAAW,MAAM;AACrC,aAAW,GAAG,SAAS,SAAS;AAC9B,QAAK,OAAO,KAAK;IACjB;AACF,aAAW,GAAG,SAAS,OAAO;AAC9B,aAAW,GAAG,aAAa;AACzB,WAAQ,gBAAgB,KAAK,OAAO,MAAM,CAAC,CAAC;IAC5C;GACF;;AAGJ,SAAgB,UAAU,QAAgB;CACxC,MAAM,OAAOA,oBAAO,WAAW,MAAM;AACrC,MAAK,OAAO,OAAO;AACnB,QAAO,gBAAgB,KAAK,OAAO,MAAM,CAAC;;AAG5C,SAAS,gBAAgB,mBAA2B;AAClD,KAAI,kBAAkB,WAAW,GAC/B,OAAM,IAAI,MAAM,uDAAuD;AAGzE,QAEE,kBAAkB,MAAM,GAAG,EAAE,GAC7B,MACA,kBAAkB,MAAM,GAAG,GAAG,GAC9B,MACA,kBAAkB,MAAM,IAAI,GAAG,GAC/B,MACA,kBAAkB,MAAM,IAAI,GAAG,GAC/B,MACA,kBAAkB,MAAM,IAAI,GAAG"}
|
package/dist/md5.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReadStream } from "node:fs";
|
|
2
|
+
import { Ora } from "ora";
|
|
3
|
+
|
|
4
|
+
//#region src/md5.d.ts
|
|
5
|
+
declare function md5Directory(directory: string, spinner?: Ora): Promise<string>;
|
|
6
|
+
declare function md5FilePath(filePath: string): Promise<string>;
|
|
7
|
+
declare function md5ReadStream(readStream: ReadStream): Promise<string>;
|
|
8
|
+
declare function md5Buffer(buffer: Buffer): string;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { md5Buffer, md5Directory, md5FilePath, md5ReadStream };
|
|
11
|
+
//# sourceMappingURL=md5.d.cts.map
|
package/dist/md5.js
CHANGED
|
@@ -2,12 +2,14 @@ import { createReadStream } from "node:fs";
|
|
|
2
2
|
import { readdir } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
|
-
import ora from "ora";
|
|
6
5
|
|
|
7
6
|
//#region src/md5.ts
|
|
8
7
|
async function md5Directory(directory, spinner) {
|
|
9
8
|
const shouldEndSpinner = !spinner;
|
|
10
|
-
|
|
9
|
+
if (!spinner) {
|
|
10
|
+
const { default: ora } = await import("ora");
|
|
11
|
+
spinner = ora("⚡️ Calculating MD5").start();
|
|
12
|
+
}
|
|
11
13
|
spinner.suffixText = directory;
|
|
12
14
|
const files = await readdir(directory, { withFileTypes: true });
|
|
13
15
|
const hashes = await Promise.all(files.map(async (file) => {
|
package/dist/md5.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"md5.js","names":[],"sources":["../src/md5.ts"],"sourcesContent":["import { type ReadStream, createReadStream } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport crypto from \"node:crypto\";\nimport
|
|
1
|
+
{"version":3,"file":"md5.js","names":[],"sources":["../src/md5.ts"],"sourcesContent":["import { type ReadStream, createReadStream } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport crypto from \"node:crypto\";\nimport type { Ora } from \"ora\";\n\n// Recursively calculate the MD5 hash of all files in a directory\nexport async function md5Directory(directory: string, spinner?: Ora) {\n const shouldEndSpinner = !spinner;\n if (!spinner) {\n const { default: ora } = await import(\"ora\");\n spinner = ora(\"⚡️ Calculating MD5\").start();\n }\n spinner.suffixText = directory;\n const files = await readdir(directory, { withFileTypes: true });\n const hashes = await Promise.all(\n files.map(async (file) => {\n const filePath = join(directory, file.name);\n if (file.isDirectory()) {\n return md5Directory(filePath, spinner);\n }\n spinner.suffixText = filePath;\n return md5FilePath(filePath);\n }),\n );\n\n const hash = crypto.createHash(\"md5\");\n for (const fileHash of hashes) {\n hash.update(fileHash);\n }\n\n if (shouldEndSpinner) {\n spinner.succeed(\"MD5 calculated\");\n spinner.suffixText = directory;\n }\n return addDashesToUUID(hash.digest(\"hex\"));\n}\n\nexport async function md5FilePath(filePath: string) {\n const readStream = createReadStream(filePath);\n return md5ReadStream(readStream);\n}\n\nexport function md5ReadStream(readStream: ReadStream) {\n return new Promise<string>((resolve, reject) => {\n const hash = crypto.createHash(\"md5\");\n readStream.on(\"data\", (data) => {\n hash.update(data);\n });\n readStream.on(\"error\", reject);\n readStream.on(\"end\", () => {\n resolve(addDashesToUUID(hash.digest(\"hex\")));\n });\n });\n}\n\nexport function md5Buffer(buffer: Buffer) {\n const hash = crypto.createHash(\"md5\");\n hash.update(buffer);\n return addDashesToUUID(hash.digest(\"hex\"));\n}\n\nfunction addDashesToUUID(uuidWithoutDashes: string) {\n if (uuidWithoutDashes.length !== 32) {\n throw new Error(\"Invalid UUID without dashes. Expected 32 characters.\");\n }\n\n return (\n // biome-ignore lint/style/useTemplate: using a template makes a long line\n uuidWithoutDashes.slice(0, 8) +\n \"-\" +\n uuidWithoutDashes.slice(8, 12) +\n \"-\" +\n uuidWithoutDashes.slice(12, 16) +\n \"-\" +\n uuidWithoutDashes.slice(16, 20) +\n \"-\" +\n uuidWithoutDashes.slice(20, 32)\n );\n}\n"],"mappings":";;;;;;AAOA,eAAsB,aAAa,WAAmB,SAAe;CACnE,MAAM,mBAAmB,CAAC;AAC1B,KAAI,CAAC,SAAS;EACZ,MAAM,EAAE,SAAS,QAAQ,MAAM,OAAO;AACtC,YAAU,IAAI,qBAAqB,CAAC,OAAO;;AAE7C,SAAQ,aAAa;CACrB,MAAM,QAAQ,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC;CAC/D,MAAM,SAAS,MAAM,QAAQ,IAC3B,MAAM,IAAI,OAAO,SAAS;EACxB,MAAM,WAAW,KAAK,WAAW,KAAK,KAAK;AAC3C,MAAI,KAAK,aAAa,CACpB,QAAO,aAAa,UAAU,QAAQ;AAExC,UAAQ,aAAa;AACrB,SAAO,YAAY,SAAS;GAC5B,CACH;CAED,MAAM,OAAO,OAAO,WAAW,MAAM;AACrC,MAAK,MAAM,YAAY,OACrB,MAAK,OAAO,SAAS;AAGvB,KAAI,kBAAkB;AACpB,UAAQ,QAAQ,iBAAiB;AACjC,UAAQ,aAAa;;AAEvB,QAAO,gBAAgB,KAAK,OAAO,MAAM,CAAC;;AAG5C,eAAsB,YAAY,UAAkB;AAElD,QAAO,cADY,iBAAiB,SAAS,CACb;;AAGlC,SAAgB,cAAc,YAAwB;AACpD,QAAO,IAAI,SAAiB,SAAS,WAAW;EAC9C,MAAM,OAAO,OAAO,WAAW,MAAM;AACrC,aAAW,GAAG,SAAS,SAAS;AAC9B,QAAK,OAAO,KAAK;IACjB;AACF,aAAW,GAAG,SAAS,OAAO;AAC9B,aAAW,GAAG,aAAa;AACzB,WAAQ,gBAAgB,KAAK,OAAO,MAAM,CAAC,CAAC;IAC5C;GACF;;AAGJ,SAAgB,UAAU,QAAgB;CACxC,MAAM,OAAO,OAAO,WAAW,MAAM;AACrC,MAAK,OAAO,OAAO;AACnB,QAAO,gBAAgB,KAAK,OAAO,MAAM,CAAC;;AAG5C,SAAS,gBAAgB,mBAA2B;AAClD,KAAI,kBAAkB,WAAW,GAC/B,OAAM,IAAI,MAAM,uDAAuD;AAGzE,QAEE,kBAAkB,MAAM,GAAG,EAAE,GAC7B,MACA,kBAAkB,MAAM,GAAG,GAAG,GAC9B,MACA,kBAAkB,MAAM,IAAI,GAAG,GAC/B,MACA,kBAAkB,MAAM,IAAI,GAAG,GAC/B,MACA,kBAAkB,MAAM,IAAI,GAAG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_idempotentTask = require('../idempotentTask.cjs');
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs);
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
7
|
+
|
|
8
|
+
//#region src/tasks/cacheImage.ts
|
|
9
|
+
const cacheImageTask = require_idempotentTask.idempotentTask({
|
|
10
|
+
label: "image",
|
|
11
|
+
filename: (absolutePath) => node_path.default.basename(absolutePath),
|
|
12
|
+
runner: async (absolutePath) => {
|
|
13
|
+
return (0, node_fs.createReadStream)(absolutePath);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const cacheImage = async (cacheRoot, absolutePath) => {
|
|
17
|
+
try {
|
|
18
|
+
return await cacheImageTask(cacheRoot, absolutePath);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(error);
|
|
21
|
+
console.trace("Error caching image", error);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
exports.cacheImage = cacheImage;
|
|
28
|
+
//# sourceMappingURL=cacheImage.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheImage.cjs","names":["idempotentTask","path"],"sources":["../../src/tasks/cacheImage.ts"],"sourcesContent":["import { idempotentTask } from \"../idempotentTask.js\";\nimport { createReadStream } from \"node:fs\";\n\nimport path from \"node:path\";\n\nconst cacheImageTask = idempotentTask({\n label: \"image\",\n filename: (absolutePath: string) => path.basename(absolutePath),\n runner: async (absolutePath) => {\n return createReadStream(absolutePath);\n },\n});\n\nexport const cacheImage = async (cacheRoot: string, absolutePath: string) => {\n try {\n return await cacheImageTask(cacheRoot, absolutePath);\n } catch (error) {\n console.error(error);\n console.trace(\"Error caching image\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;;AAKA,MAAM,iBAAiBA,sCAAe;CACpC,OAAO;CACP,WAAW,iBAAyBC,kBAAK,SAAS,aAAa;CAC/D,QAAQ,OAAO,iBAAiB;AAC9B,uCAAwB,aAAa;;CAExC,CAAC;AAEF,MAAa,aAAa,OAAO,WAAmB,iBAAyB;AAC3E,KAAI;AACF,SAAO,MAAM,eAAe,WAAW,aAAa;UAC7C,OAAO;AACd,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,uBAAuB,MAAM;AAC3C,QAAM"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TaskResult } from "../idempotentTask.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tasks/cacheImage.d.ts
|
|
4
|
+
declare const cacheImage: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { cacheImage };
|
|
7
|
+
//# sourceMappingURL=cacheImage.d.cts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_idempotentTask = require('../idempotentTask.cjs');
|
|
3
|
+
let node_child_process = require("node:child_process");
|
|
4
|
+
node_child_process = require_rolldown_runtime.__toESM(node_child_process);
|
|
5
|
+
let node_util = require("node:util");
|
|
6
|
+
node_util = require_rolldown_runtime.__toESM(node_util);
|
|
7
|
+
let debug = require("debug");
|
|
8
|
+
debug = require_rolldown_runtime.__toESM(debug);
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
11
|
+
|
|
12
|
+
//#region src/tasks/findOrCreateCaptions.ts
|
|
13
|
+
const execPromise = (0, node_util.promisify)(node_child_process.exec);
|
|
14
|
+
const log = (0, debug.default)("ef:generateCaptions");
|
|
15
|
+
const convertWhisperToEditframeFormat = (whisperData) => {
|
|
16
|
+
return {
|
|
17
|
+
segments: whisperData.segments.map((segment) => ({
|
|
18
|
+
start: Math.round(segment.start * 1e3),
|
|
19
|
+
end: Math.round(segment.end * 1e3),
|
|
20
|
+
text: segment.text.trim()
|
|
21
|
+
})),
|
|
22
|
+
word_segments: whisperData.segments.flatMap((segment) => segment.words.map((word) => ({
|
|
23
|
+
text: word.text,
|
|
24
|
+
start: Math.round(word.start * 1e3),
|
|
25
|
+
end: Math.round(word.end * 1e3)
|
|
26
|
+
})))
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const generateCaptionDataFromPath = async (absolutePath) => {
|
|
30
|
+
const command = `whisper_timestamped --language en --efficient --output_format json ${absolutePath}`;
|
|
31
|
+
log(`Running command: ${command}`);
|
|
32
|
+
const { stdout } = await execPromise(command);
|
|
33
|
+
try {
|
|
34
|
+
const captionData = convertWhisperToEditframeFormat(JSON.parse(stdout));
|
|
35
|
+
return JSON.stringify(captionData, null, 2);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
log(`Error parsing whisper output: ${error}`);
|
|
38
|
+
throw new Error(`Failed to parse whisper_timestamped output: ${error}`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const generateCaptionDataTask = require_idempotentTask.idempotentTask({
|
|
42
|
+
label: "captions",
|
|
43
|
+
filename: (absolutePath) => `${(0, node_path.basename)(absolutePath)}.captions.json`,
|
|
44
|
+
runner: generateCaptionDataFromPath
|
|
45
|
+
});
|
|
46
|
+
const findOrCreateCaptions = async (cacheRoot, absolutePath) => {
|
|
47
|
+
try {
|
|
48
|
+
return await generateCaptionDataTask(cacheRoot, absolutePath);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.trace("Error finding or creating captions", error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
exports.findOrCreateCaptions = findOrCreateCaptions;
|
|
57
|
+
exports.generateCaptionDataFromPath = generateCaptionDataFromPath;
|
|
58
|
+
//# sourceMappingURL=findOrCreateCaptions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findOrCreateCaptions.cjs","names":["exec","idempotentTask"],"sources":["../../src/tasks/findOrCreateCaptions.ts"],"sourcesContent":["import { basename } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { exec } from \"node:child_process\";\n\nimport debug from \"debug\";\n\nimport { idempotentTask } from \"../idempotentTask.js\";\n\nconst execPromise = promisify(exec);\n\nconst log = debug(\"ef:generateCaptions\");\n\ninterface WhisperWord {\n text: string;\n start: number;\n end: number;\n confidence: number;\n}\n\ninterface WhisperSegment {\n text: string;\n start: number;\n end: number;\n words: WhisperWord[];\n}\n\ninterface WhisperOutput {\n segments: WhisperSegment[];\n}\n\ninterface CaptionOutput {\n segments: Array<{\n start: number;\n end: number;\n text: string;\n }>;\n word_segments: Array<{\n text: string;\n start: number;\n end: number;\n }>;\n}\n\nconst convertWhisperToEditframeFormat = (\n whisperData: WhisperOutput,\n): CaptionOutput => {\n const segments = whisperData.segments.map((segment) => ({\n start: Math.round(segment.start * 1000), // Convert to milliseconds\n end: Math.round(segment.end * 1000),\n text: segment.text.trim(),\n }));\n\n const word_segments = whisperData.segments.flatMap((segment) =>\n segment.words.map((word) => ({\n text: word.text,\n start: Math.round(word.start * 1000), // Convert to milliseconds\n end: Math.round(word.end * 1000),\n })),\n );\n\n return { segments, word_segments };\n};\n\nexport const generateCaptionDataFromPath = async (absolutePath: string) => {\n const command = `whisper_timestamped --language en --efficient --output_format json ${absolutePath}`;\n log(`Running command: ${command}`);\n const { stdout } = await execPromise(command);\n\n try {\n const whisperData = JSON.parse(stdout) as WhisperOutput;\n const captionData = convertWhisperToEditframeFormat(whisperData);\n return JSON.stringify(captionData, null, 2);\n } catch (error) {\n log(`Error parsing whisper output: ${error}`);\n throw new Error(`Failed to parse whisper_timestamped output: ${error}`);\n }\n};\n\nconst generateCaptionDataTask = idempotentTask({\n label: \"captions\",\n filename: (absolutePath) => `${basename(absolutePath)}.captions.json`,\n runner: generateCaptionDataFromPath,\n});\n\nexport const findOrCreateCaptions = async (\n cacheRoot: string,\n absolutePath: string,\n) => {\n try {\n return await generateCaptionDataTask(cacheRoot, absolutePath);\n } catch (error) {\n console.trace(\"Error finding or creating captions\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;;;;;;AAQA,MAAM,uCAAwBA,wBAAK;AAEnC,MAAM,yBAAY,sBAAsB;AAiCxC,MAAM,mCACJ,gBACkB;AAelB,QAAO;EAAE,UAdQ,YAAY,SAAS,KAAK,aAAa;GACtD,OAAO,KAAK,MAAM,QAAQ,QAAQ,IAAK;GACvC,KAAK,KAAK,MAAM,QAAQ,MAAM,IAAK;GACnC,MAAM,QAAQ,KAAK,MAAM;GAC1B,EAAE;EAUgB,eARG,YAAY,SAAS,SAAS,YAClD,QAAQ,MAAM,KAAK,UAAU;GAC3B,MAAM,KAAK;GACX,OAAO,KAAK,MAAM,KAAK,QAAQ,IAAK;GACpC,KAAK,KAAK,MAAM,KAAK,MAAM,IAAK;GACjC,EAAE,CACJ;EAEiC;;AAGpC,MAAa,8BAA8B,OAAO,iBAAyB;CACzE,MAAM,UAAU,sEAAsE;AACtF,KAAI,oBAAoB,UAAU;CAClC,MAAM,EAAE,WAAW,MAAM,YAAY,QAAQ;AAE7C,KAAI;EAEF,MAAM,cAAc,gCADA,KAAK,MAAM,OAAO,CAC0B;AAChE,SAAO,KAAK,UAAU,aAAa,MAAM,EAAE;UACpC,OAAO;AACd,MAAI,iCAAiC,QAAQ;AAC7C,QAAM,IAAI,MAAM,+CAA+C,QAAQ;;;AAI3E,MAAM,0BAA0BC,sCAAe;CAC7C,OAAO;CACP,WAAW,iBAAiB,2BAAY,aAAa,CAAC;CACtD,QAAQ;CACT,CAAC;AAEF,MAAa,uBAAuB,OAClC,WACA,iBACG;AACH,KAAI;AACF,SAAO,MAAM,wBAAwB,WAAW,aAAa;UACtD,OAAO;AACd,UAAQ,MAAM,sCAAsC,MAAM;AAC1D,QAAM"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TaskResult } from "../idempotentTask.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tasks/findOrCreateCaptions.d.ts
|
|
4
|
+
declare const generateCaptionDataFromPath: (absolutePath: string) => Promise<string>;
|
|
5
|
+
declare const findOrCreateCaptions: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { findOrCreateCaptions, generateCaptionDataFromPath };
|
|
8
|
+
//# sourceMappingURL=findOrCreateCaptions.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findOrCreateCaptions.js","names":[],"sources":["../../src/tasks/findOrCreateCaptions.ts"],"sourcesContent":["import { basename } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { exec } from \"node:child_process\";\n\nimport debug from \"debug\";\n\nimport { idempotentTask } from \"../idempotentTask.js\";\n\nconst execPromise = promisify(exec);\n\nconst log = debug(\"ef:generateCaptions\");\n\ninterface WhisperWord {\n text: string;\n start: number;\n end: number;\n confidence: number;\n}\n\ninterface WhisperSegment {\n text: string;\n start: number;\n end: number;\n words: WhisperWord[];\n}\n\ninterface WhisperOutput {\n segments: WhisperSegment[];\n}\n\ninterface CaptionOutput {\n segments: Array<{\n start: number;\n end: number;\n text: string;\n }>;\n word_segments: Array<{\n text: string;\n start: number;\n end: number;\n }>;\n}\n\nconst convertWhisperToEditframeFormat = (whisperData: WhisperOutput): CaptionOutput => {\n const segments = whisperData.segments.map(segment => ({\n start: Math.round(segment.start * 1000), // Convert to milliseconds\n end: Math.round(segment.end * 1000),\n text: segment.text.trim(),\n }));\n\n const word_segments = whisperData.segments.flatMap(segment =>\n segment.words.map(word => ({\n text: word.text,\n start: Math.round(word.start * 1000), // Convert to milliseconds\n end: Math.round(word.end * 1000),\n }))
|
|
1
|
+
{"version":3,"file":"findOrCreateCaptions.js","names":[],"sources":["../../src/tasks/findOrCreateCaptions.ts"],"sourcesContent":["import { basename } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { exec } from \"node:child_process\";\n\nimport debug from \"debug\";\n\nimport { idempotentTask } from \"../idempotentTask.js\";\n\nconst execPromise = promisify(exec);\n\nconst log = debug(\"ef:generateCaptions\");\n\ninterface WhisperWord {\n text: string;\n start: number;\n end: number;\n confidence: number;\n}\n\ninterface WhisperSegment {\n text: string;\n start: number;\n end: number;\n words: WhisperWord[];\n}\n\ninterface WhisperOutput {\n segments: WhisperSegment[];\n}\n\ninterface CaptionOutput {\n segments: Array<{\n start: number;\n end: number;\n text: string;\n }>;\n word_segments: Array<{\n text: string;\n start: number;\n end: number;\n }>;\n}\n\nconst convertWhisperToEditframeFormat = (\n whisperData: WhisperOutput,\n): CaptionOutput => {\n const segments = whisperData.segments.map((segment) => ({\n start: Math.round(segment.start * 1000), // Convert to milliseconds\n end: Math.round(segment.end * 1000),\n text: segment.text.trim(),\n }));\n\n const word_segments = whisperData.segments.flatMap((segment) =>\n segment.words.map((word) => ({\n text: word.text,\n start: Math.round(word.start * 1000), // Convert to milliseconds\n end: Math.round(word.end * 1000),\n })),\n );\n\n return { segments, word_segments };\n};\n\nexport const generateCaptionDataFromPath = async (absolutePath: string) => {\n const command = `whisper_timestamped --language en --efficient --output_format json ${absolutePath}`;\n log(`Running command: ${command}`);\n const { stdout } = await execPromise(command);\n\n try {\n const whisperData = JSON.parse(stdout) as WhisperOutput;\n const captionData = convertWhisperToEditframeFormat(whisperData);\n return JSON.stringify(captionData, null, 2);\n } catch (error) {\n log(`Error parsing whisper output: ${error}`);\n throw new Error(`Failed to parse whisper_timestamped output: ${error}`);\n }\n};\n\nconst generateCaptionDataTask = idempotentTask({\n label: \"captions\",\n filename: (absolutePath) => `${basename(absolutePath)}.captions.json`,\n runner: generateCaptionDataFromPath,\n});\n\nexport const findOrCreateCaptions = async (\n cacheRoot: string,\n absolutePath: string,\n) => {\n try {\n return await generateCaptionDataTask(cacheRoot, absolutePath);\n } catch (error) {\n console.trace(\"Error finding or creating captions\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;AAQA,MAAM,cAAc,UAAU,KAAK;AAEnC,MAAM,MAAM,MAAM,sBAAsB;AAiCxC,MAAM,mCACJ,gBACkB;AAelB,QAAO;EAAE,UAdQ,YAAY,SAAS,KAAK,aAAa;GACtD,OAAO,KAAK,MAAM,QAAQ,QAAQ,IAAK;GACvC,KAAK,KAAK,MAAM,QAAQ,MAAM,IAAK;GACnC,MAAM,QAAQ,KAAK,MAAM;GAC1B,EAAE;EAUgB,eARG,YAAY,SAAS,SAAS,YAClD,QAAQ,MAAM,KAAK,UAAU;GAC3B,MAAM,KAAK;GACX,OAAO,KAAK,MAAM,KAAK,QAAQ,IAAK;GACpC,KAAK,KAAK,MAAM,KAAK,MAAM,IAAK;GACjC,EAAE,CACJ;EAEiC;;AAGpC,MAAa,8BAA8B,OAAO,iBAAyB;CACzE,MAAM,UAAU,sEAAsE;AACtF,KAAI,oBAAoB,UAAU;CAClC,MAAM,EAAE,WAAW,MAAM,YAAY,QAAQ;AAE7C,KAAI;EAEF,MAAM,cAAc,gCADA,KAAK,MAAM,OAAO,CAC0B;AAChE,SAAO,KAAK,UAAU,aAAa,MAAM,EAAE;UACpC,OAAO;AACd,MAAI,iCAAiC,QAAQ;AAC7C,QAAM,IAAI,MAAM,+CAA+C,QAAQ;;;AAI3E,MAAM,0BAA0B,eAAe;CAC7C,OAAO;CACP,WAAW,iBAAiB,GAAG,SAAS,aAAa,CAAC;CACtD,QAAQ;CACT,CAAC;AAEF,MAAa,uBAAuB,OAClC,WACA,iBACG;AACH,KAAI;AACF,SAAO,MAAM,wBAAwB,WAAW,aAAa;UACtD,OAAO;AACd,UAAQ,MAAM,sCAAsC,MAAM;AAC1D,QAAM"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_Probe = require('../Probe.cjs');
|
|
3
|
+
const require_generateFragmentIndex = require('../generateFragmentIndex.cjs');
|
|
4
|
+
const require_idempotentTask = require('../idempotentTask.cjs');
|
|
5
|
+
let debug = require("debug");
|
|
6
|
+
debug = require_rolldown_runtime.__toESM(debug);
|
|
7
|
+
let node_stream = require("node:stream");
|
|
8
|
+
node_stream = require_rolldown_runtime.__toESM(node_stream);
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
11
|
+
|
|
12
|
+
//#region src/tasks/generateScrubTrack.ts
|
|
13
|
+
const log = (0, debug.default)("ef:generateScrubTrack");
|
|
14
|
+
const generateScrubTrackFromPath = async (absolutePath) => {
|
|
15
|
+
log(`Generating scrub track for ${absolutePath}`);
|
|
16
|
+
const probe = await require_Probe.Probe.probePath(absolutePath);
|
|
17
|
+
if (probe.videoStreams.length === 0) throw new Error("No video stream found for scrub track generation");
|
|
18
|
+
const scrubStream = probe.createScrubTrackReadstream();
|
|
19
|
+
let startTimeOffsetMs;
|
|
20
|
+
if (probe.format.start_time && Number(probe.format.start_time) !== 0) {
|
|
21
|
+
startTimeOffsetMs = Number(probe.format.start_time) * 1e3;
|
|
22
|
+
log(`Extracted format start_time offset: ${probe.format.start_time}s (${startTimeOffsetMs}ms)`);
|
|
23
|
+
} else {
|
|
24
|
+
const videoStream = probe.videoStreams[0];
|
|
25
|
+
if (videoStream && videoStream.start_time && Number(videoStream.start_time) !== 0) {
|
|
26
|
+
startTimeOffsetMs = Number(videoStream.start_time) * 1e3;
|
|
27
|
+
log(`Extracted video stream start_time offset: ${videoStream.start_time}s (${startTimeOffsetMs}ms)`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const outputStream = new node_stream.PassThrough();
|
|
31
|
+
const indexStream = new node_stream.PassThrough();
|
|
32
|
+
scrubStream.pipe(outputStream, { end: false });
|
|
33
|
+
scrubStream.pipe(indexStream);
|
|
34
|
+
let sourceStreamEnded = false;
|
|
35
|
+
scrubStream.on("end", () => {
|
|
36
|
+
sourceStreamEnded = true;
|
|
37
|
+
});
|
|
38
|
+
scrubStream.on("error", (error) => {
|
|
39
|
+
outputStream.destroy(error);
|
|
40
|
+
indexStream.destroy(error);
|
|
41
|
+
});
|
|
42
|
+
const scrubTrackId = -1;
|
|
43
|
+
const fragmentIndexPromise = require_generateFragmentIndex.generateFragmentIndex(indexStream, startTimeOffsetMs, { 1: scrubTrackId });
|
|
44
|
+
fragmentIndexPromise.then(() => {
|
|
45
|
+
if (sourceStreamEnded) outputStream.end();
|
|
46
|
+
else scrubStream.once("end", () => {
|
|
47
|
+
outputStream.end();
|
|
48
|
+
});
|
|
49
|
+
}).catch((error) => {
|
|
50
|
+
outputStream.destroy(error);
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
stream: outputStream,
|
|
54
|
+
fragmentIndex: fragmentIndexPromise
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const generateScrubTrackTask = require_idempotentTask.idempotentTask({
|
|
58
|
+
label: "scrub-track",
|
|
59
|
+
filename: (absolutePath) => `${(0, node_path.basename)(absolutePath)}.scrub-track.mp4`,
|
|
60
|
+
runner: async (absolutePath) => {
|
|
61
|
+
const probe = await require_Probe.Probe.probePath(absolutePath);
|
|
62
|
+
if (probe.videoStreams.length === 0) throw new Error("No video stream found for scrub track generation");
|
|
63
|
+
const scrubStream = probe.createScrubTrackReadstream();
|
|
64
|
+
const finalStream = new node_stream.PassThrough();
|
|
65
|
+
let progressTimeout = null;
|
|
66
|
+
const resetProgressTimeout = () => {
|
|
67
|
+
if (progressTimeout) clearTimeout(progressTimeout);
|
|
68
|
+
progressTimeout = setTimeout(() => {
|
|
69
|
+
if (!finalStream.destroyed) {
|
|
70
|
+
console.warn(`Progress timeout triggered for scrub track - no activity for 30 seconds`);
|
|
71
|
+
finalStream.destroy(/* @__PURE__ */ new Error("Scrub track generation timeout"));
|
|
72
|
+
}
|
|
73
|
+
}, 3e4);
|
|
74
|
+
};
|
|
75
|
+
resetProgressTimeout();
|
|
76
|
+
scrubStream.on("data", () => {
|
|
77
|
+
resetProgressTimeout();
|
|
78
|
+
});
|
|
79
|
+
scrubStream.on("end", () => {
|
|
80
|
+
resetProgressTimeout();
|
|
81
|
+
finalStream.end();
|
|
82
|
+
});
|
|
83
|
+
scrubStream.on("error", (error) => {
|
|
84
|
+
if (progressTimeout) clearTimeout(progressTimeout);
|
|
85
|
+
finalStream.destroy(error);
|
|
86
|
+
});
|
|
87
|
+
scrubStream.pipe(finalStream, { end: false });
|
|
88
|
+
finalStream.on("end", () => {
|
|
89
|
+
if (progressTimeout) clearTimeout(progressTimeout);
|
|
90
|
+
});
|
|
91
|
+
return finalStream;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
const generateScrubTrack = async (cacheRoot, absolutePath) => {
|
|
95
|
+
try {
|
|
96
|
+
return await generateScrubTrackTask(cacheRoot, absolutePath);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(error);
|
|
99
|
+
console.trace("Error generating scrub track", error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
exports.generateScrubTrack = generateScrubTrack;
|
|
106
|
+
exports.generateScrubTrackFromPath = generateScrubTrackFromPath;
|
|
107
|
+
//# sourceMappingURL=generateScrubTrack.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateScrubTrack.cjs","names":["Probe","startTimeOffsetMs: number | undefined","PassThrough","generateFragmentIndex","idempotentTask","progressTimeout: NodeJS.Timeout | null"],"sources":["../../src/tasks/generateScrubTrack.ts"],"sourcesContent":["import { idempotentTask } from \"../idempotentTask.js\";\nimport debug from \"debug\";\nimport { basename } from \"node:path\";\nimport { PassThrough } from \"node:stream\";\nimport { Probe } from \"../Probe.js\";\nimport { generateFragmentIndex } from \"../generateFragmentIndex.js\";\n\nconst log = debug(\"ef:generateScrubTrack\");\n\nexport const generateScrubTrackFromPath = async (absolutePath: string) => {\n log(`Generating scrub track for ${absolutePath}`);\n\n const probe = await Probe.probePath(absolutePath);\n\n // Check if video stream exists\n if (probe.videoStreams.length === 0) {\n throw new Error(\"No video stream found for scrub track generation\");\n }\n\n // Get the scrub track stream from FFmpeg (low-res transcoded video)\n const scrubStream = probe.createScrubTrackReadstream();\n\n // Extract timing offset from probe metadata (same logic as generateTrackFragmentIndex)\n let startTimeOffsetMs: number | undefined;\n\n // First check format-level start_time\n if (probe.format.start_time && Number(probe.format.start_time) !== 0) {\n startTimeOffsetMs = Number(probe.format.start_time) * 1000;\n log(\n `Extracted format start_time offset: ${probe.format.start_time}s (${startTimeOffsetMs}ms)`,\n );\n } else {\n // Check for video stream start_time (more common)\n const videoStream = probe.videoStreams[0];\n if (\n videoStream &&\n videoStream.start_time &&\n Number(videoStream.start_time) !== 0\n ) {\n startTimeOffsetMs = Number(videoStream.start_time) * 1000;\n log(\n `Extracted video stream start_time offset: ${videoStream.start_time}s (${startTimeOffsetMs}ms)`,\n );\n }\n }\n\n // Create a PassThrough to tee the stream\n const outputStream = new PassThrough();\n const indexStream = new PassThrough();\n\n // Pipe data but DON'T end outputStream automatically - we'll control this\n scrubStream.pipe(outputStream, { end: false });\n scrubStream.pipe(indexStream);\n\n // Track when the source stream ends (but don't end output yet)\n let sourceStreamEnded = false;\n scrubStream.on(\"end\", () => {\n sourceStreamEnded = true;\n });\n\n scrubStream.on(\"error\", (error) => {\n outputStream.destroy(error);\n indexStream.destroy(error);\n });\n\n // Generate fragment index from the scrub track stream\n // Use a special track ID to identify scrub track (e.g., -1 or \"scrub\")\n // We'll use a negative track ID to distinguish from regular tracks\n const scrubTrackId = -1;\n const trackIdMapping = { 1: scrubTrackId }; // Single track 1 -> scrub track ID\n\n const fragmentIndexPromise = generateFragmentIndex(\n indexStream,\n startTimeOffsetMs,\n trackIdMapping,\n );\n\n // End outputStream only after BOTH source ends AND fragment index completes\n fragmentIndexPromise\n .then(() => {\n if (sourceStreamEnded) {\n outputStream.end();\n } else {\n // If fragment index completes first, wait for stream to end\n scrubStream.once(\"end\", () => {\n outputStream.end();\n });\n }\n })\n .catch((error) => {\n outputStream.destroy(error);\n });\n\n // Return both the stream and the index\n return {\n stream: outputStream,\n fragmentIndex: fragmentIndexPromise,\n };\n};\n\nexport const generateScrubTrackTask = idempotentTask({\n label: \"scrub-track\",\n filename: (absolutePath: string) =>\n `${basename(absolutePath)}.scrub-track.mp4`,\n runner: async (absolutePath: string) => {\n const probe = await Probe.probePath(absolutePath);\n\n if (probe.videoStreams.length === 0) {\n throw new Error(\"No video stream found for scrub track generation\");\n }\n\n // Get the scrub track stream from FFmpeg\n const scrubStream = probe.createScrubTrackReadstream();\n\n // Wrap in PassThrough with timeout handling to ensure stream completes\n const finalStream = new PassThrough();\n\n // Monitor progress and extend timeout based on actual work\n let progressTimeout: NodeJS.Timeout | null = null;\n\n const resetProgressTimeout = () => {\n if (progressTimeout) {\n clearTimeout(progressTimeout);\n }\n\n progressTimeout = setTimeout(() => {\n if (!finalStream.destroyed) {\n console.warn(\n `Progress timeout triggered for scrub track - no activity for 30 seconds`,\n );\n finalStream.destroy(new Error(\"Scrub track generation timeout\"));\n }\n }, 30000); // 30 second sliding timeout (longer for transcoding)\n };\n\n // Start the initial timeout\n resetProgressTimeout();\n\n // Monitor data flow to detect active work\n scrubStream.on(\"data\", () => {\n resetProgressTimeout(); // Reset timeout when we see data\n });\n\n scrubStream.on(\"end\", () => {\n resetProgressTimeout(); // Reset timeout when stream ends\n finalStream.end();\n });\n\n scrubStream.on(\"error\", (error) => {\n if (progressTimeout) {\n clearTimeout(progressTimeout);\n }\n finalStream.destroy(error);\n });\n\n // Pipe data through\n scrubStream.pipe(finalStream, { end: false });\n\n // Clean up timeout when stream ends\n finalStream.on(\"end\", () => {\n if (progressTimeout) {\n clearTimeout(progressTimeout);\n }\n });\n\n return finalStream;\n },\n});\n\nexport const generateScrubTrack = async (\n cacheRoot: string,\n absolutePath: string,\n) => {\n try {\n return await generateScrubTrackTask(cacheRoot, absolutePath);\n } catch (error) {\n console.error(error);\n console.trace(\"Error generating scrub track\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;;;;;;AAOA,MAAM,yBAAY,wBAAwB;AAE1C,MAAa,6BAA6B,OAAO,iBAAyB;AACxE,KAAI,8BAA8B,eAAe;CAEjD,MAAM,QAAQ,MAAMA,oBAAM,UAAU,aAAa;AAGjD,KAAI,MAAM,aAAa,WAAW,EAChC,OAAM,IAAI,MAAM,mDAAmD;CAIrE,MAAM,cAAc,MAAM,4BAA4B;CAGtD,IAAIC;AAGJ,KAAI,MAAM,OAAO,cAAc,OAAO,MAAM,OAAO,WAAW,KAAK,GAAG;AACpE,sBAAoB,OAAO,MAAM,OAAO,WAAW,GAAG;AACtD,MACE,uCAAuC,MAAM,OAAO,WAAW,KAAK,kBAAkB,KACvF;QACI;EAEL,MAAM,cAAc,MAAM,aAAa;AACvC,MACE,eACA,YAAY,cACZ,OAAO,YAAY,WAAW,KAAK,GACnC;AACA,uBAAoB,OAAO,YAAY,WAAW,GAAG;AACrD,OACE,6CAA6C,YAAY,WAAW,KAAK,kBAAkB,KAC5F;;;CAKL,MAAM,eAAe,IAAIC,yBAAa;CACtC,MAAM,cAAc,IAAIA,yBAAa;AAGrC,aAAY,KAAK,cAAc,EAAE,KAAK,OAAO,CAAC;AAC9C,aAAY,KAAK,YAAY;CAG7B,IAAI,oBAAoB;AACxB,aAAY,GAAG,aAAa;AAC1B,sBAAoB;GACpB;AAEF,aAAY,GAAG,UAAU,UAAU;AACjC,eAAa,QAAQ,MAAM;AAC3B,cAAY,QAAQ,MAAM;GAC1B;CAKF,MAAM,eAAe;CAGrB,MAAM,uBAAuBC,oDAC3B,aACA,mBAJqB,EAAE,GAAG,cAAc,CAMzC;AAGD,sBACG,WAAW;AACV,MAAI,kBACF,cAAa,KAAK;MAGlB,aAAY,KAAK,aAAa;AAC5B,gBAAa,KAAK;IAClB;GAEJ,CACD,OAAO,UAAU;AAChB,eAAa,QAAQ,MAAM;GAC3B;AAGJ,QAAO;EACL,QAAQ;EACR,eAAe;EAChB;;AAGH,MAAa,yBAAyBC,sCAAe;CACnD,OAAO;CACP,WAAW,iBACT,2BAAY,aAAa,CAAC;CAC5B,QAAQ,OAAO,iBAAyB;EACtC,MAAM,QAAQ,MAAMJ,oBAAM,UAAU,aAAa;AAEjD,MAAI,MAAM,aAAa,WAAW,EAChC,OAAM,IAAI,MAAM,mDAAmD;EAIrE,MAAM,cAAc,MAAM,4BAA4B;EAGtD,MAAM,cAAc,IAAIE,yBAAa;EAGrC,IAAIG,kBAAyC;EAE7C,MAAM,6BAA6B;AACjC,OAAI,gBACF,cAAa,gBAAgB;AAG/B,qBAAkB,iBAAiB;AACjC,QAAI,CAAC,YAAY,WAAW;AAC1B,aAAQ,KACN,0EACD;AACD,iBAAY,wBAAQ,IAAI,MAAM,iCAAiC,CAAC;;MAEjE,IAAM;;AAIX,wBAAsB;AAGtB,cAAY,GAAG,cAAc;AAC3B,yBAAsB;IACtB;AAEF,cAAY,GAAG,aAAa;AAC1B,yBAAsB;AACtB,eAAY,KAAK;IACjB;AAEF,cAAY,GAAG,UAAU,UAAU;AACjC,OAAI,gBACF,cAAa,gBAAgB;AAE/B,eAAY,QAAQ,MAAM;IAC1B;AAGF,cAAY,KAAK,aAAa,EAAE,KAAK,OAAO,CAAC;AAG7C,cAAY,GAAG,aAAa;AAC1B,OAAI,gBACF,cAAa,gBAAgB;IAE/B;AAEF,SAAO;;CAEV,CAAC;AAEF,MAAa,qBAAqB,OAChC,WACA,iBACG;AACH,KAAI;AACF,SAAO,MAAM,uBAAuB,WAAW,aAAa;UACrD,OAAO;AACd,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,gCAAgC,MAAM;AACpD,QAAM"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TrackFragmentIndex } from "../Probe.cjs";
|
|
2
|
+
import { TaskResult } from "../idempotentTask.cjs";
|
|
3
|
+
|
|
4
|
+
//#region src/tasks/generateScrubTrack.d.ts
|
|
5
|
+
declare const generateScrubTrackFromPath: (absolutePath: string) => Promise<{
|
|
6
|
+
stream: any;
|
|
7
|
+
fragmentIndex: Promise<Record<number, TrackFragmentIndex>>;
|
|
8
|
+
}>;
|
|
9
|
+
declare const generateScrubTrack: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { generateScrubTrack, generateScrubTrackFromPath };
|
|
12
|
+
//# sourceMappingURL=generateScrubTrack.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateScrubTrack.js","names":["startTimeOffsetMs: number | undefined","progressTimeout: NodeJS.Timeout | null"],"sources":["../../src/tasks/generateScrubTrack.ts"],"sourcesContent":["import { idempotentTask } from \"../idempotentTask.js\";\nimport debug from \"debug\";\nimport { basename } from \"node:path\";\nimport { PassThrough } from \"node:stream\";\nimport { Probe } from \"../Probe.js\";\nimport { generateFragmentIndex } from \"../generateFragmentIndex.js\";\n\nconst log = debug(\"ef:generateScrubTrack\");\n\nexport const generateScrubTrackFromPath = async (
|
|
1
|
+
{"version":3,"file":"generateScrubTrack.js","names":["startTimeOffsetMs: number | undefined","progressTimeout: NodeJS.Timeout | null"],"sources":["../../src/tasks/generateScrubTrack.ts"],"sourcesContent":["import { idempotentTask } from \"../idempotentTask.js\";\nimport debug from \"debug\";\nimport { basename } from \"node:path\";\nimport { PassThrough } from \"node:stream\";\nimport { Probe } from \"../Probe.js\";\nimport { generateFragmentIndex } from \"../generateFragmentIndex.js\";\n\nconst log = debug(\"ef:generateScrubTrack\");\n\nexport const generateScrubTrackFromPath = async (absolutePath: string) => {\n log(`Generating scrub track for ${absolutePath}`);\n\n const probe = await Probe.probePath(absolutePath);\n\n // Check if video stream exists\n if (probe.videoStreams.length === 0) {\n throw new Error(\"No video stream found for scrub track generation\");\n }\n\n // Get the scrub track stream from FFmpeg (low-res transcoded video)\n const scrubStream = probe.createScrubTrackReadstream();\n\n // Extract timing offset from probe metadata (same logic as generateTrackFragmentIndex)\n let startTimeOffsetMs: number | undefined;\n\n // First check format-level start_time\n if (probe.format.start_time && Number(probe.format.start_time) !== 0) {\n startTimeOffsetMs = Number(probe.format.start_time) * 1000;\n log(\n `Extracted format start_time offset: ${probe.format.start_time}s (${startTimeOffsetMs}ms)`,\n );\n } else {\n // Check for video stream start_time (more common)\n const videoStream = probe.videoStreams[0];\n if (\n videoStream &&\n videoStream.start_time &&\n Number(videoStream.start_time) !== 0\n ) {\n startTimeOffsetMs = Number(videoStream.start_time) * 1000;\n log(\n `Extracted video stream start_time offset: ${videoStream.start_time}s (${startTimeOffsetMs}ms)`,\n );\n }\n }\n\n // Create a PassThrough to tee the stream\n const outputStream = new PassThrough();\n const indexStream = new PassThrough();\n\n // Pipe data but DON'T end outputStream automatically - we'll control this\n scrubStream.pipe(outputStream, { end: false });\n scrubStream.pipe(indexStream);\n\n // Track when the source stream ends (but don't end output yet)\n let sourceStreamEnded = false;\n scrubStream.on(\"end\", () => {\n sourceStreamEnded = true;\n });\n\n scrubStream.on(\"error\", (error) => {\n outputStream.destroy(error);\n indexStream.destroy(error);\n });\n\n // Generate fragment index from the scrub track stream\n // Use a special track ID to identify scrub track (e.g., -1 or \"scrub\")\n // We'll use a negative track ID to distinguish from regular tracks\n const scrubTrackId = -1;\n const trackIdMapping = { 1: scrubTrackId }; // Single track 1 -> scrub track ID\n\n const fragmentIndexPromise = generateFragmentIndex(\n indexStream,\n startTimeOffsetMs,\n trackIdMapping,\n );\n\n // End outputStream only after BOTH source ends AND fragment index completes\n fragmentIndexPromise\n .then(() => {\n if (sourceStreamEnded) {\n outputStream.end();\n } else {\n // If fragment index completes first, wait for stream to end\n scrubStream.once(\"end\", () => {\n outputStream.end();\n });\n }\n })\n .catch((error) => {\n outputStream.destroy(error);\n });\n\n // Return both the stream and the index\n return {\n stream: outputStream,\n fragmentIndex: fragmentIndexPromise,\n };\n};\n\nexport const generateScrubTrackTask = idempotentTask({\n label: \"scrub-track\",\n filename: (absolutePath: string) =>\n `${basename(absolutePath)}.scrub-track.mp4`,\n runner: async (absolutePath: string) => {\n const probe = await Probe.probePath(absolutePath);\n\n if (probe.videoStreams.length === 0) {\n throw new Error(\"No video stream found for scrub track generation\");\n }\n\n // Get the scrub track stream from FFmpeg\n const scrubStream = probe.createScrubTrackReadstream();\n\n // Wrap in PassThrough with timeout handling to ensure stream completes\n const finalStream = new PassThrough();\n\n // Monitor progress and extend timeout based on actual work\n let progressTimeout: NodeJS.Timeout | null = null;\n\n const resetProgressTimeout = () => {\n if (progressTimeout) {\n clearTimeout(progressTimeout);\n }\n\n progressTimeout = setTimeout(() => {\n if (!finalStream.destroyed) {\n console.warn(\n `Progress timeout triggered for scrub track - no activity for 30 seconds`,\n );\n finalStream.destroy(new Error(\"Scrub track generation timeout\"));\n }\n }, 30000); // 30 second sliding timeout (longer for transcoding)\n };\n\n // Start the initial timeout\n resetProgressTimeout();\n\n // Monitor data flow to detect active work\n scrubStream.on(\"data\", () => {\n resetProgressTimeout(); // Reset timeout when we see data\n });\n\n scrubStream.on(\"end\", () => {\n resetProgressTimeout(); // Reset timeout when stream ends\n finalStream.end();\n });\n\n scrubStream.on(\"error\", (error) => {\n if (progressTimeout) {\n clearTimeout(progressTimeout);\n }\n finalStream.destroy(error);\n });\n\n // Pipe data through\n scrubStream.pipe(finalStream, { end: false });\n\n // Clean up timeout when stream ends\n finalStream.on(\"end\", () => {\n if (progressTimeout) {\n clearTimeout(progressTimeout);\n }\n });\n\n return finalStream;\n },\n});\n\nexport const generateScrubTrack = async (\n cacheRoot: string,\n absolutePath: string,\n) => {\n try {\n return await generateScrubTrackTask(cacheRoot, absolutePath);\n } catch (error) {\n console.error(error);\n console.trace(\"Error generating scrub track\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;;AAOA,MAAM,MAAM,MAAM,wBAAwB;AAE1C,MAAa,6BAA6B,OAAO,iBAAyB;AACxE,KAAI,8BAA8B,eAAe;CAEjD,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;AAGjD,KAAI,MAAM,aAAa,WAAW,EAChC,OAAM,IAAI,MAAM,mDAAmD;CAIrE,MAAM,cAAc,MAAM,4BAA4B;CAGtD,IAAIA;AAGJ,KAAI,MAAM,OAAO,cAAc,OAAO,MAAM,OAAO,WAAW,KAAK,GAAG;AACpE,sBAAoB,OAAO,MAAM,OAAO,WAAW,GAAG;AACtD,MACE,uCAAuC,MAAM,OAAO,WAAW,KAAK,kBAAkB,KACvF;QACI;EAEL,MAAM,cAAc,MAAM,aAAa;AACvC,MACE,eACA,YAAY,cACZ,OAAO,YAAY,WAAW,KAAK,GACnC;AACA,uBAAoB,OAAO,YAAY,WAAW,GAAG;AACrD,OACE,6CAA6C,YAAY,WAAW,KAAK,kBAAkB,KAC5F;;;CAKL,MAAM,eAAe,IAAI,aAAa;CACtC,MAAM,cAAc,IAAI,aAAa;AAGrC,aAAY,KAAK,cAAc,EAAE,KAAK,OAAO,CAAC;AAC9C,aAAY,KAAK,YAAY;CAG7B,IAAI,oBAAoB;AACxB,aAAY,GAAG,aAAa;AAC1B,sBAAoB;GACpB;AAEF,aAAY,GAAG,UAAU,UAAU;AACjC,eAAa,QAAQ,MAAM;AAC3B,cAAY,QAAQ,MAAM;GAC1B;CAKF,MAAM,eAAe;CAGrB,MAAM,uBAAuB,sBAC3B,aACA,mBAJqB,EAAE,GAAG,cAAc,CAMzC;AAGD,sBACG,WAAW;AACV,MAAI,kBACF,cAAa,KAAK;MAGlB,aAAY,KAAK,aAAa;AAC5B,gBAAa,KAAK;IAClB;GAEJ,CACD,OAAO,UAAU;AAChB,eAAa,QAAQ,MAAM;GAC3B;AAGJ,QAAO;EACL,QAAQ;EACR,eAAe;EAChB;;AAGH,MAAa,yBAAyB,eAAe;CACnD,OAAO;CACP,WAAW,iBACT,GAAG,SAAS,aAAa,CAAC;CAC5B,QAAQ,OAAO,iBAAyB;EACtC,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;AAEjD,MAAI,MAAM,aAAa,WAAW,EAChC,OAAM,IAAI,MAAM,mDAAmD;EAIrE,MAAM,cAAc,MAAM,4BAA4B;EAGtD,MAAM,cAAc,IAAI,aAAa;EAGrC,IAAIC,kBAAyC;EAE7C,MAAM,6BAA6B;AACjC,OAAI,gBACF,cAAa,gBAAgB;AAG/B,qBAAkB,iBAAiB;AACjC,QAAI,CAAC,YAAY,WAAW;AAC1B,aAAQ,KACN,0EACD;AACD,iBAAY,wBAAQ,IAAI,MAAM,iCAAiC,CAAC;;MAEjE,IAAM;;AAIX,wBAAsB;AAGtB,cAAY,GAAG,cAAc;AAC3B,yBAAsB;IACtB;AAEF,cAAY,GAAG,aAAa;AAC1B,yBAAsB;AACtB,eAAY,KAAK;IACjB;AAEF,cAAY,GAAG,UAAU,UAAU;AACjC,OAAI,gBACF,cAAa,gBAAgB;AAE/B,eAAY,QAAQ,MAAM;IAC1B;AAGF,cAAY,KAAK,aAAa,EAAE,KAAK,OAAO,CAAC;AAG7C,cAAY,GAAG,aAAa;AAC1B,OAAI,gBACF,cAAa,gBAAgB;IAE/B;AAEF,SAAO;;CAEV,CAAC;AAEF,MAAa,qBAAqB,OAChC,WACA,iBACG;AACH,KAAI;AACF,SAAO,MAAM,uBAAuB,WAAW,aAAa;UACrD,OAAO;AACd,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,gCAAgC,MAAM;AACpD,QAAM"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_idempotentTask = require('../idempotentTask.cjs');
|
|
3
|
+
const require_generateSingleTrack = require('../generateSingleTrack.cjs');
|
|
4
|
+
let debug = require("debug");
|
|
5
|
+
debug = require_rolldown_runtime.__toESM(debug);
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
8
|
+
|
|
9
|
+
//#region src/tasks/generateTrack.ts
|
|
10
|
+
const generateTrackFromPath = async (absolutePath, trackId) => {
|
|
11
|
+
(0, debug.default)("ef:generateTrackFragment")(`Generating track ${trackId} for ${absolutePath}`);
|
|
12
|
+
return (await require_generateSingleTrack.generateSingleTrackFromPath(absolutePath, trackId)).stream;
|
|
13
|
+
};
|
|
14
|
+
const generateTrackTask = require_idempotentTask.idempotentTask({
|
|
15
|
+
label: "track",
|
|
16
|
+
filename: (absolutePath, trackId) => `${(0, node_path.basename)(absolutePath)}.track-${trackId}.mp4`,
|
|
17
|
+
runner: generateTrackFromPath
|
|
18
|
+
});
|
|
19
|
+
const generateTrack = async (cacheRoot, absolutePath, url) => {
|
|
20
|
+
try {
|
|
21
|
+
const trackId = new URL(`http://localhost${url}`).searchParams.get("trackId");
|
|
22
|
+
if (trackId === null) throw new Error("No trackId provided. It must be specified in the query string: ?trackId=1 (for video) or ?trackId=2 (for audio)");
|
|
23
|
+
return await generateTrackTask(cacheRoot, absolutePath, Number(trackId));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(error);
|
|
26
|
+
console.trace("Error generating track", error);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
exports.generateTrack = generateTrack;
|
|
33
|
+
exports.generateTrackFromPath = generateTrackFromPath;
|
|
34
|
+
//# sourceMappingURL=generateTrack.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateTrack.cjs","names":["generateSingleTrackFromPath","idempotentTask"],"sources":["../../src/tasks/generateTrack.ts"],"sourcesContent":["import { idempotentTask } from \"../idempotentTask.js\";\nimport debug from \"debug\";\nimport { basename } from \"node:path\";\nimport { generateSingleTrackFromPath } from \"../generateSingleTrack.js\";\n\nexport const generateTrackFromPath = async (\n absolutePath: string,\n trackId: number,\n) => {\n const log = debug(\"ef:generateTrackFragment\");\n log(`Generating track ${trackId} for ${absolutePath}`);\n\n // Use the single-track implementation\n const result = await generateSingleTrackFromPath(absolutePath, trackId);\n\n // Return just the stream for compatibility with existing API\n return result.stream;\n};\n\nexport const generateTrackTask = idempotentTask({\n label: \"track\",\n filename: (absolutePath: string, trackId: number) =>\n `${basename(absolutePath)}.track-${trackId}.mp4`,\n runner: generateTrackFromPath,\n});\n\nexport const generateTrack = async (\n cacheRoot: string,\n absolutePath: string,\n url: string,\n) => {\n try {\n const trackId = new URL(`http://localhost${url}`).searchParams.get(\n \"trackId\",\n );\n if (trackId === null) {\n throw new Error(\n \"No trackId provided. It must be specified in the query string: ?trackId=1 (for video) or ?trackId=2 (for audio)\",\n );\n }\n return await generateTrackTask(cacheRoot, absolutePath, Number(trackId));\n } catch (error) {\n console.error(error);\n console.trace(\"Error generating track\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;;;AAKA,MAAa,wBAAwB,OACnC,cACA,YACG;AAEH,oBADkB,2BAA2B,CACzC,oBAAoB,QAAQ,OAAO,eAAe;AAMtD,SAHe,MAAMA,wDAA4B,cAAc,QAAQ,EAGzD;;AAGhB,MAAa,oBAAoBC,sCAAe;CAC9C,OAAO;CACP,WAAW,cAAsB,YAC/B,2BAAY,aAAa,CAAC,SAAS,QAAQ;CAC7C,QAAQ;CACT,CAAC;AAEF,MAAa,gBAAgB,OAC3B,WACA,cACA,QACG;AACH,KAAI;EACF,MAAM,UAAU,IAAI,IAAI,mBAAmB,MAAM,CAAC,aAAa,IAC7D,UACD;AACD,MAAI,YAAY,KACd,OAAM,IAAI,MACR,kHACD;AAEH,SAAO,MAAM,kBAAkB,WAAW,cAAc,OAAO,QAAQ,CAAC;UACjE,OAAO;AACd,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,0BAA0B,MAAM;AAC9C,QAAM"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TaskResult } from "../idempotentTask.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tasks/generateTrack.d.ts
|
|
4
|
+
declare const generateTrackFromPath: (absolutePath: string, trackId: number) => Promise<any>;
|
|
5
|
+
declare const generateTrack: (cacheRoot: string, absolutePath: string, url: string) => Promise<TaskResult>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { generateTrack, generateTrackFromPath };
|
|
8
|
+
//# sourceMappingURL=generateTrack.d.cts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_Probe = require('../Probe.cjs');
|
|
3
|
+
const require_generateFragmentIndex = require('../generateFragmentIndex.cjs');
|
|
4
|
+
const require_idempotentTask = require('../idempotentTask.cjs');
|
|
5
|
+
let debug = require("debug");
|
|
6
|
+
debug = require_rolldown_runtime.__toESM(debug);
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
9
|
+
|
|
10
|
+
//#region src/tasks/generateTrackFragmentIndex.ts
|
|
11
|
+
const generateTrackFragmentIndexFromPath = async (absolutePath) => {
|
|
12
|
+
const log = (0, debug.default)("ef:generateTrackFragment");
|
|
13
|
+
const probe = await require_Probe.Probe.probePath(absolutePath);
|
|
14
|
+
let startTimeOffsetMs;
|
|
15
|
+
if (probe.format.start_time && Number(probe.format.start_time) !== 0) {
|
|
16
|
+
startTimeOffsetMs = Number(probe.format.start_time) * 1e3;
|
|
17
|
+
log(`Extracted format start_time offset: ${probe.format.start_time}s (${startTimeOffsetMs}ms)`);
|
|
18
|
+
} else {
|
|
19
|
+
const videoStream = probe.streams.find((stream) => stream.codec_type === "video");
|
|
20
|
+
if (videoStream && videoStream.start_time && Number(videoStream.start_time) !== 0) {
|
|
21
|
+
startTimeOffsetMs = Number(videoStream.start_time) * 1e3;
|
|
22
|
+
log(`Extracted video stream start_time offset: ${videoStream.start_time}s (${startTimeOffsetMs}ms)`);
|
|
23
|
+
} else log("No format/stream timing offset found - will detect from composition time");
|
|
24
|
+
}
|
|
25
|
+
log(`Generating track fragment index for ${absolutePath} using single-track approach`);
|
|
26
|
+
const trackFragmentIndexes = {};
|
|
27
|
+
for (let streamIndex = 0; streamIndex < probe.streams.length; streamIndex++) {
|
|
28
|
+
const stream = probe.streams[streamIndex];
|
|
29
|
+
if (stream.codec_type !== "audio" && stream.codec_type !== "video") continue;
|
|
30
|
+
const trackId = streamIndex + 1;
|
|
31
|
+
log(`Processing track ${trackId} (${stream.codec_type})`);
|
|
32
|
+
const singleTrackIndexes = await require_generateFragmentIndex.generateFragmentIndex(probe.createTrackReadstream(streamIndex), startTimeOffsetMs, { 0: trackId });
|
|
33
|
+
Object.assign(trackFragmentIndexes, singleTrackIndexes);
|
|
34
|
+
}
|
|
35
|
+
if (probe.videoStreams.length > 0) try {
|
|
36
|
+
log("Generating scrub track fragment index");
|
|
37
|
+
const scrubStream = probe.createScrubTrackReadstream();
|
|
38
|
+
const scrubTrackId = -1;
|
|
39
|
+
const scrubFragmentIndex = await require_generateFragmentIndex.generateFragmentIndex(scrubStream, startTimeOffsetMs, { 0: scrubTrackId });
|
|
40
|
+
if (scrubFragmentIndex[scrubTrackId]) {
|
|
41
|
+
trackFragmentIndexes[scrubTrackId] = scrubFragmentIndex[scrubTrackId];
|
|
42
|
+
log("Scrub track fragment index generated successfully");
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
log(`Failed to generate scrub track fragment index: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
return trackFragmentIndexes;
|
|
48
|
+
};
|
|
49
|
+
const generateTrackFragmentIndexTask = require_idempotentTask.idempotentTask({
|
|
50
|
+
label: "trackFragmentIndex",
|
|
51
|
+
filename: (absolutePath) => `${(0, node_path.basename)(absolutePath)}.tracks.json`,
|
|
52
|
+
runner: async (absolutePath) => {
|
|
53
|
+
const index = await generateTrackFragmentIndexFromPath(absolutePath);
|
|
54
|
+
return JSON.stringify(index, null, 2);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const generateTrackFragmentIndex = async (cacheRoot, absolutePath) => {
|
|
58
|
+
try {
|
|
59
|
+
return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.trace("Error generating track fragment index", error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
exports.generateTrackFragmentIndex = generateTrackFragmentIndex;
|
|
68
|
+
exports.generateTrackFragmentIndexFromPath = generateTrackFragmentIndexFromPath;
|
|
69
|
+
//# sourceMappingURL=generateTrackFragmentIndex.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateTrackFragmentIndex.cjs","names":["Probe","startTimeOffsetMs: number | undefined","trackFragmentIndexes: Record<number, TrackFragmentIndex>","generateFragmentIndex","idempotentTask"],"sources":["../../src/tasks/generateTrackFragmentIndex.ts"],"sourcesContent":["import { idempotentTask } from \"../idempotentTask.js\";\nimport debug from \"debug\";\nimport { basename } from \"node:path\";\nimport { Probe } from \"../Probe.js\";\nimport { generateFragmentIndex } from \"../generateFragmentIndex.js\";\nimport type { TrackFragmentIndex } from \"../Probe.js\";\n\nexport const generateTrackFragmentIndexFromPath = async (\n absolutePath: string,\n) => {\n const log = debug(\"ef:generateTrackFragment\");\n const probe = await Probe.probePath(absolutePath);\n\n // Extract timing offset from probe metadata (same logic as processISOBMFF.ts)\n let startTimeOffsetMs: number | undefined;\n\n // First check format-level start_time\n if (probe.format.start_time && Number(probe.format.start_time) !== 0) {\n startTimeOffsetMs = Number(probe.format.start_time) * 1000;\n log(\n `Extracted format start_time offset: ${probe.format.start_time}s (${startTimeOffsetMs}ms)`,\n );\n } else {\n // Check for video stream start_time (more common)\n const videoStream = probe.streams.find(\n (stream) => stream.codec_type === \"video\",\n );\n if (\n videoStream &&\n videoStream.start_time &&\n Number(videoStream.start_time) !== 0\n ) {\n startTimeOffsetMs = Number(videoStream.start_time) * 1000;\n log(\n `Extracted video stream start_time offset: ${videoStream.start_time}s (${startTimeOffsetMs}ms)`,\n );\n } else {\n log(\n \"No format/stream timing offset found - will detect from composition time\",\n );\n }\n }\n\n log(\n `Generating track fragment index for ${absolutePath} using single-track approach`,\n );\n\n // FIXED: Generate fragment indexes from individual single-track files\n // This ensures byte offsets match the actual single-track files that clients will request\n const trackFragmentIndexes: Record<number, TrackFragmentIndex> = {};\n\n // Process each audio/video stream as a separate track\n for (let streamIndex = 0; streamIndex < probe.streams.length; streamIndex++) {\n const stream = probe.streams[streamIndex]!;\n\n // Only process audio and video streams\n if (stream.codec_type !== \"audio\" && stream.codec_type !== \"video\") {\n continue;\n }\n\n const trackId = streamIndex + 1; // Convert to 1-based track ID\n log(`Processing track ${trackId} (${stream.codec_type})`);\n\n // Generate single-track file and its fragment index\n const trackStream = probe.createTrackReadstream(streamIndex);\n const trackIdMapping = { 0: trackId }; // Map single-track stream index 0 to original track ID\n\n const singleTrackIndexes = await generateFragmentIndex(\n trackStream,\n startTimeOffsetMs,\n trackIdMapping,\n );\n\n // Merge the single-track index into the combined result\n Object.assign(trackFragmentIndexes, singleTrackIndexes);\n }\n\n // Generate scrub track fragment index if video stream exists\n if (probe.videoStreams.length > 0) {\n try {\n log(\"Generating scrub track fragment index\");\n // Generate scrub track stream and fragment index directly (don't generate full file)\n const scrubStream = probe.createScrubTrackReadstream();\n const scrubTrackId = -1;\n const trackIdMapping = { 0: scrubTrackId }; // Map single-track stream index 0 to scrub track ID -1\n\n const scrubFragmentIndex = await generateFragmentIndex(\n scrubStream,\n startTimeOffsetMs,\n trackIdMapping,\n );\n\n if (scrubFragmentIndex[scrubTrackId]) {\n trackFragmentIndexes[scrubTrackId] = scrubFragmentIndex[scrubTrackId]!;\n log(\"Scrub track fragment index generated successfully\");\n }\n } catch (error) {\n log(`Failed to generate scrub track fragment index: ${error}`);\n // Don't fail the entire operation if scrub track generation fails\n }\n }\n\n return trackFragmentIndexes;\n};\n\nconst generateTrackFragmentIndexTask = idempotentTask({\n label: \"trackFragmentIndex\",\n filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,\n runner: async (absolutePath: string) => {\n const index = await generateTrackFragmentIndexFromPath(absolutePath);\n return JSON.stringify(index, null, 2);\n },\n});\n\nexport const generateTrackFragmentIndex = async (\n cacheRoot: string,\n absolutePath: string,\n) => {\n try {\n return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);\n } catch (error) {\n console.trace(\"Error generating track fragment index\", error);\n throw error;\n }\n};\n"],"mappings":";;;;;;;;;;AAOA,MAAa,qCAAqC,OAChD,iBACG;CACH,MAAM,yBAAY,2BAA2B;CAC7C,MAAM,QAAQ,MAAMA,oBAAM,UAAU,aAAa;CAGjD,IAAIC;AAGJ,KAAI,MAAM,OAAO,cAAc,OAAO,MAAM,OAAO,WAAW,KAAK,GAAG;AACpE,sBAAoB,OAAO,MAAM,OAAO,WAAW,GAAG;AACtD,MACE,uCAAuC,MAAM,OAAO,WAAW,KAAK,kBAAkB,KACvF;QACI;EAEL,MAAM,cAAc,MAAM,QAAQ,MAC/B,WAAW,OAAO,eAAe,QACnC;AACD,MACE,eACA,YAAY,cACZ,OAAO,YAAY,WAAW,KAAK,GACnC;AACA,uBAAoB,OAAO,YAAY,WAAW,GAAG;AACrD,OACE,6CAA6C,YAAY,WAAW,KAAK,kBAAkB,KAC5F;QAED,KACE,2EACD;;AAIL,KACE,uCAAuC,aAAa,8BACrD;CAID,MAAMC,uBAA2D,EAAE;AAGnE,MAAK,IAAI,cAAc,GAAG,cAAc,MAAM,QAAQ,QAAQ,eAAe;EAC3E,MAAM,SAAS,MAAM,QAAQ;AAG7B,MAAI,OAAO,eAAe,WAAW,OAAO,eAAe,QACzD;EAGF,MAAM,UAAU,cAAc;AAC9B,MAAI,oBAAoB,QAAQ,IAAI,OAAO,WAAW,GAAG;EAMzD,MAAM,qBAAqB,MAAMC,oDAHb,MAAM,sBAAsB,YAAY,EAK1D,mBAJqB,EAAE,GAAG,SAAS,CAMpC;AAGD,SAAO,OAAO,sBAAsB,mBAAmB;;AAIzD,KAAI,MAAM,aAAa,SAAS,EAC9B,KAAI;AACF,MAAI,wCAAwC;EAE5C,MAAM,cAAc,MAAM,4BAA4B;EACtD,MAAM,eAAe;EAGrB,MAAM,qBAAqB,MAAMA,oDAC/B,aACA,mBAJqB,EAAE,GAAG,cAAc,CAMzC;AAED,MAAI,mBAAmB,eAAe;AACpC,wBAAqB,gBAAgB,mBAAmB;AACxD,OAAI,oDAAoD;;UAEnD,OAAO;AACd,MAAI,kDAAkD,QAAQ;;AAKlE,QAAO;;AAGT,MAAM,iCAAiCC,sCAAe;CACpD,OAAO;CACP,WAAW,iBAAiB,2BAAY,aAAa,CAAC;CACtD,QAAQ,OAAO,iBAAyB;EACtC,MAAM,QAAQ,MAAM,mCAAmC,aAAa;AACpE,SAAO,KAAK,UAAU,OAAO,MAAM,EAAE;;CAExC,CAAC;AAEF,MAAa,6BAA6B,OACxC,WACA,iBACG;AACH,KAAI;AACF,SAAO,MAAM,+BAA+B,WAAW,aAAa;UAC7D,OAAO;AACd,UAAQ,MAAM,yCAAyC,MAAM;AAC7D,QAAM"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TrackFragmentIndex } from "../Probe.cjs";
|
|
2
|
+
import { TaskResult } from "../idempotentTask.cjs";
|
|
3
|
+
|
|
4
|
+
//#region src/tasks/generateTrackFragmentIndex.d.ts
|
|
5
|
+
declare const generateTrackFragmentIndexFromPath: (absolutePath: string) => Promise<Record<number, TrackFragmentIndex>>;
|
|
6
|
+
declare const generateTrackFragmentIndex: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { generateTrackFragmentIndex, generateTrackFragmentIndexFromPath };
|
|
9
|
+
//# sourceMappingURL=generateTrackFragmentIndex.d.cts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/truncateDecimal.ts
|
|
3
|
+
function truncateDecimal(num, decimals) {
|
|
4
|
+
const factor = 10 ** decimals;
|
|
5
|
+
return Math.trunc(num * factor) / factor;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
exports.truncateDecimal = truncateDecimal;
|
|
10
|
+
//# sourceMappingURL=truncateDecimal.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncateDecimal.cjs","names":[],"sources":["../src/truncateDecimal.ts"],"sourcesContent":["// Helper to calculate AAC frame-aligned segment durations for audio\nexport function truncateDecimal(num: number, decimals: number) {\n const factor = 10 ** decimals;\n return Math.trunc(num * factor) / factor;\n}\n"],"mappings":";;AACA,SAAgB,gBAAgB,KAAa,UAAkB;CAC7D,MAAM,SAAS,MAAM;AACrB,QAAO,KAAK,MAAM,MAAM,OAAO,GAAG"}
|