@torrent-tv/proxy 2.87.5 → 2.87.7
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 +8 -0
- 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 +101 -94
- 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 +1 -1
- 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/audio-tracks/post.js +29 -0
- 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/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 +129 -129
- package/routes/api/transcode-sessions/post.js +160 -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 +4 -0
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +332 -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 +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -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 +1083 -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 +266 -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 -212
- 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 +131 -59
- 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 +522 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -36
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +983 -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 +2 -2
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- 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 +1 -1
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +1356 -1356
- 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 +657 -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 -176
- 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/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- 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/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/{services/torrent → utils}/torrent-source-key.js +38 -38
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Which files of a torrent are one video file's sound and subtitles.
|
|
3
|
-
*
|
|
4
|
-
* The case these were written against is a real torrent, and every name below
|
|
5
|
-
* is copied from it: `Drifters`, twelve episodes as `.mkv` in the root, twelve
|
|
6
|
-
* Russian soundtracks as `.mka` under `Rus Sound/`, twelve subtitle files under
|
|
7
|
-
* `Sub/[Stan WarHammer & Nesitach]/`, all sharing one base name per episode.
|
|
8
|
-
*
|
|
9
|
-
* The rule that matters most is the one about a torrent with SEVERAL videos: a
|
|
10
|
-
* sidecar is taken only when its name pairs with this episode's, because a
|
|
11
|
-
* wrong pairing would put the sound of episode 2 over the picture of episode 7
|
|
12
|
-
* and nothing downstream could notice.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import test from "node:test";
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import {
|
|
18
|
-
baseNameOf,
|
|
19
|
-
bracketTokensOf,
|
|
20
|
-
countVideoFiles,
|
|
21
|
-
extensionOf,
|
|
22
|
-
matchSidecarFiles,
|
|
23
|
-
namesPair,
|
|
24
|
-
splitTorrentPath
|
|
25
|
-
} from "../services/torrent/files.js";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The Drifters torrent, as WebTorrent reports it: every path prefixed with the
|
|
29
|
-
* torrent's own name.
|
|
30
|
-
*
|
|
31
|
-
* @param {number} episodes
|
|
32
|
-
* @returns {Array<{ path: string, name: string, length: number }>}
|
|
33
|
-
*/
|
|
34
|
-
function driftersFiles(episodes = 3) {
|
|
35
|
-
const files = [];
|
|
36
|
-
const push = (relative, length) => {
|
|
37
|
-
const name = relative.slice(relative.lastIndexOf("/") + 1);
|
|
38
|
-
files.push({ path: `Drifters/${relative}`, name, length });
|
|
39
|
-
};
|
|
40
|
-
for (let episode = 1; episode <= episodes; episode += 1) {
|
|
41
|
-
const stem = `[HorribleSubs] Drifters - ${String(episode).padStart(2, "0")} [1080p]`;
|
|
42
|
-
push(`Sub/[Stan WarHammer & Nesitach]/${stem}.ass`, 29_000);
|
|
43
|
-
push(`Rus Sound/${stem}.mka`, 30_000_000);
|
|
44
|
-
push(`${stem}.mkv`, 566_000_000);
|
|
45
|
-
}
|
|
46
|
-
return files;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
test("a path is split into folders and a name, with the torrent's own name removed", () => {
|
|
50
|
-
const split = splitTorrentPath("Drifters/Rus Sound/[HorribleSubs] Drifters - 02 [1080p].mka", "Drifters");
|
|
51
|
-
assert.deepEqual(split.folders, ["Rus Sound"]);
|
|
52
|
-
assert.equal(split.name, "[HorribleSubs] Drifters - 02 [1080p].mka");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("a path that does not begin with the torrent name is left alone", () => {
|
|
56
|
-
const split = splitTorrentPath("Sub/x.ass", "Drifters");
|
|
57
|
-
assert.deepEqual(split.folders, ["Sub"]);
|
|
58
|
-
assert.equal(split.name, "x.ass");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("extension and base name", () => {
|
|
62
|
-
assert.equal(extensionOf("[HorribleSubs] Drifters - 02 [1080p].mka"), ".mka");
|
|
63
|
-
assert.equal(extensionOf("no-extension"), "");
|
|
64
|
-
assert.equal(extensionOf(".hidden"), "");
|
|
65
|
-
assert.equal(baseNameOf("a.b.mkv"), "a.b");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("bracketed groups are read in order", () => {
|
|
69
|
-
assert.deepEqual(
|
|
70
|
-
bracketTokensOf("[HorribleSubs] Drifters - 02 [1080p]"),
|
|
71
|
-
["HorribleSubs", "1080p"]
|
|
72
|
-
);
|
|
73
|
-
assert.deepEqual(bracketTokensOf("nothing here"), []);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("equal base names pair, whatever folder each sits in", () => {
|
|
77
|
-
assert.equal(
|
|
78
|
-
namesPair("[HorribleSubs] Drifters - 02 [1080p].mka", "[HorribleSubs] Drifters - 02 [1080p].mkv"),
|
|
79
|
-
true
|
|
80
|
-
);
|
|
81
|
-
assert.equal(
|
|
82
|
-
namesPair("[HorribleSubs] Drifters - 03 [1080p].mka", "[HorribleSubs] Drifters - 02 [1080p].mkv"),
|
|
83
|
-
false
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("a shared release hash pairs two differently named files", () => {
|
|
88
|
-
assert.equal(namesPair("Ep01_rus [A1B2C3D4].mka", "[Group] Ep 01 [A1B2C3D4].mkv"), true);
|
|
89
|
-
assert.equal(namesPair("Ep01_rus [11112222].mka", "[Group] Ep 01 [A1B2C3D4].mkv"), false);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("the sound and the subtitles of THIS episode are found, and no other's", () => {
|
|
93
|
-
const files = driftersFiles(3);
|
|
94
|
-
// Episode 2's picture: index 5 in the list built above.
|
|
95
|
-
const videoIndex = files.findIndex((file) => file.name === "[HorribleSubs] Drifters - 02 [1080p].mkv");
|
|
96
|
-
const matched = matchSidecarFiles({
|
|
97
|
-
files,
|
|
98
|
-
videoIndex,
|
|
99
|
-
torrentName: "Drifters",
|
|
100
|
-
videoCount: countVideoFiles(files)
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
assert.equal(matched.audio.length, 1);
|
|
104
|
-
assert.equal(matched.audio[0].name, "[HorribleSubs] Drifters - 02 [1080p].mka");
|
|
105
|
-
assert.deepEqual(matched.audio[0].folders, ["Rus Sound"]);
|
|
106
|
-
assert.equal(matched.audio[0].extension, ".mka");
|
|
107
|
-
// A `.mka` is Matroska, so its own track table can be read — which is the
|
|
108
|
-
// whole reason no new container class was needed for a separate soundtrack.
|
|
109
|
-
assert.equal(matched.audio[0].declaresTracks, true);
|
|
110
|
-
|
|
111
|
-
assert.equal(matched.subtitles.length, 1);
|
|
112
|
-
assert.equal(matched.subtitles[0].name, "[HorribleSubs] Drifters - 02 [1080p].ass");
|
|
113
|
-
assert.deepEqual(matched.subtitles[0].folders, ["Sub", "[Stan WarHammer & Nesitach]"]);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("the picture itself is never its own sidecar", () => {
|
|
117
|
-
const files = driftersFiles(1);
|
|
118
|
-
const videoIndex = files.findIndex((file) => file.name.endsWith(".mkv"));
|
|
119
|
-
const matched = matchSidecarFiles({ files, videoIndex, torrentName: "Drifters", videoCount: 1 });
|
|
120
|
-
assert.equal(matched.audio.some((entry) => entry.fileIndex === videoIndex), false);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("a torrent with one picture takes every sidecar, however it is named", () => {
|
|
124
|
-
const files = [
|
|
125
|
-
{ path: "Film/Film.2019.1080p.mkv", name: "Film.2019.1080p.mkv", length: 5_000_000_000 },
|
|
126
|
-
{ path: "Film/Rus.mka", name: "Rus.mka", length: 300_000_000 },
|
|
127
|
-
{ path: "Film/subs/forced.srt", name: "forced.srt", length: 4_000 }
|
|
128
|
-
];
|
|
129
|
-
const matched = matchSidecarFiles({
|
|
130
|
-
files,
|
|
131
|
-
videoIndex: 0,
|
|
132
|
-
torrentName: "Film",
|
|
133
|
-
videoCount: countVideoFiles(files)
|
|
134
|
-
});
|
|
135
|
-
assert.deepEqual(matched.audio.map((entry) => entry.name), ["Rus.mka"]);
|
|
136
|
-
assert.deepEqual(matched.subtitles.map((entry) => entry.name), ["forced.srt"]);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test("a torrent with several pictures does NOT take a sidecar that names none of them", () => {
|
|
140
|
-
const files = [
|
|
141
|
-
{ path: "Pack/Ep01.mkv", name: "Ep01.mkv", length: 100 },
|
|
142
|
-
{ path: "Pack/Ep02.mkv", name: "Ep02.mkv", length: 100 },
|
|
143
|
-
{ path: "Pack/Sound/Something Else.mka", name: "Something Else.mka", length: 100 }
|
|
144
|
-
];
|
|
145
|
-
const matched = matchSidecarFiles({
|
|
146
|
-
files,
|
|
147
|
-
videoIndex: 0,
|
|
148
|
-
torrentName: "Pack",
|
|
149
|
-
videoCount: countVideoFiles(files)
|
|
150
|
-
});
|
|
151
|
-
assert.deepEqual(matched.audio, []);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("files of no interest are ignored", () => {
|
|
155
|
-
const files = [
|
|
156
|
-
{ path: "X/film.mkv", name: "film.mkv", length: 100 },
|
|
157
|
-
{ path: "X/film.nfo", name: "film.nfo", length: 10 },
|
|
158
|
-
{ path: "X/cover.jpg", name: "cover.jpg", length: 10 }
|
|
159
|
-
];
|
|
160
|
-
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "X", videoCount: 1 });
|
|
161
|
-
assert.deepEqual(matched.audio, []);
|
|
162
|
-
assert.deepEqual(matched.subtitles, []);
|
|
163
|
-
assert.deepEqual(
|
|
164
|
-
matched.images,
|
|
165
|
-
[],
|
|
166
|
-
"a stray cover is not THIS film's cover: the one-video relaxation does not reach images, " +
|
|
167
|
-
"because showing the wrong poster is a visible mistake where showing none is nothing"
|
|
168
|
-
);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("a contact sheet named by the video's whole name is paired with it", () => {
|
|
172
|
-
// The shape a pack of a hundred videos actually ships: one sheet per video,
|
|
173
|
-
// named `<video file name>.jpg`. Its BASE name is `c0930.com_chijyo0073.wmv`,
|
|
174
|
-
// which no base-name rule would match against the video's `c0930…`.
|
|
175
|
-
const files = [
|
|
176
|
-
{ path: "P/c0930.com_chijyo0073.wmv", name: "c0930.com_chijyo0073.wmv", length: 900 },
|
|
177
|
-
{
|
|
178
|
-
path: "P/Скринлисты/c0930.com_chijyo0073.wmv.jpg",
|
|
179
|
-
name: "c0930.com_chijyo0073.wmv.jpg",
|
|
180
|
-
length: 90
|
|
181
|
-
},
|
|
182
|
-
{ path: "P/other.wmv", name: "other.wmv", length: 900 },
|
|
183
|
-
{ path: "P/Скринлисты/other.wmv.jpg", name: "other.wmv.jpg", length: 90 }
|
|
184
|
-
];
|
|
185
|
-
|
|
186
|
-
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "P", videoCount: 2 });
|
|
187
|
-
|
|
188
|
-
assert.equal(matched.images.length, 1, "its own sheet, and not the other video's");
|
|
189
|
-
assert.equal(matched.images[0].fileIndex, 1);
|
|
190
|
-
assert.deepEqual(matched.images[0].folders, ["Скринлисты"]);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("a cover named like the film is paired with it, whatever its extension", () => {
|
|
194
|
-
const files = [
|
|
195
|
-
{ path: "X/Film.2019.mkv", name: "Film.2019.mkv", length: 100 },
|
|
196
|
-
{ path: "X/Film.2019.png", name: "Film.2019.png", length: 10 },
|
|
197
|
-
{ path: "X/Film.2019.webp", name: "Film.2019.webp", length: 10 }
|
|
198
|
-
];
|
|
199
|
-
|
|
200
|
-
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "X", videoCount: 1 });
|
|
201
|
-
|
|
202
|
-
assert.deepEqual(
|
|
203
|
-
matched.images.map((image) => image.name).sort(),
|
|
204
|
-
["Film.2019.png", "Film.2019.webp"]
|
|
205
|
-
);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("a raw elementary stream is recognised but declares no tracks of its own", () => {
|
|
209
|
-
const files = [
|
|
210
|
-
{ path: "X/film.mkv", name: "film.mkv", length: 100 },
|
|
211
|
-
{ path: "X/film.ac3", name: "film.ac3", length: 100 }
|
|
212
|
-
];
|
|
213
|
-
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "X", videoCount: 1 });
|
|
214
|
-
assert.equal(matched.audio.length, 1);
|
|
215
|
-
assert.equal(matched.audio[0].declaresTracks, false);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("pictures are counted, and nothing else is", () => {
|
|
219
|
-
assert.equal(countVideoFiles(driftersFiles(12)), 12);
|
|
220
|
-
assert.equal(countVideoFiles([{ name: "a.mka" }, { name: "b.srt" }]), 0);
|
|
221
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file Which files of a torrent are one video file's sound and subtitles.
|
|
3
|
+
*
|
|
4
|
+
* The case these were written against is a real torrent, and every name below
|
|
5
|
+
* is copied from it: `Drifters`, twelve episodes as `.mkv` in the root, twelve
|
|
6
|
+
* Russian soundtracks as `.mka` under `Rus Sound/`, twelve subtitle files under
|
|
7
|
+
* `Sub/[Stan WarHammer & Nesitach]/`, all sharing one base name per episode.
|
|
8
|
+
*
|
|
9
|
+
* The rule that matters most is the one about a torrent with SEVERAL videos: a
|
|
10
|
+
* sidecar is taken only when its name pairs with this episode's, because a
|
|
11
|
+
* wrong pairing would put the sound of episode 2 over the picture of episode 7
|
|
12
|
+
* and nothing downstream could notice.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import test from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import {
|
|
18
|
+
baseNameOf,
|
|
19
|
+
bracketTokensOf,
|
|
20
|
+
countVideoFiles,
|
|
21
|
+
extensionOf,
|
|
22
|
+
matchSidecarFiles,
|
|
23
|
+
namesPair,
|
|
24
|
+
splitTorrentPath
|
|
25
|
+
} from "../services/torrent/files.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The Drifters torrent, as WebTorrent reports it: every path prefixed with the
|
|
29
|
+
* torrent's own name.
|
|
30
|
+
*
|
|
31
|
+
* @param {number} episodes
|
|
32
|
+
* @returns {Array<{ path: string, name: string, length: number }>}
|
|
33
|
+
*/
|
|
34
|
+
function driftersFiles(episodes = 3) {
|
|
35
|
+
const files = [];
|
|
36
|
+
const push = (relative, length) => {
|
|
37
|
+
const name = relative.slice(relative.lastIndexOf("/") + 1);
|
|
38
|
+
files.push({ path: `Drifters/${relative}`, name, length });
|
|
39
|
+
};
|
|
40
|
+
for (let episode = 1; episode <= episodes; episode += 1) {
|
|
41
|
+
const stem = `[HorribleSubs] Drifters - ${String(episode).padStart(2, "0")} [1080p]`;
|
|
42
|
+
push(`Sub/[Stan WarHammer & Nesitach]/${stem}.ass`, 29_000);
|
|
43
|
+
push(`Rus Sound/${stem}.mka`, 30_000_000);
|
|
44
|
+
push(`${stem}.mkv`, 566_000_000);
|
|
45
|
+
}
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test("a path is split into folders and a name, with the torrent's own name removed", () => {
|
|
50
|
+
const split = splitTorrentPath("Drifters/Rus Sound/[HorribleSubs] Drifters - 02 [1080p].mka", "Drifters");
|
|
51
|
+
assert.deepEqual(split.folders, ["Rus Sound"]);
|
|
52
|
+
assert.equal(split.name, "[HorribleSubs] Drifters - 02 [1080p].mka");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("a path that does not begin with the torrent name is left alone", () => {
|
|
56
|
+
const split = splitTorrentPath("Sub/x.ass", "Drifters");
|
|
57
|
+
assert.deepEqual(split.folders, ["Sub"]);
|
|
58
|
+
assert.equal(split.name, "x.ass");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("extension and base name", () => {
|
|
62
|
+
assert.equal(extensionOf("[HorribleSubs] Drifters - 02 [1080p].mka"), ".mka");
|
|
63
|
+
assert.equal(extensionOf("no-extension"), "");
|
|
64
|
+
assert.equal(extensionOf(".hidden"), "");
|
|
65
|
+
assert.equal(baseNameOf("a.b.mkv"), "a.b");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("bracketed groups are read in order", () => {
|
|
69
|
+
assert.deepEqual(
|
|
70
|
+
bracketTokensOf("[HorribleSubs] Drifters - 02 [1080p]"),
|
|
71
|
+
["HorribleSubs", "1080p"]
|
|
72
|
+
);
|
|
73
|
+
assert.deepEqual(bracketTokensOf("nothing here"), []);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("equal base names pair, whatever folder each sits in", () => {
|
|
77
|
+
assert.equal(
|
|
78
|
+
namesPair("[HorribleSubs] Drifters - 02 [1080p].mka", "[HorribleSubs] Drifters - 02 [1080p].mkv"),
|
|
79
|
+
true
|
|
80
|
+
);
|
|
81
|
+
assert.equal(
|
|
82
|
+
namesPair("[HorribleSubs] Drifters - 03 [1080p].mka", "[HorribleSubs] Drifters - 02 [1080p].mkv"),
|
|
83
|
+
false
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("a shared release hash pairs two differently named files", () => {
|
|
88
|
+
assert.equal(namesPair("Ep01_rus [A1B2C3D4].mka", "[Group] Ep 01 [A1B2C3D4].mkv"), true);
|
|
89
|
+
assert.equal(namesPair("Ep01_rus [11112222].mka", "[Group] Ep 01 [A1B2C3D4].mkv"), false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("the sound and the subtitles of THIS episode are found, and no other's", () => {
|
|
93
|
+
const files = driftersFiles(3);
|
|
94
|
+
// Episode 2's picture: index 5 in the list built above.
|
|
95
|
+
const videoIndex = files.findIndex((file) => file.name === "[HorribleSubs] Drifters - 02 [1080p].mkv");
|
|
96
|
+
const matched = matchSidecarFiles({
|
|
97
|
+
files,
|
|
98
|
+
videoIndex,
|
|
99
|
+
torrentName: "Drifters",
|
|
100
|
+
videoCount: countVideoFiles(files)
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.equal(matched.audio.length, 1);
|
|
104
|
+
assert.equal(matched.audio[0].name, "[HorribleSubs] Drifters - 02 [1080p].mka");
|
|
105
|
+
assert.deepEqual(matched.audio[0].folders, ["Rus Sound"]);
|
|
106
|
+
assert.equal(matched.audio[0].extension, ".mka");
|
|
107
|
+
// A `.mka` is Matroska, so its own track table can be read — which is the
|
|
108
|
+
// whole reason no new container class was needed for a separate soundtrack.
|
|
109
|
+
assert.equal(matched.audio[0].declaresTracks, true);
|
|
110
|
+
|
|
111
|
+
assert.equal(matched.subtitles.length, 1);
|
|
112
|
+
assert.equal(matched.subtitles[0].name, "[HorribleSubs] Drifters - 02 [1080p].ass");
|
|
113
|
+
assert.deepEqual(matched.subtitles[0].folders, ["Sub", "[Stan WarHammer & Nesitach]"]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("the picture itself is never its own sidecar", () => {
|
|
117
|
+
const files = driftersFiles(1);
|
|
118
|
+
const videoIndex = files.findIndex((file) => file.name.endsWith(".mkv"));
|
|
119
|
+
const matched = matchSidecarFiles({ files, videoIndex, torrentName: "Drifters", videoCount: 1 });
|
|
120
|
+
assert.equal(matched.audio.some((entry) => entry.fileIndex === videoIndex), false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("a torrent with one picture takes every sidecar, however it is named", () => {
|
|
124
|
+
const files = [
|
|
125
|
+
{ path: "Film/Film.2019.1080p.mkv", name: "Film.2019.1080p.mkv", length: 5_000_000_000 },
|
|
126
|
+
{ path: "Film/Rus.mka", name: "Rus.mka", length: 300_000_000 },
|
|
127
|
+
{ path: "Film/subs/forced.srt", name: "forced.srt", length: 4_000 }
|
|
128
|
+
];
|
|
129
|
+
const matched = matchSidecarFiles({
|
|
130
|
+
files,
|
|
131
|
+
videoIndex: 0,
|
|
132
|
+
torrentName: "Film",
|
|
133
|
+
videoCount: countVideoFiles(files)
|
|
134
|
+
});
|
|
135
|
+
assert.deepEqual(matched.audio.map((entry) => entry.name), ["Rus.mka"]);
|
|
136
|
+
assert.deepEqual(matched.subtitles.map((entry) => entry.name), ["forced.srt"]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("a torrent with several pictures does NOT take a sidecar that names none of them", () => {
|
|
140
|
+
const files = [
|
|
141
|
+
{ path: "Pack/Ep01.mkv", name: "Ep01.mkv", length: 100 },
|
|
142
|
+
{ path: "Pack/Ep02.mkv", name: "Ep02.mkv", length: 100 },
|
|
143
|
+
{ path: "Pack/Sound/Something Else.mka", name: "Something Else.mka", length: 100 }
|
|
144
|
+
];
|
|
145
|
+
const matched = matchSidecarFiles({
|
|
146
|
+
files,
|
|
147
|
+
videoIndex: 0,
|
|
148
|
+
torrentName: "Pack",
|
|
149
|
+
videoCount: countVideoFiles(files)
|
|
150
|
+
});
|
|
151
|
+
assert.deepEqual(matched.audio, []);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("files of no interest are ignored", () => {
|
|
155
|
+
const files = [
|
|
156
|
+
{ path: "X/film.mkv", name: "film.mkv", length: 100 },
|
|
157
|
+
{ path: "X/film.nfo", name: "film.nfo", length: 10 },
|
|
158
|
+
{ path: "X/cover.jpg", name: "cover.jpg", length: 10 }
|
|
159
|
+
];
|
|
160
|
+
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "X", videoCount: 1 });
|
|
161
|
+
assert.deepEqual(matched.audio, []);
|
|
162
|
+
assert.deepEqual(matched.subtitles, []);
|
|
163
|
+
assert.deepEqual(
|
|
164
|
+
matched.images,
|
|
165
|
+
[],
|
|
166
|
+
"a stray cover is not THIS film's cover: the one-video relaxation does not reach images, " +
|
|
167
|
+
"because showing the wrong poster is a visible mistake where showing none is nothing"
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("a contact sheet named by the video's whole name is paired with it", () => {
|
|
172
|
+
// The shape a pack of a hundred videos actually ships: one sheet per video,
|
|
173
|
+
// named `<video file name>.jpg`. Its BASE name is `c0930.com_chijyo0073.wmv`,
|
|
174
|
+
// which no base-name rule would match against the video's `c0930…`.
|
|
175
|
+
const files = [
|
|
176
|
+
{ path: "P/c0930.com_chijyo0073.wmv", name: "c0930.com_chijyo0073.wmv", length: 900 },
|
|
177
|
+
{
|
|
178
|
+
path: "P/Скринлисты/c0930.com_chijyo0073.wmv.jpg",
|
|
179
|
+
name: "c0930.com_chijyo0073.wmv.jpg",
|
|
180
|
+
length: 90
|
|
181
|
+
},
|
|
182
|
+
{ path: "P/other.wmv", name: "other.wmv", length: 900 },
|
|
183
|
+
{ path: "P/Скринлисты/other.wmv.jpg", name: "other.wmv.jpg", length: 90 }
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "P", videoCount: 2 });
|
|
187
|
+
|
|
188
|
+
assert.equal(matched.images.length, 1, "its own sheet, and not the other video's");
|
|
189
|
+
assert.equal(matched.images[0].fileIndex, 1);
|
|
190
|
+
assert.deepEqual(matched.images[0].folders, ["Скринлисты"]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("a cover named like the film is paired with it, whatever its extension", () => {
|
|
194
|
+
const files = [
|
|
195
|
+
{ path: "X/Film.2019.mkv", name: "Film.2019.mkv", length: 100 },
|
|
196
|
+
{ path: "X/Film.2019.png", name: "Film.2019.png", length: 10 },
|
|
197
|
+
{ path: "X/Film.2019.webp", name: "Film.2019.webp", length: 10 }
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "X", videoCount: 1 });
|
|
201
|
+
|
|
202
|
+
assert.deepEqual(
|
|
203
|
+
matched.images.map((image) => image.name).sort(),
|
|
204
|
+
["Film.2019.png", "Film.2019.webp"]
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("a raw elementary stream is recognised but declares no tracks of its own", () => {
|
|
209
|
+
const files = [
|
|
210
|
+
{ path: "X/film.mkv", name: "film.mkv", length: 100 },
|
|
211
|
+
{ path: "X/film.ac3", name: "film.ac3", length: 100 }
|
|
212
|
+
];
|
|
213
|
+
const matched = matchSidecarFiles({ files, videoIndex: 0, torrentName: "X", videoCount: 1 });
|
|
214
|
+
assert.equal(matched.audio.length, 1);
|
|
215
|
+
assert.equal(matched.audio[0].declaresTracks, false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("pictures are counted, and nothing else is", () => {
|
|
219
|
+
assert.equal(countVideoFiles(driftersFiles(12)), 12);
|
|
220
|
+
assert.equal(countVideoFiles([{ name: "a.mka" }, { name: "b.srt" }]), 0);
|
|
221
|
+
});
|