@editframe/assets 0.26.3-beta.0 → 0.30.0-beta.13

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.d.ts CHANGED
@@ -93,6 +93,8 @@ declare const VideoStreamSchema: z$1.ZodObject<{
93
93
  bit_rate: z$1.ZodOptional<z$1.ZodString>;
94
94
  disposition: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
95
95
  }, "strip", z$1.ZodTypeAny, {
96
+ width: number;
97
+ height: number;
96
98
  index: number;
97
99
  codec_name: string;
98
100
  codec_long_name: string;
@@ -103,8 +105,6 @@ declare const VideoStreamSchema: z$1.ZodObject<{
103
105
  avg_frame_rate: string;
104
106
  time_base: string;
105
107
  disposition: Record<string, unknown>;
106
- width: number;
107
- height: number;
108
108
  coded_width: number;
109
109
  coded_height: number;
110
110
  start_pts?: number | undefined;
@@ -115,6 +115,8 @@ declare const VideoStreamSchema: z$1.ZodObject<{
115
115
  profile?: string | undefined;
116
116
  level?: number | undefined;
117
117
  }, {
118
+ width: number;
119
+ height: number;
118
120
  index: number;
119
121
  codec_name: string;
120
122
  codec_long_name: string;
@@ -125,8 +127,6 @@ declare const VideoStreamSchema: z$1.ZodObject<{
125
127
  avg_frame_rate: string;
126
128
  time_base: string;
127
129
  disposition: Record<string, unknown>;
128
- width: number;
129
- height: number;
130
130
  coded_width: number;
131
131
  coded_height: number;
132
132
  start_pts?: number | undefined;
@@ -227,6 +227,8 @@ declare const StreamSchema: z$1.ZodDiscriminatedUnion<"codec_type", [z$1.ZodObje
227
227
  bit_rate: z$1.ZodOptional<z$1.ZodString>;
228
228
  disposition: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
229
229
  }, "strip", z$1.ZodTypeAny, {
230
+ width: number;
231
+ height: number;
230
232
  index: number;
231
233
  codec_name: string;
232
234
  codec_long_name: string;
@@ -237,8 +239,6 @@ declare const StreamSchema: z$1.ZodDiscriminatedUnion<"codec_type", [z$1.ZodObje
237
239
  avg_frame_rate: string;
238
240
  time_base: string;
239
241
  disposition: Record<string, unknown>;
240
- width: number;
241
- height: number;
242
242
  coded_width: number;
243
243
  coded_height: number;
244
244
  start_pts?: number | undefined;
@@ -249,6 +249,8 @@ declare const StreamSchema: z$1.ZodDiscriminatedUnion<"codec_type", [z$1.ZodObje
249
249
  profile?: string | undefined;
250
250
  level?: number | undefined;
251
251
  }, {
252
+ width: number;
253
+ height: number;
252
254
  index: number;
253
255
  codec_name: string;
254
256
  codec_long_name: string;
@@ -259,8 +261,6 @@ declare const StreamSchema: z$1.ZodDiscriminatedUnion<"codec_type", [z$1.ZodObje
259
261
  avg_frame_rate: string;
260
262
  time_base: string;
261
263
  disposition: Record<string, unknown>;
262
- width: number;
263
- height: number;
264
264
  coded_width: number;
265
265
  coded_height: number;
266
266
  start_pts?: number | undefined;
@@ -380,6 +380,8 @@ declare const ProbeSchema: z$1.ZodObject<{
380
380
  bit_rate: z$1.ZodOptional<z$1.ZodString>;
381
381
  disposition: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
382
382
  }, "strip", z$1.ZodTypeAny, {
383
+ width: number;
384
+ height: number;
383
385
  index: number;
384
386
  codec_name: string;
385
387
  codec_long_name: string;
@@ -390,8 +392,6 @@ declare const ProbeSchema: z$1.ZodObject<{
390
392
  avg_frame_rate: string;
391
393
  time_base: string;
392
394
  disposition: Record<string, unknown>;
393
- width: number;
394
- height: number;
395
395
  coded_width: number;
396
396
  coded_height: number;
397
397
  start_pts?: number | undefined;
@@ -402,6 +402,8 @@ declare const ProbeSchema: z$1.ZodObject<{
402
402
  profile?: string | undefined;
403
403
  level?: number | undefined;
404
404
  }, {
405
+ width: number;
406
+ height: number;
405
407
  index: number;
406
408
  codec_name: string;
407
409
  codec_long_name: string;
@@ -412,8 +414,6 @@ declare const ProbeSchema: z$1.ZodObject<{
412
414
  avg_frame_rate: string;
413
415
  time_base: string;
414
416
  disposition: Record<string, unknown>;
415
- width: number;
416
- height: number;
417
417
  coded_width: number;
418
418
  coded_height: number;
419
419
  start_pts?: number | undefined;
@@ -512,6 +512,8 @@ declare const ProbeSchema: z$1.ZodObject<{
512
512
  start_pts?: number | undefined;
513
513
  start_time?: number | undefined;
514
514
  } | {
515
+ width: number;
516
+ height: number;
515
517
  index: number;
516
518
  codec_name: string;
517
519
  codec_long_name: string;
@@ -522,8 +524,6 @@ declare const ProbeSchema: z$1.ZodObject<{
522
524
  avg_frame_rate: string;
523
525
  time_base: string;
524
526
  disposition: Record<string, unknown>;
525
- width: number;
526
- height: number;
527
527
  coded_width: number;
528
528
  coded_height: number;
529
529
  start_pts?: number | undefined;
@@ -576,6 +576,8 @@ declare const ProbeSchema: z$1.ZodObject<{
576
576
  start_pts?: number | undefined;
577
577
  start_time?: number | undefined;
578
578
  } | {
579
+ width: number;
580
+ height: number;
579
581
  index: number;
580
582
  codec_name: string;
581
583
  codec_long_name: string;
@@ -586,8 +588,6 @@ declare const ProbeSchema: z$1.ZodObject<{
586
588
  avg_frame_rate: string;
587
589
  time_base: string;
588
590
  disposition: Record<string, unknown>;
589
- width: number;
590
- height: number;
591
591
  coded_width: number;
592
592
  coded_height: number;
593
593
  start_pts?: number | undefined;
@@ -757,6 +757,8 @@ declare const PacketProbeSchema: z$1.ZodObject<{
757
757
  bit_rate: z$1.ZodOptional<z$1.ZodString>;
758
758
  disposition: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
759
759
  }, "strip", z$1.ZodTypeAny, {
760
+ width: number;
761
+ height: number;
760
762
  index: number;
761
763
  codec_name: string;
762
764
  codec_long_name: string;
@@ -767,8 +769,6 @@ declare const PacketProbeSchema: z$1.ZodObject<{
767
769
  avg_frame_rate: string;
768
770
  time_base: string;
769
771
  disposition: Record<string, unknown>;
770
- width: number;
771
- height: number;
772
772
  coded_width: number;
773
773
  coded_height: number;
774
774
  start_pts?: number | undefined;
@@ -779,6 +779,8 @@ declare const PacketProbeSchema: z$1.ZodObject<{
779
779
  profile?: string | undefined;
780
780
  level?: number | undefined;
781
781
  }, {
782
+ width: number;
783
+ height: number;
782
784
  index: number;
783
785
  codec_name: string;
784
786
  codec_long_name: string;
@@ -789,8 +791,6 @@ declare const PacketProbeSchema: z$1.ZodObject<{
789
791
  avg_frame_rate: string;
790
792
  time_base: string;
791
793
  disposition: Record<string, unknown>;
792
- width: number;
793
- height: number;
794
794
  coded_width: number;
795
795
  coded_height: number;
796
796
  start_pts?: number | undefined;
@@ -865,6 +865,8 @@ declare const PacketProbeSchema: z$1.ZodObject<{
865
865
  start_pts?: number | undefined;
866
866
  start_time?: number | undefined;
867
867
  } | {
868
+ width: number;
869
+ height: number;
868
870
  index: number;
869
871
  codec_name: string;
870
872
  codec_long_name: string;
@@ -875,8 +877,6 @@ declare const PacketProbeSchema: z$1.ZodObject<{
875
877
  avg_frame_rate: string;
876
878
  time_base: string;
877
879
  disposition: Record<string, unknown>;
878
- width: number;
879
- height: number;
880
880
  coded_width: number;
881
881
  coded_height: number;
882
882
  start_pts?: number | undefined;
@@ -939,6 +939,8 @@ declare const PacketProbeSchema: z$1.ZodObject<{
939
939
  start_pts?: number | undefined;
940
940
  start_time?: number | undefined;
941
941
  } | {
942
+ width: number;
943
+ height: number;
942
944
  index: number;
943
945
  codec_name: string;
944
946
  codec_long_name: string;
@@ -949,8 +951,6 @@ declare const PacketProbeSchema: z$1.ZodObject<{
949
951
  avg_frame_rate: string;
950
952
  time_base: string;
951
953
  disposition: Record<string, unknown>;
952
- width: number;
953
- height: number;
954
954
  coded_width: number;
955
955
  coded_height: number;
956
956
  start_pts?: number | undefined;
@@ -1039,6 +1039,8 @@ declare abstract class ProbeBase {
1039
1039
  start_pts?: number | undefined;
1040
1040
  start_time?: number | undefined;
1041
1041
  } | {
1042
+ width: number;
1043
+ height: number;
1042
1044
  index: number;
1043
1045
  codec_name: string;
1044
1046
  codec_long_name: string;
@@ -1049,8 +1051,6 @@ declare abstract class ProbeBase {
1049
1051
  avg_frame_rate: string;
1050
1052
  time_base: string;
1051
1053
  disposition: Record<string, unknown>;
1052
- width: number;
1053
- height: number;
1054
1054
  coded_width: number;
1055
1055
  coded_height: number;
1056
1056
  start_pts?: number | undefined;
@@ -76,19 +76,6 @@ declare const VideoRenderOptions: z.ZodObject<{
76
76
  numberOfChannels: number;
77
77
  }>;
78
78
  }, "strip", z.ZodTypeAny, {
79
- audio: {
80
- codec: string;
81
- bitrate: number;
82
- sampleRate: number;
83
- numberOfChannels: number;
84
- };
85
- video: {
86
- width: number;
87
- height: number;
88
- framerate: number;
89
- codec: string;
90
- bitrate: number;
91
- };
92
79
  sequenceNumber: number;
93
80
  keyframeIntervalMs: number;
94
81
  fromMs: number;
@@ -98,15 +85,6 @@ declare const VideoRenderOptions: z.ZodObject<{
98
85
  alignedFromUs: number;
99
86
  alignedToUs: number;
100
87
  isInitSegment: boolean;
101
- noVideo?: boolean | undefined;
102
- noAudio?: boolean | undefined;
103
- }, {
104
- audio: {
105
- codec: string;
106
- bitrate: number;
107
- sampleRate: number;
108
- numberOfChannels: number;
109
- };
110
88
  video: {
111
89
  width: number;
112
90
  height: number;
@@ -114,6 +92,15 @@ declare const VideoRenderOptions: z.ZodObject<{
114
92
  codec: string;
115
93
  bitrate: number;
116
94
  };
95
+ audio: {
96
+ codec: string;
97
+ bitrate: number;
98
+ sampleRate: number;
99
+ numberOfChannels: number;
100
+ };
101
+ noVideo?: boolean | undefined;
102
+ noAudio?: boolean | undefined;
103
+ }, {
117
104
  sequenceNumber: number;
118
105
  keyframeIntervalMs: number;
119
106
  fromMs: number;
@@ -123,6 +110,19 @@ declare const VideoRenderOptions: z.ZodObject<{
123
110
  alignedFromUs: number;
124
111
  alignedToUs: number;
125
112
  isInitSegment: boolean;
113
+ video: {
114
+ width: number;
115
+ height: number;
116
+ framerate: number;
117
+ codec: string;
118
+ bitrate: number;
119
+ };
120
+ audio: {
121
+ codec: string;
122
+ bitrate: number;
123
+ sampleRate: number;
124
+ numberOfChannels: number;
125
+ };
126
126
  noVideo?: boolean | undefined;
127
127
  noAudio?: boolean | undefined;
128
128
  }>;
@@ -131,19 +131,6 @@ declare const VideoRenderOptions: z.ZodObject<{
131
131
  mode: "canvas" | "screenshot";
132
132
  strategy: "v1" | "v2";
133
133
  encoderOptions: {
134
- audio: {
135
- codec: string;
136
- bitrate: number;
137
- sampleRate: number;
138
- numberOfChannels: number;
139
- };
140
- video: {
141
- width: number;
142
- height: number;
143
- framerate: number;
144
- codec: string;
145
- bitrate: number;
146
- };
147
134
  sequenceNumber: number;
148
135
  keyframeIntervalMs: number;
149
136
  fromMs: number;
@@ -153,6 +140,19 @@ declare const VideoRenderOptions: z.ZodObject<{
153
140
  alignedFromUs: number;
154
141
  alignedToUs: number;
155
142
  isInitSegment: boolean;
143
+ video: {
144
+ width: number;
145
+ height: number;
146
+ framerate: number;
147
+ codec: string;
148
+ bitrate: number;
149
+ };
150
+ audio: {
151
+ codec: string;
152
+ bitrate: number;
153
+ sampleRate: number;
154
+ numberOfChannels: number;
155
+ };
156
156
  noVideo?: boolean | undefined;
157
157
  noAudio?: boolean | undefined;
158
158
  };
@@ -163,19 +163,6 @@ declare const VideoRenderOptions: z.ZodObject<{
163
163
  mode: "canvas" | "screenshot";
164
164
  strategy: "v1" | "v2";
165
165
  encoderOptions: {
166
- audio: {
167
- codec: string;
168
- bitrate: number;
169
- sampleRate: number;
170
- numberOfChannels: number;
171
- };
172
- video: {
173
- width: number;
174
- height: number;
175
- framerate: number;
176
- codec: string;
177
- bitrate: number;
178
- };
179
166
  sequenceNumber: number;
180
167
  keyframeIntervalMs: number;
181
168
  fromMs: number;
@@ -185,6 +172,19 @@ declare const VideoRenderOptions: z.ZodObject<{
185
172
  alignedFromUs: number;
186
173
  alignedToUs: number;
187
174
  isInitSegment: boolean;
175
+ video: {
176
+ width: number;
177
+ height: number;
178
+ framerate: number;
179
+ codec: string;
180
+ bitrate: number;
181
+ };
182
+ audio: {
183
+ codec: string;
184
+ bitrate: number;
185
+ sampleRate: number;
186
+ numberOfChannels: number;
187
+ };
188
188
  noVideo?: boolean | undefined;
189
189
  noAudio?: boolean | undefined;
190
190
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/assets",
3
- "version": "0.26.3-beta.0",
3
+ "version": "0.30.0-beta.13",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,22 +0,0 @@
1
- import { idempotentTask } from "../idempotentTask.js";
2
- import { createReadStream } from "node:fs";
3
-
4
- import path from "node:path";
5
-
6
- const cacheImageTask = idempotentTask({
7
- label: "image",
8
- filename: (absolutePath: string) => path.basename(absolutePath),
9
- runner: async (absolutePath) => {
10
- return createReadStream(absolutePath);
11
- },
12
- });
13
-
14
- export const cacheImage = async (cacheRoot: string, absolutePath: string) => {
15
- try {
16
- return await cacheImageTask(cacheRoot, absolutePath);
17
- } catch (error) {
18
- console.error(error);
19
- console.trace("Error caching image", error);
20
- throw error;
21
- }
22
- };
File without changes
@@ -1,36 +0,0 @@
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.js";
8
-
9
- const execPromise = promisify(exec);
10
-
11
- const log = debug("ef:generateCaptions");
12
-
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({
21
- label: "captions",
22
- filename: (absolutePath) => `${basename(absolutePath)}.captions.json`,
23
- runner: generateCaptionDataFromPath,
24
- });
25
-
26
- export const findOrCreateCaptions = async (
27
- cacheRoot: string,
28
- absolutePath: string,
29
- ) => {
30
- try {
31
- return await generateCaptionDataTask(cacheRoot, absolutePath);
32
- } catch (error) {
33
- console.trace("Error finding or creating captions", error);
34
- throw error;
35
- }
36
- };
@@ -1,90 +0,0 @@
1
- import { test, describe, assert } from "vitest";
2
- import { generateTrackFromPath } from "./generateTrack";
3
- import { Writable } from "node:stream";
4
- import { pipeline } from "node:stream/promises";
5
-
6
- describe("generateTrack", () => {
7
- test("should generate video track", async () => {
8
- const trackStream = await generateTrackFromPath("test-assets/10s-bars.mp4", 1);
9
-
10
- // Collect the generated track data
11
- const chunks: Buffer[] = [];
12
- const dest = new Writable({
13
- write(chunk, _encoding, callback) {
14
- chunks.push(chunk);
15
- callback();
16
- }
17
- });
18
-
19
- await pipeline(trackStream, dest);
20
-
21
- // Verify we got MP4 data
22
- assert.isAbove(chunks.length, 0, "Should generate MP4 chunks");
23
- const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
24
- assert.isAbove(totalSize, 1000, "Should generate substantial MP4 data");
25
-
26
- // Verify it's valid MP4 by checking for ftyp box
27
- const allData = Buffer.concat(chunks);
28
- const ftypIndex = allData.indexOf('ftyp');
29
- assert.isAbove(ftypIndex, -1, "Should contain ftyp box (valid MP4)");
30
-
31
- console.log(`Generated ${totalSize} bytes for video track`);
32
- }, 15000);
33
-
34
- test("should generate audio track", async () => {
35
- const trackStream = await generateTrackFromPath("test-assets/10s-bars.mp4", 2);
36
-
37
- // Collect the generated track data
38
- const chunks: Buffer[] = [];
39
- const dest = new Writable({
40
- write(chunk, _encoding, callback) {
41
- chunks.push(chunk);
42
- callback();
43
- }
44
- });
45
-
46
- await pipeline(trackStream, dest);
47
-
48
- // Verify we got MP4 data
49
- assert.isAbove(chunks.length, 0, "Should generate MP4 chunks");
50
- const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
51
- assert.isAbove(totalSize, 1000, "Should generate substantial MP4 data");
52
-
53
- // Verify it's valid MP4 by checking for ftyp box
54
- const allData = Buffer.concat(chunks);
55
- const ftypIndex = allData.indexOf('ftyp');
56
- assert.isAbove(ftypIndex, -1, "Should contain ftyp box (valid MP4)");
57
-
58
- console.log(`Generated ${totalSize} bytes for audio track`);
59
- }, 15000);
60
-
61
- test("should handle invalid track IDs gracefully", async () => {
62
- try {
63
- await generateTrackFromPath("test-assets/frame-count.mp4", 5);
64
- assert.fail("Should have thrown for invalid track ID");
65
- } catch (error) {
66
- assert.instanceOf(error, Error);
67
- assert.include(error.message, "Track 5 not found");
68
- }
69
- });
70
-
71
- test("should work with single track files", async () => {
72
- const trackStream = await generateTrackFromPath("test-assets/frame-count.mp4", 1);
73
-
74
- const chunks: Buffer[] = [];
75
- const dest = new Writable({
76
- write(chunk, _encoding, callback) {
77
- chunks.push(chunk);
78
- callback();
79
- }
80
- });
81
-
82
- await pipeline(trackStream, dest);
83
-
84
- const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
85
- assert.isAbove(totalSize, 1000, "Should generate data for single track file");
86
-
87
- console.log(`Generated ${totalSize} bytes for single track file`);
88
- }, 15000);
89
- });
90
-
@@ -1,47 +0,0 @@
1
- import { idempotentTask } from "../idempotentTask.js";
2
- import debug from "debug";
3
- import { basename } from "node:path";
4
- import { generateSingleTrackFromPath } from "../generateSingleTrack.js";
5
-
6
- export const generateTrackFromPath = async (
7
- absolutePath: string,
8
- trackId: number,
9
- ) => {
10
- const log = debug("ef:generateTrackFragment");
11
- log(`Generating track ${trackId} for ${absolutePath}`);
12
-
13
- // Use the single-track implementation
14
- const result = await generateSingleTrackFromPath(absolutePath, trackId);
15
-
16
- // Return just the stream for compatibility with existing API
17
- return result.stream;
18
- };
19
-
20
- export const generateTrackTask = idempotentTask({
21
- label: "track",
22
- filename: (absolutePath: string, trackId: number) =>
23
- `${basename(absolutePath)}.track-${trackId}.mp4`,
24
- runner: generateTrackFromPath,
25
- });
26
-
27
- export const generateTrack = async (
28
- cacheRoot: string,
29
- absolutePath: string,
30
- url: string,
31
- ) => {
32
- try {
33
- const trackId = new URL(`http://localhost${url}`).searchParams.get(
34
- "trackId",
35
- );
36
- if (trackId === null) {
37
- throw new Error(
38
- "No trackId provided. It must be specified in the query string: ?trackId=1 (for video) or ?trackId=2 (for audio)",
39
- );
40
- }
41
- return await generateTrackTask(cacheRoot, absolutePath, Number(trackId));
42
- } catch (error) {
43
- console.error(error);
44
- console.trace("Error generating track", error);
45
- throw error;
46
- }
47
- };
@@ -1,105 +0,0 @@
1
- import { test, describe, assert } from "vitest";
2
- import { generateTrackFragmentIndexFromPath } from "./generateTrackFragmentIndex";
3
-
4
- describe("generateTrackFragmentIndex", () => {
5
- test("should generate fragment index", async () => {
6
- const fragmentIndex = await generateTrackFragmentIndexFromPath("test-assets/10s-bars.mp4");
7
-
8
- // Should have multiple tracks
9
- const trackIds = Object.keys(fragmentIndex).map(Number);
10
- assert.isAbove(trackIds.length, 0, "Should have tracks");
11
-
12
- for (const trackId of trackIds) {
13
- const track = fragmentIndex[trackId]!;
14
-
15
- // Verify track structure
16
- assert.oneOf(track.type, ['video', 'audio'], `Track ${trackId} should be video or audio`);
17
- assert.isNumber(track.track, `Track ${trackId} should have track number`);
18
- assert.isNumber(track.timescale, `Track ${trackId} should have timescale`);
19
- assert.isNumber(track.duration, `Track ${trackId} should have duration`);
20
- assert.isNumber(track.sample_count, `Track ${trackId} should have sample_count`);
21
- assert.isString(track.codec, `Track ${trackId} should have codec`);
22
-
23
- // Verify init segment
24
- assert.equal(track.initSegment.offset, 0, `Track ${trackId} init should start at 0`);
25
- assert.isAbove(track.initSegment.size, 0, `Track ${trackId} init should have size`);
26
-
27
- // Verify segments
28
- assert.isArray(track.segments, `Track ${trackId} should have segments array`);
29
- assert.isAbove(track.segments.length, 0, `Track ${trackId} should have segments`);
30
-
31
- // Check each segment
32
- for (const segment of track.segments) {
33
- assert.isNumber(segment.cts, `Track ${trackId} segment should have cts`);
34
- assert.isNumber(segment.dts, `Track ${trackId} segment should have dts`);
35
- assert.isNumber(segment.duration, `Track ${trackId} segment should have duration`);
36
- assert.isNumber(segment.offset, `Track ${trackId} segment should have offset`);
37
- assert.isNumber(segment.size, `Track ${trackId} segment should have size`);
38
- }
39
-
40
- // Type-specific checks
41
- if (track.type === 'video') {
42
- assert.isNumber(track.width, `Video track ${trackId} should have width`);
43
- assert.isNumber(track.height, `Video track ${trackId} should have height`);
44
- } else if (track.type === 'audio') {
45
- assert.isNumber(track.channel_count, `Audio track ${trackId} should have channel_count`);
46
- assert.isNumber(track.sample_rate, `Audio track ${trackId} should have sample_rate`);
47
- assert.isNumber(track.sample_size, `Audio track ${trackId} should have sample_size`);
48
- }
49
- }
50
- });
51
-
52
- test("should handle single track files", async () => {
53
- const fragmentIndex = await generateTrackFragmentIndexFromPath("test-assets/frame-count.mp4");
54
-
55
- const trackIds = Object.keys(fragmentIndex).map(Number);
56
- assert.equal(trackIds.length, 1, "Should have exactly one track");
57
-
58
- const track = fragmentIndex[trackIds[0]!]!;
59
- assert.equal(track.type, "video", "Should be video track");
60
- assert.isAbove(track.segments.length, 0, "Should have segments");
61
- });
62
-
63
- test("should generate consistent results with original implementation", async () => {
64
- // Test that the new implementation produces similar structure to the old one
65
- const fragmentIndex = await generateTrackFragmentIndexFromPath("test-assets/bars-n-tone.mp4");
66
-
67
- const trackIds = Object.keys(fragmentIndex).map(Number);
68
- assert.equal(trackIds.length, 2, "Should have video and audio tracks");
69
-
70
- // Should have both video and audio
71
- const videoTrack = Object.values(fragmentIndex).find(t => t.type === 'video');
72
- const audioTrack = Object.values(fragmentIndex).find(t => t.type === 'audio');
73
-
74
- assert.exists(videoTrack, "Should have video track");
75
- assert.exists(audioTrack, "Should have audio track");
76
-
77
- // Video track checks
78
- assert.isAbove(videoTrack.width, 0, "Video should have width");
79
- assert.isAbove(videoTrack.height, 0, "Video should have height");
80
- assert.isAbove(videoTrack.segments.length, 0, "Video should have segments");
81
-
82
- // Audio track checks
83
- assert.isAbove(audioTrack.channel_count, 0, "Audio should have channels");
84
- assert.isAbove(audioTrack.sample_rate, 0, "Audio should have sample rate");
85
- assert.isAbove(audioTrack.segments.length, 0, "Audio should have segments");
86
- }, 20000);
87
-
88
- test("should preserve timing offset detection", async () => {
89
- // Test with a file that might have timing offsets
90
- const fragmentIndex = await generateTrackFragmentIndexFromPath("test-assets/frame-count.mp4");
91
-
92
- const trackIds = Object.keys(fragmentIndex).map(Number);
93
- const track = fragmentIndex[trackIds[0]!]!;
94
-
95
- assert.equal(track.startTimeOffsetMs, 200);
96
- assert.equal(track.type, "video");
97
-
98
- // Should still have valid timing data
99
- assert.isAbove(track.duration, 0, "Should have positive duration");
100
- for (const segment of track.segments) {
101
- assert.isAbove(segment.duration, 0, "Each segment should have positive duration");
102
- }
103
- }, 15000);
104
- });
105
-
@@ -1,86 +0,0 @@
1
- import { idempotentTask } from "../idempotentTask.js";
2
- import debug from "debug";
3
- import { basename } from "node:path";
4
- import { Probe } from "../Probe.js";
5
- import { generateFragmentIndex } from "../generateFragmentIndex.js";
6
- import type { TrackFragmentIndex } from "../Probe.js";
7
-
8
- export const generateTrackFragmentIndexFromPath = async (
9
- absolutePath: string,
10
- ) => {
11
- const log = debug("ef:generateTrackFragment");
12
- const probe = await Probe.probePath(absolutePath);
13
-
14
- // Extract timing offset from probe metadata (same logic as processISOBMFF.ts)
15
- let startTimeOffsetMs: number | undefined;
16
-
17
- // First check format-level start_time
18
- if (probe.format.start_time && Number(probe.format.start_time) !== 0) {
19
- startTimeOffsetMs = Number(probe.format.start_time) * 1000;
20
- log(`Extracted format start_time offset: ${probe.format.start_time}s (${startTimeOffsetMs}ms)`);
21
- } else {
22
- // Check for video stream start_time (more common)
23
- const videoStream = probe.streams.find(stream => stream.codec_type === 'video');
24
- if (videoStream && videoStream.start_time && Number(videoStream.start_time) !== 0) {
25
- startTimeOffsetMs = Number(videoStream.start_time) * 1000;
26
- log(`Extracted video stream start_time offset: ${videoStream.start_time}s (${startTimeOffsetMs}ms)`);
27
- } else {
28
- log("No format/stream timing offset found - will detect from composition time");
29
- }
30
- }
31
-
32
- log(`Generating track fragment index for ${absolutePath} using single-track approach`);
33
-
34
- // FIXED: Generate fragment indexes from individual single-track files
35
- // This ensures byte offsets match the actual single-track files that clients will request
36
- const trackFragmentIndexes: Record<number, TrackFragmentIndex> = {};
37
-
38
- // Process each audio/video stream as a separate track
39
- for (let streamIndex = 0; streamIndex < probe.streams.length; streamIndex++) {
40
- const stream = probe.streams[streamIndex]!;
41
-
42
- // Only process audio and video streams
43
- if (stream.codec_type !== 'audio' && stream.codec_type !== 'video') {
44
- continue;
45
- }
46
-
47
- const trackId = streamIndex + 1; // Convert to 1-based track ID
48
- log(`Processing track ${trackId} (${stream.codec_type})`);
49
-
50
- // Generate single-track file and its fragment index
51
- const trackStream = probe.createTrackReadstream(streamIndex);
52
- const trackIdMapping = { 0: trackId }; // Map single-track stream index 0 to original track ID
53
-
54
- const singleTrackIndexes = await generateFragmentIndex(
55
- trackStream,
56
- startTimeOffsetMs,
57
- trackIdMapping
58
- );
59
-
60
- // Merge the single-track index into the combined result
61
- Object.assign(trackFragmentIndexes, singleTrackIndexes);
62
- }
63
-
64
- return trackFragmentIndexes;
65
- };
66
-
67
- const generateTrackFragmentIndexTask = idempotentTask({
68
- label: "trackFragmentIndex",
69
- filename: (absolutePath) => `${basename(absolutePath)}.tracks.json`,
70
- runner: async (absolutePath: string) => {
71
- const index = await generateTrackFragmentIndexFromPath(absolutePath);
72
- return JSON.stringify(index, null, 2);
73
- },
74
- });
75
-
76
- export const generateTrackFragmentIndex = async (
77
- cacheRoot: string,
78
- absolutePath: string,
79
- ) => {
80
- try {
81
- return await generateTrackFragmentIndexTask(cacheRoot, absolutePath);
82
- } catch (error) {
83
- console.trace("Error generating track fragment index", error);
84
- throw error;
85
- }
86
- };