@editframe/assets 0.6.0-beta.9 → 0.7.0-beta.4

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.
Files changed (39) hide show
  1. package/dist/lib/av/MP4File.cjs +187 -0
  2. package/dist/lib/av/MP4File.js +170 -0
  3. package/dist/lib/util/execPromise.cjs +6 -0
  4. package/dist/lib/util/execPromise.js +6 -0
  5. package/dist/packages/assets/src/Probe.cjs +224 -0
  6. package/dist/packages/assets/src/Probe.d.ts +646 -0
  7. package/dist/packages/assets/src/Probe.js +207 -0
  8. package/dist/packages/assets/src/VideoRenderOptions.cjs +36 -0
  9. package/dist/packages/assets/src/VideoRenderOptions.d.ts +169 -0
  10. package/dist/packages/assets/src/VideoRenderOptions.js +36 -0
  11. package/dist/packages/assets/src/idempotentTask.cjs +57 -0
  12. package/dist/packages/assets/src/idempotentTask.d.ts +13 -0
  13. package/dist/packages/assets/src/idempotentTask.js +57 -0
  14. package/dist/packages/assets/src/index.cjs +20 -0
  15. package/dist/packages/assets/src/index.d.ts +9 -0
  16. package/dist/packages/assets/src/index.js +20 -0
  17. package/dist/packages/assets/src/md5.cjs +60 -0
  18. package/dist/packages/assets/src/md5.d.ts +6 -0
  19. package/dist/packages/assets/src/md5.js +60 -0
  20. package/dist/packages/assets/src/mp4FileWritable.cjs +21 -0
  21. package/dist/packages/assets/src/mp4FileWritable.d.ts +4 -0
  22. package/dist/packages/assets/src/mp4FileWritable.js +21 -0
  23. package/dist/packages/assets/src/tasks/cacheImage.cjs +22 -0
  24. package/dist/packages/assets/src/tasks/cacheImage.d.ts +1 -0
  25. package/dist/packages/assets/src/tasks/cacheImage.js +22 -0
  26. package/dist/packages/assets/src/tasks/findOrCreateCaptions.cjs +26 -0
  27. package/dist/packages/assets/src/tasks/findOrCreateCaptions.d.ts +1 -0
  28. package/dist/packages/assets/src/tasks/findOrCreateCaptions.js +26 -0
  29. package/dist/packages/assets/src/tasks/generateTrack.cjs +52 -0
  30. package/dist/packages/assets/src/tasks/generateTrack.d.ts +1 -0
  31. package/dist/packages/assets/src/tasks/generateTrack.js +52 -0
  32. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.cjs +105 -0
  33. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.d.ts +1 -0
  34. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.js +105 -0
  35. package/package.json +10 -4
  36. package/src/tasks/cacheImage.ts +22 -0
  37. package/src/tasks/findOrCreateCaptions.ts +29 -0
  38. package/src/tasks/generateTrack.ts +61 -0
  39. package/src/tasks/generateTrackFragmentIndex.ts +120 -0
