@torrent-tv/proxy 2.89.6 → 2.89.8

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 (92) hide show
  1. package/AGENTS.md +32 -12
  2. package/CHANGELOG.md +2340 -2330
  3. package/assets/calibration/audio/NOTICE.md +19 -19
  4. package/assets/calibration/audio/manifest.json +98 -98
  5. package/biome.json +412 -412
  6. package/docs/browser-proxy-contract.md +155 -155
  7. package/docs/container-architecture.md +273 -273
  8. package/docs/encode-run-state.md +1 -1
  9. package/docs/playback-start-forecast.md +113 -98
  10. package/package.json +51 -50
  11. package/routes/api/sources/files/get.js +106 -106
  12. package/scripts/build-audio-samples.mjs +31 -31
  13. package/services/encode/EncodeRun.js +67 -15
  14. package/services/encode/EncodeRuns.js +19 -7
  15. package/services/encode/OutputOpening.js +784 -784
  16. package/services/encode/audio-calibration.js +67 -67
  17. package/services/encode/audio-work-rate.js +15 -15
  18. package/services/encode/encode-exit.js +2 -0
  19. package/services/encode/hwaccel.js +1894 -1894
  20. package/services/encode/output/index.js +24 -24
  21. package/services/encode/piece-completeness.js +60 -2
  22. package/services/encode/run-command.js +757 -755
  23. package/services/encode/segment-formats/index.js +1 -0
  24. package/services/encode/segment-formats/mp4-boxes.js +8 -1
  25. package/services/media/ContainerOrchestrator.js +202 -202
  26. package/services/media/KeyframeTables.js +356 -356
  27. package/services/media/SubtitleCues.js +592 -592
  28. package/services/media/audio-inventory.js +299 -299
  29. package/services/media/container/AviContainer.js +268 -268
  30. package/services/media/container/Container.js +632 -632
  31. package/services/media/container/ContainerFactory.js +38 -38
  32. package/services/media/container/MatroskaContainer.js +1031 -1031
  33. package/services/media/container/Mp4Container.js +1354 -1354
  34. package/services/media/container/SubtitleFileContainer.js +339 -339
  35. package/services/media/container/ebml-stream.js +174 -174
  36. package/services/media/container/matroska-clusters.js +1165 -1165
  37. package/services/media/container/unavailable.js +77 -77
  38. package/services/media/tracks/ContainerTrack.js +84 -84
  39. package/services/media/tracks/TextSubtitleTrack.js +295 -295
  40. package/services/storage/piece-store/shared-piece-store.js +1937 -1937
  41. package/services/storage/segment-store/SegmentStore.js +1294 -1294
  42. package/services/torrent/Contents.js +382 -382
  43. package/services/torrent/episode-naming.js +182 -182
  44. package/services/torrent/torrent-pool.js +2688 -2688
  45. package/services/torrent/worker/client.js +836 -836
  46. package/services/torrent/worker/held-bytes.js +231 -231
  47. package/services/torrent/worker/worker.js +1137 -1137
  48. package/services/transport/data-channel-handler.js +1373 -1373
  49. package/services/viewer/PriorityMap.js +421 -421
  50. package/test/audio-calibration.test.js +23 -23
  51. package/test/auto-quality-step.test.js +762 -762
  52. package/test/coalesce.test.js +39 -39
  53. package/test/coverage-follows-the-disk.test.js +5 -0
  54. package/test/decode-cost.test.js +475 -475
  55. package/test/encode-run.test.js +58 -0
  56. package/test/episode-naming.test.js +131 -131
  57. package/test/failed-encoder-still-serves-what-exists.test.js +1 -1
  58. package/test/fixtures/audio-frame-boundary.json +1 -1
  59. package/test/fixtures/refused-audio-20261002.json +10 -0
  60. package/test/fmp4-presentation-clock.test.js +4 -2
  61. package/test/format-keyed-outputs.test.js +78 -78
  62. package/test/health-metrics.test.js +11 -13
  63. package/test/held-read-ownership.test.js +77 -77
  64. package/test/helpers/encode-run.js +14 -6
  65. package/test/helpers/held-file.js +77 -77
  66. package/test/helpers/manager.js +62 -62
  67. package/test/helpers/matroska-file.js +296 -296
  68. package/test/keyframe-table-budget.test.js +123 -123
  69. package/test/matroska-cluster-discovery.test.js +323 -323
  70. package/test/one-output-one-session.test.js +273 -273
  71. package/test/output-speed.test.js +6 -1
  72. package/test/piece-completeness.test.js +37 -1
  73. package/test/quality-variants.test.js +1274 -1270
  74. package/test/rate-control-in-the-output.test.js +4 -3
  75. package/test/reads-not-yet-arrived.test.js +218 -218
  76. package/test/seek-frees-the-viewers-requests.test.js +1 -1
  77. package/test/segment-closed-on-both-branches.test.js +114 -114
  78. package/test/segment-judged-by-its-own-header.test.js +222 -222
  79. package/test/segment-serve-wiring.test.js +481 -481
  80. package/test/serving-existing-output.test.js +111 -111
  81. package/test/source-files-route.test.js +113 -113
  82. package/test/subtitle-cue-walk.test.js +385 -385
  83. package/test/subtitle-metadata.test.js +15 -15
  84. package/test/subtitle-plan-waits.test.js +125 -125
  85. package/test/subtitle-track-numbering.test.js +391 -391
  86. package/test/two-viewers-one-picture.test.js +508 -508
  87. package/test/viewer-requests-admission.test.js +92 -92
  88. package/test/viewer-starts-on-map.test.js +27 -27
  89. package/test/viewer-states.test.js +5 -2
  90. package/test/viewing-generations.test.js +390 -390
  91. package/test/whole-file-held-read.test.js +30 -30
  92. package/utils/coalesce.js +41 -41
