@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
|
@@ -30,6 +30,7 @@ import { readFragments, supplyFiguresFor } from "./piece-reader.js";
|
|
|
30
30
|
import { cuesHeldFor, declaredSubtitleTracksOf, subtitleTracksOf, warmSubtitleCues } from "./subtitle-cues.js";
|
|
31
31
|
import {
|
|
32
32
|
CONTAINER_HEAD_BYTES,
|
|
33
|
+
containerKeyframesOf,
|
|
33
34
|
containerMediaInfoOf,
|
|
34
35
|
containerTracksOf,
|
|
35
36
|
warmResumePosition
|
|
@@ -440,6 +441,23 @@ async function runCommand(command, params, id) {
|
|
|
440
441
|
};
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
case Command.CONTAINER_KEYFRAMES: {
|
|
445
|
+
const torrent = await requireTorrent(params.sourceKey);
|
|
446
|
+
return {
|
|
447
|
+
index: await containerKeyframesOf(torrent, params.fileIndex, params.sourceKey, {
|
|
448
|
+
// Both edges this time. A Matroska file's Cues sit at the END, behind
|
|
449
|
+
// a SeekHead in the head, so a read that has neither waits for the
|
|
450
|
+
// swarm twice over.
|
|
451
|
+
prefetchEdges: () =>
|
|
452
|
+
pool.prefetchFileEdges(torrent, params.fileIndex, {
|
|
453
|
+
headBytes: CONTAINER_HEAD_BYTES,
|
|
454
|
+
tailBytes: CONTAINER_HEAD_BYTES,
|
|
455
|
+
timeoutMs: 60_000
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
443
461
|
case Command.WARM_POSITION: {
|
|
444
462
|
const torrent = await requireTorrent(params.sourceKey);
|
|
445
463
|
return {
|
|
@@ -1,40 +1,131 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Audio track — extends ContainerTrack with audio-specific flags.
|
|
3
|
-
*
|
|
4
|
-
* Matroska RFC 9559 §5.1.4.1:
|
|
5
|
-
* - FlagOriginal 0x55AE (default 0) — track is original language.
|
|
6
|
-
* - FlagCommentary 0x55AF — commentary track.
|
|
7
|
-
* - FlagVisualImpaired 0x55AC — audio description for visually impaired.
|
|
8
|
-
* - FlagTextDescriptions 0x55AD, FlagHearingImpaired etc. may also appear
|
|
9
|
-
* but the three above drive the audio menu (research/container-spec-conformance).
|
|
10
|
-
* MP4 ISO/IEC 14496-12: tkhd alternate_group groups alternate audio tracks.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { ContainerTrack } from "./ContainerTrack.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Audio track — extends ContainerTrack with audio-specific flags.
|
|
3
|
+
*
|
|
4
|
+
* Matroska RFC 9559 §5.1.4.1:
|
|
5
|
+
* - FlagOriginal 0x55AE (default 0) — track is original language.
|
|
6
|
+
* - FlagCommentary 0x55AF — commentary track.
|
|
7
|
+
* - FlagVisualImpaired 0x55AC — audio description for visually impaired.
|
|
8
|
+
* - FlagTextDescriptions 0x55AD, FlagHearingImpaired etc. may also appear
|
|
9
|
+
* but the three above drive the audio menu (research/container-spec-conformance).
|
|
10
|
+
* MP4 ISO/IEC 14496-12: tkhd alternate_group groups alternate audio tracks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ContainerTrack } from "./ContainerTrack.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Codec identifiers as containers write them, against the name ffmpeg prints.
|
|
17
|
+
*
|
|
18
|
+
* Needed because the browser decides whether it can play a soundtrack from that
|
|
19
|
+
* name, and for a sidecar file there is no ffmpeg banner to read it from — the
|
|
20
|
+
* track came from the container's own table, where Matroska writes `A_AC3` and
|
|
21
|
+
* MP4 writes `ac-3` for the thing ffmpeg calls `ac3`. Only what a soundtrack can
|
|
22
|
+
* actually be is listed; an identifier not here is reported as it was written,
|
|
23
|
+
* which the browser treats as one it does not know and therefore transcodes.
|
|
24
|
+
*/
|
|
25
|
+
const CODEC_NAMES = new Map([
|
|
26
|
+
["A_AAC", "aac"],
|
|
27
|
+
["A_AC3", "ac3"],
|
|
28
|
+
["A_EAC3", "eac3"],
|
|
29
|
+
["A_DTS", "dts"],
|
|
30
|
+
["A_FLAC", "flac"],
|
|
31
|
+
["A_OPUS", "opus"],
|
|
32
|
+
["A_VORBIS", "vorbis"],
|
|
33
|
+
["A_TRUEHD", "truehd"],
|
|
34
|
+
["A_MPEG/L3", "mp3"],
|
|
35
|
+
["A_MPEG/L2", "mp2"],
|
|
36
|
+
["A_ALAC", "alac"],
|
|
37
|
+
["mp4a", "aac"],
|
|
38
|
+
["ac-3", "ac3"],
|
|
39
|
+
["ec-3", "eac3"],
|
|
40
|
+
["alac", "alac"],
|
|
41
|
+
["opus", "opus"],
|
|
42
|
+
["Opus", "opus"],
|
|
43
|
+
["fLaC", "flac"],
|
|
44
|
+
["flac", "flac"]
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extensions of raw elementary streams, against the codec they carry.
|
|
49
|
+
*
|
|
50
|
+
* A bare `.ac3` has no track table to read, and its extension is the only thing
|
|
51
|
+
* that states its codec — which for an elementary stream is exactly what the
|
|
52
|
+
* extension means.
|
|
53
|
+
*/
|
|
54
|
+
const CODEC_BY_EXTENSION = new Map([
|
|
55
|
+
[".aac", "aac"],
|
|
56
|
+
[".ac3", "ac3"],
|
|
57
|
+
[".eac3", "eac3"],
|
|
58
|
+
[".dts", "dts"],
|
|
59
|
+
[".dtshd", "dts"],
|
|
60
|
+
[".flac", "flac"],
|
|
61
|
+
[".mp3", "mp3"],
|
|
62
|
+
[".mp2", "mp2"],
|
|
63
|
+
[".opus", "opus"],
|
|
64
|
+
[".ogg", "vorbis"],
|
|
65
|
+
[".oga", "vorbis"],
|
|
66
|
+
[".wav", "pcm"],
|
|
67
|
+
[".thd", "truehd"],
|
|
68
|
+
[".mlp", "truehd"],
|
|
69
|
+
[".m4a", "aac"]
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
export class AudioTrack extends ContainerTrack {
|
|
73
|
+
/**
|
|
74
|
+
* @param {object} params - See ContainerTrack plus:
|
|
75
|
+
* @param {boolean} params.isOriginal - FlagOriginal
|
|
76
|
+
* @param {boolean} params.isCommentary - FlagCommentary
|
|
77
|
+
* @param {boolean} params.isVisualImpaired - FlagVisualImpaired / descriptive audio
|
|
78
|
+
* @param {number | null} params.channels
|
|
79
|
+
* @param {number | null} params.samplingFrequency
|
|
80
|
+
*/
|
|
81
|
+
constructor(params) {
|
|
82
|
+
super({ ...params, type: "audio" });
|
|
83
|
+
this.isOriginal = params.isOriginal === true;
|
|
84
|
+
this.isCommentary = params.isCommentary === true;
|
|
85
|
+
this.isVisualImpaired = params.isVisualImpaired === true;
|
|
86
|
+
this.channels = Number.isFinite(params.channels) ? params.channels : null;
|
|
87
|
+
this.samplingFrequency = Number.isFinite(params.samplingFrequency) ? params.samplingFrequency : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The ffmpeg-side codec name for one track, from whatever the reading gave.
|
|
92
|
+
*
|
|
93
|
+
* @param {{ codec?: string, codecId?: string }} track
|
|
94
|
+
* @param {string} [extension] - The sidecar file's extension, when the track
|
|
95
|
+
* came from a file with no readable table.
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
static codecNameOf(track, extension = "") {
|
|
99
|
+
const fromBanner = typeof track?.codec === "string" ? track.codec.trim() : "";
|
|
100
|
+
if (fromBanner.length > 0) {
|
|
101
|
+
return fromBanner.toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
const codecId = typeof track?.codecId === "string" ? track.codecId.trim() : "";
|
|
104
|
+
if (codecId.length > 0) {
|
|
105
|
+
const known = CODEC_NAMES.get(codecId);
|
|
106
|
+
if (known) {
|
|
107
|
+
return known;
|
|
108
|
+
}
|
|
109
|
+
// Matroska allows a suffix — `A_AAC/MPEG4/LC`, `A_PCM/INT/LIT` — so the
|
|
110
|
+
// family is what the first two segments say.
|
|
111
|
+
const family = codecId.split("/").slice(0, 2).join("/");
|
|
112
|
+
const byFamily = CODEC_NAMES.get(family) ?? CODEC_NAMES.get(codecId.split("/")[0]);
|
|
113
|
+
if (byFamily) {
|
|
114
|
+
return byFamily;
|
|
115
|
+
}
|
|
116
|
+
if (codecId.startsWith("A_PCM")) {
|
|
117
|
+
return "pcm";
|
|
118
|
+
}
|
|
119
|
+
return codecId.toLowerCase();
|
|
120
|
+
}
|
|
121
|
+
return CODEC_BY_EXTENSION.get(extension) ?? "";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Human label helper — commentary and descriptive audio must not look like main track. */
|
|
125
|
+
audioRoleLabel() {
|
|
126
|
+
if (this.isCommentary) return "commentary";
|
|
127
|
+
if (this.isVisualImpaired) return "descriptive";
|
|
128
|
+
if (this.isOriginal) return "original";
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
}
|