@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 +26 -26
- package/dist/VideoRenderOptions.d.ts +48 -48
- package/package.json +1 -1
- package/src/tasks/cacheImage.ts +0 -22
- package/src/tasks/cacheRemoteAsset.ts +0 -0
- package/src/tasks/findOrCreateCaptions.ts +0 -36
- package/src/tasks/generateTrack.test.ts +0 -90
- package/src/tasks/generateTrack.ts +0 -47
- package/src/tasks/generateTrackFragmentIndex.test.ts +0 -105
- package/src/tasks/generateTrackFragmentIndex.ts +0 -86
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
package/src/tasks/cacheImage.ts
DELETED
|
@@ -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
|
-
};
|