@torrent-tv/proxy 2.74.1 → 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 -1453
- package/CLAUDE.md +19 -6
- package/biome.json +182 -1
- package/docs/container-architecture.md +27 -6
- 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 +9 -214
- package/services/container/AviContainer.js +266 -81
- package/services/container/Container.js +281 -80
- package/services/container/ContainerFactory.js +67 -0
- package/services/container/MatroskaContainer.js +327 -8
- package/services/container/Mp4Container.js +373 -27
- 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 -747
- 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 +370 -333
- 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/AudioTrack.js +131 -40
- 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/audio-inventory.test.js +176 -177
- 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/matroska-cues-track.test.js +192 -192
- package/test/mp4-composition-times.test.js +0 -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/video-facts.test.js +102 -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/container-index/avi.js +0 -167
- package/services/container-index/index.js +0 -118
- package/services/container-index/matroska.js +0 -336
- package/services/container-index/mp4.js +0 -358
- package/services/controllers/index.js +0 -2
- package/services/download/index.js +0 -8
- package/services/orchestrators/index.js +0 -2
- /package/services/{container-index → container}/ebml-reader.js +0 -0
- /package/services/{encode-run-state.js → encode/encode-run-state.js} +0 -0
- /package/services/{language-detect.js → tracks/language-detect.js} +0 -0
package/CLAUDE.md
CHANGED
|
@@ -33,6 +33,14 @@ Linux-only host (e.g. POSIX-only signals must degrade elsewhere).
|
|
|
33
33
|
`deselect` or `critical`, and `registry.js`, which holds one per torrent and
|
|
34
34
|
owns the cross-torrent rule that withholds the speculative levels while
|
|
35
35
|
anything urgent is missing anywhere. See `docs/download-architecture.md`.
|
|
36
|
+
- `output/` — domain layer: `OutputSpec`, `VideoOutput`, `AudioOutput`,
|
|
37
|
+
`CutGrid`. What a session PRODUCES — which tracks, in what form, cut how,
|
|
38
|
+
packaged how — and therefore its identity: two outputs whose parameters
|
|
39
|
+
agree ARE the same output, and the encoded result is reused by definition.
|
|
40
|
+
Nothing about a VIEWER appears in it (not the consumer id, not where they
|
|
41
|
+
started, not their viewport), and nothing about the request that does not
|
|
42
|
+
change a byte of the result. `hls-session-manager` builds one and keys the
|
|
43
|
+
session on it.
|
|
36
44
|
- `container/` — domain layer: `Container` (abstract, RFC 9559 / ISO 14496-12),
|
|
37
45
|
`MatroskaContainer` / `Mp4Container` / `AviContainer`, `ContainerFactory`
|
|
38
46
|
(sniff 16 bytes → precise subclass). See `docs/container-architecture.md`.
|
|
@@ -43,18 +51,23 @@ Linux-only host (e.g. POSIX-only signals must degrade elsewhere).
|
|
|
43
51
|
RFC 9559 §5.1.4.1, FlagOriginal/Commentary only on audio, tkhd
|
|
44
52
|
track_enabled / alternate_group, elng BCP47).
|
|
45
53
|
- `orchestrators/` — application layer: `ContainerOrchestrator` (detect + per-file
|
|
46
|
-
cache, `getTracks`/`getKeyframeIndex`
|
|
54
|
+
cache, `getTracks`/`getMediaInfo`/`getKeyframeIndex` — the keyframe table is
|
|
55
|
+
a property of immutable bytes like the other two, memoized on the container
|
|
56
|
+
and read once per file by whoever asks), `SubtitleOrchestrator` (wraps
|
|
47
57
|
`torrent-worker/subtitle-cues.js` + `Container` tracks, warm/push). The walk
|
|
48
58
|
itself is `MatroskaContainer.walkHeldClusters` / `Mp4Container.readHeldSamples`;
|
|
49
59
|
`subtitle-cues.js` supplies the torrent's read policy and keeps the cursor.
|
|
50
60
|
- `controllers/` — interface layer: `PlaybackController` / `SubtitleController`
|
|
51
61
|
(thin adapters over orchestrators; routes depend on controllers, not services).
|
|
52
62
|
`routes/*` are now thin HTTP translators.
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
- Everything a container states about ITSELF lives in its own class: the
|
|
64
|
+
track table, the Cues, the keyframe times they name, the cluster positions,
|
|
65
|
+
the blocks inside a cluster, the MP4 sample table, and the walk of what is
|
|
66
|
+
downloaded. `services/container-index/` is gone; `container/ebml-reader.js`
|
|
67
|
+
is the format's byte-level grammar, not its statements, and is the only
|
|
68
|
+
piece kept apart. `ContainerFactory` is the ONE place that decides what a
|
|
69
|
+
file is — by sniffing the header, since that is what the muxer wrote — and
|
|
70
|
+
it also answers `readKeyframeIndex`.
|
|
58
71
|
- `docs/download-architecture.md` — the two axes of downloading: what is
|
|
59
72
|
wanted (`demand/`) against what the swarm is told (`download/`), why urgency
|
|
60
73
|
is not a number given to the library, and why the speculative levels are
|
package/biome.json
CHANGED
|
@@ -17,5 +17,186 @@
|
|
|
17
17
|
"noPrivateImports": "error"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
},
|
|
21
|
+
"overrides": [
|
|
22
|
+
{
|
|
23
|
+
"includes": [
|
|
24
|
+
"services/demand/**",
|
|
25
|
+
"services/piece-store/**",
|
|
26
|
+
"services/segment-formats/**",
|
|
27
|
+
"services/tracks/**",
|
|
28
|
+
"services/output/**",
|
|
29
|
+
"services/viewer/**",
|
|
30
|
+
"services/encode/**",
|
|
31
|
+
"services/source/**",
|
|
32
|
+
"services/torrent/**"
|
|
33
|
+
],
|
|
34
|
+
"linter": {
|
|
35
|
+
"rules": {
|
|
36
|
+
"style": {
|
|
37
|
+
"noRestrictedImports": {
|
|
38
|
+
"level": "error",
|
|
39
|
+
"options": {
|
|
40
|
+
"patterns": [
|
|
41
|
+
{
|
|
42
|
+
"group": ["../**"],
|
|
43
|
+
"message": "This layer states facts and imports nothing above itself. Anything it needs from outside — a logger, a clock, a way to read bytes — is passed in."
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"includes": ["services/download/**"],
|
|
54
|
+
"linter": {
|
|
55
|
+
"rules": {
|
|
56
|
+
"style": {
|
|
57
|
+
"noRestrictedImports": {
|
|
58
|
+
"level": "error",
|
|
59
|
+
"options": {
|
|
60
|
+
"patterns": [
|
|
61
|
+
{
|
|
62
|
+
"group": [
|
|
63
|
+
"../*",
|
|
64
|
+
"../container/**",
|
|
65
|
+
"../controllers/**",
|
|
66
|
+
"../encode/**",
|
|
67
|
+
"../orchestrators/**",
|
|
68
|
+
"../output/**",
|
|
69
|
+
"../segment-formats/**",
|
|
70
|
+
"../torrent-worker/**",
|
|
71
|
+
"../tracks/**",
|
|
72
|
+
"../viewer/**"
|
|
73
|
+
],
|
|
74
|
+
"message": "What the swarm is told is decided from what is wanted (demand/) and what is held (piece-store/), and from nothing else."
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"includes": ["services/container/**"],
|
|
85
|
+
"linter": {
|
|
86
|
+
"rules": {
|
|
87
|
+
"style": {
|
|
88
|
+
"noRestrictedImports": {
|
|
89
|
+
"level": "error",
|
|
90
|
+
"options": {
|
|
91
|
+
"patterns": [
|
|
92
|
+
{
|
|
93
|
+
"group": [
|
|
94
|
+
"../*",
|
|
95
|
+
"../controllers/**",
|
|
96
|
+
"../demand/**",
|
|
97
|
+
"../download/**",
|
|
98
|
+
"../encode/**",
|
|
99
|
+
"../orchestrators/**",
|
|
100
|
+
"../output/**",
|
|
101
|
+
"../piece-store/**",
|
|
102
|
+
"../segment-formats/**",
|
|
103
|
+
"../torrent-worker/**",
|
|
104
|
+
"../viewer/**"
|
|
105
|
+
],
|
|
106
|
+
"message": "A container answers for itself and hands out tracks/. It knows nothing of torrents, encoders or viewers: the four functions it is built with are its whole outside world."
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"includes": ["services/orchestrators/**"],
|
|
117
|
+
"linter": {
|
|
118
|
+
"rules": {
|
|
119
|
+
"style": {
|
|
120
|
+
"noRestrictedImports": {
|
|
121
|
+
"level": "error",
|
|
122
|
+
"options": {
|
|
123
|
+
"patterns": [
|
|
124
|
+
{
|
|
125
|
+
"group": [
|
|
126
|
+
"../*",
|
|
127
|
+
"../controllers/**",
|
|
128
|
+
"../demand/**",
|
|
129
|
+
"../download/**",
|
|
130
|
+
"../output/**",
|
|
131
|
+
"../piece-store/**",
|
|
132
|
+
"../segment-formats/**",
|
|
133
|
+
"../viewer/**"
|
|
134
|
+
],
|
|
135
|
+
"message": "An orchestrator composes the layers below it. Reaching sideways into serving or downloading is what put a scenario in two places before."
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"includes": ["services/controllers/**"],
|
|
146
|
+
"linter": {
|
|
147
|
+
"rules": {
|
|
148
|
+
"style": {
|
|
149
|
+
"noRestrictedImports": {
|
|
150
|
+
"level": "error",
|
|
151
|
+
"options": {
|
|
152
|
+
"patterns": [
|
|
153
|
+
{
|
|
154
|
+
"group": [
|
|
155
|
+
"../*",
|
|
156
|
+
"../demand/**",
|
|
157
|
+
"../download/**",
|
|
158
|
+
"../encode/**",
|
|
159
|
+
"../output/**",
|
|
160
|
+
"../piece-store/**",
|
|
161
|
+
"../segment-formats/**",
|
|
162
|
+
"../torrent-worker/**",
|
|
163
|
+
"../viewer/**"
|
|
164
|
+
],
|
|
165
|
+
"message": "A controller translates a request for an orchestrator. Every layer below the orchestrator is reached through it, never around it."
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"includes": ["services/torrent-worker/**"],
|
|
176
|
+
"linter": {
|
|
177
|
+
"rules": {
|
|
178
|
+
"style": {
|
|
179
|
+
"noRestrictedImports": {
|
|
180
|
+
"level": "error",
|
|
181
|
+
"options": {
|
|
182
|
+
"patterns": [
|
|
183
|
+
{
|
|
184
|
+
"group": [
|
|
185
|
+
"../hls-session-manager.js",
|
|
186
|
+
"../controllers/**",
|
|
187
|
+
"../encode/**",
|
|
188
|
+
"../output/**",
|
|
189
|
+
"../viewer/**",
|
|
190
|
+
"../segment-formats/**"
|
|
191
|
+
],
|
|
192
|
+
"message": "The torrent thread serves bytes. What is produced from them, and for whom, is decided in the main thread and never asked of this one."
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
]
|
|
21
202
|
}
|
|
@@ -162,17 +162,38 @@ flowchart TB
|
|
|
162
162
|
## Orchestrators & Controllers
|
|
163
163
|
|
|
164
164
|
- `ContainerFactory.create({readRange,fileSize})` — sniffs 16 bytes, returns precise `Container` subclass. No torrent knowledge.
|
|
165
|
-
- `ContainerOrchestrator` — per-file cache (`sourceKey:fileIndex`), `getTracks()` / `getKeyframeIndex()`. Transport-agnostic.
|
|
165
|
+
- `ContainerOrchestrator` — per-file cache (`sourceKey:fileIndex`), `getTracks()` / `getMediaInfo()` / `getKeyframeIndex()`. Transport-agnostic.
|
|
166
|
+
- **The keyframe table is read once per file**, like the other two. `Container.readKeyframeIndex` memoizes and each format implements `parseKeyframeIndex`, so the wait belongs to the FILE: two sessions created in the same moment join one read rather than making two, which is exactly what two viewers opening one film do. A read that THREW is not remembered — the bytes it needed may simply not have arrived. Who asks: `torrent-worker/container-tracks.js` `containerKeyframesOf`, over the torrent, reached from the session manager by the `container-keyframes` command. The manager's own HTTP read remains for a manager wired without that path (every unit test).
|
|
166
167
|
- `SubtitleOrchestrator` — wraps `torrent-worker/subtitle-cues.js` (`planFor`, `cuesHeldFor`, `warmSubtitleCues`) behind the `ContainerTrack` abstraction. Routes depend on this, not on the worker directly. The reading itself is the containers' — that module supplies the torrent's read policy and keeps the cursor.
|
|
167
168
|
- `PlaybackController` / `SubtitleController` — thin interface adapters; `routes/api/*` delegate to them, handle HTTP headers (`X-Subtitle-Language`, `X-Subtitle-Cursor`) only.
|
|
168
169
|
|
|
169
170
|
## Legacy
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
Everything a container states about ITSELF is in its own class: the track table,
|
|
173
|
+
the Cues, the keyframe times they name, the cluster positions, the blocks inside
|
|
174
|
+
a cluster, the MP4 sample table. `services/container-index/` no longer exists.
|
|
175
|
+
What was kept apart is `container/ebml-reader.js` — EBML element walking is the
|
|
176
|
+
format's grammar rather than any of its statements.
|
|
177
|
+
|
|
178
|
+
`ContainerFactory` is the one place that decides what a file IS. It sniffs the
|
|
179
|
+
header, because that is what the muxer wrote while a name is what somebody
|
|
180
|
+
typed, and it answers `readKeyframeIndex` for the same reason: doing it anywhere
|
|
181
|
+
else meant a third place making that decision. `ContainerFactory.byName` exists
|
|
182
|
+
for the one path that cannot sniff — the cue walk asks the swarm for nothing, so
|
|
183
|
+
a file whose head is not downloaded has only its name.
|
|
184
|
+
|
|
185
|
+
**Two readings of one file, and which answers.** ffmpeg's `-i` banner and the
|
|
186
|
+
container's own table both describe a file's tracks, and the rule is stated once
|
|
187
|
+
per media kind on `Container`. `alignWithBanner` lines them up by position and
|
|
188
|
+
CHECKS each pair on language or title; one pair agreeing on neither, or a length
|
|
189
|
+
that differs, drops the container reading whole, because a wrong flag is worse
|
|
190
|
+
than a missing one. `mergeSubtitleFlags`, `mergeAudioFlags` and
|
|
191
|
+
`mergeVideoFacts` then say which side answers for which field: flags the banner
|
|
192
|
+
cannot express come from the container; the coded size and frame rate come from
|
|
193
|
+
the probe, because the encoder receives what the decoder produced.
|
|
194
|
+
|
|
195
|
+
Direct imports from routes are deprecated — use `orchestrators/` and
|
|
196
|
+
`controllers/` instead.
|
|
176
197
|
|
|
177
198
|
## Flags matrix
|
|
178
199
|
|
package/docs/encode-run-state.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- GENERATED from services/encode-run-state.js by scripts/render-run-graph.js.
|
|
1
|
+
<!-- GENERATED from services/encode/encode-run-state.js by scripts/render-run-graph.js.
|
|
2
2
|
Do not edit by hand: change the table and run `npm run graph`. -->
|
|
3
3
|
|
|
4
4
|
# The encoder run — states and transitions
|
package/knip.json
ADDED
package/package.json
CHANGED