@editframe/assets 0.24.1-beta.0 → 0.25.1-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.
Files changed (39) hide show
  1. package/dist/Probe.d.ts +922 -967
  2. package/dist/Probe.js +13 -8
  3. package/dist/Probe.js.map +1 -0
  4. package/dist/VideoRenderOptions.d.ts +190 -185
  5. package/dist/VideoRenderOptions.js +5 -0
  6. package/dist/VideoRenderOptions.js.map +1 -0
  7. package/dist/generateFragmentIndex.d.ts +8 -3
  8. package/dist/generateFragmentIndex.js +9 -1
  9. package/dist/generateFragmentIndex.js.map +1 -0
  10. package/dist/generateSingleTrack.js +7 -2
  11. package/dist/generateSingleTrack.js.map +1 -0
  12. package/dist/idempotentTask.d.ts +10 -11
  13. package/dist/idempotentTask.js +5 -0
  14. package/dist/idempotentTask.js.map +1 -0
  15. package/dist/index.d.ts +10 -10
  16. package/dist/index.js +2 -1
  17. package/dist/md5.d.ts +11 -6
  18. package/dist/md5.js +5 -0
  19. package/dist/md5.js.map +1 -0
  20. package/dist/tasks/cacheImage.d.ts +7 -1
  21. package/dist/tasks/cacheImage.js +6 -1
  22. package/dist/tasks/cacheImage.js.map +1 -0
  23. package/dist/tasks/findOrCreateCaptions.d.ts +8 -2
  24. package/dist/tasks/findOrCreateCaptions.js +8 -3
  25. package/dist/tasks/findOrCreateCaptions.js.map +1 -0
  26. package/dist/tasks/generateTrack.d.ts +8 -3
  27. package/dist/tasks/generateTrack.js +5 -0
  28. package/dist/tasks/generateTrack.js.map +1 -0
  29. package/dist/tasks/generateTrackFragmentIndex.d.ts +9 -3
  30. package/dist/tasks/generateTrackFragmentIndex.js +6 -1
  31. package/dist/tasks/generateTrackFragmentIndex.js.map +1 -0
  32. package/dist/truncateDecimal.js +4 -0
  33. package/dist/truncateDecimal.js.map +1 -0
  34. package/package.json +29 -22
  35. package/tsdown.config.ts +13 -0
  36. package/dist/generateSingleTrack.d.ts +0 -8
  37. package/dist/memoize.d.ts +0 -2
  38. package/dist/tasks/cacheRemoteAsset.d.ts +0 -0
  39. package/dist/truncateDecimal.d.ts +0 -1
package/dist/Probe.js CHANGED
@@ -4,8 +4,10 @@ import { promisify } from "node:util";
4
4
  import { createReadStream } from "node:fs";
5
5
  import * as z$1 from "zod";
6
6
  import debug from "debug";