@@ -169,9 +169,10 @@ test("a hardware output is unavailable against a measured link, and given while
169
169
  });
170
170
 
171
171
  test("the soundtrack the viewer hears counts toward what their link carries", () => {
172
- // 720p nominal peaks at 3.64 Mbit/s. A 4.6 Mbit/s link admits 3.68 of it:
173
- // enough for the picture, not for the picture and 128 kbit/s of sound.
174
- const linkMbps = 4.6;
172
+ // 720p nominal peaks at 3.64 Mbit/s, and the whole measured link is compared
173
+ // with the stream (no share of it is set aside since proxy 2.89.4). A 3.7 Mbit/s
174
+ // link carries the picture, not the picture and 128 kbit/s of sound (3.768).
175
+ const linkMbps = 3.7;
175
176
  assert.equal(decideFor({ mode: "manual", linkMbps }).answer.verdict, "fits");
176
177
  assert.ok(
177
178
  decideFor({ mode: "manual", linkMbps, audioLoad: { mbps: 0.128, peakClass: "known" } }).unavailable,
@@ -1,218 +1,218 @@
1
- /**
2
- * @file A read whose bytes have not arrived is not an answer about the file.
3
- *
4
- * Field 2026-10-01: the subtitle plan of an episode was read while its Cues
5
- * table was still downloading, the read was given up, and the plan kept "no
6
- * clusters" for the life of the process — the track showed nothing for a whole
7
- * session while the same table, read eleven seconds later for the keyframes,
8
- * was there. The same shape was in the container choice, the track table, the
9
- * keyframe table, MP4's `moov` and AVI's `idx1`. Every check here makes the
10
- * bytes missing first and present afterwards, and asks the same object again.
11
- *
12
- * Fakes only: a buffer and a reader over it. No torrent, no thread.
13
- */
14
-
15
- import test from "node:test";
16
- import assert from "node:assert/strict";
17
-
18
- import { MatroskaContainer } from "../services/media/container/MatroskaContainer.js";
19
- import { Mp4Container } from "../services/media/container/Mp4Container.js";
20
- import { AviContainer } from "../services/media/container/AviContainer.js";
21
- import { ContainerOrchestrator } from "../services/media/ContainerOrchestrator.js";
22
- import { KeyframeTables } from "../services/media/KeyframeTables.js";
23
- import { isUnavailable } from "../services/media/container/unavailable.js";
24
- import {
25
- ID,
26
- buildMatroska,
27
- clusterData,
28
- cueBlock,
29
- element,
30
- readerOver,
31
- stringElement,
32
- trackEntry,
33
- uintElement
34
- } from "./helpers/matroska-file.js";
35
-
36
- const quiet = { info() {}, warn() {} };
37
-
38
- function episode({ subtitleLanguage = null, cuesBeforeClusters = false, cues = [1], extraTrack = null } = {}) {
39
- const tracks = [
40
- trackEntry({ number: 1, type: 1, codecId: "V_MPEG4/ISO/AVC", language: "jpn" }),
41
- trackEntry({ number: 3, type: 17, codecId: "S_TEXT/UTF8", language: subtitleLanguage })
42
- ];
43
- if (extraTrack) {
44
- tracks.push(extraTrack);
45
- }
46
- return buildMatroska({
47
- tracks,
48
- cues,
49
- cuesBeforeClusters,
50
- clusters: [
51
- { ticks: 0, data: clusterData({ ticks: 0, blocks: [cueBlock({ track: 3, relativeTicks: 100, durationTicks: 900, text: "one" })] }) },
52
- { ticks: 5000, data: clusterData({ ticks: 5000, blocks: [cueBlock({ track: 3, relativeTicks: 0, durationTicks: 900, text: "two" })] }) }
53
- ]
54
- });
55
- }
56
-
57
- test("a subtitle plan read before the Cues table arrived is not kept; read again, it names the clusters", async () => {
58
- const { file, cuesAt, clusterAt } = episode({ cues: [1, 3] });
59
- let tailHere = false;
60
- const { read } = readerOver(file, { missing: (start) => !tailHere && start >= cuesAt });
61
- const container = new MatroskaContainer({ readRange: read, fileSize: file.length });
62
-
63
- await assert.rejects(container.readSubtitlePlan(), (error) => isUnavailable(error));
64
-
65
- tailHere = true;
66
- const plan = await container.readSubtitlePlan();
67
- assert.equal(plan.cuesState, "complete");
68
- assert.deepEqual(plan.tracks[0].clusterPositions, clusterAt, "both clusters the table names for the track");
69
- });
70
-
71
- test("a read that returns fewer bytes than asked is not taken for the element", async () => {
72
- const { file } = episode();
73
- const { read } = readerOver(file, { short: true });
74
- await assert.rejects(new MatroskaContainer({ readRange: read, fileSize: file.length }).readTracks(), (error) =>
75
- isUnavailable(error)
76
- );
77
- });
78
-
79
- test("a head that has not arrived leaves no container behind, and the next ask builds one", async () => {
80
- const { file } = episode();
81
- let headHere = false;
82
- const { read } = readerOver(file, { missing: (start) => !headHere && start === 0 });
83
- const containers = new ContainerOrchestrator();
84
- const params = { sourceKey: "s", fileIndex: 0, readRange: read, fileSize: file.length, label: "x.mkv" };
85
-
86
- await assert.rejects(containers.containerFor(params), (error) => isUnavailable(error));
87
- assert.equal(containers.known("s", 0), undefined, "nothing was kept for a head that is not here");
88
- assert.deepEqual(await containers.getTracks(params), [], "the track table answers nothing yet");
89
-
90
- headHere = true;
91
- const container = await containers.containerFor(params);
92
- assert.equal(container.formatName, "matroska");
93
- assert.equal((await containers.getTracks(params)).length, 2);
94
- });
95
-
96
- test("a keyframe table asked for while its Cues are missing stays unanswered, and is filled when they arrive", async () => {
97
- const { file, cuesAt } = episode();
98
- let tailHere = false;
99
- const { read } = readerOver(file, { missing: (start) => !tailHere && start >= cuesAt });
100
- const containers = new ContainerOrchestrator();
101
- const params = { sourceKey: "k", fileIndex: 0, readRange: read, fileSize: file.length, label: "x.mkv" };
102
- const tables = new KeyframeTables({
103
- readTable: async () => {
104
- const index = await containers.getKeyframeIndex(params);
105
- return { times: index?.times ?? null, tolerance: 0, format: "matroska" };
106
- },
107
- logger: quiet
108
- });
109
-
110
- await assert.rejects(tables.read({ sourceKey: "k", fileIndex: 0 }), (error) => isUnavailable(error));
111
- const table = tables.of({ sourceKey: "k", fileIndex: 0 });
112
- assert.equal(table.answered, false, "a read that did not arrive is not an answer");
113
-
114
- tailHere = true;
115
- tables.readAgainIfUnanswered({ sourceKey: "k", fileIndex: 0 });
116
- await tables.read({ sourceKey: "k", fileIndex: 0 });
117
- assert.equal(table.answered, true);
118
- assert.deepEqual(table.times, [0, 5], "the same object the sessions hold now carries the times");
119
- });
120
-
121
- test("a Cues element stored before the first cluster, with no SeekHead entry, is found", async () => {
122
- const { file } = episode({ cuesBeforeClusters: true });
123
- const { read } = readerOver(file);
124
- const index = await new MatroskaContainer({ readRange: read, fileSize: file.length }).parseKeyframeIndex();
125
- assert.deepEqual(index.times, [0, 5]);
126
- });
127
-
128
- test("a Tracks element larger than the head window is read by its own size", async () => {
129
- // A CodecPrivate of 80 KB pushes the end of Tracks past the 64 KB head.
130
- const big = element(
131
- ID.TRACK_ENTRY,
132
- Buffer.concat([
133
- uintElement(ID.TRACK_NUMBER, 4),
134
- uintElement(ID.TRACK_TYPE, 17),
135
- stringElement(ID.CODEC_ID, "S_TEXT/ASS"),
136
- element(0x63a2, Buffer.alloc(80 * 1024, 0x20))
137
- ])
138
- );
139
- const { file } = episode({ extraTrack: big });
140
- const { read } = readerOver(file);
141
- const tracks = await new MatroskaContainer({ readRange: read, fileSize: file.length }).readTracks();
142
- assert.deepEqual(tracks.map((track) => track.trackNumber), [1, 3, 4]);
143
- });
144
-
145
- test("the language: LanguageBCP47 wins, then Language, then the format's default `eng`; `und` stays unknown", async () => {
146
- const cases = [
147
- { entry: { language: null }, language: "eng", source: "default" },
148
- { entry: { language: "" }, language: "eng", source: "language" },
149
- { entry: { language: "rus" }, language: "rus", source: "language" },
150
- { entry: { language: null, languageBcp47: "pt-BR" }, language: "pt-BR", source: "bcp47" },
151
- { entry: { language: "por", languageBcp47: "pt-BR" }, language: "pt-BR", source: "bcp47" },
152
- { entry: { language: "und" }, language: "und", source: "language" }
153
- ];
154
- for (const { entry, language, source } of cases) {
155
- const { file } = buildMatroska({
156
- tracks: [trackEntry({ number: 3, type: 17, codecId: "S_TEXT/UTF8", ...entry })],
157
- cues: null,
158
- clusters: [{ ticks: 0, data: clusterData({ ticks: 0, blocks: [] }) }]
159
- });
160
- const { read } = readerOver(file);
161
- const [track] = await new MatroskaContainer({ readRange: read, fileSize: file.length }).readTracks();
162
- assert.equal(track.language, language, JSON.stringify(entry));
163
- assert.equal(track.languageSource, source, JSON.stringify(entry));
164
- }
165
- });
166
-
167
- /** @param {string} type @param {Buffer} payload */
168
- function box(type, payload) {
169
- const header = Buffer.alloc(8);
170
- header.writeUInt32BE(8 + payload.length, 0);
171
- header.write(type, 4, "latin1");
172
- return Buffer.concat([header, payload]);
173
- }
174
-
175
- test("an MP4 whose `moov` has not arrived keeps nothing; read again, it answers", async () => {
176
- const mvhd = Buffer.alloc(100);
177
- mvhd.writeUInt32BE(1000, 12); // timescale
178
- mvhd.writeUInt32BE(5000, 16); // duration
179
- const ftyp = box("ftyp", Buffer.from("isom\0\0\0\0isom", "latin1"));
180
- const moov = box("moov", box("mvhd", mvhd));
181
- const file = Buffer.concat([ftyp, moov]);
182
- let moovHere = false;
183
- const { read } = readerOver(file, { missing: (start) => !moovHere && start >= ftyp.length });
184
- const container = new Mp4Container({ readRange: read, fileSize: file.length });
185
-
186
- await assert.rejects(container.readMediaInfo(), (error) => isUnavailable(error));
187
- moovHere = true;
188
- assert.equal((await container.readMediaInfo()).durationSeconds, 5);
189
- });
190
-
191
- test("an MP4 `moov` larger than this reads whole is refused by name, not reported absent", async () => {
192
- const ftyp = box("ftyp", Buffer.from("isom\0\0\0\0isom", "latin1"));
193
- const header = Buffer.alloc(8);
194
- header.writeUInt32BE(40 * 1024 * 1024, 0);
195
- header.write("moov", 4, "latin1");
196
- const file = Buffer.concat([ftyp, header, Buffer.alloc(64)]);
197
- const { read } = readerOver(file);
198
- const container = new Mp4Container({ readRange: read, fileSize: file.length });
199
- assert.equal(await container.readSubtitlePlan(), null);
200
- assert.deepEqual(container.moovRefused, { size: 40 * 1024 * 1024, limit: 32 * 1024 * 1024 });
201
- });
202
-
203
- test("an AVI head that has not arrived leaves no media info behind", async () => {
204
- const avih = Buffer.alloc(56);
205
- avih.writeUInt32LE(40_000, 0); // 25 frames a second
206
- avih.writeUInt32LE(250, 16); // ten seconds
207
- const chunk = Buffer.concat([Buffer.from("avih", "latin1"), Buffer.from([56, 0, 0, 0]), avih]);
208
- const list = Buffer.concat([Buffer.from("LIST", "latin1"), Buffer.alloc(4), Buffer.from("hdrl", "latin1"), chunk]);
209
- list.writeUInt32LE(list.length - 8, 4);
210
- const riff = Buffer.concat([Buffer.from("RIFF", "latin1"), Buffer.alloc(4), Buffer.from("AVI ", "latin1"), list]);
211
- riff.writeUInt32LE(riff.length - 8, 4);
212
- let here = false;
213
- const { read } = readerOver(riff, { missing: () => !here });
214
- const container = new AviContainer({ readRange: read, fileSize: riff.length });
215
- await assert.rejects(container.readMediaInfo(), (error) => isUnavailable(error));
216
- here = true;
217
- assert.equal((await container.readMediaInfo()).durationSeconds, 10);
218
- });
1
+ /**
2
+ * @file A read whose bytes have not arrived is not an answer about the file.
3
+ *
4
+ * Field 2026-10-01: the subtitle plan of an episode was read while its Cues
5
+ * table was still downloading, the read was given up, and the plan kept "no
6
+ * clusters" for the life of the process — the track showed nothing for a whole
7
+ * session while the same table, read eleven seconds later for the keyframes,
8
+ * was there. The same shape was in the container choice, the track table, the
9
+ * keyframe table, MP4's `moov` and AVI's `idx1`. Every check here makes the
10
+ * bytes missing first and present afterwards, and asks the same object again.
11
+ *
12
+ * Fakes only: a buffer and a reader over it. No torrent, no thread.
13
+ */
14
+
15
+ import test from "node:test";
16
+ import assert from "node:assert/strict";
17
+
18
+ import { MatroskaContainer } from "../services/media/container/MatroskaContainer.js";
19
+ import { Mp4Container } from "../services/media/container/Mp4Container.js";
20
+ import { AviContainer } from "../services/media/container/AviContainer.js";
21
+ import { ContainerOrchestrator } from "../services/media/ContainerOrchestrator.js";
22
+ import { KeyframeTables } from "../services/media/KeyframeTables.js";
23
+ import { isUnavailable } from "../services/media/container/unavailable.js";
24
+ import {
25
+ ID,
26
+ buildMatroska,
27
+ clusterData,
28
+ cueBlock,
29
+ element,
30
+ readerOver,
31
+ stringElement,
32
+ trackEntry,
33
+ uintElement
34
+ } from "./helpers/matroska-file.js";
35
+
36
+ const quiet = { info() {}, warn() {} };
37
+
38
+ function episode({ subtitleLanguage = null, cuesBeforeClusters = false, cues = [1], extraTrack = null } = {}) {
39
+ const tracks = [
40
+ trackEntry({ number: 1, type: 1, codecId: "V_MPEG4/ISO/AVC", language: "jpn" }),
41
+ trackEntry({ number: 3, type: 17, codecId: "S_TEXT/UTF8", language: subtitleLanguage })
42
+ ];
43
+ if (extraTrack) {
44
+ tracks.push(extraTrack);
45
+ }
46
+ return buildMatroska({
47
+ tracks,
48
+ cues,
49
+ cuesBeforeClusters,
50
+ clusters: [
51
+ { ticks: 0, data: clusterData({ ticks: 0, blocks: [cueBlock({ track: 3, relativeTicks: 100, durationTicks: 900, text: "one" })] }) },
52
+ { ticks: 5000, data: clusterData({ ticks: 5000, blocks: [cueBlock({ track: 3, relativeTicks: 0, durationTicks: 900, text: "two" })] }) }
53
+ ]
54
+ });
55
+ }
56
+
57
+ test("a subtitle plan read before the Cues table arrived is not kept; read again, it names the clusters", async () => {
58
+ const { file, cuesAt, clusterAt } = episode({ cues: [1, 3] });
59
+ let tailHere = false;
60
+ const { read } = readerOver(file, { missing: (start) => !tailHere && start >= cuesAt });
61
+ const container = new MatroskaContainer({ readRange: read, fileSize: file.length });
62
+
63
+ await assert.rejects(container.readSubtitlePlan(), (error) => isUnavailable(error));
64
+
65
+ tailHere = true;
66
+ const plan = await container.readSubtitlePlan();
67
+ assert.equal(plan.cuesState, "complete");
68
+ assert.deepEqual(plan.tracks[0].clusterPositions, clusterAt, "both clusters the table names for the track");
69
+ });
70
+
71
+ test("a read that returns fewer bytes than asked is not taken for the element", async () => {
72
+ const { file } = episode();
73
+ const { read } = readerOver(file, { short: true });
74
+ await assert.rejects(new MatroskaContainer({ readRange: read, fileSize: file.length }).readTracks(), (error) =>
75
+ isUnavailable(error)
76
+ );
77
+ });
78
+
79
+ test("a head that has not arrived leaves no container behind, and the next ask builds one", async () => {
80
+ const { file } = episode();
81
+ let headHere = false;
82
+ const { read } = readerOver(file, { missing: (start) => !headHere && start === 0 });
83
+ const containers = new ContainerOrchestrator();
84
+ const params = { sourceKey: "s", fileIndex: 0, readRange: read, fileSize: file.length, label: "x.mkv" };
85
+
86
+ await assert.rejects(containers.containerFor(params), (error) => isUnavailable(error));
87
+ assert.equal(containers.known("s", 0), undefined, "nothing was kept for a head that is not here");
88
+ assert.deepEqual(await containers.getTracks(params), [], "the track table answers nothing yet");
89
+
90
+ headHere = true;
91
+ const container = await containers.containerFor(params);
92
+ assert.equal(container.formatName, "matroska");
93
+ assert.equal((await containers.getTracks(params)).length, 2);
94
+ });
95
+
96
+ test("a keyframe table asked for while its Cues are missing stays unanswered, and is filled when they arrive", async () => {
97
+ const { file, cuesAt } = episode();
98
+ let tailHere = false;
99
+ const { read } = readerOver(file, { missing: (start) => !tailHere && start >= cuesAt });
100
+ const containers = new ContainerOrchestrator();
101
+ const params = { sourceKey: "k", fileIndex: 0, readRange: read, fileSize: file.length, label: "x.mkv" };
102
+ const tables = new KeyframeTables({
103
+ readTable: async () => {
104
+ const index = await containers.getKeyframeIndex(params);
105
+ return { times: index?.times ?? null, tolerance: 0, format: "matroska" };
106
+ },
107
+ logger: quiet
108
+ });
109
+
110
+ await assert.rejects(tables.read({ sourceKey: "k", fileIndex: 0 }), (error) => isUnavailable(error));
111
+ const table = tables.of({ sourceKey: "k", fileIndex: 0 });
112
+ assert.equal(table.answered, false, "a read that did not arrive is not an answer");
113
+
114
+ tailHere = true;
115
+ tables.readAgainIfUnanswered({ sourceKey: "k", fileIndex: 0 });
116
+ await tables.read({ sourceKey: "k", fileIndex: 0 });
117
+ assert.equal(table.answered, true);
118
+ assert.deepEqual(table.times, [0, 5], "the same object the sessions hold now carries the times");
119
+ });
120
+
121
+ test("a Cues element stored before the first cluster, with no SeekHead entry, is found", async () => {
122
+ const { file } = episode({ cuesBeforeClusters: true });
123
+ const { read } = readerOver(file);
124
+ const index = await new MatroskaContainer({ readRange: read, fileSize: file.length }).parseKeyframeIndex();
125
+ assert.deepEqual(index.times, [0, 5]);
126
+ });
127
+
128
+ test("a Tracks element larger than the head window is read by its own size", async () => {
129
+ // A CodecPrivate of 80 KB pushes the end of Tracks past the 64 KB head.
130
+ const big = element(
131
+ ID.TRACK_ENTRY,
132
+ Buffer.concat([
133
+ uintElement(ID.TRACK_NUMBER, 4),
134
+ uintElement(ID.TRACK_TYPE, 17),
135
+ stringElement(ID.CODEC_ID, "S_TEXT/ASS"),
136
+ element(0x63a2, Buffer.alloc(80 * 1024, 0x20))
137
+ ])
138
+ );
139
+ const { file } = episode({ extraTrack: big });
140
+ const { read } = readerOver(file);
141
+ const tracks = await new MatroskaContainer({ readRange: read, fileSize: file.length }).readTracks();
142
+ assert.deepEqual(tracks.map((track) => track.trackNumber), [1, 3, 4]);
143
+ });
144
+
145
+ test("the language: LanguageBCP47 wins, then Language, then the format's default `eng`; `und` stays unknown", async () => {
146
+ const cases = [
147
+ { entry: { language: null }, language: "eng", source: "default" },
148
+ { entry: { language: "" }, language: "eng", source: "language" },
149
+ { entry: { language: "rus" }, language: "rus", source: "language" },
150
+ { entry: { language: null, languageBcp47: "pt-BR" }, language: "pt-BR", source: "bcp47" },
151
+ { entry: { language: "por", languageBcp47: "pt-BR" }, language: "pt-BR", source: "bcp47" },
152
+ { entry: { language: "und" }, language: "und", source: "language" }
153
+ ];
154
+ for (const { entry, language, source } of cases) {
155
+ const { file } = buildMatroska({
156
+ tracks: [trackEntry({ number: 3, type: 17, codecId: "S_TEXT/UTF8", ...entry })],
157
+ cues: null,
158
+ clusters: [{ ticks: 0, data: clusterData({ ticks: 0, blocks: [] }) }]
159
+ });
160
+ const { read } = readerOver(file);
161
+ const [track] = await new MatroskaContainer({ readRange: read, fileSize: file.length }).readTracks();
162
+ assert.equal(track.language, language, JSON.stringify(entry));
163
+ assert.equal(track.languageSource, source, JSON.stringify(entry));
164
+ }
165
+ });
166
+
167
+ /** @param {string} type @param {Buffer} payload */
168
+ function box(type, payload) {
169
+ const header = Buffer.alloc(8);
170
+ header.writeUInt32BE(8 + payload.length, 0);
171
+ header.write(type, 4, "latin1");
172
+ return Buffer.concat([header, payload]);
173
+ }
174
+
175
+ test("an MP4 whose `moov` has not arrived keeps nothing; read again, it answers", async () => {
176
+ const mvhd = Buffer.alloc(100);
177
+ mvhd.writeUInt32BE(1000, 12); // timescale
178
+ mvhd.writeUInt32BE(5000, 16); // duration
179
+ const ftyp = box("ftyp", Buffer.from("isom\0\0\0\0isom", "latin1"));
180
+ const moov = box("moov", box("mvhd", mvhd));
181
+ const file = Buffer.concat([ftyp, moov]);
182
+ let moovHere = false;
183
+ const { read } = readerOver(file, { missing: (start) => !moovHere && start >= ftyp.length });
184
+ const container = new Mp4Container({ readRange: read, fileSize: file.length });
185
+
186
+ await assert.rejects(container.readMediaInfo(), (error) => isUnavailable(error));
187
+ moovHere = true;
188
+ assert.equal((await container.readMediaInfo()).durationSeconds, 5);
189
+ });
190
+
191
+ test("an MP4 `moov` larger than this reads whole is refused by name, not reported absent", async () => {
192
+ const ftyp = box("ftyp", Buffer.from("isom\0\0\0\0isom", "latin1"));
193
+ const header = Buffer.alloc(8);
194
+ header.writeUInt32BE(40 * 1024 * 1024, 0);
195
+ header.write("moov", 4, "latin1");
196
+ const file = Buffer.concat([ftyp, header, Buffer.alloc(64)]);
197
+ const { read } = readerOver(file);
198
+ const container = new Mp4Container({ readRange: read, fileSize: file.length });
199
+ assert.equal(await container.readSubtitlePlan(), null);
200
+ assert.deepEqual(container.moovRefused, { size: 40 * 1024 * 1024, limit: 32 * 1024 * 1024 });
201
+ });
202
+
203
+ test("an AVI head that has not arrived leaves no media info behind", async () => {
204
+ const avih = Buffer.alloc(56);
205
+ avih.writeUInt32LE(40_000, 0); // 25 frames a second
206
+ avih.writeUInt32LE(250, 16); // ten seconds
207
+ const chunk = Buffer.concat([Buffer.from("avih", "latin1"), Buffer.from([56, 0, 0, 0]), avih]);
208
+ const list = Buffer.concat([Buffer.from("LIST", "latin1"), Buffer.alloc(4), Buffer.from("hdrl", "latin1"), chunk]);
209
+ list.writeUInt32LE(list.length - 8, 4);
210
+ const riff = Buffer.concat([Buffer.from("RIFF", "latin1"), Buffer.alloc(4), Buffer.from("AVI ", "latin1"), list]);
211
+ riff.writeUInt32LE(riff.length - 8, 4);
212
+ let here = false;
213
+ const { read } = readerOver(riff, { missing: () => !here });
214
+ const container = new AviContainer({ readRange: read, fileSize: riff.length });
215
+ await assert.rejects(container.readMediaInfo(), (error) => isUnavailable(error));
216
+ here = true;
217
+ assert.equal((await container.readMediaInfo()).durationSeconds, 10);
218
+ });
@@ -28,7 +28,7 @@ import { rm, writeFile } from "node:fs/promises";
28
28
  import path from "node:path";
29
29
  import { setTimeout as delay } from "node:timers/promises";
30
30
  import { wireOutputs } from "../services/server/wire-outputs.js";
31
- import { waitForSessionFile } from "../routes/transcode/session-file/get.js";
31
+ import { waitForSessionFile } from "../services/server/transcode-session-files.js";
32
32
  import { SourceFile } from "../services/media/SourceFile.js";
33
33
  import { Timeline } from "../services/encode/output/Timeline.js";
34
34
  import { Output } from "../services/encode/output/Output.js";