@editframe/assets 0.16.8-beta.0 → 0.17.6-beta.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/DecoderManager.d.ts +62 -0
- package/dist/DecoderManager.js +114 -0
- package/dist/EncodedAsset.d.ts +58 -16
- package/dist/EncodedAsset.js +436 -565
- package/dist/FrameBuffer.d.ts +62 -0
- package/dist/FrameBuffer.js +89 -0
- package/dist/MP4File.d.ts +9 -1
- package/dist/MP4File.js +205 -219
- package/dist/MP4SampleAnalyzer.d.ts +59 -0
- package/dist/MP4SampleAnalyzer.js +119 -0
- package/dist/Probe.d.ts +1 -0
- package/dist/Probe.js +273 -309
- package/dist/SeekStrategy.d.ts +82 -0
- package/dist/SeekStrategy.js +101 -0
- package/dist/VideoRenderOptions.js +31 -33
- package/dist/idempotentTask.js +78 -78
- package/dist/index.js +1 -15
- package/dist/md5.js +35 -51
- package/dist/memoize.js +9 -12
- package/dist/mp4FileWritable.js +16 -18
- package/dist/tasks/cacheImage.js +13 -15
- package/dist/tasks/findOrCreateCaptions.js +18 -21
- package/dist/tasks/generateTrack.js +45 -63
- package/dist/tasks/generateTrackFragmentIndex.js +88 -101
- package/package.json +4 -4
- package/types.json +1 -1
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
import { exec } from "node:child_process";
|
|
4
|
-
import debug from "debug";
|
|
5
1
|
import { idempotentTask } from "../idempotentTask.js";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { basename } from "node:path";
|
|
6
6
|
const execPromise = promisify(exec);
|
|
7
7
|
const log = debug("ef:generateCaptions");
|
|
8
8
|
const generateCaptionDataFromPath = async (absolutePath) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const command = `whisper_timestamped --language en --efficient --output_format vtt ${absolutePath}`;
|
|
10
|
+
log(`Running command: ${command}`);
|
|
11
|
+
const { stdout } = await execPromise(command);
|
|
12
|
+
return stdout;
|
|
13
13
|
};
|
|
14
14
|
const generateCaptionDataTask = idempotentTask({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
label: "captions",
|
|
16
|
+
filename: (absolutePath) => `${basename(absolutePath)}.captions.json`,
|
|
17
|
+
runner: generateCaptionDataFromPath
|
|
18
18
|
});
|
|
19
19
|
const findOrCreateCaptions = async (cacheRoot, absolutePath) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
};
|
|
27
|
-
export {
|
|
28
|
-
findOrCreateCaptions,
|
|
29
|
-
generateCaptionDataFromPath
|
|
20
|
+
try {
|
|
21
|
+
return await generateCaptionDataTask(cacheRoot, absolutePath);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.trace("Error finding or creating captions", error);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
30
26
|
};
|
|
27
|
+
export { findOrCreateCaptions, generateCaptionDataFromPath };
|
|
@@ -1,72 +1,54 @@
|
|
|
1
|
-
import { idempotentTask } from "../idempotentTask.js";
|
|
2
1
|
import { MP4File } from "../MP4File.js";
|
|
3
|
-
import
|
|
2
|
+
import { Probe } from "../Probe.js";
|
|
3
|
+
import { idempotentTask } from "../idempotentTask.js";
|
|
4
4
|
import { mp4FileWritable } from "../mp4FileWritable.js";
|
|
5
|
-
import
|
|
5
|
+
import debug from "debug";
|
|
6
6
|
import { basename } from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { PassThrough } from "node:stream";
|
|
8
8
|
const generateTrackFromPath = async (absolutePath, trackId) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
trackStream.end();
|
|
40
|
-
} catch (error) {
|
|
41
|
-
trackStream.destroy(error);
|
|
42
|
-
}
|
|
43
|
-
})();
|
|
44
|
-
return trackStream;
|
|
9
|
+
const log = debug("ef:generateTrackFragment");
|
|
10
|
+
const probe = await Probe.probePath(absolutePath);
|
|
11
|
+
const readStream = probe.createConformingReadstream();
|
|
12
|
+
const mp4File = new MP4File();
|
|
13
|
+
const trackStream = new PassThrough();
|
|
14
|
+
log(`Generating track for ${absolutePath}`);
|
|
15
|
+
readStream.pipe(mp4FileWritable(mp4File));
|
|
16
|
+
(async () => {
|
|
17
|
+
try {
|
|
18
|
+
let bytesWritten = 0;
|
|
19
|
+
for await (const fragment of mp4File.fragmentIterator()) {
|
|
20
|
+
log("Writing fragment", fragment);
|
|
21
|
+
if (fragment.track === trackId) {
|
|
22
|
+
const written = trackStream.write(Buffer.from(fragment.data), "binary");
|
|
23
|
+
if (!written) {
|
|
24
|
+
log(`Waiting for drain for track ${trackId}`);
|
|
25
|
+
await new Promise((resolve) => trackStream.once("drain", resolve));
|
|
26
|
+
}
|
|
27
|
+
bytesWritten += fragment.data.byteLength;
|
|
28
|
+
}
|
|
29
|
+
if (!readStream.readableEnded) await Promise.race([new Promise((resolve) => readStream.once("end", resolve)), new Promise((resolve) => readStream.once("data", resolve))]);
|
|
30
|
+
}
|
|
31
|
+
trackStream.end();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
trackStream.destroy(error);
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
return trackStream;
|
|
45
37
|
};
|
|
46
38
|
const generateTrackTask = idempotentTask({
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
label: "track",
|
|
40
|
+
filename: (absolutePath, trackId) => `${basename(absolutePath)}.track-${trackId}.mp4`,
|
|
41
|
+
runner: generateTrackFromPath
|
|
50
42
|
});
|
|
51
43
|
const generateTrack = async (cacheRoot, absolutePath, url) => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return await generateTrackTask(cacheRoot, absolutePath, Number(trackId));
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.error(error);
|
|
64
|
-
console.trace("Error generating track", error);
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
export {
|
|
69
|
-
generateTrack,
|
|
70
|
-
generateTrackFromPath,
|
|
71
|
-
generateTrackTask
|
|
44
|
+
try {
|
|
45
|
+
const trackId = new URL(`http://localhost${url}`).searchParams.get("trackId");
|
|
46
|
+
if (trackId === null) throw new Error("No trackId provided. IT must be specified in the query string: ?trackId=0");
|
|
47
|
+
return await generateTrackTask(cacheRoot, absolutePath, Number(trackId));
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(error);
|
|
50
|
+
console.trace("Error generating track", error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
72
53
|
};
|
|
54
|
+
export { generateTrack, generateTrackFromPath };
|
|
@@ -1,110 +1,97 @@
|
|
|
1
|
-
import { idempotentTask } from "../idempotentTask.js";
|
|
2
1
|
import { MP4File } from "../MP4File.js";
|
|
3
|
-
import
|
|
2
|
+
import { Probe } from "../Probe.js";
|
|
3
|
+
import { idempotentTask } from "../idempotentTask.js";
|
|
4
4
|
import { mp4FileWritable } from "../mp4FileWritable.js";
|
|
5
|
+
import debug from "debug";
|
|
5
6
|
import { basename } from "node:path";
|
|
6
|
-
import { Probe } from "../Probe.js";
|
|
7
7
|
const generateTrackFragmentIndexFromPath = async (absolutePath) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
cts: fragment.cts,
|
|
81
|
-
dts: fragment.dts,
|
|
82
|
-
duration: fragment.duration,
|
|
83
|
-
offset: trackByteOffsets[fragment.track],
|
|
84
|
-
size: fragment.data.byteLength
|
|
85
|
-
});
|
|
86
|
-
trackByteOffsets[fragment.track] += fragment.data.byteLength;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return trackFragmentIndexes;
|
|
8
|
+
const log = debug("ef:generateTrackFragment");
|
|
9
|
+
const probe = await Probe.probePath(absolutePath);
|
|
10
|
+
const readStream = probe.createConformingReadstream();
|
|
11
|
+
const mp4File = new MP4File();
|
|
12
|
+
log(`Generating track fragment index for ${absolutePath}`);
|
|
13
|
+
readStream.pipe(mp4FileWritable(mp4File));
|
|
14
|
+
await new Promise((resolve, reject) => {
|
|
15
|
+
readStream.on("end", resolve);
|
|
16
|
+
readStream.on("error", reject);
|
|
17
|
+
});
|
|
18
|
+
const trackFragmentIndexes = {};
|
|
19
|
+
const trackByteOffsets = {};
|
|
20
|
+
for await (const fragment of mp4File.fragmentIterator()) {
|
|
21
|
+
const track = mp4File.getInfo().tracks.find((track$1) => track$1.id === fragment.track);
|
|
22
|
+
if (!track) throw new Error("Track not found");
|
|
23
|
+
if (fragment.segment === "init") {
|
|
24
|
+
trackByteOffsets[fragment.track] = fragment.data.byteLength;
|
|
25
|
+
if (track?.type === "video") {
|
|
26
|
+
const videoTrack = mp4File.getInfo().videoTracks.find((track$1) => track$1.id === fragment.track);
|
|
27
|
+
if (!videoTrack) throw new Error("Video track not found");
|
|
28
|
+
trackFragmentIndexes[fragment.track] = {
|
|
29
|
+
track: fragment.track,
|
|
30
|
+
type: "video",
|
|
31
|
+
width: videoTrack.video.width,
|
|
32
|
+
height: videoTrack.video.height,
|
|
33
|
+
timescale: track.timescale,
|
|
34
|
+
sample_count: videoTrack.nb_samples,
|
|
35
|
+
codec: videoTrack.codec,
|
|
36
|
+
duration: videoTrack.duration,
|
|
37
|
+
initSegment: {
|
|
38
|
+
offset: 0,
|
|
39
|
+
size: fragment.data.byteLength
|
|
40
|
+
},
|
|
41
|
+
segments: []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (track?.type === "audio") {
|
|
45
|
+
const audioTrack = mp4File.getInfo().audioTracks.find((track$1) => track$1.id === fragment.track);
|
|
46
|
+
if (!audioTrack) throw new Error("Audio track not found");
|
|
47
|
+
trackFragmentIndexes[fragment.track] = {
|
|
48
|
+
track: fragment.track,
|
|
49
|
+
type: "audio",
|
|
50
|
+
channel_count: audioTrack.audio.channel_count,
|
|
51
|
+
sample_rate: audioTrack.audio.sample_rate,
|
|
52
|
+
sample_size: audioTrack.audio.sample_size,
|
|
53
|
+
sample_count: audioTrack.nb_samples,
|
|
54
|
+
timescale: track.timescale,
|
|
55
|
+
codec: audioTrack.codec,
|
|
56
|
+
duration: audioTrack.duration,
|
|
57
|
+
initSegment: {
|
|
58
|
+
offset: 0,
|
|
59
|
+
size: fragment.data.byteLength
|
|
60
|
+
},
|
|
61
|
+
segments: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
const fragmentIndex = trackFragmentIndexes[fragment.track];
|
|
66
|
+
if (trackByteOffsets[fragment.track] === void 0) throw new Error("Fragment index not found");
|
|
67
|
+
if (!fragmentIndex) throw new Error("Fragment index not found");
|
|
68
|
+
fragmentIndex.duration += fragment.duration;
|
|
69
|
+
fragmentIndex.segments.push({
|
|
70
|
+
cts: fragment.cts,
|
|
71
|
+
dts: fragment.dts,
|
|
72
|
+
duration: fragment.duration,
|
|
73
|
+
offset: trackByteOffsets[fragment.track],
|
|
74
|
+
size: fragment.data.byteLength
|
|
75
|
+
});
|
|
76
|
+
trackByteOffsets[fragment.track] += fragment.data.byteLength;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return trackFragmentIndexes;
|
|
90
80
|
};
|
|
91
81
|
const generateTrackFragmentIndexTask = idempotentTask({
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
82
|
+
label: "trackFragmentIndex",
|
|
83
|
+
filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,
|
|
84
|
+
runner: async (absolutePath) => {
|
|
85
|
+
const index = await generateTrackFragmentIndexFromPath(absolutePath);
|
|
86
|
+
return JSON.stringify(index, null, 2);
|
|
87
|
+
}
|
|
98
88
|
});
|
|
99
89
|
const generateTrackFragmentIndex = async (cacheRoot, absolutePath) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
};
|
|
107
|
-
export {
|
|
108
|
-
generateTrackFragmentIndex,
|
|
109
|
-
generateTrackFragmentIndexFromPath
|
|
90
|
+
try {
|
|
91
|
+
return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.trace("Error generating track fragment index", error);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
110
96
|
};
|
|
97
|
+
export { generateTrackFragmentIndex, generateTrackFragmentIndexFromPath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.6-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"debug": "^4.3.5",
|
|
41
41
|
"mp4box": "^0.5.2",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"ora": "^8.0.1",
|
|
43
|
+
"zod": "^3.23.8"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/dom-webcodecs": "^0.1.11",
|
|
47
47
|
"@types/node": "^20.14.13",
|
|
48
48
|
"rollup-plugin-tsconfig-paths": "^1.5.2",
|
|
49
49
|
"typescript": "^5.5.4",
|
|
50
|
-
"vite-plugin-dts": "^4.
|
|
50
|
+
"vite-plugin-dts": "^4.5.4",
|
|
51
51
|
"vite-tsconfig-paths": "^4.3.2"
|
|
52
52
|
}
|
|
53
53
|
}
|