@torrent-tv/proxy 2.90.0 → 2.92.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 (146) hide show
  1. package/bin/cli.js +5 -2
  2. package/package.json +18 -2
  3. package/routes/api/playback-plan/post.js +21 -4
  4. package/routes/api/sources/files/get.js +20 -28
  5. package/routes/api/sources/post.js +4 -2
  6. package/routes/api/sources/stats/get.js +4 -11
  7. package/routes/api/sources/viewer/post.js +16 -0
  8. package/routes/api/sources/warm/post.js +6 -215
  9. package/routes/api/subtitles/get.js +22 -125
  10. package/routes/api/subtitles/post.js +26 -0
  11. package/routes/api/transcode-sessions/post.js +24 -2
  12. package/routes/media/get.js +80 -0
  13. package/routes/stream/get.js +26 -94
  14. package/routes/transcode/audio-warm/get.js +9 -21
  15. package/routes/transcode/variant-warm/get.js +8 -14
  16. package/server.js +410 -46
  17. package/services/encode/AdmittedInput.js +136 -0
  18. package/services/encode/CushionReport.js +1 -76
  19. package/services/encode/EncodeInputs.js +142 -0
  20. package/services/encode/EncodeOrchestrator.js +4 -1
  21. package/services/encode/EncodeRun.js +13 -4
  22. package/services/encode/EncodeRuns.js +132 -52
  23. package/services/encode/InputFailures.js +37 -0
  24. package/services/encode/MatroskaInput.js +158 -0
  25. package/services/encode/OutputOpening.js +16 -64
  26. package/services/encode/Renditions.js +1 -17
  27. package/services/encode/admitted-command.js +60 -0
  28. package/services/encode/annex-b-input.js +37 -0
  29. package/services/encode/bit-payload.js +29 -0
  30. package/services/encode/output/OutputCatalog.js +1 -1
  31. package/services/encode/output/Timeline.js +11 -0
  32. package/services/encode/pcm-normalization.js +30 -0
  33. package/services/encode/piece-completeness.js +54 -1
  34. package/services/encode/quality/HostLoad.js +19 -73
  35. package/services/encode/quality/QualityOffer.js +1 -1
  36. package/services/encode/run-command.js +1 -589
  37. package/services/encode/segment-formats/mp4-boxes.js +3 -1
  38. package/services/encode/segment-formats/presentation-segment.js +114 -0
  39. package/services/media/ContainerOrchestrator.js +118 -48
  40. package/services/media/KeyframeTables.js +16 -115
  41. package/services/media/MediaReadRequests.js +120 -0
  42. package/services/media/ProbeRequests.js +135 -0
  43. package/services/media/SegmentInputs.js +48 -0
  44. package/services/media/SourceFile.js +6 -0
  45. package/services/media/SubtitleOrchestrator.js +190 -4
  46. package/services/media/await-plan.js +47 -0
  47. package/services/media/container/AsfContainer.js +217 -0
  48. package/services/media/container/AviContainer.js +343 -34
  49. package/services/media/container/CadencedPictureOrder.js +45 -0
  50. package/services/media/container/Container.js +7 -3
  51. package/services/media/container/ContainerFactory.js +10 -3
  52. package/services/media/container/FfprobeContainer.js +222 -0
  53. package/services/media/container/MatroskaContainer.js +72 -46
  54. package/services/media/container/Mp4Container.js +472 -139
  55. package/services/media/container/MpegPsContainer.js +281 -0
  56. package/services/media/container/MpegTsContainer.js +277 -0
  57. package/services/media/container/PacketIndex.js +270 -0
  58. package/services/media/container/PacketRecords.js +237 -0
  59. package/services/media/container/RetainedBytes.js +39 -0
  60. package/services/media/container/RetainedReads.js +51 -0
  61. package/services/media/container/ac3-frame.js +46 -0
  62. package/services/media/container/asf-packets.js +153 -0
  63. package/services/media/container/avc-picture-order.js +50 -0
  64. package/services/media/container/avi-open-dml.js +47 -0
  65. package/services/media/container/dts-frame.js +51 -0
  66. package/services/media/container/ebml-stream.js +12 -1
  67. package/services/media/container/ffprobe-record.js +45 -0
  68. package/services/media/container/flv-packet-ranges.js +27 -0
  69. package/services/media/container/following-packet-time.js +37 -0
  70. package/services/media/container/h264-configuration.js +183 -0
  71. package/services/media/container/h264-elementary-index.js +245 -0
  72. package/services/media/container/hevc-configuration.js +165 -0
  73. package/services/media/container/hevc-elementary-index.js +132 -0
  74. package/services/media/container/hevc-picture-order.js +66 -0
  75. package/services/media/container/latm-frame.js +99 -0
  76. package/services/media/container/matroska-packets.js +207 -0
  77. package/services/media/container/memory-unavailable.js +8 -0
  78. package/services/media/container/mp3-packet-dependencies.js +41 -0
  79. package/services/media/container/mp4-fragments.js +148 -0
  80. package/services/media/container/mpeg-audio-frame.js +19 -0
  81. package/services/media/container/mpeg-audio-reservoir.js +27 -0
  82. package/services/media/container/mpeg-elementary-index.js +196 -0
  83. package/services/media/container/mpeg-sequence-reader.js +69 -0
  84. package/services/media/container/mpeg-ts-packets.js +93 -0
  85. package/services/media/container/mpeg-video-configuration.js +50 -0
  86. package/services/media/container/mpeg4-picture-timing.js +108 -0
  87. package/services/media/container/pes-header.js +34 -0
  88. package/services/media/playback-declarations.js +57 -0
  89. package/services/media/playback-planner.js +72 -230
  90. package/services/media/probe-packets.js +61 -0
  91. package/services/media/read-playback-declarations.js +18 -0
  92. package/services/media/tracks/AudioTrack.js +9 -0
  93. package/services/media/tracks/TextSubtitleTrack.js +4 -1
  94. package/services/server/OutputLifecycle.js +2 -0
  95. package/services/server/SegmentServing.js +40 -33
  96. package/services/server/ViewerRequests.js +22 -41
  97. package/services/server/controllers/PlaybackController.js +4 -0
  98. package/services/server/controllers/SubtitleController.js +8 -29
  99. package/services/server/transcode-session-files.js +58 -99
  100. package/services/server/wire-outputs.js +58 -15
  101. package/services/storage/IndexMemory.js +85 -0
  102. package/services/storage/MachineBudget.js +27 -3
  103. package/services/storage/allowance.js +8 -1
  104. package/services/storage/piece-store/piece-disk-store.js +18 -3
  105. package/services/storage/piece-store/piece-lru.js +8 -4
  106. package/services/storage/piece-store/shared-piece-store.js +26 -11
  107. package/services/storage/segment-store/SegmentStore.js +49 -18
  108. package/services/storage/wire.js +7 -0
  109. package/services/torrent/Contents.js +52 -20
  110. package/services/torrent/demand/DemandRegister.js +13 -4
  111. package/services/torrent/demand/Window.js +13 -2
  112. package/services/torrent/demand-progress.js +25 -0
  113. package/services/torrent/download/DeadlineScheduler.js +167 -0
  114. package/services/torrent/download/FutureDownload.js +166 -0
  115. package/services/torrent/download/SwarmSelection.js +30 -59
  116. package/services/torrent/download/registry.js +45 -5
  117. package/services/torrent/download/withdraw-claim.js +2 -0
  118. package/services/torrent/torrent-pool.js +39 -600
  119. package/services/torrent/worker/channel.js +8 -1
  120. package/services/torrent/worker/client.js +74 -72
  121. package/services/torrent/worker/files-in-use.js +9 -0
  122. package/services/torrent/worker/handle-state.js +12 -0
  123. package/services/torrent/worker/held-bytes.js +82 -99
  124. package/services/torrent/worker/held-file-bytes.js +17 -0
  125. package/services/torrent/worker/piece-reader.js +67 -683
  126. package/services/torrent/worker/pool-adapter.js +37 -123
  127. package/services/torrent/worker/protocol.js +6 -17
  128. package/services/torrent/worker/whole-file-stats.js +2 -0
  129. package/services/torrent/worker/worker.js +86 -156
  130. package/services/transport/data-channel-handler.js +56 -13
  131. package/services/transport/tunnel-client.js +111 -20
  132. package/services/viewer/DownloadMaps.js +168 -0
  133. package/services/viewer/NativeSourceMap.js +33 -0
  134. package/services/viewer/PriorityMap.js +49 -40
  135. package/services/viewer/PriorityOrchestrator.js +42 -13
  136. package/services/viewer/SourcePreparation.js +278 -0
  137. package/services/viewer/Viewer.js +11 -0
  138. package/services/viewer/Viewers.js +109 -2
  139. package/services/viewer/playback-readiness.js +50 -63
  140. package/services/viewer/segment-demands.js +37 -0
  141. package/services/viewer/urgent-output-ready.js +26 -0
  142. package/services/encode/run-inputs.js +0 -53
  143. package/services/media/keyframe-probe.js +0 -157
  144. package/services/media/media-info-probe.js +0 -93
  145. package/services/torrent/worker/background-fill.js +0 -213
  146. package/services/torrent/worker/resume-warm.js +0 -107