@@ -0,0 +1,61 @@
1
+ import { idempotentTask } from "../idempotentTask";
2
+ import { MP4File } from "@/av/MP4File";
3
+ import debug from "debug";
4
+ import { mp4FileWritable } from "../mp4FileWritable";
5
+ import { PassThrough } from "node:stream";
6
+ import { basename } from "node:path";
7
+ import { Probe } from "../Probe";
8
+
9
+ const generateTrackTask = idempotentTask({
10
+ label: "track",
11
+ filename: (absolutePath: string, trackId: number) =>
12
+ `${basename(absolutePath)}.track-${trackId}.mp4`,
13
+ runner: async (absolutePath, trackId: number) => {
14
+ const log = debug("ef:generateTrackFragment");
15
+ const probe = await Probe.probePath(absolutePath);
16
+ const readStream = probe.createConformingReadstream();
17
+ const mp4File = new MP4File();
18
+
19
+ log(`Generating track fragment index for ${absolutePath}`);
20
+ readStream.pipe(mp4FileWritable(mp4File));
21
+
22
+ await new Promise((resolve, reject) => {
23
+ readStream.on("end", resolve);
24
+ readStream.on("error", reject);
25
+ });
26
+
27
+ const trackStream = new PassThrough();
28
+
29
+ for await (const fragment of mp4File.fragmentIterator()) {
30
+ if (fragment.track !== trackId) {
31
+ continue;
32
+ }
33
+ trackStream.write(Buffer.from(fragment.data), "binary");
34
+ }
35
+ trackStream.end();
36
+
37
+ return trackStream;
38
+ },
39
+ });
40
+
41
+ export const generateTrack = async (
42
+ cacheRoot: string,
43
+ absolutePath: string,
44
+ url: string,
45
+ ) => {
46
+ try {
47
+ const trackId = new URL(
48
+ `http://localhost${url}` ?? "bad-url",
49
+ ).searchParams.get("trackId");
50
+ if (trackId === null) {
51
+ throw new Error(
52
+ "No trackId provided. IT must be specified in the query string: ?trackId=0",
53
+ );
54
+ }
55
+ return await generateTrackTask(cacheRoot, absolutePath, Number(trackId));
56
+ } catch (error) {
57
+ console.error(error);
58
+ console.trace("Error generating track fragment index", error);
59
+ throw error;
60
+ }
61
+ };
@@ -0,0 +1,120 @@
1
+ import { idempotentTask } from "../idempotentTask";
2
+ import { MP4File } from "@/av/MP4File";
3
+ import debug from "debug";
4
+ import { mp4FileWritable } from "../mp4FileWritable";
5
+ import { basename } from "node:path";
6
+ import { Probe, type TrackFragmentIndex } from "../Probe";
7
+
8
+ const generateTrackFragmentIndexFromPath = async (absolutePath: string) => {
9
+ const log = debug("ef:generateTrackFragment");
10
+ const probe = await Probe.probePath(absolutePath);
11
+ const readStream = probe.createConformingReadstream();
12
+
13
+ const mp4File = new MP4File();
14
+
15
+ log(`Generating track fragment index for ${absolutePath}`);
16
+ readStream.pipe(mp4FileWritable(mp4File));
17
+ await new Promise((resolve, reject) => {
18
+ readStream.on("end", resolve);
19
+ readStream.on("error", reject);
20
+ });
21
+
22
+ const trackFragmentIndexes: Record<number, TrackFragmentIndex> = {};
23
+ const trackByteOffsets: Record<number, number> = {};
24
+ for await (const fragment of mp4File.fragmentIterator()) {
25
+ const track = mp4File
26
+ .getInfo()
27
+ .tracks.find((track) => track.id === fragment.track);
28
+
29
+ if (!track) {
30
+ throw new Error("Track not found");
31
+ }
32
+
33
+ if (fragment.segment === "init") {
34
+ trackByteOffsets[fragment.track] = fragment.data.byteLength;
35
+ if (track?.type === "video") {
36
+ const videoTrack = mp4File
37
+ .getInfo()
38
+ .videoTracks.find((track) => track.id === fragment.track);
39
+ if (!videoTrack) {
40
+ throw new Error("Video track not found");
41
+ }
42
+ trackFragmentIndexes[fragment.track] = {
43
+ track: fragment.track,
44
+ type: "video",
45
+ width: videoTrack.video.width,
46
+ height: videoTrack.video.height,
47
+ timescale: track.timescale,
48
+ sample_count: videoTrack.nb_samples,
49
+ duration: 0,
50
+ initSegment: {
51
+ offset: 0,
52
+ size: fragment.data.byteLength,
53
+ },
54
+ segments: [],
55
+ };
56
+ }
57
+ if (track?.type === "audio") {
58
+ const audioTrack = mp4File
59
+ .getInfo()
60
+ .audioTracks.find((track) => track.id === fragment.track);
61
+ if (!audioTrack) {
62
+ throw new Error("Audio track not found");
63
+ }
64
+ trackFragmentIndexes[fragment.track] = {
65
+ track: fragment.track,
66
+ type: "audio",
67
+ channel_count: audioTrack.audio.channel_count,
68
+ sample_rate: audioTrack.audio.sample_rate,
69
+ sample_size: audioTrack.audio.sample_size,
70
+ sample_count: audioTrack.nb_samples,
71
+ timescale: track.timescale,
72
+ duration: 0,
73
+ initSegment: {
74
+ offset: 0,
75
+ size: fragment.data.byteLength,
76
+ },
77
+ segments: [],
78
+ };
79
+ }
80
+ } else {
81
+ const fragmentIndex = trackFragmentIndexes[fragment.track];
82
+ if (trackByteOffsets[fragment.track] === undefined) {
83
+ throw new Error("Fragment index not found");
84
+ }
85
+ if (!fragmentIndex) {
86
+ throw new Error("Fragment index not found");
87
+ }
88
+ fragmentIndex.duration += fragment.duration;
89
+ fragmentIndex.segments.push({
90
+ cts: fragment.cts,
91
+ dts: fragment.dts,
92
+ duration: fragment.duration,
93
+ // biome-ignore lint/style/noNonNullAssertion: This was checked above
94
+ offset: trackByteOffsets[fragment.track]!,
95
+ size: fragment.data.byteLength,
96
+ });
97
+ // biome-ignore lint/style/noNonNullAssertion: this was checked above
98
+ trackByteOffsets[fragment.track]! += fragment.data.byteLength;
99
+ }
100
+ }
101
+ return JSON.stringify(trackFragmentIndexes, null, 2);
102
+ };
103
+
104
+ const generateTrackFragmentIndexTask = idempotentTask({
105
+ label: "trackFragmentIndex",
106
+ filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,
107
+ runner: generateTrackFragmentIndexFromPath,
108
+ });
109
+
110
+ export const generateTrackFragmentIndex = async (
111
+ cacheRoot: string,
112
+ absolutePath: string,
113
+ ) => {
114
+ try {
115
+ return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);
116
+ } catch (error) {
117
+ console.trace("Error generating track fragment index", error);
118
+ throw error;
119
+ }
120
+ };