@torrent-tv/proxy 2.87.10 → 2.87.12
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/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +2191 -2181
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +124 -101
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +50 -50
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +136 -129
- package/routes/api/transcode-sessions/post.js +173 -160
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/server.js +725 -725
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +758 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -2162
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1141 -1083
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +315 -266
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +245 -245
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1146 -1146
- package/services/server/ViewerRequests.js +727 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/playback-readiness.js +620 -0
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -983
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2675 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +365 -365
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +682 -657
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +161 -161
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +381 -381
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +323 -0
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# track-selection — delta spec
|
|
2
|
-
|
|
3
|
-
## ADDED Requirements
|
|
4
|
-
|
|
5
|
-
### Requirement: The playback plan lists every track
|
|
6
|
-
The playback plan SHALL include `audioTracks` and `subtitleTracks` arrays
|
|
7
|
-
parsed from the probe: for each track its type-relative index (what
|
|
8
|
-
`-map 0:a:N` / `0:s:N` selects), codec, language tag, `title` metadata,
|
|
9
|
-
default disposition, and — for subtitles — a `textBased` flag. Output-side
|
|
10
|
-
streams of the probe run MUST NOT leak into the inventory.
|
|
11
|
-
|
|
12
|
-
#### Scenario: MKV with an embedded subtitle
|
|
13
|
-
- **WHEN** the plan is requested for an MKV with one video, one audio and one
|
|
14
|
-
ASS subtitle stream
|
|
15
|
-
- **THEN** the plan lists exactly one audio track and one subtitle track with
|
|
16
|
-
`textBased: true`
|
|
17
|
-
|
|
18
|
-
### Requirement: Audio track selection
|
|
19
|
-
`POST /api/transcode-sessions` SHALL accept `audioTrackIndex` (type-relative,
|
|
20
|
-
default 0) and the session SHALL map that audio track. The index SHALL be
|
|
21
|
-
part of the session identity so different tracks never share a session.
|
|
22
|
-
|
|
23
|
-
#### Scenario: Second audio track
|
|
24
|
-
- **WHEN** a session is created with `audioTrackIndex: 1`
|
|
25
|
-
- **THEN** ffmpeg maps `0:a:1` and a later request with `audioTrackIndex: 0`
|
|
26
|
-
gets a different session
|
|
27
|
-
|
|
28
|
-
### Requirement: Embedded subtitles as WebVTT
|
|
29
|
-
`GET /api/subtitles?sourceKey&fileIndex&trackIndex` SHALL stream the chosen
|
|
30
|
-
embedded TEXT subtitle track converted to WebVTT, starting the response as
|
|
31
|
-
soon as ffmpeg produces output. A track that produces no output (image-based
|
|
32
|
-
or broken) SHALL return 422 before any body. Extraction MUST stop when the
|
|
33
|
-
client disconnects.
|
|
34
|
-
|
|
35
|
-
#### Scenario: Text track extracted
|
|
36
|
-
- **WHEN** the client requests a text subtitle track
|
|
37
|
-
- **THEN** the response is `text/vtt` starting with `WEBVTT` and real cues
|
|
38
|
-
|
|
39
|
-
#### Scenario: Image-based track refused
|
|
40
|
-
- **WHEN** the client requests a PGS/VobSub track
|
|
41
|
-
- **THEN** the proxy responds 422 with an explanatory error
|
|
1
|
+
# track-selection — delta spec
|
|
2
|
+
|
|
3
|
+
## ADDED Requirements
|
|
4
|
+
|
|
5
|
+
### Requirement: The playback plan lists every track
|
|
6
|
+
The playback plan SHALL include `audioTracks` and `subtitleTracks` arrays
|
|
7
|
+
parsed from the probe: for each track its type-relative index (what
|
|
8
|
+
`-map 0:a:N` / `0:s:N` selects), codec, language tag, `title` metadata,
|
|
9
|
+
default disposition, and — for subtitles — a `textBased` flag. Output-side
|
|
10
|
+
streams of the probe run MUST NOT leak into the inventory.
|
|
11
|
+
|
|
12
|
+
#### Scenario: MKV with an embedded subtitle
|
|
13
|
+
- **WHEN** the plan is requested for an MKV with one video, one audio and one
|
|
14
|
+
ASS subtitle stream
|
|
15
|
+
- **THEN** the plan lists exactly one audio track and one subtitle track with
|
|
16
|
+
`textBased: true`
|
|
17
|
+
|
|
18
|
+
### Requirement: Audio track selection
|
|
19
|
+
`POST /api/transcode-sessions` SHALL accept `audioTrackIndex` (type-relative,
|
|
20
|
+
default 0) and the session SHALL map that audio track. The index SHALL be
|
|
21
|
+
part of the session identity so different tracks never share a session.
|
|
22
|
+
|
|
23
|
+
#### Scenario: Second audio track
|
|
24
|
+
- **WHEN** a session is created with `audioTrackIndex: 1`
|
|
25
|
+
- **THEN** ffmpeg maps `0:a:1` and a later request with `audioTrackIndex: 0`
|
|
26
|
+
gets a different session
|
|
27
|
+
|
|
28
|
+
### Requirement: Embedded subtitles as WebVTT
|
|
29
|
+
`GET /api/subtitles?sourceKey&fileIndex&trackIndex` SHALL stream the chosen
|
|
30
|
+
embedded TEXT subtitle track converted to WebVTT, starting the response as
|
|
31
|
+
soon as ffmpeg produces output. A track that produces no output (image-based
|
|
32
|
+
or broken) SHALL return 422 before any body. Extraction MUST stop when the
|
|
33
|
+
client disconnects.
|
|
34
|
+
|
|
35
|
+
#### Scenario: Text track extracted
|
|
36
|
+
- **WHEN** the client requests a text subtitle track
|
|
37
|
+
- **THEN** the response is `text/vtt` starting with `WEBVTT` and real cues
|
|
38
|
+
|
|
39
|
+
#### Scenario: Image-based track refused
|
|
40
|
+
- **WHEN** the client requests a PGS/VobSub track
|
|
41
|
+
- **THEN** the proxy responds 422 with an explanatory error
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Tasks: Track inventory, audio selection and embedded-subtitle extraction
|
|
2
|
-
|
|
3
|
-
## 1. Implementation
|
|
4
|
-
|
|
5
|
-
- [x] 1.1 playback-planner: input-section stream scanner (index, codec,
|
|
6
|
-
language, title, default, textBased); `audioTracks`/`subtitleTracks`
|
|
7
|
-
in the plan (verified against real ffmpeg output from the owner's MKV:
|
|
8
|
-
hevc + flac + ass(eng) parsed, output streams excluded)
|
|
9
|
-
- [x] 1.2 hls-session-manager: `audioTrackIndex` option → `-map 0:a:N`,
|
|
10
|
-
part of the session key; transcode-sessions route passthrough
|
|
11
|
-
- [x] 1.3 routes/api/subtitles/get.js: streaming WebVTT extraction, 422 on
|
|
12
|
-
no-output tracks, kill on client disconnect, 30 min cap (verified:
|
|
13
|
-
real cues extracted from the embedded ASS track over the LAN proxy)
|
|
14
|
-
- [x] 1.4 torrent-pool: mask the announce query string (passkey)
|
|
15
|
-
|
|
16
|
-
## 2. Release
|
|
17
|
-
|
|
18
|
-
- [ ] 2.1 `npm run patch` (2.9.26; needs npm 2FA), then ha-addon 0.2.48
|
|
19
|
-
- [ ] 2.2 After the addon updates: verify plan lists tracks and
|
|
20
|
-
/api/subtitles serves VTT from the addon proxy
|
|
1
|
+
# Tasks: Track inventory, audio selection and embedded-subtitle extraction
|
|
2
|
+
|
|
3
|
+
## 1. Implementation
|
|
4
|
+
|
|
5
|
+
- [x] 1.1 playback-planner: input-section stream scanner (index, codec,
|
|
6
|
+
language, title, default, textBased); `audioTracks`/`subtitleTracks`
|
|
7
|
+
in the plan (verified against real ffmpeg output from the owner's MKV:
|
|
8
|
+
hevc + flac + ass(eng) parsed, output streams excluded)
|
|
9
|
+
- [x] 1.2 hls-session-manager: `audioTrackIndex` option → `-map 0:a:N`,
|
|
10
|
+
part of the session key; transcode-sessions route passthrough
|
|
11
|
+
- [x] 1.3 routes/api/subtitles/get.js: streaming WebVTT extraction, 422 on
|
|
12
|
+
no-output tracks, kill on client disconnect, 30 min cap (verified:
|
|
13
|
+
real cues extracted from the embedded ASS track over the LAN proxy)
|
|
14
|
+
- [x] 1.4 torrent-pool: mask the announce query string (passkey)
|
|
15
|
+
|
|
16
|
+
## 2. Release
|
|
17
|
+
|
|
18
|
+
- [ ] 2.1 `npm run patch` (2.9.26; needs npm 2FA), then ha-addon 0.2.48
|
|
19
|
+
- [ ] 2.2 After the addon updates: verify plan lists tracks and
|
|
20
|
+
/api/subtitles serves VTT from the addon proxy
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
schema: spec-driven
|
|
2
|
-
created: 2026-07-07
|
|
1
|
+
schema: spec-driven
|
|
2
|
+
created: 2026-07-07
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
# Proposal: Transcode quality — source fps, realtime budget, HDR, manual quality
|
|
2
|
-
|
|
3
|
-
## Why
|
|
4
|
-
|
|
5
|
-
The transcode pipeline was tuned for "make it play at all" and left several
|
|
6
|
-
quality/robustness gaps flagged in the project analysis: output was hard-
|
|
7
|
-
locked to 24 fps (25/30 fps content played with resampling judder); the
|
|
8
|
-
encoder profile could be slower than realtime on weak hosts (stalls instead
|
|
9
|
-
of graceful degradation); 10-bit/HDR sources transcoded to 8-bit H.264
|
|
10
|
-
without tone mapping (washed-out colours); and the viewer had no way to force
|
|
11
|
-
a resolution. This change is the transcode-stage batch (proxy side; the
|
|
12
|
-
manual-quality menu also needs the server UI).
|
|
13
|
-
|
|
14
|
-
## What Changes
|
|
15
|
-
|
|
16
|
-
- **Source fps** (DONE): the output frame rate is inherited from the source
|
|
17
|
-
(rounded to an integer, capped at 30), replacing the fixed 24. The
|
|
18
|
-
fixed-GOP encoders keep the fps↔GOP relationship exact so keyframes stay on
|
|
19
|
-
the segment grid; time-based-keyframe encoders (nvenc) just use it as the
|
|
20
|
-
rate; VAAPI/QSV already inherited.
|
|
21
|
-
- **Realtime budget** (planned): the startup benchmark picks the
|
|
22
|
-
encoder/preset/resolution/fps combination whose predicted throughput stays
|
|
23
|
-
above realtime; a source that would not encode in time is downscaled
|
|
24
|
-
(720/540p) rather than refused, and a sustained runtime `speed<1` triggers
|
|
25
|
-
a restart with a lighter profile. `-maxrate`/`-bufsize` cap bitrate spikes.
|
|
26
|
-
- **HDR tone mapping** (planned): detect 10-bit/HDR (pix_fmt,
|
|
27
|
-
color_transfer smpte2084/HLG) and insert a tone-map chain when re-encoding
|
|
28
|
-
to 8-bit H.264, so colours are not washed out. Depends on the ffmpeg build
|
|
29
|
-
having the tonemap filters.
|
|
30
|
-
- **Manual quality** (planned): a player Quality menu — Auto (current
|
|
31
|
-
viewport/DPR behaviour) plus forced resolutions — the proxy already
|
|
32
|
-
honours a requested target height.
|
|
33
|
-
|
|
34
|
-
## Capabilities
|
|
35
|
-
|
|
36
|
-
### New Capabilities
|
|
37
|
-
|
|
38
|
-
- `transcode-quality`: output frame rate, realtime encode budget, HDR tone
|
|
39
|
-
mapping, and explicit quality selection.
|
|
40
|
-
|
|
41
|
-
### Modified Capabilities
|
|
42
|
-
|
|
43
|
-
<!-- none -->
|
|
44
|
-
|
|
45
|
-
## Impact
|
|
46
|
-
|
|
47
|
-
- `services/encode/hwaccel.js` (fps, benchmark, tonemap args),
|
|
48
|
-
`services/hls-session-manager.js` (fps probe, runtime speed watch),
|
|
49
|
-
`services/playback-planner.js` (HDR/fps in the plan);
|
|
50
|
-
server player UI for the Quality menu; ha-addon bump.
|
|
51
|
-
- Released as a batch (proxy 2.9.29 + addon) after the pieces land.
|
|
1
|
+
# Proposal: Transcode quality — source fps, realtime budget, HDR, manual quality
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
The transcode pipeline was tuned for "make it play at all" and left several
|
|
6
|
+
quality/robustness gaps flagged in the project analysis: output was hard-
|
|
7
|
+
locked to 24 fps (25/30 fps content played with resampling judder); the
|
|
8
|
+
encoder profile could be slower than realtime on weak hosts (stalls instead
|
|
9
|
+
of graceful degradation); 10-bit/HDR sources transcoded to 8-bit H.264
|
|
10
|
+
without tone mapping (washed-out colours); and the viewer had no way to force
|
|
11
|
+
a resolution. This change is the transcode-stage batch (proxy side; the
|
|
12
|
+
manual-quality menu also needs the server UI).
|
|
13
|
+
|
|
14
|
+
## What Changes
|
|
15
|
+
|
|
16
|
+
- **Source fps** (DONE): the output frame rate is inherited from the source
|
|
17
|
+
(rounded to an integer, capped at 30), replacing the fixed 24. The
|
|
18
|
+
fixed-GOP encoders keep the fps↔GOP relationship exact so keyframes stay on
|
|
19
|
+
the segment grid; time-based-keyframe encoders (nvenc) just use it as the
|
|
20
|
+
rate; VAAPI/QSV already inherited.
|
|
21
|
+
- **Realtime budget** (planned): the startup benchmark picks the
|
|
22
|
+
encoder/preset/resolution/fps combination whose predicted throughput stays
|
|
23
|
+
above realtime; a source that would not encode in time is downscaled
|
|
24
|
+
(720/540p) rather than refused, and a sustained runtime `speed<1` triggers
|
|
25
|
+
a restart with a lighter profile. `-maxrate`/`-bufsize` cap bitrate spikes.
|
|
26
|
+
- **HDR tone mapping** (planned): detect 10-bit/HDR (pix_fmt,
|
|
27
|
+
color_transfer smpte2084/HLG) and insert a tone-map chain when re-encoding
|
|
28
|
+
to 8-bit H.264, so colours are not washed out. Depends on the ffmpeg build
|
|
29
|
+
having the tonemap filters.
|
|
30
|
+
- **Manual quality** (planned): a player Quality menu — Auto (current
|
|
31
|
+
viewport/DPR behaviour) plus forced resolutions — the proxy already
|
|
32
|
+
honours a requested target height.
|
|
33
|
+
|
|
34
|
+
## Capabilities
|
|
35
|
+
|
|
36
|
+
### New Capabilities
|
|
37
|
+
|
|
38
|
+
- `transcode-quality`: output frame rate, realtime encode budget, HDR tone
|
|
39
|
+
mapping, and explicit quality selection.
|
|
40
|
+
|
|
41
|
+
### Modified Capabilities
|
|
42
|
+
|
|
43
|
+
<!-- none -->
|
|
44
|
+
|
|
45
|
+
## Impact
|
|
46
|
+
|
|
47
|
+
- `services/encode/hwaccel.js` (fps, benchmark, tonemap args),
|
|
48
|
+
`services/hls-session-manager.js` (fps probe, runtime speed watch),
|
|
49
|
+
`services/playback-planner.js` (HDR/fps in the plan);
|
|
50
|
+
server player UI for the Quality menu; ha-addon bump.
|
|
51
|
+
- Released as a batch (proxy 2.9.29 + addon) after the pieces land.
|
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
# transcode-quality — delta spec
|
|
2
|
-
|
|
3
|
-
## ADDED Requirements
|
|
4
|
-
|
|
5
|
-
### Requirement: Output frame rate follows the source
|
|
6
|
-
When re-encoding video, the proxy SHALL NOT force a fixed 24 fps. The output
|
|
7
|
-
rate SHALL follow the source, and the fps handling SHALL depend on how the
|
|
8
|
-
chosen encoder places keyframes:
|
|
9
|
-
|
|
10
|
-
- Frame-count-GOP encoders (software libx264, v4l2m2m) SHALL use an INTEGER
|
|
11
|
-
output rate — source rate rounded and capped — with the `fps` filter and
|
|
12
|
-
the GOP length (`segmentDuration × fps`) using that same integer, so a
|
|
13
|
-
keyframe lands on every segment boundary and segments do not drift off the
|
|
14
|
-
synthetic playlist's uniform grid. When the source rate is unknown they
|
|
15
|
-
SHALL fall back to the default rate.
|
|
16
|
-
- Time-based-keyframe encoders (nvenc, vaapi, qsv) SHALL inherit the exact
|
|
17
|
-
source rate with no fps filter (their keyframes are forced by output time,
|
|
18
|
-
so any rate segments correctly); no rounding, no cap.
|
|
19
|
-
|
|
20
|
-
#### Scenario: 25 fps source on the software encoder
|
|
21
|
-
- **WHEN** a 25 fps video is re-encoded with libx264 and 4-second segments
|
|
22
|
-
- **THEN** the output is 25 fps and the GOP is 100 frames (keyframe every
|
|
23
|
-
segment)
|
|
24
|
-
|
|
25
|
-
#### Scenario: High-fps source on the software encoder
|
|
26
|
-
- **WHEN** a 60 fps video is re-encoded with libx264/v4l2m2m
|
|
27
|
-
- **THEN** the output rate is capped at 30 fps (speed guard)
|
|
28
|
-
|
|
29
|
-
#### Scenario: Fractional source on a hardware time-based encoder
|
|
30
|
-
- **WHEN** a 23.976 fps video is re-encoded with nvenc/vaapi/qsv
|
|
31
|
-
- **THEN** the exact source rate is kept (no fps filter) and segments are
|
|
32
|
-
still cut on time
|
|
33
|
-
|
|
34
|
-
#### Scenario: Unknown source rate
|
|
35
|
-
- **WHEN** the source frame rate cannot be probed on the software path
|
|
36
|
-
- **THEN** the output falls back to the default rate and playback still
|
|
37
|
-
segments correctly
|
|
38
|
-
|
|
39
|
-
### Requirement: Software encode fits a realtime budget at startup
|
|
40
|
-
|
|
41
|
-
For the software encoder, the proxy SHALL choose the output resolution and
|
|
42
|
-
libx264 preset a startup benchmark predicts this host can encode faster than
|
|
43
|
-
realtime (with a margin), rather than always encoding at the client-requested
|
|
44
|
-
resolution. The client-requested box, capped to the source resolution (never
|
|
45
|
-
upscaled), is the ceiling; the proxy SHALL pick the highest resolution rung at
|
|
46
|
-
or below that ceiling that clears the realtime margin, then the highest-quality
|
|
47
|
-
preset that still clears it at that resolution. When even the lowest rung
|
|
48
|
-
cannot clear the margin, the proxy SHALL use the lowest rung (best effort). The
|
|
49
|
-
realtime need SHALL be computed from the session's actual output frame rate.
|
|
50
|
-
Hardware encoders and the no-benchmark case SHALL keep the ceiling resolution
|
|
51
|
-
and the default preset.
|
|
52
|
-
|
|
53
|
-
#### Scenario: Weak host, source above realtime capacity
|
|
54
|
-
- **WHEN** the software benchmark shows the host cannot encode the source-capped
|
|
55
|
-
resolution faster than realtime (e.g. 720p60→30 that runs below 1×)
|
|
56
|
-
- **THEN** the proxy downscales to the highest ladder rung that clears the
|
|
57
|
-
realtime margin (e.g. 480p) instead of encoding sub-realtime at full size
|
|
58
|
-
|
|
59
|
-
#### Scenario: Capable host
|
|
60
|
-
- **WHEN** the benchmark shows ample headroom at the ceiling resolution
|
|
61
|
-
- **THEN** the proxy keeps the ceiling resolution and spends the headroom on a
|
|
62
|
-
higher-quality (slower) preset
|
|
63
|
-
|
|
64
|
-
#### Scenario: Hardware encoder
|
|
65
|
-
- **WHEN** a hardware encoder is selected
|
|
66
|
-
- **THEN** no benchmark-based downscale is applied and the ceiling resolution
|
|
67
|
-
is used
|
|
68
|
-
|
|
69
|
-
### Requirement: Software encode downswitches at runtime when CPU-bound
|
|
70
|
-
|
|
71
|
-
The proxy SHALL, for the software encoder, step the output resolution one rung
|
|
72
|
-
down the ladder and restart the encode at the segment currently being watched
|
|
73
|
-
when a transcode runs below realtime for a sustained window. Before downscaling
|
|
74
|
-
it SHALL determine whether the limit is the encoder or a download-starved
|
|
75
|
-
input — comparing the torrent download rate with the source's average byte rate
|
|
76
|
-
(a fully-downloaded file is never download-bound) — and SHALL NOT downscale when
|
|
77
|
-
the limit is the download (it SHALL log that instead). The downswitch SHALL be
|
|
78
|
-
bounded by a sustained-slow window, a post-action cooldown, a maximum number of
|
|
79
|
-
steps, and a resolution floor, and SHALL reset its slow window on every encode
|
|
80
|
-
(re)start. There SHALL be no automatic upswitch in this version.
|
|
81
|
-
|
|
82
|
-
#### Scenario: Sustained CPU-bound transcode
|
|
83
|
-
- **WHEN** a software transcode's encoder speed stays below realtime for the
|
|
84
|
-
sustained window while the input download keeps up
|
|
85
|
-
- **THEN** the proxy downscales one rung and restarts at the current segment,
|
|
86
|
-
up to the step cap / resolution floor
|
|
87
|
-
|
|
88
|
-
#### Scenario: Download-limited, not CPU-limited
|
|
89
|
-
- **WHEN** the encoder speed is below realtime but the torrent cannot download
|
|
90
|
-
the source's byte rate and the file is not fully downloaded
|
|
91
|
-
- **THEN** the proxy does not downscale and logs that the download is the limit
|
|
92
|
-
|
|
93
|
-
#### Scenario: No thrash after a switch
|
|
94
|
-
- **WHEN** a downswitch (or a viewer seek) has just restarted the encode
|
|
95
|
-
- **THEN** the slow window is reset and no further downswitch occurs until a new
|
|
96
|
-
sustained-slow window elapses after the cooldown
|
|
97
|
-
|
|
98
|
-
### Requirement: Manual quality forces a constant resolution
|
|
99
|
-
|
|
100
|
-
The viewer SHALL be able to force a specific output resolution instead of Auto.
|
|
101
|
-
When a resolution is forced the proxy SHALL encode exactly that box (capped to
|
|
102
|
-
the source, never upscaled) with the realtime budget disabled — no startup
|
|
103
|
-
auto-downscale and no runtime downswitch — so the resolution stays constant for
|
|
104
|
-
the whole session. The browser SHALL offer Auto plus resolutions at or below the
|
|
105
|
-
source height, built from the source resolution reported in the playback plan.
|
|
106
|
-
Selecting a quality SHALL re-open the stream at the new resolution with the
|
|
107
|
-
playback position preserved. Auto SHALL keep the current realtime-budget
|
|
108
|
-
behaviour.
|
|
109
|
-
|
|
110
|
-
#### Scenario: Forced resolution is constant
|
|
111
|
-
- **WHEN** the viewer forces a resolution (e.g. 480p)
|
|
112
|
-
- **THEN** the proxy encodes at that resolution for the whole session, with no
|
|
113
|
-
budget downscale or runtime downswitch, and playback resumes at the same
|
|
114
|
-
position
|
|
115
|
-
|
|
116
|
-
#### Scenario: Auto
|
|
117
|
-
- **WHEN** the viewer selects Auto
|
|
118
|
-
- **THEN** the proxy applies the realtime budget (startup selection + runtime
|
|
119
|
-
downswitch) as before
|
|
120
|
-
|
|
121
|
-
### Requirement: HDR sources are tone-mapped when re-encoded to SDR
|
|
122
|
-
|
|
123
|
-
The proxy SHALL apply an HDR→BT.709 SDR tone-map chain when re-encoding an HDR
|
|
124
|
-
source (a BT.2020 PQ `smpte2084` or HLG `arib-std-b67` transfer) to 8-bit SDR on
|
|
125
|
-
the software path, so the output is not washed-out — provided this ffmpeg build
|
|
126
|
-
has the required filters. The proxy SHALL detect filter availability (`zscale`
|
|
127
|
-
and `tonemap`) at startup and, when either is missing, SHALL fall back to a
|
|
128
|
-
plain 8-bit convert without failing playback.
|
|
129
|
-
|
|
130
|
-
#### Scenario: HDR source, filters available
|
|
131
|
-
- **WHEN** an HDR source is re-encoded on the software path and the build has
|
|
132
|
-
`zscale` + `tonemap`
|
|
133
|
-
- **THEN** the tone-map chain is inserted and the output is BT.709 SDR (not
|
|
134
|
-
washed-out)
|
|
135
|
-
|
|
136
|
-
#### Scenario: HDR source, filters missing
|
|
137
|
-
- **WHEN** an HDR source is re-encoded but the build lacks `zscale`/`tonemap`
|
|
138
|
-
- **THEN** playback still proceeds with a plain 8-bit convert (no tone map) and
|
|
139
|
-
the limitation is logged
|
|
1
|
+
# transcode-quality — delta spec
|
|
2
|
+
|
|
3
|
+
## ADDED Requirements
|
|
4
|
+
|
|
5
|
+
### Requirement: Output frame rate follows the source
|
|
6
|
+
When re-encoding video, the proxy SHALL NOT force a fixed 24 fps. The output
|
|
7
|
+
rate SHALL follow the source, and the fps handling SHALL depend on how the
|
|
8
|
+
chosen encoder places keyframes:
|
|
9
|
+
|
|
10
|
+
- Frame-count-GOP encoders (software libx264, v4l2m2m) SHALL use an INTEGER
|
|
11
|
+
output rate — source rate rounded and capped — with the `fps` filter and
|
|
12
|
+
the GOP length (`segmentDuration × fps`) using that same integer, so a
|
|
13
|
+
keyframe lands on every segment boundary and segments do not drift off the
|
|
14
|
+
synthetic playlist's uniform grid. When the source rate is unknown they
|
|
15
|
+
SHALL fall back to the default rate.
|
|
16
|
+
- Time-based-keyframe encoders (nvenc, vaapi, qsv) SHALL inherit the exact
|
|
17
|
+
source rate with no fps filter (their keyframes are forced by output time,
|
|
18
|
+
so any rate segments correctly); no rounding, no cap.
|
|
19
|
+
|
|
20
|
+
#### Scenario: 25 fps source on the software encoder
|
|
21
|
+
- **WHEN** a 25 fps video is re-encoded with libx264 and 4-second segments
|
|
22
|
+
- **THEN** the output is 25 fps and the GOP is 100 frames (keyframe every
|
|
23
|
+
segment)
|
|
24
|
+
|
|
25
|
+
#### Scenario: High-fps source on the software encoder
|
|
26
|
+
- **WHEN** a 60 fps video is re-encoded with libx264/v4l2m2m
|
|
27
|
+
- **THEN** the output rate is capped at 30 fps (speed guard)
|
|
28
|
+
|
|
29
|
+
#### Scenario: Fractional source on a hardware time-based encoder
|
|
30
|
+
- **WHEN** a 23.976 fps video is re-encoded with nvenc/vaapi/qsv
|
|
31
|
+
- **THEN** the exact source rate is kept (no fps filter) and segments are
|
|
32
|
+
still cut on time
|
|
33
|
+
|
|
34
|
+
#### Scenario: Unknown source rate
|
|
35
|
+
- **WHEN** the source frame rate cannot be probed on the software path
|
|
36
|
+
- **THEN** the output falls back to the default rate and playback still
|
|
37
|
+
segments correctly
|
|
38
|
+
|
|
39
|
+
### Requirement: Software encode fits a realtime budget at startup
|
|
40
|
+
|
|
41
|
+
For the software encoder, the proxy SHALL choose the output resolution and
|
|
42
|
+
libx264 preset a startup benchmark predicts this host can encode faster than
|
|
43
|
+
realtime (with a margin), rather than always encoding at the client-requested
|
|
44
|
+
resolution. The client-requested box, capped to the source resolution (never
|
|
45
|
+
upscaled), is the ceiling; the proxy SHALL pick the highest resolution rung at
|
|
46
|
+
or below that ceiling that clears the realtime margin, then the highest-quality
|
|
47
|
+
preset that still clears it at that resolution. When even the lowest rung
|
|
48
|
+
cannot clear the margin, the proxy SHALL use the lowest rung (best effort). The
|
|
49
|
+
realtime need SHALL be computed from the session's actual output frame rate.
|
|
50
|
+
Hardware encoders and the no-benchmark case SHALL keep the ceiling resolution
|
|
51
|
+
and the default preset.
|
|
52
|
+
|
|
53
|
+
#### Scenario: Weak host, source above realtime capacity
|
|
54
|
+
- **WHEN** the software benchmark shows the host cannot encode the source-capped
|
|
55
|
+
resolution faster than realtime (e.g. 720p60→30 that runs below 1×)
|
|
56
|
+
- **THEN** the proxy downscales to the highest ladder rung that clears the
|
|
57
|
+
realtime margin (e.g. 480p) instead of encoding sub-realtime at full size
|
|
58
|
+
|
|
59
|
+
#### Scenario: Capable host
|
|
60
|
+
- **WHEN** the benchmark shows ample headroom at the ceiling resolution
|
|
61
|
+
- **THEN** the proxy keeps the ceiling resolution and spends the headroom on a
|
|
62
|
+
higher-quality (slower) preset
|
|
63
|
+
|
|
64
|
+
#### Scenario: Hardware encoder
|
|
65
|
+
- **WHEN** a hardware encoder is selected
|
|
66
|
+
- **THEN** no benchmark-based downscale is applied and the ceiling resolution
|
|
67
|
+
is used
|
|
68
|
+
|
|
69
|
+
### Requirement: Software encode downswitches at runtime when CPU-bound
|
|
70
|
+
|
|
71
|
+
The proxy SHALL, for the software encoder, step the output resolution one rung
|
|
72
|
+
down the ladder and restart the encode at the segment currently being watched
|
|
73
|
+
when a transcode runs below realtime for a sustained window. Before downscaling
|
|
74
|
+
it SHALL determine whether the limit is the encoder or a download-starved
|
|
75
|
+
input — comparing the torrent download rate with the source's average byte rate
|
|
76
|
+
(a fully-downloaded file is never download-bound) — and SHALL NOT downscale when
|
|
77
|
+
the limit is the download (it SHALL log that instead). The downswitch SHALL be
|
|
78
|
+
bounded by a sustained-slow window, a post-action cooldown, a maximum number of
|
|
79
|
+
steps, and a resolution floor, and SHALL reset its slow window on every encode
|
|
80
|
+
(re)start. There SHALL be no automatic upswitch in this version.
|
|
81
|
+
|
|
82
|
+
#### Scenario: Sustained CPU-bound transcode
|
|
83
|
+
- **WHEN** a software transcode's encoder speed stays below realtime for the
|
|
84
|
+
sustained window while the input download keeps up
|
|
85
|
+
- **THEN** the proxy downscales one rung and restarts at the current segment,
|
|
86
|
+
up to the step cap / resolution floor
|
|
87
|
+
|
|
88
|
+
#### Scenario: Download-limited, not CPU-limited
|
|
89
|
+
- **WHEN** the encoder speed is below realtime but the torrent cannot download
|
|
90
|
+
the source's byte rate and the file is not fully downloaded
|
|
91
|
+
- **THEN** the proxy does not downscale and logs that the download is the limit
|
|
92
|
+
|
|
93
|
+
#### Scenario: No thrash after a switch
|
|
94
|
+
- **WHEN** a downswitch (or a viewer seek) has just restarted the encode
|
|
95
|
+
- **THEN** the slow window is reset and no further downswitch occurs until a new
|
|
96
|
+
sustained-slow window elapses after the cooldown
|
|
97
|
+
|
|
98
|
+
### Requirement: Manual quality forces a constant resolution
|
|
99
|
+
|
|
100
|
+
The viewer SHALL be able to force a specific output resolution instead of Auto.
|
|
101
|
+
When a resolution is forced the proxy SHALL encode exactly that box (capped to
|
|
102
|
+
the source, never upscaled) with the realtime budget disabled — no startup
|
|
103
|
+
auto-downscale and no runtime downswitch — so the resolution stays constant for
|
|
104
|
+
the whole session. The browser SHALL offer Auto plus resolutions at or below the
|
|
105
|
+
source height, built from the source resolution reported in the playback plan.
|
|
106
|
+
Selecting a quality SHALL re-open the stream at the new resolution with the
|
|
107
|
+
playback position preserved. Auto SHALL keep the current realtime-budget
|
|
108
|
+
behaviour.
|
|
109
|
+
|
|
110
|
+
#### Scenario: Forced resolution is constant
|
|
111
|
+
- **WHEN** the viewer forces a resolution (e.g. 480p)
|
|
112
|
+
- **THEN** the proxy encodes at that resolution for the whole session, with no
|
|
113
|
+
budget downscale or runtime downswitch, and playback resumes at the same
|
|
114
|
+
position
|
|
115
|
+
|
|
116
|
+
#### Scenario: Auto
|
|
117
|
+
- **WHEN** the viewer selects Auto
|
|
118
|
+
- **THEN** the proxy applies the realtime budget (startup selection + runtime
|
|
119
|
+
downswitch) as before
|
|
120
|
+
|
|
121
|
+
### Requirement: HDR sources are tone-mapped when re-encoded to SDR
|
|
122
|
+
|
|
123
|
+
The proxy SHALL apply an HDR→BT.709 SDR tone-map chain when re-encoding an HDR
|
|
124
|
+
source (a BT.2020 PQ `smpte2084` or HLG `arib-std-b67` transfer) to 8-bit SDR on
|
|
125
|
+
the software path, so the output is not washed-out — provided this ffmpeg build
|
|
126
|
+
has the required filters. The proxy SHALL detect filter availability (`zscale`
|
|
127
|
+
and `tonemap`) at startup and, when either is missing, SHALL fall back to a
|
|
128
|
+
plain 8-bit convert without failing playback.
|
|
129
|
+
|
|
130
|
+
#### Scenario: HDR source, filters available
|
|
131
|
+
- **WHEN** an HDR source is re-encoded on the software path and the build has
|
|
132
|
+
`zscale` + `tonemap`
|
|
133
|
+
- **THEN** the tone-map chain is inserted and the output is BT.709 SDR (not
|
|
134
|
+
washed-out)
|
|
135
|
+
|
|
136
|
+
#### Scenario: HDR source, filters missing
|
|
137
|
+
- **WHEN** an HDR source is re-encoded but the build lacks `zscale`/`tonemap`
|
|
138
|
+
- **THEN** playback still proceeds with a plain 8-bit convert (no tone map) and
|
|
139
|
+
the limitation is logged
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
# Tasks: Transcode quality
|
|
2
|
-
|
|
3
|
-
## 1. Source fps
|
|
4
|
-
|
|
5
|
-
- [x] 1.1 hwaccel: `chooseOutputFps` (integer, capped); `buildVideoArgs`
|
|
6
|
-
takes `fps`, threaded into the filter + frame-count GOP (software,
|
|
7
|
-
v4l2m2m) and the filter only (nvenc); VAAPI/QSV unchanged (already
|
|
8
|
-
inherit); startup test-encode/benchmark keep the fixed rate
|
|
9
|
-
- [x] 1.2 hls-session-manager: parse source fps from the probe, compute
|
|
10
|
-
`session.outputFps`, pass it into `buildVideoArgs`
|
|
11
|
-
- [x] 1.3 Unit-verify fps choice and fps↔GOP consistency (25→100, 24→96,
|
|
12
|
-
default→96); syntax checks
|
|
13
|
-
|
|
14
|
-
## 2. Realtime budget
|
|
15
|
-
|
|
16
|
-
- [x] 2.1 Startup: benchmark picks resolution + preset within a realtime
|
|
17
|
-
margin; downscale below the client-target ceiling instead of refusing
|
|
18
|
-
(`chooseSoftwareEncodeSettings`/`buildResolutionLadder` in hwaccel;
|
|
19
|
-
`#chooseEncodeBudget` + `encodeWidth`/`encodeHeight` in the session
|
|
20
|
-
manager; fixed the needed-pixels calc to use the session's `outputFps`
|
|
21
|
-
not the fixed `TRANSCODE_FPS`). Verified on the FIFA host profile
|
|
22
|
-
(720p60→480p) + strong/weak/no-benchmark profiles.
|
|
23
|
-
- [x] 2.2 Runtime `speed<1` watch → step down the resolution ladder + restart
|
|
24
|
-
at the current segment (hard-restart tier). Gate on CPU-bound only:
|
|
25
|
-
compare `getFileStats().downloadSpeed` with the source byte-rate so a
|
|
26
|
-
download-starved input is NOT misread as an encoder limit (don't degrade
|
|
27
|
-
quality for a download bottleneck — log it instead). Hysteresis +
|
|
28
|
-
cooldown + floor + max steps; slow window reset on every (re)start; no
|
|
29
|
-
upswitch in v1 (oscillation risk). `#enforceRealtimeBudget` +
|
|
30
|
-
`#classifyTranscodeBound` + `#applyBudgetDownshift` in the session
|
|
31
|
-
manager; `getSourceStats` injected from server.js.
|
|
32
|
-
NOTE (follow-up 2.2b): seamless switch via `EXT-X-DISCONTINUITY` /
|
|
33
|
-
parallel encoder tier keyed by host resources — the hard restart can blip
|
|
34
|
-
at the switch point.
|
|
35
|
-
- [ ] 2.3 `-maxrate`/`-bufsize`
|
|
36
|
-
|
|
37
|
-
## 3. HDR tone mapping
|
|
38
|
-
|
|
39
|
-
- [x] 3.1 Detect HDR from the probe (PQ `smpte2084` / HLG `arib-std-b67`
|
|
40
|
-
transfer); insert a `zscale`+`tonemap` (hable) BT.2020→BT.709 SDR chain
|
|
41
|
-
when re-encoding video on the software path (after the downscale).
|
|
42
|
-
- [x] 3.2 Guard on tonemap-filter availability: startup `detectTonemapSupport`
|
|
43
|
-
checks `ffmpeg -filters` for `zscale` + `tonemap`; when missing, HDR
|
|
44
|
-
falls back to the plain 8-bit convert (washed-out but plays). Logged.
|
|
45
|
-
NOTE (follow-up 3.3): hardware-encoder tone mapping (tonemap_vaapi / npp /
|
|
46
|
-
opencl) — software path only for now.
|
|
47
|
-
|
|
48
|
-
## 4. Manual quality
|
|
49
|
-
|
|
50
|
-
- [x] 4.1 Proxy honours a forced resolution: `manualQuality` flag on the
|
|
51
|
-
transcode-session request encodes the requested box exactly (capped to
|
|
52
|
-
source, budget + runtime downswitch disabled); `manualQuality` in the
|
|
53
|
-
session key; playback plan reports source `videoWidth`/`videoHeight`.
|
|
54
|
-
- [x] 4.2 Server Quality menu (Auto + forced resolutions <= source). Custom
|
|
55
|
-
media-chrome submenu mirroring the audio menu; selection re-opens the
|
|
56
|
-
stream at the forced resolution with the position preserved (reuses the
|
|
57
|
-
audio-switch machinery); shared settings button shows for audio OR
|
|
58
|
-
quality.
|
|
59
|
-
|
|
60
|
-
## 5. Release
|
|
61
|
-
|
|
62
|
-
- [ ] 5.1 Batch release: proxy 2.9.29 + ha-addon bump; field-test on the
|
|
63
|
-
owner's hardware (25/30 fps content plays without judder; seek intact)
|
|
1
|
+
# Tasks: Transcode quality
|
|
2
|
+
|
|
3
|
+
## 1. Source fps
|
|
4
|
+
|
|
5
|
+
- [x] 1.1 hwaccel: `chooseOutputFps` (integer, capped); `buildVideoArgs`
|
|
6
|
+
takes `fps`, threaded into the filter + frame-count GOP (software,
|
|
7
|
+
v4l2m2m) and the filter only (nvenc); VAAPI/QSV unchanged (already
|
|
8
|
+
inherit); startup test-encode/benchmark keep the fixed rate
|
|
9
|
+
- [x] 1.2 hls-session-manager: parse source fps from the probe, compute
|
|
10
|
+
`session.outputFps`, pass it into `buildVideoArgs`
|
|
11
|
+
- [x] 1.3 Unit-verify fps choice and fps↔GOP consistency (25→100, 24→96,
|
|
12
|
+
default→96); syntax checks
|
|
13
|
+
|
|
14
|
+
## 2. Realtime budget
|
|
15
|
+
|
|
16
|
+
- [x] 2.1 Startup: benchmark picks resolution + preset within a realtime
|
|
17
|
+
margin; downscale below the client-target ceiling instead of refusing
|
|
18
|
+
(`chooseSoftwareEncodeSettings`/`buildResolutionLadder` in hwaccel;
|
|
19
|
+
`#chooseEncodeBudget` + `encodeWidth`/`encodeHeight` in the session
|
|
20
|
+
manager; fixed the needed-pixels calc to use the session's `outputFps`
|
|
21
|
+
not the fixed `TRANSCODE_FPS`). Verified on the FIFA host profile
|
|
22
|
+
(720p60→480p) + strong/weak/no-benchmark profiles.
|
|
23
|
+
- [x] 2.2 Runtime `speed<1` watch → step down the resolution ladder + restart
|
|
24
|
+
at the current segment (hard-restart tier). Gate on CPU-bound only:
|
|
25
|
+
compare `getFileStats().downloadSpeed` with the source byte-rate so a
|
|
26
|
+
download-starved input is NOT misread as an encoder limit (don't degrade
|
|
27
|
+
quality for a download bottleneck — log it instead). Hysteresis +
|
|
28
|
+
cooldown + floor + max steps; slow window reset on every (re)start; no
|
|
29
|
+
upswitch in v1 (oscillation risk). `#enforceRealtimeBudget` +
|
|
30
|
+
`#classifyTranscodeBound` + `#applyBudgetDownshift` in the session
|
|
31
|
+
manager; `getSourceStats` injected from server.js.
|
|
32
|
+
NOTE (follow-up 2.2b): seamless switch via `EXT-X-DISCONTINUITY` /
|
|
33
|
+
parallel encoder tier keyed by host resources — the hard restart can blip
|
|
34
|
+
at the switch point.
|
|
35
|
+
- [ ] 2.3 `-maxrate`/`-bufsize`
|
|
36
|
+
|
|
37
|
+
## 3. HDR tone mapping
|
|
38
|
+
|
|
39
|
+
- [x] 3.1 Detect HDR from the probe (PQ `smpte2084` / HLG `arib-std-b67`
|
|
40
|
+
transfer); insert a `zscale`+`tonemap` (hable) BT.2020→BT.709 SDR chain
|
|
41
|
+
when re-encoding video on the software path (after the downscale).
|
|
42
|
+
- [x] 3.2 Guard on tonemap-filter availability: startup `detectTonemapSupport`
|
|
43
|
+
checks `ffmpeg -filters` for `zscale` + `tonemap`; when missing, HDR
|
|
44
|
+
falls back to the plain 8-bit convert (washed-out but plays). Logged.
|
|
45
|
+
NOTE (follow-up 3.3): hardware-encoder tone mapping (tonemap_vaapi / npp /
|
|
46
|
+
opencl) — software path only for now.
|
|
47
|
+
|
|
48
|
+
## 4. Manual quality
|
|
49
|
+
|
|
50
|
+
- [x] 4.1 Proxy honours a forced resolution: `manualQuality` flag on the
|
|
51
|
+
transcode-session request encodes the requested box exactly (capped to
|
|
52
|
+
source, budget + runtime downswitch disabled); `manualQuality` in the
|
|
53
|
+
session key; playback plan reports source `videoWidth`/`videoHeight`.
|
|
54
|
+
- [x] 4.2 Server Quality menu (Auto + forced resolutions <= source). Custom
|
|
55
|
+
media-chrome submenu mirroring the audio menu; selection re-opens the
|
|
56
|
+
stream at the forced resolution with the position preserved (reuses the
|
|
57
|
+
audio-switch machinery); shared settings button shows for audio OR
|
|
58
|
+
quality.
|
|
59
|
+
|
|
60
|
+
## 5. Release
|
|
61
|
+
|
|
62
|
+
- [ ] 5.1 Batch release: proxy 2.9.29 + ha-addon bump; field-test on the
|
|
63
|
+
owner's hardware (25/30 fps content plays without judder; seek intact)
|