7
- var execPromise = promisify(exec);
8
- var log = debug("ef:assets:probe");
7
+
8
+ //#region src/Probe.ts
9
+ const execPromise = promisify(exec);
10
+ const log = debug("ef:assets:probe");
9
11
  const AudioStreamSchema = z$1.object({
10
12
  index: z$1.number(),
11
13
  codec_name: z$1.string(),
@@ -52,7 +54,7 @@ const VideoStreamSchema = z$1.object({
52
54
  bit_rate: z$1.string().optional(),
53
55
  disposition: z$1.record(z$1.unknown())
54
56
  });
55
- var ProbeFormatSchema = z$1.object({
57
+ const ProbeFormatSchema = z$1.object({
56
58
  filename: z$1.string(),
57
59
  nb_streams: z$1.number(),
58
60
  nb_programs: z$1.number(),
@@ -71,12 +73,12 @@ const DataStreamSchema = z$1.object({
71
73
  duration_ts: z$1.number().optional(),
72
74
  start_pts: z$1.number().optional()
73
75
  });
74
- var StreamSchema = z$1.discriminatedUnion("codec_type", [
76
+ const StreamSchema = z$1.discriminatedUnion("codec_type", [
75
77
  AudioStreamSchema,
76
78
  VideoStreamSchema,
77
79
  DataStreamSchema
78
80
  ]);
79
- var PacketSchema = z$1.object({
81
+ const PacketSchema = z$1.object({
80
82
  stream_index: z$1.number(),
81
83
  pts: z$1.number(),
82
84
  pts_time: z$1.coerce.number(),
@@ -86,16 +88,16 @@ var PacketSchema = z$1.object({
86
88
  pos: z$1.coerce.number().optional(),
87
89
  flags: z$1.string().optional()
88
90
  });
89
- var ProbeSchema = z$1.object({
91
+ const ProbeSchema = z$1.object({
90
92
  streams: z$1.array(StreamSchema),
91
93
  format: ProbeFormatSchema
92
94
  });
93
- var PacketProbeSchema = z$1.object({
95
+ const PacketProbeSchema = z$1.object({
94
96
  packets: z$1.array(PacketSchema),
95
97
  format: ProbeFormatSchema,
96
98
  streams: z$1.array(StreamSchema)
97
99
  });
98
- var buildProbeArgs = (options) => {
100
+ const buildProbeArgs = (options) => {
99
101
  const streamEntries = "stream=index,codec_name,codec_long_name,codec_type,codec_tag_string,codec_tag,profile,level,width,height,coded_width,coded_height,r_frame_rate,avg_frame_rate,time_base,start_pts,start_time,duration_ts,duration,bit_rate,sample_fmt,sample_rate,channels,channel_layout,bits_per_sample,initial_padding,disposition";
100
102
  return [
101
103
  "-v",
@@ -416,4 +418,7 @@ var PacketProbe = class PacketProbe extends ProbeBase {
416
418
  return truncateDecimal(Math.round(totalDuration * 1e4) / 1e4, 5);
417
419
  }
418
420
  };
421
+
422
+ //#endregion
419
423
  export { PacketProbe, Probe };
424
+ //# sourceMappingURL=Probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Probe.js","names":["z","chunks: Uint8Array[]","absolutePath: string"],"sources":["../src/Probe.ts"],"sourcesContent":["import { exec, spawn } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { createReadStream } from \"node:fs\";\n\nimport * as z from \"zod\";\nimport debug from \"debug\";\nimport type { Readable } from \"node:stream\";\nimport { truncateDecimal } from \"./truncateDecimal\";\n\nconst execPromise = promisify(exec);\n\nconst log = debug(\"ef:assets:probe\");\n\nexport const AudioStreamSchema = z.object({\n index: z.number(),\n codec_name: z.string(),\n codec_long_name: z.string(),\n codec_type: z.literal(\"audio\"),\n codec_tag_string: z.string(),\n codec_tag: z.string(),\n sample_fmt: z.string(),\n sample_rate: z.string(),\n channels: z.number(),\n channel_layout: z.string().optional(),\n bits_per_sample: z.number(),\n initial_padding: z.number().optional(),\n r_frame_rate: z.string(),\n avg_frame_rate: z.string(),\n time_base: z.string(),\n start_pts: z.number().optional(),\n start_time: z.coerce.number().optional(),\n duration_ts: z.number(),\n duration: z.coerce.number(),\n bit_rate: z.string(),\n disposition: z.record(z.unknown()),\n});\n\nexport type AudioStreamSchema = z.infer<typeof AudioStreamSchema>;\n\nexport const VideoStreamSchema = z.object({\n index: z.number(),\n codec_name: z.string(),\n codec_long_name: z.string(),\n codec_type: z.literal(\"video\"),\n codec_tag_string: z.string(),\n codec_tag: z.string(),\n profile: z.string().optional(),\n level: z.number().optional(),\n width: z.number(),\n height: z.number(),\n coded_width: z.number(),\n coded_height: z.number(),\n r_frame_rate: z.string(),\n avg_frame_rate: z.string(),\n time_base: z.string(),\n start_pts: z.number().optional(),\n start_time: z.coerce.number().optional(),\n duration_ts: z.number().optional(),\n duration: z.coerce.number().optional(),\n bit_rate: z.string().optional(),\n disposition: z.record(z.unknown()),\n});\n\nexport type VideoStreamSchema = z.infer<typeof VideoStreamSchema>;\n\nconst ProbeFormatSchema = z.object({\n filename: z.string(),\n nb_streams: z.number(),\n nb_programs: z.number(),\n format_name: z.string(),\n format_long_name: z.string(),\n start_time: z.string().optional(),\n duration: z.string().optional(),\n size: z.string().optional(),\n bit_rate: z.string().optional(),\n probe_score: z.number(),\n});\n\nexport const DataStreamSchema = z.object({\n index: z.number(),\n codec_type: z.literal(\"data\"),\n duration: z.string().optional(),\n duration_ts: z.number().optional(),\n start_pts: z.number().optional(),\n});\n\nexport type DataStreamSchema = z.infer<typeof DataStreamSchema>;\n\nconst StreamSchema = z.discriminatedUnion(\"codec_type\", [\n AudioStreamSchema,\n VideoStreamSchema,\n DataStreamSchema,\n]);\n\nexport type StreamSchema = z.infer<typeof StreamSchema>;\n\nconst PacketSchema = z.object({\n stream_index: z.number(),\n pts: z.number(),\n pts_time: z.coerce.number(),\n dts: z.number(),\n dts_time: z.coerce.number(),\n duration: z.coerce.number().optional(),\n pos: z.coerce.number().optional(),\n flags: z.string().optional(),\n});\n\nexport type PacketSchema = z.infer<typeof PacketSchema>;\n\nconst ProbeSchema = z.object({\n streams: z.array(StreamSchema),\n format: ProbeFormatSchema,\n});\n\nconst PacketProbeSchema = z.object({\n packets: z.array(PacketSchema),\n format: ProbeFormatSchema,\n streams: z.array(StreamSchema),\n});\n\nexport type ProbeSchema = z.infer<typeof ProbeSchema>;\nexport type PacketProbeSchema = z.infer<typeof PacketProbeSchema>;\n\nexport interface TrackSegment {\n cts: number;\n dts: number;\n duration: number;\n offset: number;\n size: number;\n}\n\nexport interface AudioTrackFragmentIndex {\n track: number;\n type: \"audio\";\n timescale: number;\n duration: number;\n channel_count: number;\n sample_rate: number;\n sample_size: number;\n sample_count: number;\n codec: string;\n startTimeOffsetMs?: number;\n initSegment: {\n offset: 0;\n size: number;\n };\n segments: Array<TrackSegment>;\n}\n\nexport interface VideoTrackFragmentIndex {\n track: number;\n type: \"video\";\n timescale: number;\n duration: number;\n width: number;\n height: number;\n sample_count: number;\n codec: string;\n startTimeOffsetMs?: number;\n initSegment: {\n offset: 0;\n size: number;\n };\n segments: Array<TrackSegment>;\n}\n\nexport type TrackFragmentIndex =\n | AudioTrackFragmentIndex\n | VideoTrackFragmentIndex;\n\nconst buildProbeArgs = (options: { showPackets?: boolean }) => {\n const streamEntries = \"stream=index,codec_name,codec_long_name,codec_type,codec_tag_string,codec_tag,profile,level,width,height,coded_width,coded_height,r_frame_rate,avg_frame_rate,time_base,start_pts,start_time,duration_ts,duration,bit_rate,sample_fmt,sample_rate,channels,channel_layout,bits_per_sample,initial_padding,disposition\";\n const packetEntries = \"packet=stream_index,pts,pts_time,dts,dts_time,duration,pos,flags\";\n\n return [\n \"-v\", \"error\",\n \"-show_format\",\n \"-show_streams\",\n \"-of\", \"json\",\n ...(options.showPackets\n ? [\"-show_entries\", `${streamEntries}:${packetEntries}`]\n : [\"-show_entries\", streamEntries]\n ),\n ];\n}\n\nclass FFProbeRunner {\n static async probePath(absolutePath: string, includePackets: boolean): Promise<any> {\n const probeCommand = `ffprobe ${buildProbeArgs({ showPackets: includePackets }).join(\" \")} ${absolutePath}`;\n log(\"Probing\", probeCommand);\n const probeResult = await execPromise(probeCommand);\n log(\"Probe result\", probeResult.stdout);\n log(\"Probe stderr\", probeResult.stderr);\n return JSON.parse(probeResult.stdout);\n }\n\n static async probeStream(stream: Readable, includePackets: boolean): Promise<any> {\n const probe = spawn(\n \"ffprobe\",\n [\n \"-i\",\n \"-\",\n ...buildProbeArgs({ showPackets: includePackets }),\n ],\n { stdio: [\"pipe\", \"pipe\", \"pipe\"] },\n );\n\n const chunks: Uint8Array[] = [];\n\n // Handle process exit/error before data processing\n const processExit = new Promise<never>((_, reject) => {\n probe.on(\"exit\", (code) => {\n if (code !== 0) {\n reject(new Error(`ffprobe exited with code ${code}`));\n }\n });\n probe.on(\"error\", (err) => reject(err));\n });\n\n probe.stderr.on(\"data\", (data) => {\n log(data.toString());\n });\n\n probe.stdout.on(\"data\", (data) => {\n chunks.push(data);\n });\n\n // Handle pipe errors\n probe.stdin.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EPIPE\") {\n log(\"ffprobe closed input pipe\");\n return;\n }\n log(\"ffprobe stdin error\", error);\n });\n\n stream.pipe(probe.stdin);\n\n try {\n const json = await Promise.race([\n new Promise<any>((resolve, reject) => {\n probe.stdout.on(\"end\", () => {\n try {\n const buffer = Buffer.concat(chunks).toString(\"utf8\");\n resolve(JSON.parse(buffer));\n } catch (error) {\n reject(error);\n }\n });\n }),\n processExit,\n ]);\n\n return json;\n } finally {\n // Clean up regardless of success or failure\n stream.unpipe(probe.stdin);\n probe.stdin.end();\n stream.destroy();\n }\n }\n}\n\nabstract class ProbeBase {\n abstract data: ProbeSchema | PacketProbeSchema;\n\n get audioStreams() {\n return this.data.streams.filter(\n (stream) => stream.codec_type === \"audio\",\n ) as AudioStreamSchema[];\n }\n\n get videoStreams() {\n return this.data.streams.filter(\n (stream) => stream.codec_type === \"video\",\n ) as VideoStreamSchema[];\n }\n\n get streams() {\n return this.data.streams;\n }\n\n get format() {\n return this.data.format;\n }\n\n get mustReencodeAudio() {\n return this.audioStreams.some((stream) => stream.codec_name !== \"aac\");\n }\n\n get mustReencodeVideo() {\n return false;\n }\n\n get mustRemux() {\n return this.format.format_name !== \"mp4\" ||\n this.data.streams.some(stream => stream.codec_type !== \"audio\" && stream.codec_type !== \"video\");\n }\n\n get hasNonAudioOrVideoStreams() {\n return this.data.streams.some(\n (stream) => stream.codec_type !== \"audio\" && stream.codec_type !== \"video\",\n );\n }\n\n get hasAudio() {\n return this.audioStreams.length > 0;\n }\n\n get hasVideo() {\n return this.videoStreams.length > 0;\n }\n\n get isAudioOnly() {\n return this.audioStreams.length > 0 && this.videoStreams.length === 0;\n }\n\n get isMp3() {\n return this.audioStreams.some((stream) =>\n stream.codec_name === \"mp3\"\n );\n }\n\n get isVideoOnly() {\n return this.audioStreams.length === 0 && this.videoStreams.length > 0;\n }\n\n get mustProcess() {\n return this.mustReencodeAudio || this.mustReencodeVideo || this.mustRemux;\n }\n\n get audioTimebase() {\n const audioStream = this.audioStreams[0];\n if (!audioStream) {\n return null;\n }\n const [num, den] = audioStream.time_base.split(\"/\").map(Number);\n if (num === undefined || den === undefined) {\n return null;\n }\n return { num, den };\n }\n\n get videoTimebase() {\n const videoStream = this.videoStreams[0];\n if (!videoStream) {\n return null;\n }\n const [num, den] = videoStream.time_base.split(\"/\").map(Number);\n if (num === undefined || den === undefined) {\n return null;\n }\n return { num, den };\n }\n\n get ffmpegAudioInputOptions() {\n if (!this.hasAudio) {\n return [];\n }\n if (this.isMp3) {\n return [\"-c:a\", \"mp3\"];\n }\n return []\n }\n\n get ffmpegVideoInputOptions() {\n return [];\n }\n\n get ffmpegAudioOutputOptions() {\n if (!this.hasAudio) {\n return [];\n }\n if (this.mustReencodeAudio) {\n // biome-ignore format: keep cli argument paired together\n return [\n \"-c:a\", \"aac\",\n \"-b:a\", \"192k\",\n \"-ar\", \"48000\",\n ];\n }\n return [\"-c:a\", \"copy\"];\n }\n\n get ffmpegVideoOutputOptions() {\n if (!this.hasVideo) {\n return [];\n }\n if (this.mustReencodeVideo) {\n // biome-ignore format: keep cli argument paired together\n return [\n \"-c:v\", \"h264\",\n // Filter out SEI NAL units that aren't supported by the webcodecs decoder\n \"-bsf:v\", \"filter_units=remove_types=6\",\n \"-pix_fmt\", \"yuv420p\",\n ];\n }\n // biome-ignore format: keep cli argument paired together\n return [\n \"-c:v\", \"copy\",\n // Filter out SEI NAL units that aren't supported by the webcodecs decoder\n \"-bsf:v\", \"filter_units=remove_types=6\",\n ];\n }\n\n protected constructor(\n protected absolutePath: string,\n ) { }\n\n createConformingReadstream() {\n if (this.absolutePath === \"pipe:0\") {\n throw new Error(\"Cannot create conforming readstream from pipe\");\n }\n if (!this.mustProcess) {\n return createReadStream(this.absolutePath);\n }\n\n const fragmenterArgs = this.isAudioOnly ? [\n \"-movflags\", \"frag_keyframe\",\n \"-frag_duration\", \"4000000\", // Fragment every 4 seconds (in microseconds)\n ] : [\n \"-movflags\", \"frag_keyframe\",\n ]\n\n // biome-ignore format: keep cli argument paired together\n const ffmpegConformanceArgs = [\n ...this.ffmpegAudioInputOptions,\n ...this.ffmpegVideoInputOptions,\n \"-i\", this.absolutePath,\n ...this.ffmpegAudioOutputOptions,\n ...this.ffmpegVideoOutputOptions,\n \"-f\", \"mp4\",\n \"-bitexact\", // Ensure deterministic output\n ...fragmenterArgs,\n \"pipe:1\"\n ];\n\n\n log(\"Running ffmpeg\", ffmpegConformanceArgs);\n\n const ffmpegConformer = spawn(\"ffmpeg\", ffmpegConformanceArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n ffmpegConformer.stderr.on(\"data\", (data) => {\n log(\"CONFORMER: \", data.toString());\n });\n\n // biome-ignore format: keep cli argument paired together\n const ffmpegFragmentArgs = [\n \"-i\", \"-\",\n \"-c\", \"copy\",\n \"-f\", \"mp4\",\n \"-bitexact\", // Ensure deterministic output\n ...fragmenterArgs,\n \"pipe:1\"\n ];\n\n log(\"Running ffmpeg\", ffmpegFragmentArgs);\n\n const ffmpegFragmenter = spawn(\"ffmpeg\", ffmpegFragmentArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n ffmpegConformer.stdout.pipe(ffmpegFragmenter.stdin);\n ffmpegFragmenter.stderr.on(\"data\", (data) => {\n log(\"FRAGMENTER: \", data.toString());\n });\n\n ffmpegConformer.on(\"error\", (error) => {\n ffmpegFragmenter.stdout.emit(\"error\", error);\n })\n\n ffmpegFragmenter.on(\"error\", (error) => {\n ffmpegFragmenter.stdout.emit(\"error\", error);\n })\n\n return ffmpegFragmenter.stdout;\n }\n\n createTrackReadstream(trackIndex: number) {\n if (this.absolutePath === \"pipe:0\") {\n throw new Error(\"Cannot create track readstream from pipe\");\n }\n\n const track = this.data.streams[trackIndex];\n if (!track) {\n throw new Error(`Track ${trackIndex} not found`);\n }\n\n const isAudioTrack = track.codec_type === \"audio\";\n const isVideoTrack = track.codec_type === \"video\";\n\n if (!isAudioTrack && !isVideoTrack) {\n throw new Error(`Track ${trackIndex} is not audio or video`);\n }\n\n const fragmenterArgs = isAudioTrack ? [\n \"-movflags\", \"empty_moov+default_base_moof\",\n \"-frag_duration\", \"4000000\", // Fragment every 4 seconds (in microseconds)\n ] : [\n \"-movflags\", \"frag_keyframe+empty_moov+default_base_moof\",\n ];\n\n // Create single-track MP4 with proper fragmentation\n // Use conforming stream system to handle codec compatibility\n const codecOptions = isAudioTrack && this.mustReencodeAudio\n ? this.ffmpegAudioOutputOptions\n : [\"-c\", \"copy\"];\n\n const ffmpegArgs = [\n ...this.ffmpegAudioInputOptions,\n ...this.ffmpegVideoInputOptions,\n \"-i\", this.absolutePath,\n \"-map\", `0:${trackIndex}`, // Select only this track\n ...codecOptions, // Use conforming stream codec options\n \"-f\", \"mp4\",\n \"-bitexact\", // Ensure deterministic output\n ...fragmenterArgs,\n \"pipe:1\"\n ];\n\n log(\"Creating track stream\", ffmpegArgs);\n\n const ffmpegProcess = spawn(\"ffmpeg\", ffmpegArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n ffmpegProcess.stderr.on(\"data\", (data) => {\n log(`TRACK ${trackIndex}: `, data.toString());\n });\n\n ffmpegProcess.on(\"error\", (error) => {\n ffmpegProcess.stdout.emit(\"error\", error);\n });\n\n return ffmpegProcess.stdout;\n }\n}\n\nexport class Probe extends ProbeBase {\n data: ProbeSchema;\n\n static async probePath(absolutePath: string): Promise<Probe> {\n const json = await FFProbeRunner.probePath(absolutePath, false);\n return new Probe(absolutePath, json);\n }\n\n static async probeStream(stream: Readable): Promise<Probe> {\n const json = await FFProbeRunner.probeStream(stream, false);\n return new Probe(\"pipe:0\", json);\n }\n\n constructor(\n absolutePath: string,\n rawData: any,\n ) {\n super(absolutePath);\n this.data = ProbeSchema.parse(rawData);\n }\n}\n\nexport class PacketProbe extends ProbeBase {\n data: PacketProbeSchema;\n\n static async probePath(absolutePath: string): Promise<PacketProbe> {\n const json = await FFProbeRunner.probePath(absolutePath, true);\n return new PacketProbe(absolutePath, json);\n }\n\n static async probeStream(stream: Readable): Promise<PacketProbe> {\n const json = await FFProbeRunner.probeStream(stream, true);\n return new PacketProbe(\"pipe:0\", json);\n }\n\n constructor(\n absolutePath: string,\n rawData: any,\n ) {\n super(absolutePath);\n this.data = PacketProbeSchema.parse(rawData);\n }\n\n get packets() {\n return this.data.packets;\n }\n\n get bestEffortAudioDuration() {\n const stream = this.audioStreams[0];\n if (!stream) {\n throw new Error(\"No audio stream found\");\n }\n return truncateDecimal(((stream.duration_ts ?? 0) - (stream.start_pts ?? 0)) / (this.audioTimebase?.den ?? 0), 5);\n }\n\n get videoPacketDuration() {\n const videoStream = this.videoStreams[0];\n if (!videoStream) {\n return [];\n }\n const videoPackets = this.packets.filter(packet => packet.stream_index === videoStream.index);\n\n const frameRate = videoStream.r_frame_rate;\n const [num, den] = frameRate.split('/').map(Number);\n if (!num || !den) {\n return [];\n }\n const packetDuration = den / num;\n\n // Calculate duration using actual packet PTS timing data\n if (videoPackets.length === 0) {\n return [];\n }\n\n const ptsTimes = videoPackets.map(p => p.pts_time);\n const minPts = Math.min(...ptsTimes);\n const maxPts = Math.max(...ptsTimes);\n const totalDuration = (maxPts - minPts) + packetDuration;\n\n return truncateDecimal(Math.round(totalDuration * 10000) / 10000, 5);\n }\n}\n\n\n\n"],"mappings":"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"}
@@ -1,193 +1,198 @@
1
- import { z } from 'zod';
2
- export declare const VideoRenderOptions: z.ZodObject<{
3
- mode: z.ZodEnum<["canvas", "screenshot"]>;
4
- strategy: z.ZodEnum<["v1", "v2"]>;
5
- showFrameBox: z.ZodOptional<z.ZodBoolean>;
6
- enableTracing: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
7
- encoderOptions: z.ZodObject<{
8
- sequenceNumber: z.ZodNumber;
9
- keyframeIntervalMs: z.ZodNumber;
10
- /**
11
- * The nominal start time of the segment in milliseconds.
12
- * Does not include any padding.
13
- */
14
- fromMs: z.ZodNumber;
15
- /**
16
- * The nominal end time of the segment in milliseconds.
17
- * Does not include any padding.
18
- */
19
- toMs: z.ZodNumber;
20
- /**
21
- * Whether or not this segment has audio padding at the start.
22
- */
23
- shouldPadStart: z.ZodBoolean;
24
- /**
25
- * Whether or not this segment has audio padding at the end.
26
- */
27
- shouldPadEnd: z.ZodBoolean;
28
- /**
29
- * The aligned start time of the segment in microseconds.
30
- * This includes the padding if any.
31
- */
32
- alignedFromUs: z.ZodNumber;
33
- /**
34
- * The aligned end time of the segment in microseconds.
35
- * This includes the padding if any.
36
- */
37
- alignedToUs: z.ZodNumber;
38
- isInitSegment: z.ZodBoolean;
39
- noVideo: z.ZodOptional<z.ZodBoolean>;
40
- noAudio: z.ZodOptional<z.ZodBoolean>;
41
- video: z.ZodObject<{
42
- width: z.ZodNumber;
43
- height: z.ZodNumber;
44
- framerate: z.ZodNumber;
45
- codec: z.ZodString;
46
- bitrate: z.ZodNumber;
47
- }, "strip", z.ZodTypeAny, {
48
- width: number;
49
- height: number;
50
- framerate: number;
51
- codec: string;
52
- bitrate: number;
53
- }, {
54
- width: number;
55
- height: number;
56
- framerate: number;
57
- codec: string;
58
- bitrate: number;
59
- }>;
60
- audio: z.ZodObject<{
61
- sampleRate: z.ZodNumber;
62
- codec: z.ZodString;
63
- numberOfChannels: z.ZodNumber;
64
- bitrate: z.ZodNumber;
65
- }, "strip", z.ZodTypeAny, {
66
- codec: string;
67
- bitrate: number;
68
- sampleRate: number;
69
- numberOfChannels: number;
70
- }, {
71
- codec: string;
72
- bitrate: number;
73
- sampleRate: number;
74
- numberOfChannels: number;
75
- }>;
1
+ import { z } from "zod";
2
+
3
+ //#region src/VideoRenderOptions.d.ts
4
+ declare const VideoRenderOptions: z.ZodObject<{
5
+ mode: z.ZodEnum<["canvas", "screenshot"]>;
6
+ strategy: z.ZodEnum<["v1", "v2"]>;
7
+ showFrameBox: z.ZodOptional<z.ZodBoolean>;
8
+ enableTracing: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
9
+ encoderOptions: z.ZodObject<{
10
+ sequenceNumber: z.ZodNumber;
11
+ keyframeIntervalMs: z.ZodNumber;
12
+ /**
13
+ * The nominal start time of the segment in milliseconds.
14
+ * Does not include any padding.
15
+ */
16
+ fromMs: z.ZodNumber;
17
+ /**
18
+ * The nominal end time of the segment in milliseconds.
19
+ * Does not include any padding.
20
+ */
21
+ toMs: z.ZodNumber;
22
+ /**
23
+ * Whether or not this segment has audio padding at the start.
24
+ */
25
+ shouldPadStart: z.ZodBoolean;
26
+ /**
27
+ * Whether or not this segment has audio padding at the end.
28
+ */
29
+ shouldPadEnd: z.ZodBoolean;
30
+ /**
31
+ * The aligned start time of the segment in microseconds.
32
+ * This includes the padding if any.
33
+ */
34
+ alignedFromUs: z.ZodNumber;
35
+ /**
36
+ * The aligned end time of the segment in microseconds.
37
+ * This includes the padding if any.
38
+ */
39
+ alignedToUs: z.ZodNumber;
40
+ isInitSegment: z.ZodBoolean;
41
+ noVideo: z.ZodOptional<z.ZodBoolean>;
42
+ noAudio: z.ZodOptional<z.ZodBoolean>;
43
+ video: z.ZodObject<{
44
+ width: z.ZodNumber;
45
+ height: z.ZodNumber;
46
+ framerate: z.ZodNumber;
47
+ codec: z.ZodString;
48
+ bitrate: z.ZodNumber;
76
49
  }, "strip", z.ZodTypeAny, {
77
- audio: {
78
- codec: string;
79
- bitrate: number;
80
- sampleRate: number;
81
- numberOfChannels: number;
82
- };
83
- video: {
84
- width: number;
85
- height: number;
86
- framerate: number;
87
- codec: string;
88
- bitrate: number;
89
- };
90
- sequenceNumber: number;
91
- keyframeIntervalMs: number;
92
- fromMs: number;
93
- toMs: number;
94
- shouldPadStart: boolean;
95
- shouldPadEnd: boolean;
96
- alignedFromUs: number;
97
- alignedToUs: number;
98
- isInitSegment: boolean;
99
- noVideo?: boolean | undefined;
100
- noAudio?: boolean | undefined;
50
+ width: number;
51
+ height: number;
52
+ framerate: number;
53
+ codec: string;
54
+ bitrate: number;
101
55
  }, {
102
- audio: {
103
- codec: string;
104
- bitrate: number;
105
- sampleRate: number;
106
- numberOfChannels: number;
107
- };
108
- video: {
109
- width: number;
110
- height: number;
111
- framerate: number;
112
- codec: string;
113
- bitrate: number;
114
- };
115
- sequenceNumber: number;
116
- keyframeIntervalMs: number;
117
- fromMs: number;
118
- toMs: number;
119
- shouldPadStart: boolean;
120
- shouldPadEnd: boolean;
121
- alignedFromUs: number;
122
- alignedToUs: number;
123
- isInitSegment: boolean;
124
- noVideo?: boolean | undefined;
125
- noAudio?: boolean | undefined;
56
+ width: number;
57
+ height: number;
58
+ framerate: number;
59
+ codec: string;
60
+ bitrate: number;
126
61
  }>;
127
- fetchHost: z.ZodString;
62
+ audio: z.ZodObject<{
63
+ sampleRate: z.ZodNumber;
64
+ codec: z.ZodString;
65
+ numberOfChannels: z.ZodNumber;
66
+ bitrate: z.ZodNumber;
67
+ }, "strip", z.ZodTypeAny, {
68
+ codec: string;
69
+ bitrate: number;
70
+ sampleRate: number;
71
+ numberOfChannels: number;
72
+ }, {
73
+ codec: string;
74
+ bitrate: number;
75
+ sampleRate: number;
76
+ numberOfChannels: number;
77
+ }>;
78
+ }, "strip", z.ZodTypeAny, {
79
+ sequenceNumber: number;
80
+ keyframeIntervalMs: number;
81
+ fromMs: number;
82
+ toMs: number;
83
+ shouldPadStart: boolean;
84
+ shouldPadEnd: boolean;
85
+ alignedFromUs: number;
86
+ alignedToUs: number;
87
+ isInitSegment: boolean;
88
+ video: {
89
+ width: number;
90
+ height: number;
91
+ framerate: number;
92
+ codec: string;
93
+ bitrate: number;
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
+ }, {
104
+ sequenceNumber: number;
105
+ keyframeIntervalMs: number;
106
+ fromMs: number;
107
+ toMs: number;
108
+ shouldPadStart: boolean;
109
+ shouldPadEnd: boolean;
110
+ alignedFromUs: number;
111
+ alignedToUs: number;
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
+ noVideo?: boolean | undefined;
127
+ noAudio?: boolean | undefined;
128
+ }>;
129
+ fetchHost: z.ZodString;
128
130
  }, "strip", z.ZodTypeAny, {
129
- mode: "canvas" | "screenshot";
130
- strategy: "v1" | "v2";
131
- encoderOptions: {
132
- audio: {
133
- codec: string;
134
- bitrate: number;
135
- sampleRate: number;
136
- numberOfChannels: number;
137
- };
138
- video: {
139
- width: number;
140
- height: number;
141
- framerate: number;
142
- codec: string;
143
- bitrate: number;
144
- };
145
- sequenceNumber: number;
146
- keyframeIntervalMs: number;
147
- fromMs: number;
148
- toMs: number;
149
- shouldPadStart: boolean;
150
- shouldPadEnd: boolean;
151
- alignedFromUs: number;
152
- alignedToUs: number;
153
- isInitSegment: boolean;
154
- noVideo?: boolean | undefined;
155
- noAudio?: boolean | undefined;
131
+ mode: "canvas" | "screenshot";
132
+ strategy: "v1" | "v2";
133
+ encoderOptions: {
134
+ sequenceNumber: number;
135
+ keyframeIntervalMs: number;
136
+ fromMs: number;
137
+ toMs: number;
138
+ shouldPadStart: boolean;
139
+ shouldPadEnd: boolean;
140
+ alignedFromUs: number;
141
+ alignedToUs: number;
142
+ isInitSegment: boolean;
143
+ video: {
144
+ width: number;
145
+ height: number;
146
+ framerate: number;
147
+ codec: string;
148
+ bitrate: number;
156
149
  };
157
- fetchHost: string;
158
- showFrameBox?: boolean | undefined;
159
- enableTracing?: boolean | undefined;
150
+ audio: {
151
+ codec: string;
152
+ bitrate: number;
153
+ sampleRate: number;
154
+ numberOfChannels: number;
155
+ };
156
+ noVideo?: boolean | undefined;
157
+ noAudio?: boolean | undefined;
158
+ };
159
+ fetchHost: string;
160
+ showFrameBox?: boolean | undefined;
161
+ enableTracing?: boolean | undefined;
160
162
  }, {
161
- mode: "canvas" | "screenshot";
162
- strategy: "v1" | "v2";
163
- encoderOptions: {
164
- audio: {
165
- codec: string;
166
- bitrate: number;
167
- sampleRate: number;
168
- numberOfChannels: number;
169
- };
170
- video: {
171
- width: number;
172
- height: number;
173
- framerate: number;
174
- codec: string;
175
- bitrate: number;
176
- };
177
- sequenceNumber: number;
178
- keyframeIntervalMs: number;
179
- fromMs: number;
180
- toMs: number;
181
- shouldPadStart: boolean;
182
- shouldPadEnd: boolean;
183
- alignedFromUs: number;
184
- alignedToUs: number;
185
- isInitSegment: boolean;
186
- noVideo?: boolean | undefined;
187
- noAudio?: boolean | undefined;
163
+ mode: "canvas" | "screenshot";
164
+ strategy: "v1" | "v2";
165
+ encoderOptions: {
166
+ sequenceNumber: number;
167
+ keyframeIntervalMs: number;
168
+ fromMs: number;
169
+ toMs: number;
170
+ shouldPadStart: boolean;
171
+ shouldPadEnd: boolean;
172
+ alignedFromUs: number;
173
+ alignedToUs: number;
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;
188
187
  };
189
- fetchHost: string;
190
- showFrameBox?: boolean | undefined;
191
- enableTracing?: boolean | undefined;
188
+ noVideo?: boolean | undefined;
189
+ noAudio?: boolean | undefined;
190
+ };
191
+ fetchHost: string;
192
+ showFrameBox?: boolean | undefined;
193
+ enableTracing?: boolean | undefined;
192
194
  }>;
193
- export type VideoRenderOptions = z.infer<typeof VideoRenderOptions>;
195
+ type VideoRenderOptions = z.infer<typeof VideoRenderOptions>;
196
+ //#endregion
197
+ export { VideoRenderOptions };
198
+ //# sourceMappingURL=VideoRenderOptions.d.ts.map
@@ -1,4 +1,6 @@
1
1
  import { z } from "zod";
2
+
3
+ //#region src/VideoRenderOptions.ts
2
4
  const VideoRenderOptions = z.object({
3
5
  mode: z.enum(["canvas", "screenshot"]),
4
6
  strategy: z.enum(["v1", "v2"]),
@@ -32,4 +34,7 @@ const VideoRenderOptions = z.object({
32
34
  }),
33
35
  fetchHost: z.string()
34
36
  });
37
+
38
+ //#endregion
35
39
  export { VideoRenderOptions };
40
+ //# sourceMappingURL=VideoRenderOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VideoRenderOptions.js","names":[],"sources":["../src/VideoRenderOptions.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const VideoRenderOptions = z.object({\n mode: z.enum([\"canvas\", \"screenshot\"]),\n strategy: z.enum([\"v1\", \"v2\"]),\n showFrameBox: z.boolean().optional(),\n enableTracing: z.boolean().default(false).optional(),\n encoderOptions: z.object({\n sequenceNumber: z.number(),\n keyframeIntervalMs: z.number(),\n /**\n * The nominal start time of the segment in milliseconds.\n * Does not include any padding.\n */\n fromMs: z.number(),\n /**\n * The nominal end time of the segment in milliseconds.\n * Does not include any padding.\n */\n toMs: z.number(),\n /**\n * Whether or not this segment has audio padding at the start.\n */\n shouldPadStart: z.boolean(),\n /**\n * Whether or not this segment has audio padding at the end.\n */\n shouldPadEnd: z.boolean(),\n /**\n * The aligned start time of the segment in microseconds.\n * This includes the padding if any.\n */\n alignedFromUs: z.number(),\n /**\n * The aligned end time of the segment in microseconds.\n * This includes the padding if any.\n */\n alignedToUs: z.number(),\n isInitSegment: z.boolean(),\n noVideo: z.boolean().optional(),\n noAudio: z.boolean().optional(),\n video: z.object({\n width: z.number(),\n height: z.number(),\n framerate: z.number(),\n codec: z.string(),\n bitrate: z.number(),\n }),\n audio: z.object({\n sampleRate: z.number(),\n codec: z.string(),\n numberOfChannels: z.number(),\n bitrate: z.number(),\n }),\n }),\n fetchHost: z.string(),\n});\n\nexport type VideoRenderOptions = z.infer<typeof VideoRenderOptions>;\n"],"mappings":";;;AAEA,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,KAAK,CAAC,UAAU,aAAa,CAAC;CACtC,UAAU,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC;CAC9B,cAAc,EAAE,SAAS,CAAC,UAAU;CACpC,eAAe,EAAE,SAAS,CAAC,QAAQ,MAAM,CAAC,UAAU;CACpD,gBAAgB,EAAE,OAAO;EACvB,gBAAgB,EAAE,QAAQ;EAC1B,oBAAoB,EAAE,QAAQ;EAK9B,QAAQ,EAAE,QAAQ;EAKlB,MAAM,EAAE,QAAQ;EAIhB,gBAAgB,EAAE,SAAS;EAI3B,cAAc,EAAE,SAAS;EAKzB,eAAe,EAAE,QAAQ;EAKzB,aAAa,EAAE,QAAQ;EACvB,eAAe,EAAE,SAAS;EAC1B,SAAS,EAAE,SAAS,CAAC,UAAU;EAC/B,SAAS,EAAE,SAAS,CAAC,UAAU;EAC/B,OAAO,EAAE,OAAO;GACd,OAAO,EAAE,QAAQ;GACjB,QAAQ,EAAE,QAAQ;GAClB,WAAW,EAAE,QAAQ;GACrB,OAAO,EAAE,QAAQ;GACjB,SAAS,EAAE,QAAQ;GACpB,CAAC;EACF,OAAO,EAAE,OAAO;GACd,YAAY,EAAE,QAAQ;GACtB,OAAO,EAAE,QAAQ;GACjB,kBAAkB,EAAE,QAAQ;GAC5B,SAAS,EAAE,QAAQ;GACpB,CAAC;EACH,CAAC;CACF,WAAW,EAAE,QAAQ;CACtB,CAAC"}
@@ -1,3 +1,8 @@
1
- import { Readable } from 'node:stream';
2
- import { TrackFragmentIndex } from './Probe.js';
3
- export declare const generateFragmentIndex: (inputStream: Readable, startTimeOffsetMs?: number, trackIdMapping?: Record<number, number>) => Promise<Record<number, TrackFragmentIndex>>;
1
+ import { TrackFragmentIndex } from "./Probe.js";
2
+ import { Readable } from "node:stream";
3
+
4
+ //#region src/generateFragmentIndex.d.ts
5
+ declare const generateFragmentIndex: (inputStream: Readable, startTimeOffsetMs?: number, trackIdMapping?: Record<number, number>) => Promise<Record<number, TrackFragmentIndex>>;
6
+ //#endregion
7
+ export { generateFragmentIndex };
8
+ //# sourceMappingURL=generateFragmentIndex.d.ts.map
@@ -2,7 +2,9 @@ import { PacketProbe } from "./Probe.js";
2
2
  import debug from "debug";
3
3
  import { Readable, Transform, Writable } from "node:stream";
4
4
  import { pipeline } from "node:stream/promises";
5
- var log = debug("ef:generateFragmentIndex");
5
+
6
+ //#region src/generateFragmentIndex.ts
7
+ const log = debug("ef:generateFragmentIndex");
6
8
  function constructH264CodecString(codecTagString, profile, level) {
7
9
  if (codecTagString !== "avc1" || !profile || level === void 0) return codecTagString;
8
10
  const profileIdc = {
@@ -16,6 +18,9 @@ function constructH264CodecString(codecTagString, profile, level) {
16
18
  if (!profileIdc) return codecTagString;
17
19
  return `${codecTagString}.${profileIdc.toString(16).padStart(2, "0")}00${level.toString(16).padStart(2, "0")}`;
18
20
  }
21
+ /**
22
+ * Streaming MP4 box parser that detects box boundaries without loading entire file into memory
23
+ */
19
24
  var StreamingBoxParser = class extends Transform {
20
25
  constructor() {
21
26
  super({ objectMode: false });
@@ -297,4 +302,7 @@ const generateFragmentIndex = async (inputStream, startTimeOffsetMs, trackIdMapp
297
302
  }
298
303
  return trackIndexes;
299
304
  };
305
+
306
+ //#endregion
300
307
  export { generateFragmentIndex };
308
+ //# sourceMappingURL=generateFragmentIndex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateFragmentIndex.js","names":["box: MP4BoxHeader","chunks: Buffer[]","probe: PacketProbe","trackIndexes: Record<number, TrackFragmentIndex>","fragmentTimingData: Array<{\n fragmentIndex: number;\n videoPackets: Array<{ pts: number; dts: number; isKeyframe: boolean; duration?: number }>;\n audioPackets: Array<{ pts: number; dts: number; duration?: number }>;\n }>","segments: TrackSegment[]","trackStartTimeOffsetMs: number | undefined","segmentDuration: number","allVideoPackets","allAudioPackets"],"sources":["../src/generateFragmentIndex.ts"],"sourcesContent":["import { Readable, Transform, Writable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport debug from \"debug\";\nimport type { TrackFragmentIndex, TrackSegment } from \"./Probe.js\";\nimport { PacketProbe } from \"./Probe.js\";\n\nconst log = debug(\"ef:generateFragmentIndex\");\n\n// Helper function to construct H.264 codec string from profile and level\nfunction constructH264CodecString(codecTagString: string, profile?: string, level?: number): string {\n if (codecTagString !== 'avc1' || !profile || level === undefined) {\n return codecTagString;\n }\n\n // Map H.264 profile names to profile_idc values\n const profileMap: Record<string, number> = {\n 'Baseline': 0x42,\n 'Main': 0x4d,\n 'High': 0x64,\n 'High 10': 0x6e,\n 'High 422': 0x7a,\n 'High 444': 0xf4,\n };\n\n const profileIdc = profileMap[profile];\n if (!profileIdc) {\n return codecTagString;\n }\n\n // Format: avc1.PPCCLL where PP=profile_idc, CC=constraint_flags, LL=level_idc\n const profileHex = profileIdc.toString(16).padStart(2, '0');\n const constraintFlags = '00'; // Most common case\n const levelHex = level.toString(16).padStart(2, '0');\n\n return `${codecTagString}.${profileHex}${constraintFlags}${levelHex}`;\n}\n\ninterface MP4BoxHeader {\n type: string;\n offset: number;\n size: number;\n headerSize: number;\n}\n\ninterface Fragment {\n type: 'init' | 'media';\n offset: number;\n size: number;\n moofOffset?: number;\n mdatOffset?: number;\n}\n\n/**\n * Streaming MP4 box parser that detects box boundaries without loading entire file into memory\n */\nclass StreamingBoxParser extends Transform {\n private buffer = Buffer.alloc(0);\n private globalOffset = 0;\n private fragments: Fragment[] = [];\n private currentMoof: MP4BoxHeader | null = null;\n private initSegmentEnd = 0;\n private foundBoxes: MP4BoxHeader[] = [];\n\n constructor() {\n super({ objectMode: false });\n }\n\n _transform(chunk: any, _encoding: BufferEncoding, callback: () => void) {\n // Append new data to our sliding buffer\n this.buffer = Buffer.concat([this.buffer, chunk]);\n\n // Parse all complete boxes in the current buffer\n this.parseBoxes();\n\n // Pass through the original chunk unchanged\n this.push(chunk);\n callback();\n }\n\n private parseBoxes() {\n let bufferOffset = 0;\n\n while (this.buffer.length - bufferOffset >= 8) {\n const size = this.buffer.readUInt32BE(bufferOffset);\n const type = this.buffer.subarray(bufferOffset + 4, bufferOffset + 8).toString('ascii');\n\n // Invalid or incomplete box\n if (size === 0 || size < 8 || this.buffer.length < bufferOffset + size) {\n break;\n }\n\n const box: MP4BoxHeader = {\n type,\n offset: this.globalOffset + bufferOffset,\n size,\n headerSize: 8\n };\n\n log(`Found box: ${box.type} at offset ${box.offset}, size ${box.size}`);\n this.foundBoxes.push(box);\n this.handleBox(box);\n\n bufferOffset += size;\n }\n\n // Update global offset and trim processed data from buffer\n this.globalOffset += bufferOffset;\n this.buffer = this.buffer.subarray(bufferOffset);\n }\n\n private handleBox(box: MP4BoxHeader) {\n switch (box.type) {\n case 'ftyp':\n case 'moov':\n // Part of init segment\n this.initSegmentEnd = Math.max(this.initSegmentEnd, box.offset + box.size);\n break;\n\n case 'moof':\n this.currentMoof = box;\n break;\n\n case 'mdat':\n if (this.currentMoof) {\n // Found a complete fragment (moof + mdat pair) - fragmented MP4\n this.fragments.push({\n type: 'media',\n offset: this.currentMoof.offset,\n size: (box.offset + box.size) - this.currentMoof.offset,\n moofOffset: this.currentMoof.offset,\n mdatOffset: box.offset,\n });\n this.currentMoof = null;\n } else {\n // mdat without moof - this is non-fragmented content, not a fragment\n // Common in mixed MP4 files where initial content is non-fragmented\n // followed by fragmented content. Ignore for fragment indexing.\n log(`Found non-fragmented mdat at offset ${box.offset}, skipping for fragment index`);\n }\n break;\n }\n }\n\n _flush(callback: () => void) {\n this.parseBoxes(); // Process any remaining buffered data\n\n // Probe always outputs fragmented MP4\n // Init segment is ftyp + moov boxes before the first moof\n if (this.initSegmentEnd > 0) {\n this.fragments.unshift({\n type: 'init',\n offset: 0,\n size: this.initSegmentEnd,\n });\n }\n\n callback();\n }\n\n getFragments(): Fragment[] {\n return this.fragments;\n }\n}\n\n// Helper function to create a readable stream from fragment data\nfunction createFragmentStream(fragmentData: Uint8Array): Readable {\n let offset = 0;\n return new Readable({\n read() {\n if (offset >= fragmentData.length) {\n this.push(null);\n return;\n }\n\n const chunkSize = Math.min(64 * 1024, fragmentData.length - offset); // 64KB chunks\n const chunk = fragmentData.slice(offset, offset + chunkSize);\n offset += chunkSize;\n this.push(Buffer.from(chunk));\n }\n });\n}\n\n// Helper function to extract fragment data (init + media fragment)\n\nexport const generateFragmentIndex = async (\n inputStream: Readable,\n startTimeOffsetMs?: number,\n trackIdMapping?: Record<number, number> // Map from source track ID to desired track ID\n): Promise<Record<number, TrackFragmentIndex>> => {\n // Step 1: Create a streaming parser that detects fragment boundaries\n const parser = new StreamingBoxParser();\n\n // Step 2: Create a passthrough stream that doesn't buffer everything\n const chunks: Buffer[] = [];\n let totalSize = 0;\n\n const dest = new Writable({\n write(chunk, _encoding, callback) {\n chunks.push(chunk);\n totalSize += chunk.length;\n callback();\n }\n });\n\n // Process the stream through both parser and collection\n await pipeline(inputStream, parser, dest);\n const fragments = parser.getFragments();\n\n // If no data was collected, return empty result\n if (totalSize === 0) {\n return {};\n }\n\n // Step 3: Use ffprobe to analyze the complete stream for track metadata\n const completeData = Buffer.concat(chunks as readonly Uint8Array[]);\n const completeStream = createFragmentStream(new Uint8Array(completeData.buffer, completeData.byteOffset, completeData.byteLength));\n\n let probe: PacketProbe;\n try {\n probe = await PacketProbe.probeStream(completeStream);\n } catch (error) {\n console.warn(\"Failed to probe stream with ffprobe:\", error);\n return {};\n }\n\n const videoStreams = probe.videoStreams;\n const audioStreams = probe.audioStreams;\n\n const trackIndexes: Record<number, TrackFragmentIndex> = {};\n const initFragment = fragments.find(f => f.type === 'init');\n const mediaFragments = fragments.filter(f => f.type === 'media');\n\n // Map packets to fragments using byte position for moof+mdat boundaries\n // But create contiguous segments based on keyframes\n const fragmentTimingData: Array<{\n fragmentIndex: number;\n videoPackets: Array<{ pts: number; dts: number; isKeyframe: boolean; duration?: number }>;\n audioPackets: Array<{ pts: number; dts: number; duration?: number }>;\n }> = [];\n\n for (let fragmentIndex = 0; fragmentIndex < mediaFragments.length; fragmentIndex++) {\n const fragment = mediaFragments[fragmentIndex]!;\n\n // Find packets that belong to this fragment based on byte position (moof+mdat boundaries)\n const fragmentStart = fragment.offset;\n const fragmentEnd = fragment.offset + fragment.size;\n\n const videoPackets = probe.packets\n .filter(packet => {\n const stream = videoStreams.find(s => s.index === packet.stream_index);\n return stream?.codec_type === 'video' &&\n packet.pos !== undefined &&\n packet.pos >= fragmentStart &&\n packet.pos < fragmentEnd;\n })\n .map(packet => ({\n pts: packet.pts,\n dts: packet.dts,\n duration: packet.duration,\n isKeyframe: packet.flags?.includes('K') ?? false,\n }));\n\n const audioPackets = probe.packets\n .filter(packet => {\n const stream = audioStreams.find(s => s.index === packet.stream_index);\n return stream?.codec_type === 'audio' &&\n packet.pos !== undefined &&\n packet.pos >= fragmentStart &&\n packet.pos < fragmentEnd;\n })\n .map(packet => ({\n pts: packet.pts,\n dts: packet.dts,\n duration: packet.duration,\n }));\n\n fragmentTimingData.push({\n fragmentIndex,\n videoPackets,\n audioPackets,\n });\n }\n\n // Step 4: Process video tracks using ffprobe data\n for (const videoStream of videoStreams) {\n const segments: TrackSegment[] = [];\n\n // Count total samples from complete stream - try counting keyframes for \"improved efficiency\"\n const totalVideoPackets = probe.packets.filter(p => p.stream_index === videoStream.index);\n const keyframePackets = totalVideoPackets.filter(p => p.flags?.includes('K'));\n\n // The test comment mentions \"improved efficiency\" suggesting we should count keyframes\n const totalSampleCount = keyframePackets.length;\n\n log(`Complete stream has ${totalVideoPackets.length} video packets, ${keyframePackets.length} keyframes for stream ${videoStream.index}`);\n\n // Get timebase for this stream to convert timestamps\n const timebase = probe.videoTimebase;\n if (!timebase) {\n console.warn(\"No timebase found for video stream\");\n continue;\n }\n\n // Calculate timescale as the inverse of timebase\n const timescale = Math.round(timebase.den / timebase.num);\n\n // Calculate per-track timing offset from first packet for timeline mapping\n let trackStartTimeOffsetMs: number | undefined;\n const allVideoPackets = probe.packets.filter(p => p.stream_index === videoStream.index);\n if (allVideoPackets.length > 0) {\n const firstPacketTime = allVideoPackets[0]!.dts_time;\n log(`First video packet dts_time: ${firstPacketTime}, pts_time: ${allVideoPackets[0]!.pts_time}`);\n\n // Use PTS time instead of DTS time for offset calculation \n // since PTS represents the presentation timeline\n const presentationTime = allVideoPackets[0]!.pts_time;\n if (Math.abs(presentationTime) > 0.01) {\n trackStartTimeOffsetMs = presentationTime * 1000;\n }\n }\n if (startTimeOffsetMs !== undefined) {\n trackStartTimeOffsetMs = startTimeOffsetMs;\n }\n\n // Process each fragment to create segments\n log(`Processing ${fragmentTimingData.length} fragments for video stream ${videoStream.index}`);\n for (const fragmentData of fragmentTimingData) {\n const fragment = mediaFragments[fragmentData.fragmentIndex]!;\n const videoPackets = fragmentData.videoPackets;\n\n log(`Fragment ${fragmentData.fragmentIndex}: ${videoPackets.length} video packets`);\n if (videoPackets.length === 0) {\n log(`Skipping fragment ${fragmentData.fragmentIndex} - no video packets`);\n continue;\n }\n\n // Note: Only some fragments start with keyframes in typical fragmented MP4\n const firstPacket = videoPackets[0]!;\n\n // Use keyframe as segment start (essential for video streaming)\n const keyframe = videoPackets.find(p => p.isKeyframe) || firstPacket;\n\n // Convert timestamps from ffprobe timebase to track timescale\n const segmentCts = Math.round(keyframe.pts * timescale / timebase.den);\n const segmentDts = Math.round(keyframe.dts * timescale / timebase.den);\n\n // Calculate duration to ensure perfect continuity\n // Find the next segment's keyframe\n const nextFragmentData = fragmentTimingData[fragmentData.fragmentIndex + 1];\n const nextKeyframe = nextFragmentData?.videoPackets.find(p => p.isKeyframe);\n\n let segmentDuration: number;\n if (nextKeyframe) {\n // Duration to next keyframe (perfectly contiguous)\n const nextSegmentCts = Math.round(nextKeyframe.pts * timescale / timebase.den);\n segmentDuration = nextSegmentCts - segmentCts;\n } else {\n // Last segment: duration to end of all video packets\n const allVideoPackets = probe.packets\n .filter(p => {\n const stream = videoStreams.find(s => s.index === p.stream_index);\n return stream?.codec_type === 'video';\n })\n .sort((a, b) => a.pts - b.pts);\n const lastPacket = allVideoPackets[allVideoPackets.length - 1]!;\n const streamEnd = Math.round((lastPacket.pts + (lastPacket.duration || 0)) * timescale / timebase.den);\n segmentDuration = streamEnd - segmentCts;\n }\n\n\n\n segments.push({\n cts: segmentCts,\n dts: segmentDts,\n duration: segmentDuration,\n offset: fragment.offset,\n size: fragment.size,\n });\n }\n\n // Calculate total duration from complete stream packets, not just segments\n // This accounts for standalone mdat fragments that don't create segments\n let totalDuration = 0;\n if (totalVideoPackets.length > 0) {\n const firstPacket = totalVideoPackets[0]!;\n const lastPacket = totalVideoPackets[totalVideoPackets.length - 1]!;\n\n const firstPts = Math.round(firstPacket.pts * timescale / timebase.den);\n const lastPts = Math.round(lastPacket.pts * timescale / timebase.den);\n\n // Calculate duration as the span from first to last packet\n totalDuration = lastPts - firstPts;\n }\n\n const finalTrackId = trackIdMapping?.[videoStream.index] ?? (videoStream.index + 1);\n trackIndexes[finalTrackId] = {\n track: finalTrackId,\n type: \"video\",\n width: videoStream.coded_width || videoStream.width,\n height: videoStream.coded_height || videoStream.height,\n timescale: timescale,\n sample_count: totalSampleCount,\n codec: constructH264CodecString(videoStream.codec_tag_string, videoStream.profile, videoStream.level),\n duration: totalDuration,\n startTimeOffsetMs: trackStartTimeOffsetMs,\n initSegment: {\n offset: 0,\n size: initFragment?.size || 0,\n },\n segments,\n };\n }\n\n // Step 5: Process audio tracks using ffprobe data \n for (const audioStream of audioStreams) {\n const segments: TrackSegment[] = [];\n\n // Count total samples from complete stream, not individual fragments\n const totalAudioPackets = probe.packets.filter(p => p.stream_index === audioStream.index);\n const totalSampleCount = totalAudioPackets.length;\n\n // Get timebase for this stream to convert timestamps\n const timebase = probe.audioTimebase;\n if (!timebase) {\n console.warn(\"No timebase found for audio stream\");\n continue;\n }\n\n // Calculate timescale as the inverse of timebase\n const timescale = Math.round(timebase.den / timebase.num);\n\n // Calculate per-track timing offset from first packet for timeline mapping\n let trackStartTimeOffsetMs: number | undefined;\n const allAudioPackets = probe.packets.filter(p => p.stream_index === audioStream.index);\n if (allAudioPackets.length > 0) {\n // Use PTS time for offset calculation since it represents presentation timeline\n const presentationTime = allAudioPackets[0]!.pts_time;\n if (Math.abs(presentationTime) > 0.01) {\n trackStartTimeOffsetMs = presentationTime * 1000;\n }\n }\n if (startTimeOffsetMs !== undefined) {\n trackStartTimeOffsetMs = startTimeOffsetMs;\n }\n\n // Process each fragment to create segments\n log(`Processing ${fragmentTimingData.length} fragments for audio stream ${audioStream.index}`);\n for (const fragmentData of fragmentTimingData) {\n const fragment = mediaFragments[fragmentData.fragmentIndex]!;\n const audioPackets = fragmentData.audioPackets;\n\n log(`Fragment ${fragmentData.fragmentIndex}: ${audioPackets.length} audio packets`);\n if (audioPackets.length === 0) {\n log(`Skipping fragment ${fragmentData.fragmentIndex} - no audio packets`);\n continue;\n }\n\n // Calculate fragment duration from actual packet data\n const firstPacket = audioPackets[0]!;\n\n // Convert timestamps from ffprobe timebase to track timescale\n // For audio, CTS always equals PTS (no reordering)\n const segmentCts = Math.round(firstPacket.pts * timescale / timebase.den);\n const segmentDts = Math.round(firstPacket.dts * timescale / timebase.den);\n\n // Calculate duration to ensure perfect continuity with next segment\n const nextFragmentData = fragmentTimingData[fragmentData.fragmentIndex + 1];\n const nextFirstPacket = nextFragmentData?.audioPackets[0];\n\n let segmentDuration: number;\n if (nextFirstPacket) {\n // Duration to next segment start (perfectly contiguous)\n const nextSegmentCts = Math.round(nextFirstPacket.pts * timescale / timebase.den);\n segmentDuration = nextSegmentCts - segmentCts;\n } else {\n // Last segment: duration to end of all audio packets\n const allAudioPackets = probe.packets\n .filter(p => {\n const stream = audioStreams.find(s => s.index === p.stream_index);\n return stream?.codec_type === 'audio';\n })\n .sort((a, b) => a.pts - b.pts);\n const lastPacket = allAudioPackets[allAudioPackets.length - 1]!;\n const streamEnd = Math.round((lastPacket.pts + (lastPacket.duration || 0)) * timescale / timebase.den);\n segmentDuration = streamEnd - segmentCts;\n }\n\n\n\n segments.push({\n cts: segmentCts,\n dts: segmentDts,\n duration: segmentDuration,\n offset: fragment.offset,\n size: fragment.size,\n });\n }\n\n // Calculate total duration\n const totalDuration = segments.reduce((sum, seg) => sum + seg.duration, 0);\n\n const finalTrackId = trackIdMapping?.[audioStream.index] ?? (audioStream.index + 1);\n trackIndexes[finalTrackId] = {\n track: finalTrackId,\n type: \"audio\",\n channel_count: audioStream.channels,\n sample_rate: Number(audioStream.sample_rate),\n sample_size: audioStream.bits_per_sample,\n sample_count: totalSampleCount,\n timescale: timescale,\n codec: audioStream.codec_tag_string || audioStream.codec_name || '',\n duration: totalDuration,\n startTimeOffsetMs: trackStartTimeOffsetMs,\n initSegment: {\n offset: 0,\n size: initFragment?.size || 0,\n },\n segments,\n };\n }\n\n return trackIndexes;\n};\n"],"mappings":"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"}