@editframe/assets 0.49.5 → 0.49.7

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.
@@ -1,11 +1,10 @@
1
1
  import { Probe } from "../Probe.js";
2
2
  import { generateFragmentIndex } from "../generateFragmentIndex.js";
3
3
  import { idempotentTask } from "../idempotentTask.js";
4
- import { generateWebmSegmentIndex } from "../generateWebmSegmentIndex.js";
5
- import { createReadStream } from "node:fs";
4
+ import { generateWebmSegmentIndex, patchWebmForSegmentedServing } from "../generateWebmSegmentIndex.js";
5
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
6
6
  import debug from "debug";
7
- import { PassThrough } from "node:stream";
8
- import { mkdtemp, rm } from "node:fs/promises";
7
+ import { PassThrough, Readable } from "node:stream";
9
8
  import { tmpdir } from "node:os";
10
9
  import { basename, join } from "node:path";
11
10
  //#region src/tasks/generateScrubTrack.ts
@@ -19,30 +18,26 @@ const generateScrubTrackFromPath = async (absolutePath) => {
19
18
  log("VP9 alpha source: generating WebM scrub track");
20
19
  const tmpDir = await mkdtemp(join(tmpdir(), "ef-scrub-webm-"));
21
20
  const tmpPath = join(tmpDir, "scrub.webm");
22
- await probe.transcodeWebmScrubToFile(tmpPath);
23
- const scrubTrack = (await generateWebmSegmentIndex(tmpPath, startTimeOffsetMs))[1];
24
- if (!scrubTrack) throw new Error("No track 1 in VP9 scrub WebM segment index");
25
- const fragmentIndex = { [-1]: {
26
- ...scrubTrack,
27
- track: -1
28
- } };
29
- const outputStream = createReadStream(tmpPath);
30
- outputStream.on("close", () => {
31
- rm(tmpDir, {
32
- recursive: true,
33
- force: true
34
- }).catch(() => {});
35
- });
36
- outputStream.on("error", () => {
21
+ try {
22
+ await probe.transcodeWebmScrubToFile(tmpPath);
23
+ const scrubTrack = (await generateWebmSegmentIndex(tmpPath, startTimeOffsetMs))[1];
24
+ if (!scrubTrack) throw new Error("No track 1 in VP9 scrub WebM segment index");
25
+ const fragmentIndex = { [-1]: {
26
+ ...scrubTrack,
27
+ track: -1
28
+ } };
29
+ const buf = await readFile(tmpPath);
30
+ patchWebmForSegmentedServing(buf);
31
+ return {
32
+ stream: Readable.from(buf),
33
+ fragmentIndex: Promise.resolve(fragmentIndex)
34
+ };
35
+ } finally {
37
36
  rm(tmpDir, {
38
37
  recursive: true,
39
38
  force: true
40
39
  }).catch(() => {});
41
- });
42
- return {
43
- stream: outputStream,
44
- fragmentIndex: Promise.resolve(fragmentIndex)
45
- };
40
+ }
46
41
  }
47
42
  const scrubStream = probe.createScrubTrackReadstream();
48
43
  const outputStream = new PassThrough();