@@ -0,0 +1,153 @@
1
+ import { PacketIndex } from "./PacketIndex.js";
2
+ import { PacketRecords } from "./PacketRecords.js";
3
+ import { followingPacketTime } from "./following-packet-time.js";
4
+ import { isMp3Codec } from "./mp3-packet-dependencies.js";
5
+
6
+ const DATA = "3626b2758e66cf11a6d900aa0062ce6c";
7
+
8
+ /** ASF payload addresses, including fragmented and compressed media objects. */
9
+ export async function readAsfPackets({ readRange, fileSize, tracks, info, firstPicture = false, packetMemory, state = {} }) {
10
+ if (!state.initialized) {
11
+ const at = info.dataOffset;
12
+ const data = await readRange(at, at + 49);
13
+ if (data.subarray(0, 16).toString("hex") !== DATA) throw new Error("ASF Data Object is absent.");
14
+ const size = Number(data.readBigUInt64LE(16));
15
+ const count = Number(data.readBigUInt64LE(40));
16
+ if (!Number.isSafeInteger(size) || size < 50 || at + size > fileSize || !Number.isSafeInteger(count)) {
17
+ throw new Error("ASF Data Object exceeds the file.");
18
+ }
19
+ Object.assign(state, { initialized: true, at: at + 50, end: at + size, count, packets: 0,
20
+ frames: new Map(tracks.map(track => [track.trackNumber, new PacketRecords(packetMemory)])), assembling: new Map() });
21
+ }
22
+ let { at, packets } = state;
23
+ const { end, count, frames, assembling } = state;
24
+ const declared = new Map(tracks.map(track => [track.trackNumber, track]));
25
+ while (at < end && (!count || packets < count)) {
26
+ const previousObjects = new Map([...assembling].map(([id, object]) => [id, { ...object, ranges: [...object.ranges] }]));
27
+ const previousLengths = new Map([...frames].map(([id, stream]) => [id, stream.length]));
28
+ try {
29
+ const fields = new Fields(readRange, at, end);
30
+ let flags = await fields.uint(1);
31
+ if (flags & 0x80) {
32
+ if (flags & 0x60) throw new Error("ASF error-correction length type is unsupported.");
33
+ fields.skip(flags & 15);
34
+ flags = await fields.uint(1);
35
+ }
36
+ const properties = await fields.uint(1);
37
+ const length = await fields.sized(flags >> 5, info.packetLength);
38
+ await fields.sized(flags >> 1, 0);
39
+ const padding = await fields.sized(flags >> 3, 0);
40
+ const timestamp = await fields.uint(4);
41
+ await fields.uint(2);
42
+ if (!length || length > end - at || padding >= length) throw new Error("ASF packet length or padding is invalid.");
43
+ const limit = at + length - padding;
44
+ fields.limit = limit;
45
+ const payloadFlags = flags & 1 ? await fields.uint(1) : 0x81;
46
+ const payloadCount = flags & 1 ? payloadFlags & 63 : 1;
47
+ if (!payloadCount) throw new Error("ASF packet has no payloads.");
48
+ for (let payload = 0; payload < payloadCount; payload++) {
49
+ const stream = await fields.uint(1);
50
+ const id = stream & 127, keyframe = (stream & 128) !== 0;
51
+ if (!declared.has(id)) throw new Error("ASF payload names an undeclared stream.");
52
+ const number = await fields.sized(properties >> 4, 0);
53
+ const offset = await fields.sized(properties >> 2, 0);
54
+ const replicaLength = await fields.sized(properties, 0);
55
+ let objectSize = null, time = timestamp, delta = null;
56
+ if (replicaLength >= 8) {
57
+ objectSize = await fields.uint(4);
58
+ time = await fields.uint(4);
59
+ fields.skip(replicaLength - 8);
60
+ } else if (replicaLength === 1) {
61
+ time = offset;
62
+ delta = await fields.uint(1);
63
+ } else if (replicaLength !== 0) throw new Error("ASF replicated payload data is invalid.");
64
+ const payloadSize = flags & 1 ? await fields.sized(payloadFlags >> 6, 0) : limit - fields.at;
65
+ if (!payloadSize || payloadSize > limit - fields.at) throw new Error("ASF payload exceeds its packet.");
66
+ const payloadEnd = fields.at + payloadSize;
67
+ if (delta !== null) {
68
+ while (fields.at < payloadEnd) {
69
+ const frameSize = await fields.uint(1);
70
+ if (!frameSize || frameSize > payloadEnd - fields.at) throw new Error("ASF compressed frame exceeds its payload.");
71
+ frames.get(id).push({ pts: time / 1000 - info.prerollSeconds, ...(delta > 0 ? { duration: delta / 1000 } : {}),
72
+ ...(isMp3Codec(declared.get(id)?.codecId) ? { decodeFromIndex: frames.get(id).length, decodeDependencyUnknown: true } : {}),
73
+ keyframe, ranges: [[fields.at, fields.at + frameSize - 1]] });
74
+ fields.skip(frameSize);
75
+ time += delta;
76
+ }
77
+ } else {
78
+ let object = assembling.get(id);
79
+ if (offset === 0) {
80
+ if (object) throw new Error("ASF media object ended before all fragments arrived.");
81
+ if (!(objectSize > 0)) throw new Error("ASF media object size is absent.");
82
+ object = { number, size: objectSize, received: 0, pts: time / 1000 - info.prerollSeconds, keyframe, ranges: [] };
83
+ assembling.set(id, object);
84
+ }
85
+ if (!object || object.number !== number || object.received !== offset ||
86
+ (objectSize !== null && object.size !== objectSize) || offset + payloadSize > object.size) {
87
+ throw new Error("ASF media object fragments are incomplete or inconsistent.");
88
+ }
89
+ object.ranges.push([fields.at, payloadEnd - 1]);
90
+ object.received += payloadSize;
91
+ fields.skip(payloadSize);
92
+ if (object.received === object.size) {
93
+ frames.get(id).push({ pts: object.pts, keyframe: object.keyframe, ranges: object.ranges,
94
+ ...(isMp3Codec(declared.get(id)?.codecId) ? { decodeFromIndex: frames.get(id).length, decodeDependencyUnknown: true } : {}) });
95
+ assembling.delete(id);
96
+ }
97
+ }
98
+ }
99
+ if (fields.at !== limit) throw new Error("ASF payload count differs from its packet size.");
100
+ at += Math.max(length, info.minimumPacketLength ?? length);
101
+ packets++;
102
+ state.at = at;
103
+ state.packets = packets;
104
+ if (firstPicture) {
105
+ const video = tracks.find(track => track.type === "video");
106
+ const stream = video && frames.get(video.trackNumber);
107
+ if (stream?.length) return { startTimeSeconds: stream.at(0).pts };
108
+ }
109
+ } catch (error) {
110
+ assembling.clear();
111
+ for (const [id, object] of previousObjects) assembling.set(id, object);
112
+ for (const [id, length] of previousLengths) frames.get(id).length = length;
113
+ throw error;
114
+ }
115
+ }
116
+ if ((count && packets !== count) || assembling.size) throw new Error("ASF Data Object ends with incomplete packets or media objects.");
117
+ if (firstPicture) return { startTimeSeconds: null };
118
+ const index = new PacketIndex();
119
+ for (const track of tracks) {
120
+ if (!["video", "audio"].includes(track.type)) continue;
121
+ const stream = frames.get(track.trackNumber);
122
+ index.declareTrack(track.trackNumber, { type: track.type, codecId: track.codecId, codecRanges: track.codecRanges ?? [],
123
+ prerollSeconds: track.seekPrerollSeconds ?? 0 });
124
+ const following = (track.defaultDurationSeconds === undefined || track.defaultDurationSeconds === null) && stream.some(frame => frame.duration === undefined)
125
+ ? followingPacketTime(stream, info.durationSeconds, packetMemory) : null;
126
+ try { for (let position = 0; position < stream.length; position++) {
127
+ const stated = stream.durationAt(position), pts = stream.ptsAt(position);
128
+ const duration = !Number.isNaN(stated) ? stated : track.defaultDurationSeconds ?? following(pts) - pts;
129
+ if (!(duration > 0)) throw new Error("ASF frame duration cannot be determined.");
130
+ if (duration !== stated) stream.setDuration(position, duration);
131
+ } } finally { following?.dispose(); }
132
+ index.sharePackets(track.trackNumber, stream);
133
+ index.complete(track.trackNumber);
134
+ }
135
+ return index;
136
+ }
137
+
138
+ class Fields {
139
+ constructor(read, at, limit) { this.read = read; this.at = at; this.limit = limit; }
140
+ skip(count) {
141
+ if (!Number.isSafeInteger(count) || count < 0 || count > this.limit - this.at) throw new Error("ASF packet field exceeds its bounds.");
142
+ this.at += count;
143
+ }
144
+ async uint(width) {
145
+ const at = this.at;
146
+ this.skip(width);
147
+ return (await this.read(at, at + width - 1)).readUIntLE(0, width);
148
+ }
149
+ sized(type, fallback) {
150
+ const width = [0, 1, 2, 4][type & 3];
151
+ return width ? this.uint(width) : fallback;
152
+ }
153
+ }
@@ -0,0 +1,50 @@
1
+ import { Bits, unescapeNal } from "./h264-configuration.js";
2
+
3
+ /** AVC frame picture-order counts, retaining reference and frame-number wraps. */
4
+ export class AvcPictureOrder {
5
+ #params;
6
+ #msb = 0;
7
+ #lsb = 0;
8
+ #frameNumber = 0;
9
+ #frameOffset = 0;
10
+ constructor(params) { this.#params = params; }
11
+ read(nal) {
12
+ const params = this.#params;
13
+ if (![0, 1, 2].includes(params.type) || params.separateColourPlane || params.frameNumberBits > 16 ||
14
+ (params.type === 0 && params.pocBits > 16) ||
15
+ (params.type === 1 && !Array.isArray(params.referenceOffsets))) {
16
+ throw new Error("AVC missing timestamps require supported frame picture-order declarations.");
17
+ }
18
+ const bits = new Bits(unescapeNal(nal));
19
+ bits.ue(); bits.ue(); bits.ue();
20
+ const frameNumber = bits.uint(params.frameNumberBits);
21
+ const idr = (nal[0] & 31) === 5;
22
+ if (idr) { bits.ue(); this.#msb = 0; this.#lsb = 0; this.#frameNumber = 0; this.#frameOffset = 0; }
23
+ if (params.type !== 0) {
24
+ if (!idr && frameNumber < this.#frameNumber) this.#frameOffset += 2 ** params.frameNumberBits;
25
+ this.#frameNumber = frameNumber;
26
+ const reference = (nal[0] & 0x60) !== 0;
27
+ const absolute = this.#frameOffset + frameNumber;
28
+ if (params.type === 2) return { count: idr ? 0 : 2 * absolute - (reference ? 0 : 1), idr };
29
+ const cycle = params.referenceOffsets;
30
+ const frame = cycle.length ? Math.max(0, absolute - (reference ? 0 : 1)) : 0;
31
+ let expected = 0;
32
+ if (frame > 0) {
33
+ expected = Math.floor((frame - 1) / cycle.length) * cycle.reduce((sum, value) => sum + value, 0);
34
+ for (let index = 0; index <= (frame - 1) % cycle.length; index++) expected += cycle[index];
35
+ }
36
+ if (!reference) expected += params.nonReferenceOffset;
37
+ const top = expected + (params.deltaAlwaysZero ? 0 : bits.se());
38
+ const bottom = top + params.bottomOffset +
39
+ (!params.deltaAlwaysZero && params.bottomFieldOrderPresent ? bits.se() : 0);
40
+ return { count: Math.min(top, bottom), idr };
41
+ }
42
+ const lsb = bits.uint(params.pocBits), cycle = 2 ** params.pocBits;
43
+ let msb = this.#msb;
44
+ if (lsb < this.#lsb && this.#lsb - lsb >= cycle / 2) msb += cycle;
45
+ else if (lsb > this.#lsb && lsb - this.#lsb > cycle / 2) msb -= cycle;
46
+ const bottom = params.bottomFieldOrderPresent ? bits.se() : 0;
47
+ if (nal[0] & 0x60) { this.#msb = msb; this.#lsb = lsb; }
48
+ return { count: Math.min(msb + lsb, msb + lsb + bottom), idr };
49
+ }
50
+ }
@@ -0,0 +1,47 @@
1
+ /** OpenDML indexes address stream payloads directly, including later RIFF AVIX forms. */
2
+ export async function* openDmlPackets({ readRange, fileSize, indexes, streamId }) {
3
+ const seen = new Set();
4
+ for (const chunk of indexes) yield* entries(chunk, false);
5
+
6
+ async function* entries(chunk, child) {
7
+ if (seen.has(chunk.start)) throw new Error("AVI OpenDML repeats an index address.");
8
+ seen.add(chunk.start);
9
+ if (chunk.start < 0 || chunk.end > fileSize || chunk.end - chunk.start < 24) throw new Error("AVI OpenDML index header is truncated.");
10
+ const header = await readRange(chunk.start, chunk.start + 23);
11
+ const words = header.readUInt16LE(), subtype = header[2], type = header[3];
12
+ const count = header.readUInt32LE(4), chunkId = header.toString("ascii", 8, 12);
13
+ if (!/^[0-9]{2}(db|dc|wb)$/.test(chunkId) || Number(chunkId.slice(0, 2)) !== streamId) throw new Error("AVI OpenDML index refers to another stream.");
14
+ const entryBytes = words * 4;
15
+ if (!entryBytes || count > Math.floor((chunk.end - chunk.start - 24) / entryBytes)) throw new Error("AVI OpenDML index entry count exceeds its chunk.");
16
+ if (type === 0) {
17
+ if (child || words !== 4 || subtype !== 0) throw new Error("AVI OpenDML super index structure is invalid.");
18
+ for (let at = 0; at < count; at++) {
19
+ const entry = await readRange(chunk.start + 24 + at * 16, chunk.start + 39 + at * 16);
20
+ const offset = safeOffset(entry.readBigUInt64LE()), size = entry.readUInt32LE(8);
21
+ if (!offset && !size) continue;
22
+ if (size < 32 || offset + size > fileSize) throw new Error("AVI OpenDML child index exceeds the file.");
23
+ const prefix = await readRange(offset, offset + 7);
24
+ if (!/^ix[0-9]{2}$/.test(prefix.toString("ascii", 0, 4)) || prefix.readUInt32LE(4) + 8 !== size) throw new Error("AVI OpenDML child index size disagrees with its header.");
25
+ yield* entries({ start: offset + 8, end: offset + size }, true);
26
+ }
27
+ return;
28
+ }
29
+ if (type !== 1 || !((subtype === 0 && words === 2) || (subtype === 1 && words === 3))) throw new Error("AVI OpenDML standard index structure is invalid.");
30
+ const base = safeOffset(header.readBigUInt64LE(12));
31
+ for (let at = 0; at < count; at++) {
32
+ const entry = await readRange(chunk.start + 24 + at * entryBytes, chunk.start + 23 + (at + 1) * entryBytes);
33
+ const start = base + entry.readUInt32LE(), encodedSize = entry.readUInt32LE(4), length = encodedSize & 0x7fffffff;
34
+ if (!Number.isSafeInteger(start) || start < 8 || start + length > fileSize) throw new Error("AVI OpenDML packet exceeds the file.");
35
+ if (subtype === 1 && entry.readUInt32LE(8) >= length) throw new Error("AVI OpenDML second field exceeds its frame.");
36
+ if (!length) { yield { chunkId, start, length, keyframe: false }; continue; }
37
+ const prefix = await readRange(start - 8, start - 1);
38
+ if (prefix.toString("ascii", 0, 4) !== chunkId || prefix.readUInt32LE(4) !== length) throw new Error("AVI OpenDML packet disagrees with its chunk header.");
39
+ yield { chunkId, start, length, keyframe: !(encodedSize & 0x80000000) };
40
+ }
41
+ }
42
+ }
43
+
44
+ function safeOffset(value) {
45
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("AVI OpenDML address exceeds exact integer precision.");
46
+ return Number(value);
47
+ }
@@ -0,0 +1,51 @@
1
+ /** DTS core headers declare physical frame length and PCM block duration. */
2
+ export function dtsFrame(bytes) {
3
+ if (bytes.length < 24) throw new Error("DTS core header is truncated.");
4
+ const sync = bytes.readUInt32BE();
5
+ const packed = sync === 0x1fffe800 || sync === 0xff1f00e8;
6
+ const little = sync === 0xfe7f0180 || sync === 0xff1f00e8;
7
+ if (![0x7ffe8001, 0xfe7f0180, 0x1fffe800, 0xff1f00e8].includes(sync)) {
8
+ throw new Error("DTS core synchronization is invalid.");
9
+ }
10
+ let position = 32;
11
+ const bit = logical => {
12
+ const physical = packed ? Math.floor(logical / 14) * 16 + 2 + logical % 14 : logical;
13
+ let byte = physical >> 3;
14
+ if (little) byte ^= 1;
15
+ if (byte >= bytes.length) throw new Error("DTS core header is truncated.");
16
+ return (bytes[byte] >> (7 - (physical & 7))) & 1;
17
+ };
18
+ const read = count => {
19
+ let value = 0;
20
+ for (let at = 0; at < count; at++) value = value * 2 + bit(position++);
21
+ return value;
22
+ };
23
+ read(1);
24
+ if (read(5) !== 31) throw new Error("DTS deficit sample count is invalid.");
25
+ const crc = read(1);
26
+ const blocks = read(7) + 1;
27
+ if (blocks % 8 !== 0) throw new Error("DTS PCM block count is invalid.");
28
+ const logicalSize = read(14) + 1;
29
+ if (logicalSize < 96) throw new Error("DTS core frame length is invalid.");
30
+ const mode = read(6);
31
+ const channels = [1, 2, 2, 2, 2, 3, 3, 4, 4, 5][mode];
32
+ const sampleRate = [0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0, 12000, 24000, 48000, 96000, 192000][read(4)];
33
+ if (!channels || !sampleRate) throw new Error("DTS core audio format is invalid.");
34
+ read(5);
35
+ if (read(1)) throw new Error("DTS reserved header bit is set.");
36
+ read(4);
37
+ read(3);
38
+ const extended = read(1);
39
+ if (extended) throw new Error("DTS core extensions require extension-specific declarations.");
40
+ read(1);
41
+ const lfe = read(2);
42
+ if (lfe === 3) throw new Error("DTS LFE declaration is invalid.");
43
+ read(1);
44
+ if (crc) read(16);
45
+ read(1);
46
+ read(4);
47
+ read(2);
48
+ if ([4, 7].includes(read(3))) throw new Error("DTS PCM resolution is invalid.");
49
+ return { size: packed ? Math.ceil(logicalSize * 8 / 14) * 2 : logicalSize,
50
+ sampleRate, channels: channels + Number(lfe > 0), duration: blocks * 32 / sampleRate, codecId: "dts" };
51
+ }
@@ -130,7 +130,13 @@ export class ElementReader {
130
130
  if (room <= 1) {
131
131
  return null;
132
132
  }
133
- const bytes = await this.bytes(at, Math.min(MAX_HEADER_BYTES, room));
133
+ const first = await this.bytes(at, 1);
134
+ const idWidth = vintWidth(first[0]);
135
+ if (!idWidth || idWidth > 4 || idWidth >= room) return null;
136
+ const beginning = await this.bytes(at, idWidth + 1);
137
+ const sizeWidth = vintWidth(beginning[idWidth]);
138
+ if (!sizeWidth || idWidth + sizeWidth > room) return null;
139
+ const bytes = await this.bytes(at, idWidth + sizeWidth);
134
140
  const id = readVint(bytes, 0, true);
135
141
  if (!id) {
136
142
  return null;
@@ -172,3 +178,8 @@ export class ElementReader {
172
178
  return this.bytes(element.dataOffset, available);
173
179
  }
174
180
  }
181
+
182
+ function vintWidth(byte) {
183
+ for (let width = 1; width <= 8; width++) if (byte & (1 << (8 - width))) return width;
184
+ return 0;
185
+ }
@@ -0,0 +1,45 @@
1
+ /** Decode ffprobe's compact C-escaped records without splitting escaped separators. */
2
+ export function ffprobeRecord(line) {
3
+ const fields = [];
4
+ let field = "", escaped = false;
5
+ for (const character of line.replace(/\r$/, "")) {
6
+ if (escaped) {
7
+ field += ({ n: "\n", r: "\r", t: "\t", f: "\f", b: "\b" })[character] ?? character;
8
+ escaped = false;
9
+ } else if (character === "\\") escaped = true;
10
+ else if (character === "|") { fields.push(field); field = ""; }
11
+ else field += character;
12
+ }
13
+ if (escaped) throw new Error("Truncated ffprobe escape.");
14
+ fields.push(field);
15
+ const kind = fields.shift();
16
+ const record = { kind };
17
+ for (const [position, value] of fields.entries()) {
18
+ // ffprobe 5 emits an empty nested section even when its entries are disabled.
19
+ if (value === "side_data" && fields.slice(position + 1).every(field => field === "")) break;
20
+ const separator = value.indexOf("=");
21
+ if (separator < 1) throw new Error(`Malformed ffprobe field: ${value.slice(0, 80)}`);
22
+ const key = value.slice(0, separator);
23
+ if (Object.hasOwn(record, key)) throw new Error(`Duplicate ffprobe field: ${key}`);
24
+ record[key] = value.slice(separator + 1);
25
+ }
26
+ return record;
27
+ }
28
+
29
+ /** Stream extradata's documented hexadecimal dump contains addresses and ASCII. */
30
+ export function ffprobeExtradata(dump, expectedSize) {
31
+ const chunks = [];
32
+ let offset = 0;
33
+ for (const line of String(dump ?? "").split("\n")) {
34
+ if (!line.trim()) continue;
35
+ const match = /^([0-9a-fA-F]{8}):\s([0-9a-fA-F ]+?)\s{2,}/.exec(line);
36
+ if (!match || Number.parseInt(match[1], 16) !== offset) throw new Error("Invalid ffprobe extradata address.");
37
+ const hex = match[2].replaceAll(" ", "");
38
+ if (hex.length % 2) throw new Error("Truncated ffprobe extradata.");
39
+ const bytes = Buffer.from(hex, "hex");
40
+ chunks.push(bytes);
41
+ offset += bytes.length;
42
+ }
43
+ if (offset !== expectedSize) throw new Error("Incomplete ffprobe extradata.");
44
+ return Buffer.concat(chunks);
45
+ }
@@ -0,0 +1,27 @@
1
+ /** Adobe FLV 10.1 Annex E: demuxer positions name tags rather than their payload. */
2
+ export async function flvPacketRanges({ readRange, fileSize, position, size, track, pts, dts }) {
3
+ if (position + 11 > fileSize) throw new Error("FLV tag header exceeds the source.");
4
+ const header = await readRange(position, position + 10);
5
+ const type = header[0];
6
+ if (type & 0xe0 || header.readUIntBE(8, 3) !== 0) throw new Error("Filtered, encrypted or reserved FLV tags are unsupported.");
7
+ if (type !== (track.type === "video" ? 9 : 8)) throw new Error("FLV tag does not contain the declared track.");
8
+ const dataSize = header.readUIntBE(1, 3);
9
+ const end = position + 11 + dataSize;
10
+ if (!dataSize || end + 4 > fileSize) throw new Error("FLV tag exceeds the source.");
11
+ const timestamp = (header.readUIntBE(4, 3) + header[7] * 0x1000000) / 1000;
12
+ if (Math.abs(timestamp - dts) > 1e-6) throw new Error("FLV tag and demuxer decode timestamps disagree.");
13
+ const framing = track.type === "video" ? 5 : track.codecId === "aac" ? 2 : track.codecId === "mp3" ? 1 : null;
14
+ if (framing === null || (track.type === "video" && track.codecId !== "h264")) throw new Error("FLV packet framing is unsupported for the declared codec.");
15
+ if (dataSize <= framing || dataSize - framing !== size) throw new Error("FLV tag and demuxer payload lengths disagree.");
16
+ const prefix = await readRange(position + 11, position + 10 + framing);
17
+ if (track.type === "video") {
18
+ if ((prefix[0] & 15) !== 7 || prefix[1] !== 1) throw new Error("FLV tag does not contain AVC frame data.");
19
+ const offset = prefix.readIntBE(2, 3) / 1000;
20
+ if (Math.abs(timestamp + offset - pts) > 1e-6) throw new Error("FLV composition time and demuxer presentation timestamp disagree.");
21
+ } else if (track.codecId === "aac") {
22
+ if ((prefix[0] >> 4) !== 10 || prefix[1] !== 1) throw new Error("FLV tag does not contain AAC frame data.");
23
+ } else if ((prefix[0] >> 4) !== 2) throw new Error("FLV tag does not contain MPEG layer III frame data.");
24
+ const previous = await readRange(end, end + 3);
25
+ if (previous.readUInt32BE(0) !== 11 + dataSize) throw new Error("FLV previous-tag size is invalid.");
26
+ return [[position + 11 + framing, end - 1]];
27
+ }
@@ -0,0 +1,37 @@
1
+ import { IndexMemoryUnavailable } from "./memory-unavailable.js";
2
+
3
+ /** Next distinct presentation timestamp, with admitted temporary sorting bytes. */
4
+ export function followingPacketTime(records, endTime, memory = {}) {
5
+ let held = 0;
6
+ let times;
7
+ const owner = { dispose() {
8
+ if (held) allocation.release?.(held);
9
+ held = 0;
10
+ times = null;
11
+ allocation.dispose?.();
12
+ } };
13
+ const allocation = memory.forRecord?.(owner, "timing") ?? memory;
14
+ const bytes = records.length * Float64Array.BYTES_PER_ELEMENT;
15
+ if (allocation.reserve?.(bytes) === false) {
16
+ allocation.dispose?.();
17
+ throw new IndexMemoryUnavailable(bytes);
18
+ }
19
+ held = bytes;
20
+ try {
21
+ times = new Float64Array(records.length);
22
+ for (let index = 0; index < records.length; index++) times[index] = records.ptsAt(index);
23
+ times.sort();
24
+ }
25
+ catch (error) { owner.dispose(); throw error; }
26
+ const next = time => {
27
+ let left = 0, right = times.length;
28
+ while (left < right) {
29
+ const middle = left + Math.floor((right - left) / 2);
30
+ if (times[middle] <= time) left = middle + 1;
31
+ else right = middle;
32
+ }
33
+ return left < times.length ? times[left] : endTime;
34
+ };
35
+ next.dispose = owner.dispose;
36
+ return next;
37
+ }
@@ -0,0 +1,183 @@
1
+ /** H.264 SPS facts required to bound decode reordering and describe frames. */
2
+ export function h264Configuration(avcc) {
3
+ if (!Buffer.isBuffer(avcc) || avcc.length < 7 || avcc[0] !== 1) throw new Error("AVC decoder configuration is invalid.");
4
+ if ((avcc[4] & 3) === 2) throw new Error("AVC NAL length size is reserved.");
5
+ const count = avcc[5] & 31;
6
+ if (!count) throw new Error("AVC configuration declares no SPS.");
7
+ let at = 6;
8
+ const sequences = [];
9
+ for (let index = 0; index < count; index++) {
10
+ if (at + 2 > avcc.length) throw new Error("AVC SPS length is truncated.");
11
+ const size = avcc.readUInt16BE(at); at += 2;
12
+ if (!size || at + size > avcc.length) throw new Error("AVC SPS exceeds its configuration.");
13
+ sequences.push(readSps(avcc.subarray(at, at + size))); at += size;
14
+ }
15
+ const first = sequences[0];
16
+ if (sequences.some(sequence => sequence.width !== first.width || sequence.height !== first.height || sequence.bitDepth !== first.bitDepth)) {
17
+ throw new Error("AVC SPS changes require packet-specific decoder configuration.");
18
+ }
19
+ return { ...first, reorderDepth: Math.max(...sequences.map(sequence => sequence.reorderDepth)), nalLengthBytes: (avcc[4] & 3) + 1 };
20
+ }
21
+
22
+ function readSps(nal, includeOrder = false) {
23
+ if ((nal[0] & 31) !== 7 || nal[0] & 128) throw new Error("AVC configuration entry is not an SPS.");
24
+ const bytes = [];
25
+ for (let at = 1, zeroes = 0; at < nal.length; at++) {
26
+ const byte = nal[at];
27
+ if (zeroes >= 2 && byte === 3) {
28
+ if (at + 1 >= nal.length || nal[at + 1] > 3) throw new Error("AVC emulation prevention byte is invalid.");
29
+ zeroes = 0; continue;
30
+ }
31
+ bytes.push(byte); zeroes = byte === 0 ? zeroes + 1 : 0;
32
+ }
33
+ const bits = new Bits(Buffer.from(bytes));
34
+ const profile = bits.uint(8), constraints = bits.uint(8), level = bits.uint(8);
35
+ bits.ue();
36
+ let chroma = 1, separate = 0, depth = 8;
37
+ if ([44, 83, 86, 100, 110, 118, 122, 128, 134, 135, 138, 139, 244].includes(profile)) {
38
+ chroma = bits.ue();
39
+ if (chroma > 3) throw new Error("AVC chroma format is invalid.");
40
+ if (chroma === 3) separate = bits.uint(1);
41
+ depth += bits.ue();
42
+ const chromaDepth = 8 + bits.ue();
43
+ if (depth > 14 || chromaDepth > 14) throw new Error("AVC bit depth is invalid.");
44
+ bits.uint(1);
45
+ if (bits.uint(1)) {
46
+ for (let index = 0; index < (chroma === 3 ? 12 : 8); index++) {
47
+ if (!bits.uint(1)) continue;
48
+ let last = 8, next = 8;
49
+ for (let sample = 0; sample < (index < 6 ? 16 : 64); sample++) {
50
+ if (next) next = (last + bits.se() + 256) % 256;
51
+ last = next || last;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ const frameNumberBits = bits.ue() + 4;
57
+ const order = bits.ue();
58
+ let pocBits = null;
59
+ let orderCycle = null;
60
+ if (order === 0) pocBits = bits.ue() + 4;
61
+ else if (order === 1) {
62
+ const deltaAlwaysZero = bits.uint(1) === 1, nonReferenceOffset = bits.se(), bottomOffset = bits.se();
63
+ const cycle = bits.ue();
64
+ if (cycle > 255) throw new Error("AVC POC cycle is invalid.");
65
+ const referenceOffsets = [];
66
+ for (let index = 0; index < cycle; index++) referenceOffsets.push(bits.se());
67
+ orderCycle = { deltaAlwaysZero, nonReferenceOffset, bottomOffset, referenceOffsets };
68
+ } else if (order !== 2) throw new Error("AVC picture order count type is invalid.");
69
+ const references = bits.ue();
70
+ if (references > 16) throw new Error("AVC reference frame count is invalid.");
71
+ bits.uint(1);
72
+ const columns = bits.ue() + 1, rows = bits.ue() + 1;
73
+ const frameOnly = bits.uint(1);
74
+ const pictureMacroblocks = columns * rows * (2 - frameOnly);
75
+ if (!Number.isSafeInteger(pictureMacroblocks) || pictureMacroblocks > 696320) {
76
+ throw new Error("AVC picture exceeds the supported decode buffer bound.");
77
+ }
78
+ if (!frameOnly) bits.uint(1);
79
+ bits.uint(1);
80
+ let left = 0, right = 0, top = 0, bottom = 0;
81
+ if (bits.uint(1)) { left = bits.ue(); right = bits.ue(); top = bits.ue(); bottom = bits.ue(); }
82
+ let fps = null, reorderDepth = null, timingTickSeconds = null, picStructPresent = false, seiDelayBits = 0;
83
+ if (bits.uint(1)) {
84
+ if (bits.uint(1)) { if (bits.uint(8) === 255) { bits.uint(16); bits.uint(16); } }
85
+ if (bits.uint(1)) bits.uint(1);
86
+ if (bits.uint(1)) { bits.uint(3); bits.uint(1); if (bits.uint(1)) { bits.uint(8); bits.uint(8); bits.uint(8); } }
87
+ if (bits.uint(1)) { bits.ue(); bits.ue(); }
88
+ if (bits.uint(1)) {
89
+ const tick = bits.uint(32), scale = bits.uint(32), fixed = bits.uint(1);
90
+ if (!tick || !scale) throw new Error("AVC timing declaration is invalid.");
91
+ timingTickSeconds = tick / scale;
92
+ if (fixed) fps = scale / (2 * tick);
93
+ }
94
+ const nalHrd = bits.uint(1);
95
+ const nalDelay = nalHrd ? hrd(bits) : 0;
96
+ const vclHrd = bits.uint(1);
97
+ const vclDelay = vclHrd ? hrd(bits) : 0;
98
+ seiDelayBits = nalHrd ? nalDelay : vclDelay;
99
+ if (nalHrd || vclHrd) bits.uint(1);
100
+ picStructPresent = bits.uint(1) === 1;
101
+ if (bits.uint(1)) {
102
+ bits.uint(1); bits.ue(); bits.ue(); bits.ue(); bits.ue();
103
+ reorderDepth = bits.ue();
104
+ const buffering = bits.ue();
105
+ if (reorderDepth > buffering || buffering > 16) throw new Error("AVC reorder buffer declaration is invalid.");
106
+ }
107
+ }
108
+ if (reorderDepth === null) {
109
+ const dpb = new Map([[10, 396], [11, 900], [12, 2376], [13, 2376], [20, 2376], [21, 4752], [22, 8100],
110
+ [30, 8100], [31, 18000], [32, 20480], [40, 32768], [41, 32768], [42, 34816], [50, 110400],
111
+ [51, 184320], [52, 184320], [60, 696320], [61, 696320], [62, 696320]]).get(level);
112
+ if (!dpb) throw new Error("AVC level has no declared decode buffer bound.");
113
+ const constrained = constraints & 16 && [44, 86, 100, 110, 122, 244].includes(profile);
114
+ reorderDepth = constrained ? 0 : Math.min(16, Math.floor(dpb / pictureMacroblocks));
115
+ }
116
+ const arrayType = separate ? 0 : chroma;
117
+ const cropX = arrayType === 1 || arrayType === 2 ? 2 : 1;
118
+ const cropY = (arrayType === 1 ? 2 : 1) * (2 - frameOnly);
119
+ const width = columns * 16 - (left + right) * cropX;
120
+ const height = rows * 16 * (2 - frameOnly) - (top + bottom) * cropY;
121
+ if (!(width > 0 && height > 0)) throw new Error("AVC crop exceeds its picture dimensions.");
122
+ return { width, height, fps, bitDepth: depth, reorderDepth, frameOnly: frameOnly === 1, timingTickSeconds, picStructPresent, seiDelayBits,
123
+ ...(includeOrder ? { pictureOrder: { type: order, frameNumberBits, pocBits, separateColourPlane: Boolean(separate),
124
+ ...(orderCycle ?? {}) } } : {}) };
125
+ }
126
+
127
+ export function avcOrderParameters(sps, pps) {
128
+ const facts = readSps(sps, true);
129
+ const bits = new Bits(unescapeNal(pps));
130
+ bits.ue(); bits.ue(); bits.uint(1);
131
+ return { ...facts.pictureOrder, bottomFieldOrderPresent: bits.uint(1) === 1 };
132
+ }
133
+
134
+ export function unescapeNal(nal) {
135
+ const bytes = [];
136
+ for (let at = 1, zeroes = 0; at < nal.length; at++) {
137
+ const byte = nal[at];
138
+ if (zeroes >= 2 && byte === 3) {
139
+ if (at + 1 >= nal.length || nal[at + 1] > 3) throw new Error("AVC escape byte is invalid.");
140
+ zeroes = 0; continue;
141
+ }
142
+ bytes.push(byte);
143
+ zeroes = byte === 0 ? zeroes + 1 : 0;
144
+ }
145
+ return Buffer.from(bytes);
146
+ }
147
+
148
+ /** Build the decoder declaration from the complete in-band SPS and PPS units. */
149
+ export function avcConfigurationFromUnits(sps, pps) {
150
+ if (!Buffer.isBuffer(sps) || !Buffer.isBuffer(pps) || sps.length < 4 || !pps.length ||
151
+ sps.length > 65535 || pps.length > 65535 || (pps[0] & 31) !== 8) throw new Error("AVC parameter-set lengths are invalid.");
152
+ const header = Buffer.from([1, sps[1], sps[2], sps[3], 0xff, 0xe1, sps.length >> 8, sps.length & 255]);
153
+ const tail = Buffer.from([1, pps.length >> 8, pps.length & 255]);
154
+ const bytes = Buffer.concat([header, sps, tail, pps]);
155
+ return { ...h264Configuration(bytes), codecPrivateB64: bytes.toString("base64"), packetFraming: "annex-b" };
156
+ }
157
+
158
+ function hrd(bits) {
159
+ const count = bits.ue() + 1;
160
+ if (count > 32) throw new Error("AVC HRD entry count is invalid.");
161
+ bits.uint(4); bits.uint(4);
162
+ for (let index = 0; index < count; index++) { bits.ue(); bits.ue(); bits.uint(1); }
163
+ bits.uint(5);
164
+ const removal = bits.uint(5) + 1, output = bits.uint(5) + 1;
165
+ bits.uint(5);
166
+ return removal + output;
167
+ }
168
+
169
+ export class Bits {
170
+ constructor(bytes, label = "AVC") { this.bytes = bytes; this.at = 0; this.label = label; }
171
+ uint(count) {
172
+ if (count < 0 || count > 32 || this.at + count > this.bytes.length * 8) throw new Error(`${this.label} SPS bits are truncated.`);
173
+ let value = 0;
174
+ for (let index = 0; index < count; index++, this.at++) value = value * 2 + ((this.bytes[this.at >> 3] >> (7 - (this.at & 7))) & 1);
175
+ return value;
176
+ }
177
+ ue() {
178
+ let zeroes = 0;
179
+ while (!this.uint(1)) if (++zeroes > 31) throw new Error(`${this.label} Exp-Golomb value is invalid.`);
180
+ return 2 ** zeroes - 1 + this.uint(zeroes);
181
+ }
182
+ se() { const code = this.ue(); return code & 1 ? (code + 1) / 2 : -code / 2; }
183
+ }