@editframe/assets 0.7.0-beta.6 → 0.8.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/EncodedAsset.d.ts +102 -0
  2. package/dist/EncodedAsset.js +564 -0
  3. package/dist/MP4File.d.ts +31 -0
  4. package/dist/{packages/assets/src/Probe.d.ts → Probe.d.ts} +45 -41
  5. package/dist/{packages/assets/src/Probe.js → Probe.js} +54 -5
  6. package/dist/index.d.ts +9 -0
  7. package/dist/index.js +24 -0
  8. package/dist/{packages/assets/src/md5.d.ts → md5.d.ts} +1 -0
  9. package/dist/{packages/assets/src/md5.js → md5.js} +6 -0
  10. package/dist/memoize.d.ts +2 -0
  11. package/dist/memoize.js +14 -0
  12. package/dist/{packages/assets/src/mp4FileWritable.d.ts → mp4FileWritable.d.ts} +1 -1
  13. package/dist/tasks/cacheImage.d.ts +1 -0
  14. package/dist/tasks/findOrCreateCaptions.d.ts +2 -0
  15. package/dist/tasks/findOrCreateCaptions.js +30 -0
  16. package/dist/tasks/generateTrack.d.ts +5 -0
  17. package/dist/{packages/assets/src/tasks → tasks}/generateTrack.js +26 -23
  18. package/dist/tasks/generateTrackFragmentIndex.d.ts +4 -0
  19. package/dist/{packages/assets/src/tasks → tasks}/generateTrackFragmentIndex.js +12 -6
  20. package/package.json +14 -10
  21. package/src/tasks/cacheImage.ts +1 -1
  22. package/src/tasks/findOrCreateCaptions.ts +18 -11
  23. package/src/tasks/generateTrack.ts +36 -31
  24. package/src/tasks/generateTrackFragmentIndex.ts +16 -9
  25. package/dist/lib/av/MP4File.cjs +0 -187
  26. package/dist/lib/util/execPromise.cjs +0 -6
  27. package/dist/lib/util/execPromise.js +0 -6
  28. package/dist/packages/assets/src/Probe.cjs +0 -224
  29. package/dist/packages/assets/src/VideoRenderOptions.cjs +0 -36
  30. package/dist/packages/assets/src/idempotentTask.cjs +0 -57
  31. package/dist/packages/assets/src/index.cjs +0 -20
  32. package/dist/packages/assets/src/index.d.ts +0 -9
  33. package/dist/packages/assets/src/index.js +0 -20
  34. package/dist/packages/assets/src/md5.cjs +0 -60
  35. package/dist/packages/assets/src/mp4FileWritable.cjs +0 -21
  36. package/dist/packages/assets/src/tasks/cacheImage.cjs +0 -22
  37. package/dist/packages/assets/src/tasks/cacheImage.d.ts +0 -1
  38. package/dist/packages/assets/src/tasks/findOrCreateCaptions.cjs +0 -26
  39. package/dist/packages/assets/src/tasks/findOrCreateCaptions.d.ts +0 -1
  40. package/dist/packages/assets/src/tasks/findOrCreateCaptions.js +0 -26
  41. package/dist/packages/assets/src/tasks/generateTrack.cjs +0 -52
  42. package/dist/packages/assets/src/tasks/generateTrack.d.ts +0 -1
  43. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.cjs +0 -105
  44. package/dist/packages/assets/src/tasks/generateTrackFragmentIndex.d.ts +0 -1
  45. /package/dist/{lib/av/MP4File.js → MP4File.js} +0 -0
  46. /package/dist/{packages/assets/src/VideoRenderOptions.d.ts → VideoRenderOptions.d.ts} +0 -0
  47. /package/dist/{packages/assets/src/VideoRenderOptions.js → VideoRenderOptions.js} +0 -0
  48. /package/dist/{packages/assets/src/idempotentTask.d.ts → idempotentTask.d.ts} +0 -0
  49. /package/dist/{packages/assets/src/idempotentTask.js → idempotentTask.js} +0 -0
  50. /package/dist/{packages/assets/src/mp4FileWritable.js → mp4FileWritable.js} +0 -0
  51. /package/dist/{packages/assets/src/tasks → tasks}/cacheImage.js +0 -0
@@ -1,5 +1,5 @@
1
1
  import { idempotentTask } from "../idempotentTask.js";
2
- import { MP4File } from "../../../../lib/av/MP4File.js";
2
+ import { MP4File } from "../MP4File.js";
3
3
  import debug from "debug";
4
4
  import { mp4FileWritable } from "../mp4FileWritable.js";
5
5
  import { basename } from "node:path";