@@ -84,15 +79,17 @@ const generateScrubTrackTask = idempotentTask({
84
79
  log("VP9 alpha source: generating WebM scrub track for cache");
85
80
  const tmpDir = await mkdtemp(join(tmpdir(), "ef-scrub-webm-"));
86
81
  const tmpPath = join(tmpDir, "scrub.webm");
87
- await probe.transcodeWebmScrubToFile(tmpPath);
88
- const fileStream = createReadStream(tmpPath);
89
- fileStream.on("close", () => {
82
+ try {
83
+ await probe.transcodeWebmScrubToFile(tmpPath);
84
+ const buf = await readFile(tmpPath);
85
+ patchWebmForSegmentedServing(buf);
86
+ return Readable.from(buf);
87
+ } finally {
90
88
  rm(tmpDir, {
91
89
  recursive: true,
92
90
  force: true
93
91
  }).catch(() => {});
94
- });
95
- return fileStream;
92
+ }
96
93
  }
97
94
  const scrubStream = probe.createScrubTrackReadstream();
98
95
  const finalStream = new PassThrough();
@@ -1 +1 @@
1
- {"version":3,"file":"generateScrubTrack.js","names":[],"sources":["../../src/tasks/generateScrubTrack.ts"],"mappings":";;;;;;;;;;;AAYA,MAAM,MAAM,MAAM,wBAAwB;AAE1C,MAAa,6BAA6B,OAAO,iBAAyB;AACxE,KAAI,8BAA8B,eAAe;CAEjD,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;AAEjD,KAAI,MAAM,aAAa,WAAW,EAChC,OAAM,IAAI,MAAM,mDAAmD;CAGrE,MAAM,oBAAoB,MAAM;AAEhC,KAAI,MAAM,eAAe;AACvB,MAAI,gDAAgD;EAEpD,MAAM,SAAS,MAAM,QAAQ,KAAK,QAAQ,EAAE,iBAAiB,CAAC;EAC9D,MAAM,UAAU,KAAK,QAAQ,aAAa;AAE1C,QAAM,MAAM,yBAAyB,QAAQ;EAG7C,MAAM,cADW,MAAM,yBAAyB,SAAS,kBAAkB,EAC/C;AAC5B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,6CAA6C;EAC9E,MAAM,gBAAoD,GACvD,KAAK;GAAE,GAAG;GAAY,OAAO;GAAI,EACnC;EAED,MAAM,eAAe,iBAAiB,QAAQ;AAC9C,eAAa,GAAG,eAAe;AAC7B,MAAG,QAAQ;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC,CAAC,YAAY,GAAG;IAC5D;AACF,eAAa,GAAG,eAAe;AAC7B,MAAG,QAAQ;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC,CAAC,YAAY,GAAG;IAC5D;AAEF,SAAO;GACL,QAAQ;GACR,eAAe,QAAQ,QAAQ,cAAc;GAC9C;;CAIH,MAAM,cAAc,MAAM,4BAA4B;CAEtD,MAAM,eAAe,IAAI,aAAa;CACtC,MAAM,cAAc,IAAI,aAAa;AAErC,aAAY,KAAK,cAAc,EAAE,KAAK,OAAO,CAAC;AAC9C,aAAY,KAAK,YAAY;CAE7B,IAAI,oBAAoB;AACxB,aAAY,GAAG,aAAa;AAC1B,sBAAoB;GACpB;AAEF,aAAY,GAAG,UAAU,UAAU;AACjC,eAAa,QAAQ,MAAM;AAC3B,cAAY,QAAQ,MAAM;GAC1B;CAIF,MAAM,uBAAuB,sBAC3B,aACA,mBAJqB,EAAE,GAAG,IAAa,CAMxC;AAED,sBACG,WAAW;AACV,MAAI,kBACF,cAAa,KAAK;MAElB,aAAY,KAAK,aAAa;AAC5B,gBAAa,KAAK;IAClB;GAEJ,CACD,OAAO,UAAU;AAChB,eAAa,QAAQ,MAAM;GAC3B;AAEJ,QAAO;EAAE,QAAQ;EAAc,eAAe;EAAsB;;AAGtE,MAAa,yBAAyB,eAAe;CACnD,OAAO;CACP,WAAW,iBAAyB;EAElC,MAAM,MAAM,aAAa,SAAS,QAAQ,GAAG,SAAS;AACtD,SAAO,GAAG,SAAS,aAAa,CAAC,eAAe;;CAElD,QAAQ,OAAO,iBAAyB;EACtC,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;AAEjD,MAAI,MAAM,aAAa,WAAW,EAChC,OAAM,IAAI,MAAM,mDAAmD;AAGrE,MAAI,MAAM,eAAe;AACvB,OAAI,0DAA0D;GAE9D,MAAM,SAAS,MAAM,QAAQ,KAAK,QAAQ,EAAE,iBAAiB,CAAC;GAC9D,MAAM,UAAU,KAAK,QAAQ,aAAa;AAE1C,SAAM,MAAM,yBAAyB,QAAQ;GAE7C,MAAM,aAAa,iBAAiB,QAAQ;AAC5C,cAAW,GAAG,eAAe;AAC3B,OAAG,QAAQ;KAAE,WAAW;KAAM,OAAO;KAAM,CAAC,CAAC,YAAY,GAAG;KAC5D;AACF,UAAO;;EAIT,MAAM,cAAc,MAAM,4BAA4B;EACtD,MAAM,cAAc,IAAI,aAAa;EAErC,IAAI,kBAAyC;EAE7C,MAAM,6BAA6B;AACjC,OAAI,gBACF,cAAa,gBAAgB;AAG/B,qBAAkB,iBAAiB;AACjC,QAAI,CAAC,YAAY,WAAW;AAC1B,aAAQ,KAAK,0EAA0E;AACvF,iBAAY,wBAAQ,IAAI,MAAM,iCAAiC,CAAC;;MAEjE,IAAM;;AAGX,wBAAsB;AAEtB,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;AAEF,cAAY,KAAK,aAAa,EAAE,KAAK,OAAO,CAAC;AAE7C,cAAY,GAAG,aAAa;AAC1B,OAAI,gBACF,cAAa,gBAAgB;IAE/B;AAEF,SAAO;;CAEV,CAAC;AAEF,MAAa,qBAAqB,OAAO,WAAmB,iBAAyB;AACnF,KAAI;AACF,SAAO,MAAM,uBAAuB,WAAW,aAAa;UACrD,OAAO;AACd,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,gCAAgC,MAAM;AACpD,QAAM"}
1
+ {"version":3,"file":"generateScrubTrack.js","names":[],"sources":["../../src/tasks/generateScrubTrack.ts"],"mappings":"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"}
@@ -3,28 +3,45 @@ const require_Probe = require("../Probe.cjs");
3
3
  const require_idempotentTask = require("../idempotentTask.cjs");
4
4
  const require_generateWebmSegmentIndex = require("../generateWebmSegmentIndex.cjs");
5
5
  const require_generateSingleTrack = require("../generateSingleTrack.cjs");
6
+ let node_fs_promises = require("node:fs/promises");
6
7
  let debug = require("debug");
7
8
  debug = require_runtime.__toESM(debug);
8
9
  let node_stream = require("node:stream");
9
- let node_fs_promises = require("node:fs/promises");
10
+ let node_os = require("node:os");
10
11
  let node_path = require("node:path");
11
12
  //#region src/tasks/generateTrack.ts
13
+ /** Target segment duration for keyframe-aligned WebM. Each cluster in the
14
+ * output is independently decodable, so the segment carving in
15
+ * generateWebmSegmentIndex can produce safe byte-range slices. */
16
+ const VP9_ALPHA_SEGMENT_MS = 2e3;
12
17
  const generateTrackFromPath = async (absolutePath, trackId) => {
13
18
  const log = (0, debug.default)("ef:generateTrackFragment");
14
19
  log(`Generating track ${trackId} for ${absolutePath}`);
15
- if ((await require_Probe.Probe.probePath(absolutePath)).hasAlphaVideo) {
16
- log(`VP9 alpha WebM: copying source with Segment size + SeekHead patched`);
17
- const buf = await (0, node_fs_promises.readFile)(absolutePath);
18
- require_generateWebmSegmentIndex.patchWebmForSegmentedServing(buf);
19
- return node_stream.Readable.from(buf);
20
+ const probe = await require_Probe.Probe.probePath(absolutePath);
21
+ if (probe.hasAlphaVideo) {
22
+ log(`VP9 alpha WebM: re-encoding with forced keyframes every ${VP9_ALPHA_SEGMENT_MS}ms`);
23
+ const tmpDir = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "ef-vp9-alpha-"));
24
+ const tmpPath = (0, node_path.join)(tmpDir, "track.webm");
25
+ try {
26
+ await probe.transcodeWebmKeyframeAligned(tmpPath, VP9_ALPHA_SEGMENT_MS);
27
+ const buf = await (0, node_fs_promises.readFile)(tmpPath);
28
+ require_generateWebmSegmentIndex.patchWebmForSegmentedServing(buf);
29
+ return node_stream.Readable.from(buf);
30
+ } finally {
31
+ (0, node_fs_promises.rm)(tmpDir, {
32
+ recursive: true,
33
+ force: true
34
+ }).catch(() => {});
35
+ }
20
36
  }
21
37
  return (await require_generateSingleTrack.generateSingleTrackFromPath(absolutePath, trackId)).stream;
22
38
  };
23
39
  const generateTrackTask = require_idempotentTask.idempotentTask({
24
40
  label: "track",
25
41
  filename: (absolutePath, _trackId) => {
26
- const ext = absolutePath.endsWith(".webm") ? "webm" : "mp4";
27
- return `${(0, node_path.basename)(absolutePath)}.track-1.${ext}`;
42
+ const lower = absolutePath.toLowerCase();
43
+ const isWebm = lower.endsWith(".webm") || /[._]webm[._]/.test(lower);
44
+ return `${(0, node_path.basename)(absolutePath)}.track-1.${isWebm ? "webm" : "mp4"}`;
28
45
  },
29
46
  runner: generateTrackFromPath
30
47
  });
