@torrent-tv/proxy 2.75.0 → 2.76.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.
- package/CHANGELOG.md +1504 -1461
- package/CLAUDE.md +11 -1
- package/biome.json +182 -1
- package/docs/container-architecture.md +2 -1
- package/docs/encode-run-state.md +1 -1
- package/knip.json +14 -0
- package/package.json +1 -1
- package/routes/api/sources/warm/post.js +1 -1
- package/routes/api/transcode-sessions/post.js +185 -185
- package/routes/api/transcode-sessions/progress/get.js +5 -1
- package/routes/transcode/audio-file/get.js +11 -1
- package/routes/transcode/audio-warm/get.js +11 -1
- package/routes/transcode/session-file/get.js +1 -1
- package/routes/transcode/variant-file/get.js +10 -1
- package/scripts/render-run-graph.js +2 -2
- package/server.js +25 -0
- package/services/audio-inventory.js +206 -201
- package/services/container/AviContainer.js +1 -1
- package/services/container/Container.js +33 -1
- package/services/container/MatroskaContainer.js +1 -1
- package/services/container/Mp4Container.js +1 -1
- package/services/container/SubtitleFileContainer.js +0 -1
- package/services/controllers/SubtitleController.js +128 -128
- package/services/demand/index.js +7 -10
- package/services/download/registry.js +0 -14
- package/services/encode/CoverageMap.js +281 -0
- package/services/encode/EncodePlan.js +255 -0
- package/services/encode/EncodeRun.js +587 -0
- package/services/encode/Encoder.js +84 -0
- package/services/encode/NvencEncoder.js +45 -0
- package/services/encode/QsvEncoder.js +47 -0
- package/services/encode/SegmentDemand.js +0 -0
- package/services/encode/SegmentStore.js +529 -0
- package/services/encode/SoftwareEncoder.js +111 -0
- package/services/encode/V4l2m2mEncoder.js +53 -0
- package/services/encode/VaapiEncoder.js +53 -0
- package/services/encode/args.js +200 -0
- package/services/{encode-exit.js → encode/encode-exit.js} +17 -0
- package/services/encode/index.js +9 -0
- package/services/encode/run-command.js +647 -0
- package/services/hls-session-manager.js +11073 -10711
- package/services/hwaccel.js +1688 -1992
- package/services/orchestrators/EncodeOrchestrator.js +359 -0
- package/services/output/LiveOutputs.js +213 -0
- package/services/output/Output.js +94 -0
- package/services/output/OutputSpec.js +195 -0
- package/services/output/Timeline.js +220 -0
- package/services/output/index.js +1 -0
- package/services/output/ladder.js +26 -0
- package/services/playback-planner.js +806 -775
- package/services/produced-index.js +222 -300
- package/services/source/SourceFile.js +346 -0
- package/services/{sidecar-files.js → torrent/files.js} +107 -11
- package/services/torrent/naming.js +619 -0
- package/services/torrent-worker/client.js +10 -0
- package/services/torrent-worker/container-tracks.js +71 -43
- package/services/torrent-worker/pool-adapter.js +18 -0
- package/services/torrent-worker/protocol.js +7 -0
- package/services/torrent-worker/subtitle-cues.js +549 -549
- package/services/torrent-worker/worker.js +18 -0
- package/services/tracks/TextSubtitleTrack.js +287 -287
- package/services/tracks/index.js +15 -14
- package/services/viewer/Viewer.js +145 -0
- package/services/viewer/Viewers.js +124 -0
- package/test/auto-quality-step.test.js +508 -506
- package/test/behind-head-repair.test.js +17 -7
- package/test/coverage-map.test.js +153 -0
- package/test/cut-times-timeline.test.js +6 -5
- package/test/cuts-follow-published-grid.test.js +4 -4
- package/test/decode-cost.test.js +31 -12
- package/test/encode-exit.test.js +1 -1
- package/test/encode-orchestrator.test.js +196 -0
- package/test/encode-plan.test.js +245 -0
- package/test/encode-run-state.test.js +2 -2
- package/test/encode-run.test.js +168 -0
- package/test/encoder-kinds.test.js +122 -0
- package/test/held-request-width.test.js +9 -3
- package/test/helpers/encode-run.js +128 -0
- package/test/keyframe-index-accuracy.test.js +19 -12
- package/test/keyframes-belong-to-the-file.test.js +132 -0
- package/test/orchestrator-wired.test.js +164 -0
- package/test/output-shape.test.js +68 -0
- package/test/output-spec.test.js +157 -0
- package/test/produced-copy-choice.test.js +58 -92
- package/test/produced-index.test.js +142 -188
- package/test/quality-variants.test.js +1079 -1075
- package/test/run-graph-drift.test.js +1 -1
- package/test/run-intervals.test.js +329 -0
- package/test/run-position-follows-published-grid.test.js +4 -4
- package/test/seek-landing.test.js +8 -8
- package/test/seek-target-not-superseded.test.js +21 -9
- package/test/segment-demand.test.js +82 -0
- package/test/segment-serve-wiring.test.js +47 -52
- package/test/segment-store.test.js +187 -0
- package/test/segments-are-shared.test.js +175 -0
- package/test/sidecar-naming.test.js +142 -0
- package/test/source-file.test.js +133 -0
- package/test/stale-request-after-seek.test.js +18 -12
- package/test/subtitle-language.test.js +252 -252
- package/test/timeline.test.js +95 -0
- package/test/{sidecar-files.test.js → torrent-files.test.js} +44 -1
- package/test/torrent-naming.test.js +255 -0
- package/test/tracks-begin-together.test.js +44 -32
- package/test/two-viewers-one-picture.test.js +347 -0
- package/test/viewer-outputs.test.js +273 -0
- package/test/viewer.test.js +91 -0
- package/utils/perf.js +1 -63
- package/services/container/index.js +0 -6
- package/services/controllers/index.js +0 -2
- package/services/download/index.js +0 -8
- package/services/orchestrators/index.js +0 -2
- /package/services/{encode-run-state.js → encode/encode-run-state.js} +0 -0
- /package/services/{language-detect.js → tracks/language-detect.js} +0 -0
|
@@ -1,188 +1,142 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The session's one statement of what it has produced.
|
|
3
|
-
*
|
|
4
|
-
* Every check builds real directories and real files, because what is under
|
|
5
|
-
* test is exactly the reading of them: which run's copy wins, what an empty
|
|
6
|
-
* file counts as, and whether a directory that has not changed is read again.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import test from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import os from "node:os";
|
|
13
|
-
import { mkdtempSync,
|
|
14
|
-
|
|
15
|
-
import { ProducedIndex } from "../services/produced-index.js";
|
|
16
|
-
|
|
17
|
-
/** The naming the fMP4 format uses, reduced to what the index asks of it. */
|
|
18
|
-
const segmentFormat = {
|
|
19
|
-
isSegmentFileName: (name) => /^segment-\d{5}\.mp4$/.test(name),
|
|
20
|
-
segmentIndexFromName: (name) => {
|
|
21
|
-
const match = /^segment-(\d{5})\.mp4$/.exec(name);
|
|
22
|
-
return match ? Number(match[1]) : -1;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const nameOf = (index) => `segment-${String(index).padStart(5, "0")}.mp4`;
|
|
27
|
-
|
|
28
|
-
function
|
|
29
|
-
const dirPath = mkdtempSync(path.join(os.tmpdir(), "produced-index-"));
|
|
30
|
-
return {
|
|
31
|
-
dirPath,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
assert.equal(index.
|
|
85
|
-
assert.equal(index.pathOf(nameOf(
|
|
86
|
-
} finally {
|
|
87
|
-
s.remove();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("
|
|
92
|
-
const s =
|
|
93
|
-
try {
|
|
94
|
-
s.
|
|
95
|
-
s.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// the same tick must not be told the file is there.
|
|
144
|
-
index.invalidate();
|
|
145
|
-
assert.equal(index.pathOf(nameOf(9)), null);
|
|
146
|
-
} finally {
|
|
147
|
-
s.remove();
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("an unchanged run is not listed again", () => {
|
|
152
|
-
const s = session();
|
|
153
|
-
try {
|
|
154
|
-
const run = s.run(1);
|
|
155
|
-
run.write(1, "piece");
|
|
156
|
-
const index = s.index();
|
|
157
|
-
|
|
158
|
-
index.segmentNumbers();
|
|
159
|
-
const afterFirst = index.directoryReads;
|
|
160
|
-
assert.equal(afterFirst, 1, "the first question reads the run");
|
|
161
|
-
|
|
162
|
-
for (let asked = 0; asked < 20; asked += 1) {
|
|
163
|
-
index.segmentNumbers();
|
|
164
|
-
index.pathOf(nameOf(1));
|
|
165
|
-
}
|
|
166
|
-
assert.equal(
|
|
167
|
-
index.directoryReads,
|
|
168
|
-
afterFirst,
|
|
169
|
-
"forty more questions about a run that has not changed must read nothing"
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
run.write(2, "another piece");
|
|
173
|
-
index.segmentNumbers();
|
|
174
|
-
assert.equal(index.directoryReads, afterFirst + 1, "a run that gained a file is read once more");
|
|
175
|
-
} finally {
|
|
176
|
-
s.remove();
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("a session directory that does not exist answers with nothing", () => {
|
|
181
|
-
const index = new ProducedIndex({
|
|
182
|
-
dirPath: path.join(os.tmpdir(), "produced-index-absent-0000"),
|
|
183
|
-
segmentFormat
|
|
184
|
-
});
|
|
185
|
-
assert.deepEqual(index.runDirs(), []);
|
|
186
|
-
assert.equal(index.pathOf(nameOf(1)), null);
|
|
187
|
-
assert.deepEqual([...index.segmentNumbers()], []);
|
|
188
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file The session's one statement of what it has produced.
|
|
3
|
+
*
|
|
4
|
+
* Every check builds real directories and real files, because what is under
|
|
5
|
+
* test is exactly the reading of them: which run's copy wins, what an empty
|
|
6
|
+
* file counts as, and whether a directory that has not changed is read again.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { mkdtempSync, writeFileSync, rmSync, unlinkSync } from "node:fs";
|
|
14
|
+
|
|
15
|
+
import { ProducedIndex } from "../services/produced-index.js";
|
|
16
|
+
|
|
17
|
+
/** The naming the fMP4 format uses, reduced to what the index asks of it. */
|
|
18
|
+
const segmentFormat = {
|
|
19
|
+
isSegmentFileName: (name) => /^segment-\d{5}\.mp4$/.test(name),
|
|
20
|
+
segmentIndexFromName: (name) => {
|
|
21
|
+
const match = /^segment-(\d{5})\.mp4$/.exec(name);
|
|
22
|
+
return match ? Number(match[1]) : -1;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const nameOf = (index) => `segment-${String(index).padStart(5, "0")}.mp4`;
|
|
27
|
+
|
|
28
|
+
function output() {
|
|
29
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "produced-index-"));
|
|
30
|
+
return {
|
|
31
|
+
dirPath,
|
|
32
|
+
write(index, contents) {
|
|
33
|
+
writeFileSync(path.join(dirPath, nameOf(index)), contents);
|
|
34
|
+
return path.join(dirPath, nameOf(index));
|
|
35
|
+
},
|
|
36
|
+
writeNamed(name, contents) {
|
|
37
|
+
writeFileSync(path.join(dirPath, name), contents);
|
|
38
|
+
return path.join(dirPath, name);
|
|
39
|
+
},
|
|
40
|
+
index: () => new ProducedIndex({ dirPath, segmentFormat }),
|
|
41
|
+
remove: () => rmSync(dirPath, { recursive: true, force: true })
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test("a file with bytes in it is produced; one that was only opened is not", () => {
|
|
46
|
+
const s = output();
|
|
47
|
+
try {
|
|
48
|
+
s.write(1, "a piece");
|
|
49
|
+
s.write(2, "");
|
|
50
|
+
const index = s.index();
|
|
51
|
+
|
|
52
|
+
assert.deepEqual([...index.segmentNumbers()], [1]);
|
|
53
|
+
assert.equal(index.pathOf(nameOf(1)), path.join(s.dirPath, nameOf(1)));
|
|
54
|
+
// The empty file exists and is not an answer — this is the pair of beliefs
|
|
55
|
+
// that stopped playback for ten minutes on 2026-09-03, now one belief.
|
|
56
|
+
assert.equal(index.pathOf(nameOf(2)), null);
|
|
57
|
+
} finally {
|
|
58
|
+
s.remove();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
test("a file that is not a segment is found by name", () => {
|
|
65
|
+
const s = output();
|
|
66
|
+
try {
|
|
67
|
+
s.writeNamed("init.mp4", "header");
|
|
68
|
+
const index = s.index();
|
|
69
|
+
assert.equal(index.pathOf("init.mp4"), path.join(s.dirPath, "init.mp4"));
|
|
70
|
+
assert.ok(index.fileNames().includes("init.mp4"));
|
|
71
|
+
} finally {
|
|
72
|
+
s.remove();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("a directory that is gone stops answering", () => {
|
|
77
|
+
const s = output();
|
|
78
|
+
try {
|
|
79
|
+
s.write(4, "piece");
|
|
80
|
+
const index = s.index();
|
|
81
|
+
assert.equal(index.segmentNumbers().size, 1);
|
|
82
|
+
|
|
83
|
+
rmSync(s.dirPath, { recursive: true, force: true });
|
|
84
|
+
assert.equal(index.segmentNumbers().size, 0);
|
|
85
|
+
assert.equal(index.pathOf(nameOf(4)), null);
|
|
86
|
+
} finally {
|
|
87
|
+
s.remove();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("a file removed on purpose is not still an answer", () => {
|
|
92
|
+
const s = output();
|
|
93
|
+
try {
|
|
94
|
+
const written = s.write(9, "piece");
|
|
95
|
+
const index = s.index();
|
|
96
|
+
assert.equal(index.pathOf(nameOf(9)), written);
|
|
97
|
+
|
|
98
|
+
unlinkSync(written);
|
|
99
|
+
// The directory's own time has moved, but a caller that deletes and asks in
|
|
100
|
+
// the same tick must not be told the file is there.
|
|
101
|
+
index.invalidate();
|
|
102
|
+
assert.equal(index.pathOf(nameOf(9)), null);
|
|
103
|
+
} finally {
|
|
104
|
+
s.remove();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("an unchanged directory is not listed again", () => {
|
|
109
|
+
const s = output();
|
|
110
|
+
try {
|
|
111
|
+
s.write(1, "piece");
|
|
112
|
+
const index = s.index();
|
|
113
|
+
|
|
114
|
+
index.segmentNumbers();
|
|
115
|
+
assert.equal(index.directoryReads, 1, "the first question reads what is there and no more");
|
|
116
|
+
|
|
117
|
+
for (let asked = 0; asked < 20; asked += 1) {
|
|
118
|
+
index.segmentNumbers();
|
|
119
|
+
index.pathOf(nameOf(1));
|
|
120
|
+
}
|
|
121
|
+
assert.equal(
|
|
122
|
+
index.directoryReads,
|
|
123
|
+
1,
|
|
124
|
+
"forty more questions about a directory that has not changed must read nothing"
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
s.write(2, "another piece");
|
|
128
|
+
index.segmentNumbers();
|
|
129
|
+
assert.equal(index.directoryReads, 2, "a directory that gained a file is read once more");
|
|
130
|
+
} finally {
|
|
131
|
+
s.remove();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("an output directory that does not exist answers with nothing", () => {
|
|
136
|
+
const index = new ProducedIndex({
|
|
137
|
+
dirPath: path.join(os.tmpdir(), "produced-index-absent-0000"),
|
|
138
|
+
segmentFormat
|
|
139
|
+
});
|
|
140
|
+
assert.equal(index.pathOf(nameOf(1)), null);
|
|
141
|
+
assert.deepEqual([...index.segmentNumbers()], []);
|
|
142
|
+
});
|