@@ -36,7 +36,8 @@ const generateTrackFragmentIndexFromPath = async (absolutePath) => {
36
36
  height: videoTrack.video.height,
37
37
  timescale: track.timescale,
38
38
  sample_count: videoTrack.nb_samples,
39
- duration: 0,
39
+ codec: videoTrack.codec,
40
+ duration: videoTrack.duration,
40
41
  initSegment: {
41
42
  offset: 0,
42
43
  size: fragment.data.byteLength
@@ -57,7 +58,8 @@ const generateTrackFragmentIndexFromPath = async (absolutePath) => {
57
58
  sample_size: audioTrack.audio.sample_size,
58
59
  sample_count: audioTrack.nb_samples,
59
60
  timescale: track.timescale,
60
- duration: 0,
61
+ codec: audioTrack.codec,
62
+ duration: audioTrack.duration,
61
63
  initSegment: {
62
64
  offset: 0,
63
65
  size: fragment.data.byteLength
@@ -85,12 +87,15 @@ const generateTrackFragmentIndexFromPath = async (absolutePath) => {
85
87
  trackByteOffsets[fragment.track] += fragment.data.byteLength;
86
88
  }
87
89
  }
88
- return JSON.stringify(trackFragmentIndexes, null, 2);
90
+ return trackFragmentIndexes;
89
91
  };
90
92
  const generateTrackFragmentIndexTask = idempotentTask({
91
93
  label: "trackFragmentIndex",
92
94
  filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,
93
- runner: generateTrackFragmentIndexFromPath
95
+ runner: async (absolutePath) => {
96
+ const index = await generateTrackFragmentIndexFromPath(absolutePath);
97
+ return JSON.stringify(index, null, 2);
98
+ }
94
99
  });
95
100
  const generateTrackFragmentIndex = async (cacheRoot, absolutePath) => {
96
101
  try {
@@ -101,5 +106,6 @@ const generateTrackFragmentIndex = async (cacheRoot, absolutePath) => {
101
106
  }
102
107
  };
103
108
  export {
104
- generateTrackFragmentIndex
109
+ generateTrackFragmentIndex,
110
+ generateTrackFragmentIndexFromPath
105
111
  };
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "@editframe/assets",
3
- "version": "0.7.0-beta.6",
3
+ "version": "0.8.0-beta.1",
4
4
  "description": "",
5
5
  "exports": {
6
6
  ".": {
7
- "import": {
8
- "default": "./dist/packages/assets/src/index.js",
9
- "types": "./dist/packages/assets/src/index.d.ts"
10
- },
11
- "require": {
12
- "default": "./dist/packages/assets/src/index.cjs",
13
- "types": "./dist/packages/assets/src/index.d.ts"
14
- }
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js"
9
+ },
10
+ "./EncodedAsset.js": {
11
+ "types": "./dist/EncodedAsset.d.ts",
12
+ "import": "./dist/EncodedAsset.js"
13
+ },
14
+ "./MP4File.js": {
15
+ "types": "./dist/MP4File.d.ts",
16
+ "import": "./dist/MP4File.js"
15
17
  }
16
18
  },
17
19
  "type": "module",
@@ -29,8 +31,10 @@
29
31
  },
30
32
  "devDependencies": {
31
33
  "@types/dom-webcodecs": "^0.1.11",
32
- "@types/node": "^20.14.9",
34
+ "@types/node": "^20.14.13",
35
+ "ora": "^8.0.1",
33
36
  "rollup-plugin-tsconfig-paths": "^1.5.2",
37
+ "typescript": "^5.5.4",
34
38
  "vite-plugin-dts": "^3.9.1",
35
39
  "vite-tsconfig-paths": "^4.3.2"
36
40
  }
@@ -1,4 +1,4 @@
1
- import { idempotentTask } from "../idempotentTask";
1
+ import { idempotentTask } from "../idempotentTask.ts";
2
2
  import { createReadStream } from "node:fs";
3
3
 
4
4
  import path from "node:path";
@@ -1,19 +1,26 @@
1
- import { execPromise } from "@/util/execPromise";
2
- import { idempotentTask } from "../idempotentTask";
3
- import debug from "debug";
4
1
  import { basename } from "node:path";
2
+ import { promisify } from "node:util";
3
+ import { exec } from "node:child_process";
4
+
5
+ import debug from "debug";
6
+
7
+ import { idempotentTask } from "../idempotentTask.ts";
8
+
9
+ const execPromise = promisify(exec);
5
10
 
6
11
  const log = debug("ef:generateCaptions");
7
12
 
8
- const generateCaptionData = idempotentTask({
13
+ export const generateCaptionDataFromPath = async (absolutePath: string) => {
14
+ const command = `whisper_timestamped --language en --efficient --output_format vtt ${absolutePath}`;
15
+ log(`Running command: ${command}`);
16
+ const { stdout } = await execPromise(command);
17
+ return stdout;
18
+ };
19
+
20
+ const generateCaptionDataTask = idempotentTask({
9
21
  label: "captions",
10
22
  filename: (absolutePath) => `${basename(absolutePath)}.captions.json`,
11
- runner: async (absolutePath) => {
12
- const command = `whisper_timestamped --language en --efficient --output_format vtt ${absolutePath}`;
13
- log(`Running command: ${command}`);
14
- const { stdout } = await execPromise(command);
15
- return stdout;
16
- },
23
+ runner: generateCaptionDataFromPath,
17
24
  });
18
25
 
19
26
  export const findOrCreateCaptions = async (
@@ -21,7 +28,7 @@ export const findOrCreateCaptions = async (
21
28
  absolutePath: string,
22
29
  ) => {
23
30
  try {
24
- return await generateCaptionData(cacheRoot, absolutePath);
31
+ return await generateCaptionDataTask(cacheRoot, absolutePath);
25
32
  } catch (error) {
26
33
  console.trace("Error finding or creating captions", error);
27
34
  throw error;
@@ -1,41 +1,46 @@
1
- import { idempotentTask } from "../idempotentTask";
2
- import { MP4File } from "@/av/MP4File";
1
+ import { idempotentTask } from "../idempotentTask.ts";
2
+ import { MP4File } from "../MP4File.ts";
3
3
  import debug from "debug";
4
- import { mp4FileWritable } from "../mp4FileWritable";
4
+ import { mp4FileWritable } from "../mp4FileWritable.ts";
5
5
  import { PassThrough } from "node:stream";
6
6
  import { basename } from "node:path";
7
- import { Probe } from "../Probe";
7
+ import { Probe } from "../Probe.ts";
8
8
 
9
- const generateTrackTask = idempotentTask({
9
+ export const generateTrackFromPath = async (
10
+ absolutePath: string,
11
+ trackId: number,
12
+ ) => {
13
+ const log = debug("ef:generateTrackFragment");
14
+ const probe = await Probe.probePath(absolutePath);
15
+ const readStream = probe.createConformingReadstream();
16
+ const mp4File = new MP4File();
17
+
18
+ log(`Generating track for ${absolutePath}`);
19
+ readStream.pipe(mp4FileWritable(mp4File));
20
+
21
+ await new Promise((resolve, reject) => {
22
+ readStream.on("end", resolve);
23
+ readStream.on("error", reject);
24
+ });
25
+
26
+ const trackStream = new PassThrough();
27
+
28
+ for await (const fragment of mp4File.fragmentIterator()) {
29
+ if (fragment.track !== trackId) {
30
+ continue;
31
+ }
32
+ trackStream.write(Buffer.from(fragment.data), "binary");
33
+ }
34
+ trackStream.end();
35
+
36
+ return trackStream;
37
+ };
38
+
39
+ export const generateTrackTask = idempotentTask({
10
40
  label: "track",
11
41
  filename: (absolutePath: string, trackId: number) =>
12
42
  `${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
- },
43
+ runner: generateTrackFromPath,
39
44
  });
40
45
 
41
46
  export const generateTrack = async (
@@ -1,11 +1,13 @@
1
- import { idempotentTask } from "../idempotentTask";
2
- import { MP4File } from "@/av/MP4File";
1
+ import { idempotentTask } from "../idempotentTask.ts";
2
+ import { MP4File } from "../MP4File.ts";
3
3
  import debug from "debug";
4
- import { mp4FileWritable } from "../mp4FileWritable";
4
+ import { mp4FileWritable } from "../mp4FileWritable.ts";
5
5
  import { basename } from "node:path";
6
- import { Probe, type TrackFragmentIndex } from "../Probe";
6
+ import { Probe, type TrackFragmentIndex } from "../Probe.ts";
7
7
 
8
- const generateTrackFragmentIndexFromPath = async (absolutePath: string) => {
8
+ export const generateTrackFragmentIndexFromPath = async (
9
+ absolutePath: string,
10
+ ) => {
9
11
  const log = debug("ef:generateTrackFragment");
10
12
  const probe = await Probe.probePath(absolutePath);
11
13
  const readStream = probe.createConformingReadstream();
@@ -46,7 +48,8 @@ const generateTrackFragmentIndexFromPath = async (absolutePath: string) => {
46
48
  height: videoTrack.video.height,
47
49
  timescale: track.timescale,
48
50
  sample_count: videoTrack.nb_samples,
49
- duration: 0,
51
+ codec: videoTrack.codec,
52
+ duration: videoTrack.duration,
50
53
  initSegment: {
51
54
  offset: 0,
52
55
  size: fragment.data.byteLength,
@@ -69,7 +72,8 @@ const generateTrackFragmentIndexFromPath = async (absolutePath: string) => {
69
72
  sample_size: audioTrack.audio.sample_size,
70
73
  sample_count: audioTrack.nb_samples,
71
74
  timescale: track.timescale,
72
- duration: 0,
75
+ codec: audioTrack.codec,
76
+ duration: audioTrack.duration,
73
77
  initSegment: {
74
78
  offset: 0,
75
79
  size: fragment.data.byteLength,
@@ -98,13 +102,16 @@ const generateTrackFragmentIndexFromPath = async (absolutePath: string) => {
98
102
  trackByteOffsets[fragment.track]! += fragment.data.byteLength;
99
103
  }
100
104
  }
101
- return JSON.stringify(trackFragmentIndexes, null, 2);
105
+ return trackFragmentIndexes;
102
106
  };
103
107
 
104
108
  const generateTrackFragmentIndexTask = idempotentTask({
105
109
  label: "trackFragmentIndex",
106
110
  filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,
107
- runner: generateTrackFragmentIndexFromPath,
111
+ runner: async (absolutePath: string) => {
112
+ const index = await generateTrackFragmentIndexFromPath(absolutePath);
113
+ return JSON.stringify(index, null, 2);
114
+ },
108
115
  });
109
116
 
110
117
  export const generateTrackFragmentIndex = async (
@@ -1,187 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const MP4Box = require("mp4box");
4
- const debug = require("debug");
5
- function _interopNamespaceDefault(e) {
6
- const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
7
- if (e) {
8
- for (const k in e) {
9
- if (k !== "default") {
10
- const d = Object.getOwnPropertyDescriptor(e, k);
11
- Object.defineProperty(n, k, d.get ? d : {
12
- enumerable: true,
13
- get: () => e[k]
14
- });
15
- }
16
- }
17
- }
18
- n.default = e;
19
- return Object.freeze(n);
20
- }
21
- const MP4Box__namespace = /* @__PURE__ */ _interopNamespaceDefault(MP4Box);
22
- const log = debug("ef:av:mp4file");
23
- class MP4File extends MP4Box__namespace.ISOFile {
24
- constructor() {
25
- super(...arguments);
26
- this.readyPromise = new Promise((resolve, reject) => {
27
- this.onReady = () => resolve();
28
- this.onError = reject;
29
- });
30
- this.waitingForSamples = [];
31
- this._hasSeenLastSamples = false;
32
- this._arrayBufferFileStart = 0;
33
- }
34
- setSegmentOptions(id, user, options) {
35
- const trak = this.getTrackById(id);
36
- if (trak) {
37
- trak.nextSample = 0;
38
- this.fragmentedTracks.push({
39
- id,
40
- user,
41
- trak,
42
- segmentStream: null,
43
- nb_samples: "nbSamples" in options && options.nbSamples || 1e3,
44
- rapAlignement: ("rapAlignement" in options && options.rapAlignement) ?? true
45
- });
46
- }
47
- }
48
- /**
49
- * Fragments all tracks in a file into separate array buffers.
50
- */
51
- async fragmentAllTracks() {
52
- const trackBuffers = {};
53
- for await (const segment of this.fragmentIterator()) {
54
- (trackBuffers[segment.track] ??= []).push(segment.data);
55
- }
56
- return trackBuffers;
57
- }
58
- async *fragmentIterator() {
59
- await this.readyPromise;
60
- const trackInfo = {};
61
- for (const videoTrack of this.getInfo().videoTracks) {
62
- trackInfo[videoTrack.id] = { index: 0, complete: false };
63
- this.setSegmentOptions(videoTrack.id, null, {
64
- rapAlignement: true
65
- });
66
- }
67
- for (const audioTrack of this.getInfo().audioTracks) {
68
- trackInfo[audioTrack.id] = { index: 0, complete: false };
69
- const sampleRate = audioTrack.audio.sample_rate;
70
- const probablePacketSize = 1024;
71
- const probableFourSecondsOfSamples = Math.ceil(
72
- sampleRate / probablePacketSize * 4
73
- );
74
- this.setSegmentOptions(audioTrack.id, null, {
75
- nbSamples: probableFourSecondsOfSamples
76
- });
77
- }
78
- const initSegments = this.initializeSegmentation();
79
- for (const initSegment of initSegments) {
80
- yield {
81
- track: initSegment.id,
82
- segment: "init",
83
- data: initSegment.buffer,
84
- complete: false
85
- };
86
- }
87
- const fragmentStartSamples = {};
88
- let finishedReading = false;
89
- const allTracksFinished = () => {
90
- for (const fragmentedTrack of this.fragmentedTracks) {
91
- if (!trackInfo[fragmentedTrack.id]?.complete) {
92
- return false;
93
- }
94
- }
95
- return true;
96
- };
97
- while (!(finishedReading && allTracksFinished())) {
98
- for (const fragTrak of this.fragmentedTracks) {
99
- const trak = fragTrak.trak;
100
- if (trak.nextSample === void 0) {
101
- throw new Error("trak.nextSample is undefined");
102
- }
103
- if (trak.samples === void 0) {
104
- throw new Error("trak.samples is undefined");
105
- }
106
- while (trak.nextSample < trak.samples.length) {
107
- let result = void 0;
108
- const fragTrakNextSample = trak.samples[trak.nextSample];
109
- if (fragTrakNextSample) {
110
- fragmentStartSamples[fragTrak.id] ||= fragTrakNextSample;
111
- }
112
- try {
113
- result = this.createFragment(
114
- fragTrak.id,
115
- trak.nextSample,
116
- fragTrak.segmentStream
117
- );
118
- } catch (error) {
119
- console.error("Failed to createFragment", error);
120
- }
121
- if (result) {
122
- fragTrak.segmentStream = result;
123
- trak.nextSample++;
124
- } else {
125
- finishedReading = await this.waitForMoreSamples();
126
- break;
127
- }
128
- const nextSample = trak.samples[trak.nextSample];
129
- const emitSegment = (
130
- // if rapAlignement is true, we emit a fragment when we have a rap sample coming up next
131
- fragTrak.rapAlignement === true && nextSample?.is_sync || // if rapAlignement is false, we emit a fragment when we have the required number of samples
132
- !fragTrak.rapAlignement && trak.nextSample % fragTrak.nb_samples === 0 || // // if this is the last sample, we emit the fragment
133
- // finished ||
134
- // if we have more samples than the number of samples requested, we emit the fragment
135
- trak.nextSample >= trak.samples.length
136
- );
137
- if (emitSegment) {
138
- const trackInfoForFrag = trackInfo[fragTrak.id];
139
- if (!trackInfoForFrag) {
140
- throw new Error("trackInfoForFrag is undefined");
141
- }
142
- if (trak.nextSample >= trak.samples.length) {
143
- trackInfoForFrag.complete = true;
144
- }
145
- log(
146
- `Yielding fragment #${trackInfoForFrag.index} for track=${fragTrak.id}`
147
- );
148
- const startSample = fragmentStartSamples[fragTrak.id];
149
- const endSample = trak.samples[trak.nextSample - 1];
150
- if (!startSample || !endSample) {
151
- throw new Error("startSample or endSample is undefined");
152
- }
153
- yield {
154
- track: fragTrak.id,
155
- segment: trackInfoForFrag.index,
156
- data: fragTrak.segmentStream.buffer,
157
- complete: trackInfoForFrag.complete,
158
- cts: startSample.cts,
159
- dts: startSample.dts,
160
- duration: endSample.cts - startSample.cts + endSample.duration
161
- };
162
- trackInfoForFrag.index += 1;
163
- fragTrak.segmentStream = null;
164
- delete fragmentStartSamples[fragTrak.id];
165
- }
166
- }
167
- }
168
- finishedReading = await this.waitForMoreSamples();
169
- }
170
- }
171
- waitForMoreSamples() {
172
- if (this._hasSeenLastSamples) {
173
- return Promise.resolve(true);
174
- }
175
- return new Promise((resolve) => {
176
- this.waitingForSamples.push(resolve);
177
- });
178
- }
179
- processSamples(last) {
180
- this._hasSeenLastSamples = last;
181
- for (const observer of this.waitingForSamples) {
182
- observer(last);
183
- }
184
- this.waitingForSamples = [];
185
- }
186
- }
187
- exports.MP4File = MP4File;
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const node_child_process = require("node:child_process");
4
- const node_util = require("node:util");
5
- const execPromise = node_util.promisify(node_child_process.exec);
6
- exports.execPromise = execPromise;
@@ -1,6 +0,0 @@
1
- import { exec } from "node:child_process";
2
- import { promisify } from "node:util";
3
- const execPromise = promisify(exec);
4
- export {
5
- execPromise
6
- };