@@ -42,5 +59,6 @@ const generateTrack = async (cacheRoot, absolutePath, url) => {
42
59
  //#endregion
43
60
  exports.generateTrack = generateTrack;
44
61
  exports.generateTrackFromPath = generateTrackFromPath;
62
+ exports.generateTrackTask = generateTrackTask;
45
63
 
46
64
  //# sourceMappingURL=generateTrack.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateTrack.cjs","names":["Probe","Readable","generateSingleTrackFromPath","idempotentTask"],"sources":["../../src/tasks/generateTrack.ts"],"mappings":";;;;;;;;;;;AASA,MAAa,wBAAwB,OAAO,cAAsB,YAAoB;CACpF,MAAM,OAAA,GAAA,MAAA,SAAY,2BAA2B;AAC7C,KAAI,oBAAoB,QAAQ,OAAO,eAAe;AAYtD,MAVc,MAAMA,cAAAA,MAAM,UAAU,aAAa,EAUvC,eAAe;AACvB,MAAI,sEAAsE;EAC1E,MAAM,MAAM,OAAA,GAAA,iBAAA,UAAe,aAAa;AACxC,mCAAA,6BAA6B,IAAI;AACjC,SAAOC,YAAAA,SAAS,KAAK,IAAI;;AAI3B,SADe,MAAMC,4BAAAA,4BAA4B,cAAc,QAAQ,EACzD;;AAGhB,MAAa,oBAAoBC,uBAAAA,eAAe;CAC9C,OAAO;CACP,WAAW,cAAsB,aAAqB;EAEpD,MAAM,MAAM,aAAa,SAAS,QAAQ,GAAG,SAAS;AACtD,SAAO,IAAA,GAAA,UAAA,UAAY,aAAa,CAAC,WAAW;;CAE9C,QAAQ;CACT,CAAC;AAEF,MAAa,gBAAgB,OAAO,WAAmB,cAAsB,QAAgB;AAC3F,KAAI;EACF,MAAM,UAAU,IAAI,IAAI,mBAAmB,MAAM,CAAC,aAAa,IAAI,UAAU;AAC7E,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"}
1
+ {"version":3,"file":"generateTrack.cjs","names":["Probe","Readable","generateSingleTrackFromPath","idempotentTask"],"sources":["../../src/tasks/generateTrack.ts"],"mappings":";;;;;;;;;;;;;;;AAaA,MAAM,uBAAuB;AAE7B,MAAa,wBAAwB,OAAO,cAAsB,YAAoB;CACpF,MAAM,OAAA,GAAA,MAAA,SAAY,2BAA2B;AAC7C,KAAI,oBAAoB,QAAQ,OAAO,eAAe;CAEtD,MAAM,QAAQ,MAAMA,cAAAA,MAAM,UAAU,aAAa;AAYjD,KAAI,MAAM,eAAe;AACvB,MAAI,2DAA2D,qBAAqB,IAAI;EACxF,MAAM,SAAS,OAAA,GAAA,iBAAA,UAAA,GAAA,UAAA,OAAA,GAAA,QAAA,SAA2B,EAAE,gBAAgB,CAAC;EAC7D,MAAM,WAAA,GAAA,UAAA,MAAe,QAAQ,aAAa;AAC1C,MAAI;AACF,SAAM,MAAM,6BAA6B,SAAS,qBAAqB;GACvE,MAAM,MAAM,OAAA,GAAA,iBAAA,UAAe,QAAQ;AACnC,oCAAA,6BAA6B,IAAI;AACjC,UAAOC,YAAAA,SAAS,KAAK,IAAI;YACjB;AACR,IAAA,GAAA,iBAAA,IAAG,QAAQ;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC,CAAC,YAAY,GAAG;;;AAKhE,SADe,MAAMC,4BAAAA,4BAA4B,cAAc,QAAQ,EACzD;;AAGhB,MAAa,oBAAoBC,uBAAAA,eAAe;CAC9C,OAAO;CACP,WAAW,cAAsB,aAAqB;EAKpD,MAAM,QAAQ,aAAa,aAAa;EACxC,MAAM,SAAS,MAAM,SAAS,QAAQ,IAAI,eAAe,KAAK,MAAM;AACpE,SAAO,IAAA,GAAA,UAAA,UAAY,aAAa,CAAC,WAAW,SAAS,SAAS;;CAEhE,QAAQ;CACT,CAAC;AAEF,MAAa,gBAAgB,OAAO,WAAmB,cAAsB,QAAgB;AAC3F,KAAI;EACF,MAAM,UAAU,IAAI,IAAI,mBAAmB,MAAM,CAAC,aAAa,IAAI,UAAU;AAC7E,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"}
@@ -2,27 +2,44 @@ import { Probe } from "../Probe.js";
2
2
  import { idempotentTask } from "../idempotentTask.js";
3
3
  import { patchWebmForSegmentedServing } from "../generateWebmSegmentIndex.js";
4
4
  import { generateSingleTrackFromPath } from "../generateSingleTrack.js";
5
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
5
6
  import debug from "debug";
6
7
  import { Readable } from "node:stream";
7
- import { readFile } from "node:fs/promises";
8
- import { basename } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+ import { basename, join } from "node:path";
9
10
  //#region src/tasks/generateTrack.ts
11
+ /** Target segment duration for keyframe-aligned WebM. Each cluster in the
12
+ * output is independently decodable, so the segment carving in
13
+ * generateWebmSegmentIndex can produce safe byte-range slices. */
14
+ const VP9_ALPHA_SEGMENT_MS = 2e3;
10
15
  const generateTrackFromPath = async (absolutePath, trackId) => {
11
16
  const log = debug("ef:generateTrackFragment");
12
17
  log(`Generating track ${trackId} for ${absolutePath}`);
13
- if ((await Probe.probePath(absolutePath)).hasAlphaVideo) {
14
- log(`VP9 alpha WebM: copying source with Segment size + SeekHead patched`);
15
- const buf = await readFile(absolutePath);
16
- patchWebmForSegmentedServing(buf);
17
- return Readable.from(buf);
18
+ const probe = await Probe.probePath(absolutePath);
19
+ if (probe.hasAlphaVideo) {
20
+ log(`VP9 alpha WebM: re-encoding with forced keyframes every ${VP9_ALPHA_SEGMENT_MS}ms`);
21
+ const tmpDir = await mkdtemp(join(tmpdir(), "ef-vp9-alpha-"));
22
+ const tmpPath = join(tmpDir, "track.webm");
23
+ try {
24
+ await probe.transcodeWebmKeyframeAligned(tmpPath, VP9_ALPHA_SEGMENT_MS);
25
+ const buf = await readFile(tmpPath);
26
+ patchWebmForSegmentedServing(buf);
27
+ return Readable.from(buf);
28
+ } finally {
29
+ rm(tmpDir, {
30
+ recursive: true,
31
+ force: true
32
+ }).catch(() => {});
33
+ }
18
34
  }
19
35
  return (await generateSingleTrackFromPath(absolutePath, trackId)).stream;
20
36
  };
21
37
  const generateTrackTask = idempotentTask({
22
38
  label: "track",
23
39
  filename: (absolutePath, _trackId) => {
24
- const ext = absolutePath.endsWith(".webm") ? "webm" : "mp4";
25
- return `${basename(absolutePath)}.track-1.${ext}`;
40
+ const lower = absolutePath.toLowerCase();
41
+ const isWebm = lower.endsWith(".webm") || /[._]webm[._]/.test(lower);
42
+ return `${basename(absolutePath)}.track-1.${isWebm ? "webm" : "mp4"}`;
26
43
  },
27
44
  runner: generateTrackFromPath
28
45
  });
@@ -38,6 +55,6 @@ const generateTrack = async (cacheRoot, absolutePath, url) => {
38
55
  }
39
56
  };
40
57
  //#endregion
41
- export { generateTrack, generateTrackFromPath };
58
+ export { generateTrack, generateTrackFromPath, generateTrackTask };
42
59
 
43
60
  //# sourceMappingURL=generateTrack.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateTrack.js","names":[],"sources":["../../src/tasks/generateTrack.ts"],"mappings":";;;;;;;;;AASA,MAAa,wBAAwB,OAAO,cAAsB,YAAoB;CACpF,MAAM,MAAM,MAAM,2BAA2B;AAC7C,KAAI,oBAAoB,QAAQ,OAAO,eAAe;AAYtD,MAVc,MAAM,MAAM,UAAU,aAAa,EAUvC,eAAe;AACvB,MAAI,sEAAsE;EAC1E,MAAM,MAAM,MAAM,SAAS,aAAa;AACxC,+BAA6B,IAAI;AACjC,SAAO,SAAS,KAAK,IAAI;;AAI3B,SADe,MAAM,4BAA4B,cAAc,QAAQ,EACzD;;AAGhB,MAAa,oBAAoB,eAAe;CAC9C,OAAO;CACP,WAAW,cAAsB,aAAqB;EAEpD,MAAM,MAAM,aAAa,SAAS,QAAQ,GAAG,SAAS;AACtD,SAAO,GAAG,SAAS,aAAa,CAAC,WAAW;;CAE9C,QAAQ;CACT,CAAC;AAEF,MAAa,gBAAgB,OAAO,WAAmB,cAAsB,QAAgB;AAC3F,KAAI;EACF,MAAM,UAAU,IAAI,IAAI,mBAAmB,MAAM,CAAC,aAAa,IAAI,UAAU;AAC7E,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"}
1
+ {"version":3,"file":"generateTrack.js","names":[],"sources":["../../src/tasks/generateTrack.ts"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,uBAAuB;AAE7B,MAAa,wBAAwB,OAAO,cAAsB,YAAoB;CACpF,MAAM,MAAM,MAAM,2BAA2B;AAC7C,KAAI,oBAAoB,QAAQ,OAAO,eAAe;CAEtD,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;AAYjD,KAAI,MAAM,eAAe;AACvB,MAAI,2DAA2D,qBAAqB,IAAI;EACxF,MAAM,SAAS,MAAM,QAAQ,KAAK,QAAQ,EAAE,gBAAgB,CAAC;EAC7D,MAAM,UAAU,KAAK,QAAQ,aAAa;AAC1C,MAAI;AACF,SAAM,MAAM,6BAA6B,SAAS,qBAAqB;GACvE,MAAM,MAAM,MAAM,SAAS,QAAQ;AACnC,gCAA6B,IAAI;AACjC,UAAO,SAAS,KAAK,IAAI;YACjB;AACR,MAAG,QAAQ;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC,CAAC,YAAY,GAAG;;;AAKhE,SADe,MAAM,4BAA4B,cAAc,QAAQ,EACzD;;AAGhB,MAAa,oBAAoB,eAAe;CAC9C,OAAO;CACP,WAAW,cAAsB,aAAqB;EAKpD,MAAM,QAAQ,aAAa,aAAa;EACxC,MAAM,SAAS,MAAM,SAAS,QAAQ,IAAI,eAAe,KAAK,MAAM;AACpE,SAAO,GAAG,SAAS,aAAa,CAAC,WAAW,SAAS,SAAS;;CAEhE,QAAQ;CACT,CAAC;AAEF,MAAa,gBAAgB,OAAO,WAAmB,cAAsB,QAAgB;AAC3F,KAAI;EACF,MAAM,UAAU,IAAI,IAAI,mBAAmB,MAAM,CAAC,aAAa,IAAI,UAAU;AAC7E,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"}
@@ -3,37 +3,18 @@ const require_Probe = require("../Probe.cjs");
3
3
  const require_generateFragmentIndex = require("../generateFragmentIndex.cjs");
4
4
  const require_idempotentTask = require("../idempotentTask.cjs");
5
5
  const require_generateWebmSegmentIndex = require("../generateWebmSegmentIndex.cjs");
6
+ const require_generateTrack = require("./generateTrack.cjs");
6
7
  let debug = require("debug");
7
8
  debug = require_runtime.__toESM(debug);
8
9
  let node_path = require("node:path");
9
10
  //#region src/tasks/generateTrackFragmentIndex.ts
10
- const generateTrackFragmentIndexFromPath = async (absolutePath) => {
11
- const log = (0, debug.default)("ef:generateTrackFragment");
12
- const probe = await require_Probe.Probe.probePath(absolutePath);
11
+ const log = (0, debug.default)("ef:generateTrackFragment");
12
+ /**
13
+ * Build the fragment index for an MP4 source — produces a fragmented MP4
14
+ * stream per track and an MP4 scrub track, indexes each via mp4box.
15
+ */
16
+ async function generateMp4FragmentIndexFromPath(probe) {
13
17
  const startTimeOffsetMs = probe.startTimeOffsetMs;
14
- if (startTimeOffsetMs !== void 0) log(`Extracted start_time offset: ${startTimeOffsetMs}ms`);
15
- else log("No format/stream timing offset found - will detect from composition time");
16
- if (probe.hasAlphaVideo) {
17
- log(`VP9 alpha WebM: using EBML cluster index (no transcoding)`);
18
- const webmIndex = await require_generateWebmSegmentIndex.generateWebmSegmentIndex(absolutePath, startTimeOffsetMs);
19
- const scrubResult = await (async () => {
20
- try {
21
- log("Generating VP9 WebM scrub track fragment index for alpha source");
22
- const { generateScrubTrackFromPath } = await Promise.resolve().then(() => require("./generateScrubTrack.cjs"));
23
- const { fragmentIndex } = await generateScrubTrackFromPath(absolutePath);
24
- const result = await fragmentIndex;
25
- log("VP9 scrub track fragment index generated successfully");
26
- return result;
27
- } catch (error) {
28
- log(`Failed to generate VP9 scrub track fragment index: ${error}`);
29
- return null;
30
- }
31
- })();
32
- const trackFragmentIndexes = { ...webmIndex };
33
- if (scrubResult) Object.assign(trackFragmentIndexes, scrubResult);
34
- return trackFragmentIndexes;
35
- }
36
- log(`Generating track fragment index for ${absolutePath} using single-track approach`);
37
18
  const trackTasks = probe.streams.map((stream, streamIndex) => {
38
19
  if (stream.codec_type !== "audio" && stream.codec_type !== "video") return null;
39
20
  const trackId = streamIndex + 1;
@@ -56,17 +37,58 @@ const generateTrackFragmentIndexFromPath = async (absolutePath) => {
56
37
  for (const result of trackResults) Object.assign(trackFragmentIndexes, result);
57
38
  if (scrubResult) Object.assign(trackFragmentIndexes, scrubResult);
58
39
  return trackFragmentIndexes;
40
+ }
41
+ /**
42
+ * Build the fragment index for a VP9-alpha WebM source. The byte ranges
43
+ * reference `cachedTrackPath` — the keyframe-aligned re-encoded WebM that
44
+ * `generateTrackFragmentIndex` materializes upstream. Indexing the original
45
+ * source instead would point to clusters that aren't decodable in isolation.
46
+ */
47
+ async function generateVp9AlphaFragmentIndexFromPath(probe, cachedTrackPath) {
48
+ log(`VP9 alpha WebM: building index from re-encoded cache ${cachedTrackPath}`);
49
+ const webmIndex = await require_generateWebmSegmentIndex.generateWebmSegmentIndex(cachedTrackPath, probe.startTimeOffsetMs);
50
+ let scrubResult = null;
51
+ try {
52
+ log("Generating VP9 WebM scrub track fragment index for alpha source");
53
+ const { generateScrubTrackFromPath } = await Promise.resolve().then(() => require("./generateScrubTrack.cjs"));
54
+ const { fragmentIndex } = await generateScrubTrackFromPath(probe.absolutePath);
55
+ scrubResult = await fragmentIndex;
56
+ log("VP9 scrub track fragment index generated successfully");
57
+ } catch (error) {
58
+ log(`Failed to generate VP9 scrub track fragment index: ${error}`);
59
+ }
60
+ const trackFragmentIndexes = { ...webmIndex };
61
+ if (scrubResult) Object.assign(trackFragmentIndexes, scrubResult);
62
+ return trackFragmentIndexes;
63
+ }
64
+ /**
65
+ * Public entry — dispatches MP4 vs VP9 alpha based on probe inspection.
66
+ *
67
+ * VP9 alpha sources require `cachedTrackPath` to be the path of the cached
68
+ * keyframe-aligned re-encode. Use `generateTrackFragmentIndex` (the
69
+ * orchestrator below) to materialize that cache automatically.
70
+ */
71
+ const generateTrackFragmentIndexFromPath = async (absolutePath, cachedTrackPath) => {
72
+ const probe = await require_Probe.Probe.probePath(absolutePath);
73
+ if (probe.startTimeOffsetMs !== void 0) log(`Extracted start_time offset: ${probe.startTimeOffsetMs}ms`);
74
+ if (probe.hasAlphaVideo) {
75
+ if (!cachedTrackPath) throw new Error("VP9 alpha sources require cachedTrackPath; call generateTrackFragmentIndex (orchestrator) instead.");
76
+ return generateVp9AlphaFragmentIndexFromPath(probe, cachedTrackPath);
77
+ }
78
+ log(`Generating MP4 track fragment index for ${absolutePath}`);
79
+ return generateMp4FragmentIndexFromPath(probe);
59
80
  };
60
81
  const generateTrackFragmentIndexTask = require_idempotentTask.idempotentTask({
61
82
  label: "trackFragmentIndex",
62
- filename: (absolutePath) => `${(0, node_path.basename)(absolutePath)}.tracks.json`,
63
- runner: async (absolutePath) => {
64
- const index = await generateTrackFragmentIndexFromPath(absolutePath);
83
+ filename: (absolutePath, _cachedTrackPath) => `${(0, node_path.basename)(absolutePath)}.tracks.json`,
84
+ runner: async (absolutePath, cachedTrackPath) => {
85
+ const index = await generateTrackFragmentIndexFromPath(absolutePath, cachedTrackPath);
65
86
  return JSON.stringify(index, null, 2);
66
87
  }
67
88
  });
68
89
  const generateTrackFragmentIndex = async (cacheRoot, absolutePath) => {
69
90
  try {
91
+ if ((await require_Probe.Probe.probePath(absolutePath)).hasAlphaVideo) return await generateTrackFragmentIndexTask(cacheRoot, absolutePath, (await require_generateTrack.generateTrackTask(cacheRoot, absolutePath, 1)).cachePath);
70
92
  return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);
71
93
  } catch (error) {
72
94
  console.trace("Error generating track fragment index", error);
@@ -1 +1 @@
1
- {"version":3,"file":"generateTrackFragmentIndex.cjs","names":["Probe","generateWebmSegmentIndex","generateFragmentIndex","idempotentTask"],"sources":["../../src/tasks/generateTrackFragmentIndex.ts"],"mappings":";;;;;;;;;AAQA,MAAa,qCAAqC,OAAO,iBAAyB;CAChF,MAAM,OAAA,GAAA,MAAA,SAAY,2BAA2B;CAC7C,MAAM,QAAQ,MAAMA,cAAAA,MAAM,UAAU,aAAa;CAEjD,MAAM,oBAAoB,MAAM;AAChC,KAAI,sBAAsB,KAAA,EACxB,KAAI,gCAAgC,kBAAkB,IAAI;KAE1D,KAAI,2EAA2E;AAOjF,KAAI,MAAM,eAAe;AACvB,MAAI,4DAA4D;EAChE,MAAM,YAAY,MAAMC,iCAAAA,yBAAyB,cAAc,kBAAkB;EAkBjF,MAAM,cAAc,OAdmD,YAAY;AACjF,OAAI;AACF,QAAI,kEAAkE;IACtE,MAAM,EAAE,+BAA+B,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,2BAAA,CAAA;IAC7C,MAAM,EAAE,kBAAkB,MAAM,2BAA2B,aAAa;IACxE,MAAM,SAAS,MAAM;AACrB,QAAI,wDAAwD;AAC5D,WAAO;YACA,OAAO;AACd,QAAI,sDAAsD,QAAQ;AAClE,WAAO;;MAEP;EAGJ,MAAM,uBAA2D,EAAE,GAAG,WAAW;AACjF,MAAI,YAAa,QAAO,OAAO,sBAAsB,YAAY;AACjE,SAAO;;AAIT,KAAI,uCAAuC,aAAa,8BAA8B;CAEtF,MAAM,aAAa,MAAM,QACtB,KAAK,QAAQ,gBAAgB;AAC5B,MAAI,OAAO,eAAe,WAAW,OAAO,eAAe,QACzD,QAAO;EAET,MAAM,UAAU,cAAc;AAC9B,MAAI,oBAAoB,QAAQ,IAAI,OAAO,WAAW,GAAG;AAGzD,SAAOC,8BAAAA,sBAFa,MAAM,sBAAsB,YAAY,EAElB,mBADnB,EAAE,GAAG,SAAS,CACuC;GAC5E,CACD,QAAQ,SAA8D,SAAS,KAAK;CAEvF,MAAM,YACJ,MAAM,aAAa,SAAS,KACvB,YAAY;AACX,MAAI;AACF,OAAI,wCAAwC;GAE5C,MAAM,SAAS,MAAMA,8BAAAA,sBADD,MAAM,4BAA4B,EACE,mBAAmB,EAAE,GAAG,IAAI,CAAC;AACrF,OAAI,oDAAoD;AACxD,UAAO;WACA,OAAO;AACd,OAAI,kDAAkD,QAAQ;AAC9D,UAAO;;KAEP,GACJ,QAAQ,QAAQ,KAAK;CAE3B,MAAM,CAAC,cAAc,eAAe,MAAM,QAAQ,IAAI,CAAC,QAAQ,IAAI,WAAW,EAAE,UAAU,CAAC;CAE3F,MAAM,uBAA2D,EAAE;AACnE,MAAK,MAAM,UAAU,aACnB,QAAO,OAAO,sBAAsB,OAAO;AAE7C,KAAI,YACF,QAAO,OAAO,sBAAsB,YAAY;AAGlD,QAAO;;AAGT,MAAM,iCAAiCC,uBAAAA,eAAe;CACpD,OAAO;CACP,WAAW,iBAAiB,IAAA,GAAA,UAAA,UAAY,aAAa,CAAC;CACtD,QAAQ,OAAO,iBAAyB;EACtC,MAAM,QAAQ,MAAM,mCAAmC,aAAa;AACpE,SAAO,KAAK,UAAU,OAAO,MAAM,EAAE;;CAExC,CAAC;AAEF,MAAa,6BAA6B,OAAO,WAAmB,iBAAyB;AAC3F,KAAI;AACF,SAAO,MAAM,+BAA+B,WAAW,aAAa;UAC7D,OAAO;AACd,UAAQ,MAAM,yCAAyC,MAAM;AAC7D,QAAM"}
1
+ {"version":3,"file":"generateTrackFragmentIndex.cjs","names":["generateFragmentIndex","generateWebmSegmentIndex","Probe","idempotentTask","generateTrackTask"],"sources":["../../src/tasks/generateTrackFragmentIndex.ts"],"mappings":";;;;;;;;;;AASA,MAAM,OAAA,GAAA,MAAA,SAAY,2BAA2B;;;;;AAM7C,eAAe,iCACb,OAC6C;CAC7C,MAAM,oBAAoB,MAAM;CAEhC,MAAM,aAAa,MAAM,QACtB,KAAK,QAAQ,gBAAgB;AAC5B,MAAI,OAAO,eAAe,WAAW,OAAO,eAAe,QACzD,QAAO;EAET,MAAM,UAAU,cAAc;AAC9B,MAAI,oBAAoB,QAAQ,IAAI,OAAO,WAAW,GAAG;AAGzD,SAAOA,8BAAAA,sBAFa,MAAM,sBAAsB,YAAY,EAElB,mBADnB,EAAE,GAAG,SAAS,CACuC;GAC5E,CACD,QAAQ,SAA8D,SAAS,KAAK;CAEvF,MAAM,YACJ,MAAM,aAAa,SAAS,KACvB,YAAY;AACX,MAAI;AACF,OAAI,wCAAwC;GAE5C,MAAM,SAAS,MAAMA,8BAAAA,sBADD,MAAM,4BAA4B,EACE,mBAAmB,EAAE,GAAG,IAAI,CAAC;AACrF,OAAI,oDAAoD;AACxD,UAAO;WACA,OAAO;AACd,OAAI,kDAAkD,QAAQ;AAC9D,UAAO;;KAEP,GACJ,QAAQ,QAAQ,KAAK;CAE3B,MAAM,CAAC,cAAc,eAAe,MAAM,QAAQ,IAAI,CAAC,QAAQ,IAAI,WAAW,EAAE,UAAU,CAAC;CAE3F,MAAM,uBAA2D,EAAE;AACnE,MAAK,MAAM,UAAU,aAAc,QAAO,OAAO,sBAAsB,OAAO;AAC9E,KAAI,YAAa,QAAO,OAAO,sBAAsB,YAAY;AACjE,QAAO;;;;;;;;AAST,eAAe,sCACb,OACA,iBAC6C;AAC7C,KAAI,wDAAwD,kBAAkB;CAC9E,MAAM,YAAY,MAAMC,iCAAAA,yBAAyB,iBAAiB,MAAM,kBAAkB;CAI1F,IAAI,cAAyD;AAC7D,KAAI;AACF,MAAI,kEAAkE;EACtE,MAAM,EAAE,+BAA+B,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,2BAAA,CAAA;EAC7C,MAAM,EAAE,kBAAkB,MAAM,2BAA2B,MAAM,aAAa;AAC9E,gBAAc,MAAM;AACpB,MAAI,wDAAwD;UACrD,OAAO;AACd,MAAI,sDAAsD,QAAQ;;CAGpE,MAAM,uBAA2D,EAAE,GAAG,WAAW;AACjF,KAAI,YAAa,QAAO,OAAO,sBAAsB,YAAY;AACjE,QAAO;;;;;;;;;AAUT,MAAa,qCAAqC,OAChD,cACA,oBACG;CACH,MAAM,QAAQ,MAAMC,cAAAA,MAAM,UAAU,aAAa;AAEjD,KAAI,MAAM,sBAAsB,KAAA,EAC9B,KAAI,gCAAgC,MAAM,kBAAkB,IAAI;AAGlE,KAAI,MAAM,eAAe;AACvB,MAAI,CAAC,gBACH,OAAM,IAAI,MACR,qGACD;AAEH,SAAO,sCAAsC,OAAO,gBAAgB;;AAGtE,KAAI,2CAA2C,eAAe;AAC9D,QAAO,iCAAiC,MAAM;;AAKhD,MAAM,iCAAiCC,uBAAAA,eAAe;CACpD,OAAO;CACP,WAAW,cAAsB,qBAC/B,IAAA,GAAA,UAAA,UAAY,aAAa,CAAC;CAC5B,QAAQ,OAAO,cAAsB,oBAA6B;EAChE,MAAM,QAAQ,MAAM,mCAAmC,cAAc,gBAAgB;AACrF,SAAO,KAAK,UAAU,OAAO,MAAM,EAAE;;CAExC,CAAC;AAEF,MAAa,6BAA6B,OAAO,WAAmB,iBAAyB;AAC3F,KAAI;AAKF,OADc,MAAMD,cAAAA,MAAM,UAAU,aAAa,EACvC,cAER,QAAO,MAAM,+BAA+B,WAAW,eADnC,MAAME,sBAAAA,kBAAkB,WAAW,cAAc,EAAE,EACU,UAAU;AAE7F,SAAO,MAAM,+BAA+B,WAAW,aAAa;UAC7D,OAAO;AACd,UAAQ,MAAM,yCAAyC,MAAM;AAC7D,QAAM"}
@@ -2,7 +2,14 @@ import { TrackFragmentIndex } from "../Probe.cjs";
2
2
  import { TaskResult } from "../idempotentTask.cjs";
3
3
 
4
4
  //#region src/tasks/generateTrackFragmentIndex.d.ts
5
- declare const generateTrackFragmentIndexFromPath: (absolutePath: string) => Promise<Record<number, TrackFragmentIndex>>;
5
+ /**
6
+ * Public entry — dispatches MP4 vs VP9 alpha based on probe inspection.
7
+ *
8
+ * VP9 alpha sources require `cachedTrackPath` to be the path of the cached
9
+ * keyframe-aligned re-encode. Use `generateTrackFragmentIndex` (the
10
+ * orchestrator below) to materialize that cache automatically.
11
+ */
12
+ declare const generateTrackFragmentIndexFromPath: (absolutePath: string, cachedTrackPath?: string) => Promise<Record<number, TrackFragmentIndex>>;
6
13
  declare const generateTrackFragmentIndex: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
7
14
  //#endregion
8
15
  export { generateTrackFragmentIndex, generateTrackFragmentIndexFromPath };
@@ -2,7 +2,14 @@ import { TrackFragmentIndex } from "../Probe.js";
2
2
  import { TaskResult } from "../idempotentTask.js";
3
3
 
4
4
  //#region src/tasks/generateTrackFragmentIndex.d.ts
5
- declare const generateTrackFragmentIndexFromPath: (absolutePath: string) => Promise<Record<number, TrackFragmentIndex>>;
5
+ /**
6
+ * Public entry — dispatches MP4 vs VP9 alpha based on probe inspection.
7
+ *
8
+ * VP9 alpha sources require `cachedTrackPath` to be the path of the cached
9
+ * keyframe-aligned re-encode. Use `generateTrackFragmentIndex` (the
10
+ * orchestrator below) to materialize that cache automatically.
11
+ */
12
+ declare const generateTrackFragmentIndexFromPath: (absolutePath: string, cachedTrackPath?: string) => Promise<Record<number, TrackFragmentIndex>>;
6
13
  declare const generateTrackFragmentIndex: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
7
14
  //#endregion
8
15
  export { generateTrackFragmentIndex, generateTrackFragmentIndexFromPath };
@@ -2,36 +2,17 @@ import { Probe } from "../Probe.js";
2
2
  import { generateFragmentIndex } from "../generateFragmentIndex.js";
3
3
  import { idempotentTask } from "../idempotentTask.js";
4
4
  import { generateWebmSegmentIndex } from "../generateWebmSegmentIndex.js";
5
+ import { generateTrackTask } from "./generateTrack.js";
5
6
  import debug from "debug";
6
7
  import { basename } from "node:path";
7
8
  //#region src/tasks/generateTrackFragmentIndex.ts
8
- const generateTrackFragmentIndexFromPath = async (absolutePath) => {
9
- const log = debug("ef:generateTrackFragment");
10
- const probe = await Probe.probePath(absolutePath);
9
+ const log = debug("ef:generateTrackFragment");
10
+ /**
11
+ * Build the fragment index for an MP4 source — produces a fragmented MP4
12
+ * stream per track and an MP4 scrub track, indexes each via mp4box.
13
+ */
14
+ async function generateMp4FragmentIndexFromPath(probe) {
11
15
  const startTimeOffsetMs = probe.startTimeOffsetMs;
12
- if (startTimeOffsetMs !== void 0) log(`Extracted start_time offset: ${startTimeOffsetMs}ms`);
13
- else log("No format/stream timing offset found - will detect from composition time");
14
- if (probe.hasAlphaVideo) {
15
- log(`VP9 alpha WebM: using EBML cluster index (no transcoding)`);
16
- const webmIndex = await generateWebmSegmentIndex(absolutePath, startTimeOffsetMs);
17
- const scrubResult = await (async () => {
18
- try {
19
- log("Generating VP9 WebM scrub track fragment index for alpha source");
20
- const { generateScrubTrackFromPath } = await import("./generateScrubTrack.js");
21
- const { fragmentIndex } = await generateScrubTrackFromPath(absolutePath);
22
- const result = await fragmentIndex;
23
- log("VP9 scrub track fragment index generated successfully");
24
- return result;
25
- } catch (error) {
26
- log(`Failed to generate VP9 scrub track fragment index: ${error}`);
27
- return null;
28
- }
29
- })();
30
- const trackFragmentIndexes = { ...webmIndex };
31
- if (scrubResult) Object.assign(trackFragmentIndexes, scrubResult);
32
- return trackFragmentIndexes;
33
- }
34
- log(`Generating track fragment index for ${absolutePath} using single-track approach`);
35
16
  const trackTasks = probe.streams.map((stream, streamIndex) => {
36
17
  if (stream.codec_type !== "audio" && stream.codec_type !== "video") return null;
37
18
  const trackId = streamIndex + 1;
@@ -54,17 +35,58 @@ const generateTrackFragmentIndexFromPath = async (absolutePath) => {
54
35
  for (const result of trackResults) Object.assign(trackFragmentIndexes, result);
55
36
  if (scrubResult) Object.assign(trackFragmentIndexes, scrubResult);
56
37
  return trackFragmentIndexes;
38
+ }
39
+ /**
40
+ * Build the fragment index for a VP9-alpha WebM source. The byte ranges
41
+ * reference `cachedTrackPath` — the keyframe-aligned re-encoded WebM that
42
+ * `generateTrackFragmentIndex` materializes upstream. Indexing the original
43
+ * source instead would point to clusters that aren't decodable in isolation.
44
+ */
45
+ async function generateVp9AlphaFragmentIndexFromPath(probe, cachedTrackPath) {
46
+ log(`VP9 alpha WebM: building index from re-encoded cache ${cachedTrackPath}`);
47
+ const webmIndex = await generateWebmSegmentIndex(cachedTrackPath, probe.startTimeOffsetMs);
48
+ let scrubResult = null;
49
+ try {
50
+ log("Generating VP9 WebM scrub track fragment index for alpha source");
51
+ const { generateScrubTrackFromPath } = await import("./generateScrubTrack.js");
52
+ const { fragmentIndex } = await generateScrubTrackFromPath(probe.absolutePath);
53
+ scrubResult = await fragmentIndex;
54
+ log("VP9 scrub track fragment index generated successfully");
55
+ } catch (error) {
56
+ log(`Failed to generate VP9 scrub track fragment index: ${error}`);
57
+ }
58
+ const trackFragmentIndexes = { ...webmIndex };
59
+ if (scrubResult) Object.assign(trackFragmentIndexes, scrubResult);
60
+ return trackFragmentIndexes;
61
+ }
62
+ /**
63
+ * Public entry — dispatches MP4 vs VP9 alpha based on probe inspection.
64
+ *
65
+ * VP9 alpha sources require `cachedTrackPath` to be the path of the cached
66
+ * keyframe-aligned re-encode. Use `generateTrackFragmentIndex` (the
67
+ * orchestrator below) to materialize that cache automatically.
68
+ */
69
+ const generateTrackFragmentIndexFromPath = async (absolutePath, cachedTrackPath) => {
70
+ const probe = await Probe.probePath(absolutePath);
71
+ if (probe.startTimeOffsetMs !== void 0) log(`Extracted start_time offset: ${probe.startTimeOffsetMs}ms`);
72
+ if (probe.hasAlphaVideo) {
73
+ if (!cachedTrackPath) throw new Error("VP9 alpha sources require cachedTrackPath; call generateTrackFragmentIndex (orchestrator) instead.");
74
+ return generateVp9AlphaFragmentIndexFromPath(probe, cachedTrackPath);
75
+ }
76
+ log(`Generating MP4 track fragment index for ${absolutePath}`);
77
+ return generateMp4FragmentIndexFromPath(probe);
57
78
  };
58
79
  const generateTrackFragmentIndexTask = idempotentTask({
59
80
  label: "trackFragmentIndex",
60
- filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,
61
- runner: async (absolutePath) => {
62
- const index = await generateTrackFragmentIndexFromPath(absolutePath);
81
+ filename: (absolutePath, _cachedTrackPath) => `${basename(absolutePath)}.tracks.json`,
82
+ runner: async (absolutePath, cachedTrackPath) => {
83
+ const index = await generateTrackFragmentIndexFromPath(absolutePath, cachedTrackPath);
63
84
  return JSON.stringify(index, null, 2);
64
85
  }
65
86
  });
66
87
  const generateTrackFragmentIndex = async (cacheRoot, absolutePath) => {
67
88
  try {
89
+ if ((await Probe.probePath(absolutePath)).hasAlphaVideo) return await generateTrackFragmentIndexTask(cacheRoot, absolutePath, (await generateTrackTask(cacheRoot, absolutePath, 1)).cachePath);
68
90
  return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);
69
91
  } catch (error) {
70
92
  console.trace("Error generating track fragment index", error);
@@ -1 +1 @@
1
- {"version":3,"file":"generateTrackFragmentIndex.js","names":[],"sources":["../../src/tasks/generateTrackFragmentIndex.ts"],"mappings":";;;;;;;AAQA,MAAa,qCAAqC,OAAO,iBAAyB;CAChF,MAAM,MAAM,MAAM,2BAA2B;CAC7C,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;CAEjD,MAAM,oBAAoB,MAAM;AAChC,KAAI,sBAAsB,KAAA,EACxB,KAAI,gCAAgC,kBAAkB,IAAI;KAE1D,KAAI,2EAA2E;AAOjF,KAAI,MAAM,eAAe;AACvB,MAAI,4DAA4D;EAChE,MAAM,YAAY,MAAM,yBAAyB,cAAc,kBAAkB;EAkBjF,MAAM,cAAc,OAdmD,YAAY;AACjF,OAAI;AACF,QAAI,kEAAkE;IACtE,MAAM,EAAE,+BAA+B,MAAM,OAAO;IACpD,MAAM,EAAE,kBAAkB,MAAM,2BAA2B,aAAa;IACxE,MAAM,SAAS,MAAM;AACrB,QAAI,wDAAwD;AAC5D,WAAO;YACA,OAAO;AACd,QAAI,sDAAsD,QAAQ;AAClE,WAAO;;MAEP;EAGJ,MAAM,uBAA2D,EAAE,GAAG,WAAW;AACjF,MAAI,YAAa,QAAO,OAAO,sBAAsB,YAAY;AACjE,SAAO;;AAIT,KAAI,uCAAuC,aAAa,8BAA8B;CAEtF,MAAM,aAAa,MAAM,QACtB,KAAK,QAAQ,gBAAgB;AAC5B,MAAI,OAAO,eAAe,WAAW,OAAO,eAAe,QACzD,QAAO;EAET,MAAM,UAAU,cAAc;AAC9B,MAAI,oBAAoB,QAAQ,IAAI,OAAO,WAAW,GAAG;AAGzD,SAAO,sBAFa,MAAM,sBAAsB,YAAY,EAElB,mBADnB,EAAE,GAAG,SAAS,CACuC;GAC5E,CACD,QAAQ,SAA8D,SAAS,KAAK;CAEvF,MAAM,YACJ,MAAM,aAAa,SAAS,KACvB,YAAY;AACX,MAAI;AACF,OAAI,wCAAwC;GAE5C,MAAM,SAAS,MAAM,sBADD,MAAM,4BAA4B,EACE,mBAAmB,EAAE,GAAG,IAAI,CAAC;AACrF,OAAI,oDAAoD;AACxD,UAAO;WACA,OAAO;AACd,OAAI,kDAAkD,QAAQ;AAC9D,UAAO;;KAEP,GACJ,QAAQ,QAAQ,KAAK;CAE3B,MAAM,CAAC,cAAc,eAAe,MAAM,QAAQ,IAAI,CAAC,QAAQ,IAAI,WAAW,EAAE,UAAU,CAAC;CAE3F,MAAM,uBAA2D,EAAE;AACnE,MAAK,MAAM,UAAU,aACnB,QAAO,OAAO,sBAAsB,OAAO;AAE7C,KAAI,YACF,QAAO,OAAO,sBAAsB,YAAY;AAGlD,QAAO;;AAGT,MAAM,iCAAiC,eAAe;CACpD,OAAO;CACP,WAAW,iBAAiB,GAAG,SAAS,aAAa,CAAC;CACtD,QAAQ,OAAO,iBAAyB;EACtC,MAAM,QAAQ,MAAM,mCAAmC,aAAa;AACpE,SAAO,KAAK,UAAU,OAAO,MAAM,EAAE;;CAExC,CAAC;AAEF,MAAa,6BAA6B,OAAO,WAAmB,iBAAyB;AAC3F,KAAI;AACF,SAAO,MAAM,+BAA+B,WAAW,aAAa;UAC7D,OAAO;AACd,UAAQ,MAAM,yCAAyC,MAAM;AAC7D,QAAM"}
1
+ {"version":3,"file":"generateTrackFragmentIndex.js","names":[],"sources":["../../src/tasks/generateTrackFragmentIndex.ts"],"mappings":";;;;;;;;AASA,MAAM,MAAM,MAAM,2BAA2B;;;;;AAM7C,eAAe,iCACb,OAC6C;CAC7C,MAAM,oBAAoB,MAAM;CAEhC,MAAM,aAAa,MAAM,QACtB,KAAK,QAAQ,gBAAgB;AAC5B,MAAI,OAAO,eAAe,WAAW,OAAO,eAAe,QACzD,QAAO;EAET,MAAM,UAAU,cAAc;AAC9B,MAAI,oBAAoB,QAAQ,IAAI,OAAO,WAAW,GAAG;AAGzD,SAAO,sBAFa,MAAM,sBAAsB,YAAY,EAElB,mBADnB,EAAE,GAAG,SAAS,CACuC;GAC5E,CACD,QAAQ,SAA8D,SAAS,KAAK;CAEvF,MAAM,YACJ,MAAM,aAAa,SAAS,KACvB,YAAY;AACX,MAAI;AACF,OAAI,wCAAwC;GAE5C,MAAM,SAAS,MAAM,sBADD,MAAM,4BAA4B,EACE,mBAAmB,EAAE,GAAG,IAAI,CAAC;AACrF,OAAI,oDAAoD;AACxD,UAAO;WACA,OAAO;AACd,OAAI,kDAAkD,QAAQ;AAC9D,UAAO;;KAEP,GACJ,QAAQ,QAAQ,KAAK;CAE3B,MAAM,CAAC,cAAc,eAAe,MAAM,QAAQ,IAAI,CAAC,QAAQ,IAAI,WAAW,EAAE,UAAU,CAAC;CAE3F,MAAM,uBAA2D,EAAE;AACnE,MAAK,MAAM,UAAU,aAAc,QAAO,OAAO,sBAAsB,OAAO;AAC9E,KAAI,YAAa,QAAO,OAAO,sBAAsB,YAAY;AACjE,QAAO;;;;;;;;AAST,eAAe,sCACb,OACA,iBAC6C;AAC7C,KAAI,wDAAwD,kBAAkB;CAC9E,MAAM,YAAY,MAAM,yBAAyB,iBAAiB,MAAM,kBAAkB;CAI1F,IAAI,cAAyD;AAC7D,KAAI;AACF,MAAI,kEAAkE;EACtE,MAAM,EAAE,+BAA+B,MAAM,OAAO;EACpD,MAAM,EAAE,kBAAkB,MAAM,2BAA2B,MAAM,aAAa;AAC9E,gBAAc,MAAM;AACpB,MAAI,wDAAwD;UACrD,OAAO;AACd,MAAI,sDAAsD,QAAQ;;CAGpE,MAAM,uBAA2D,EAAE,GAAG,WAAW;AACjF,KAAI,YAAa,QAAO,OAAO,sBAAsB,YAAY;AACjE,QAAO;;;;;;;;;AAUT,MAAa,qCAAqC,OAChD,cACA,oBACG;CACH,MAAM,QAAQ,MAAM,MAAM,UAAU,aAAa;AAEjD,KAAI,MAAM,sBAAsB,KAAA,EAC9B,KAAI,gCAAgC,MAAM,kBAAkB,IAAI;AAGlE,KAAI,MAAM,eAAe;AACvB,MAAI,CAAC,gBACH,OAAM,IAAI,MACR,qGACD;AAEH,SAAO,sCAAsC,OAAO,gBAAgB;;AAGtE,KAAI,2CAA2C,eAAe;AAC9D,QAAO,iCAAiC,MAAM;;AAKhD,MAAM,iCAAiC,eAAe;CACpD,OAAO;CACP,WAAW,cAAsB,qBAC/B,GAAG,SAAS,aAAa,CAAC;CAC5B,QAAQ,OAAO,cAAsB,oBAA6B;EAChE,MAAM,QAAQ,MAAM,mCAAmC,cAAc,gBAAgB;AACrF,SAAO,KAAK,UAAU,OAAO,MAAM,EAAE;;CAExC,CAAC;AAEF,MAAa,6BAA6B,OAAO,WAAmB,iBAAyB;AAC3F,KAAI;AAKF,OADc,MAAM,MAAM,UAAU,aAAa,EACvC,cAER,QAAO,MAAM,+BAA+B,WAAW,eADnC,MAAM,kBAAkB,WAAW,cAAc,EAAE,EACU,UAAU;AAE7F,SAAO,MAAM,+BAA+B,WAAW,aAAa;UAC7D,OAAO;AACd,UAAQ,MAAM,yCAAyC,MAAM;AAC7D,QAAM"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/assets",
3
- "version": "0.49.5",
3
+ "version": "0.49.7",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",