@torrent-tv/proxy 2.89.7 → 2.89.8
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 +358 -338
- package/CHANGELOG.md +2340 -2335
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/calibration/audio/NOTICE.md +19 -19
- package/assets/calibration/audio/manifest.json +98 -98
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +412 -412
- package/docs/browser-proxy-contract.md +155 -155
- package/docs/container-architecture.md +273 -273
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +765 -765
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +113 -113
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +273 -273
- 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 +51 -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/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +106 -106
- 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 +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- 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 +416 -416
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- 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/build-audio-samples.mjs +31 -31
- package/scripts/render-run-graph.js +173 -173
- package/server.js +748 -748
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1192 -1192
- package/services/encode/EncodePlan.js +1368 -1368
- package/services/encode/EncodeRun.js +36 -12
- 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 +784 -784
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2283 -2283
- package/services/encode/RunClock.js +87 -87
- package/services/encode/RunProgress.js +79 -79
- 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 +418 -418
- package/services/encode/audio-calibration.js +67 -67
- package/services/encode/audio-work-rate.js +15 -15
- 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 +116 -116
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +43 -43
- 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 +24 -24
- 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 +1043 -1043
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +24 -24
- package/services/encode/quality/QualityController.js +758 -758
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +337 -337
- package/services/encode/quality/output-format.js +319 -319
- 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 +757 -757
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +53 -53
- package/services/encode/segment-formats/fmp4.js +476 -476
- package/services/encode/segment-formats/init-compat.js +324 -324
- 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 +202 -202
- package/services/media/KeyframeTables.js +356 -356
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +592 -592
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +299 -299
- package/services/media/container/AviContainer.js +268 -268
- package/services/media/container/Container.js +632 -632
- package/services/media/container/ContainerFactory.js +38 -38
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1031 -1031
- package/services/media/container/Mp4Container.js +1354 -1354
- package/services/media/container/SubtitleFileContainer.js +339 -339
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/container/ebml-stream.js +174 -174
- package/services/media/container/matroska-clusters.js +1165 -1165
- package/services/media/container/unavailable.js +77 -77
- 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 +143 -143
- package/services/media/tracks/ContainerTrack.js +84 -84
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +295 -295
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- 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 +1152 -1152
- package/services/server/ViewerRequests.js +785 -785
- 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/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +1029 -1029
- 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 +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1937 -1937
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1294 -1294
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +382 -382
- 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/episode-naming.js +182 -182
- 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 +264 -264
- package/services/torrent/torrent-pool.js +2688 -2688
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +836 -836
- 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 +231 -231
- 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 +371 -371
- 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 +1137 -1137
- package/services/transport/data-channel-handler.js +1373 -1373
- 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 +421 -421
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- 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/media-time.js +355 -355
- package/services/viewer/playback-readiness.js +635 -635
- 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-buffer-continuity.test.js +77 -77
- package/test/audio-calibration.test.js +23 -23
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +762 -762
- 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/coalesce.test.js +39 -39
- 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 +233 -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 +475 -475
- 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 +275 -275
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +426 -426
- package/test/encode-plan-viewers.test.js +725 -725
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +20 -0
- 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 +68 -68
- package/test/episode-naming.test.js +131 -131
- 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/fixtures/audio-frame-boundary.json +1 -1
- 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 +36 -38
- package/test/held-read-ownership.test.js +77 -77
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +248 -240
- package/test/helpers/held-file.js +77 -77
- package/test/helpers/manager.js +62 -62
- package/test/helpers/matroska-file.js +296 -296
- 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 +123 -123
- 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 +291 -291
- package/test/matroska-cluster-discovery.test.js +323 -323
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -62
- 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 +273 -273
- 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 +207 -207
- 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 +92 -87
- 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 +401 -401
- 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 +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- 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 +518 -518
- package/test/playback-start-regressions.test.js +101 -101
- 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 +160 -160
- package/test/quality-variants.test.js +1274 -1270
- package/test/rate-control-in-the-output.test.js +181 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/reads-not-yet-arrived.test.js +218 -218
- 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 +64 -64
- package/test/same-height-switch.test.js +663 -663
- 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 +114 -114
- 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 +222 -222
- package/test/segment-serve-wiring.test.js +481 -481
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -352
- 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 +111 -111
- 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/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +113 -113
- 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 +237 -237
- 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 +385 -385
- 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-metadata.test.js +15 -15
- package/test/subtitle-plan-waits.test.js +125 -125
- package/test/subtitle-track-numbering.test.js +391 -391
- package/test/supply-margin.test.js +190 -190
- 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 +508 -508
- 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 +92 -92
- package/test/viewer-starts-on-map.test.js +27 -27
- package/test/viewer-states.test.js +192 -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/whole-file-held-read.test.js +30 -30
- 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/coalesce.js +41 -41
- 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,182 +1,182 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What a picture's NAME says about which episode it is.
|
|
3
|
-
*
|
|
4
|
-
* A season pack states its structure in names — `s01e02_Murder.in.the.Mews.avi`,
|
|
5
|
-
* `Reacher.S04E01.1080p.rus.LostFilm.TV.mkv`, `[HorribleSubs] Drifters - 01
|
|
6
|
-
* [1080p].mkv` under a folder, or an episode number under `Season_01/`. This
|
|
7
|
-
* reads that statement and nothing else: which season, which episode numbers,
|
|
8
|
-
* which part of an episode, whether it is a special, and the text on either
|
|
9
|
-
* side of the marker.
|
|
10
|
-
*
|
|
11
|
-
* The numbers are the RELEASE's numbering. They are often the same as a
|
|
12
|
-
* metadata provider's and sometimes not — one release numbers the two parts of
|
|
13
|
-
* a feature-length episode as two episodes, which shifts every number after
|
|
14
|
-
* them — so nothing here claims to know which episode of the show a file is.
|
|
15
|
-
* That is decided later, against the provider's own list, by whoever holds it.
|
|
16
|
-
*
|
|
17
|
-
* A name without a marker returns `null`: absence of a number is absence of
|
|
18
|
-
* information, not evidence that the file is not an episode.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { stripExtension } from "./naming.js";
|
|
22
|
-
|
|
23
|
-
/** Most episodes one file may claim before the claim is read as noise. */
|
|
24
|
-
const MAX_EPISODES_PER_FILE = 8;
|
|
25
|
-
|
|
26
|
-
/** Longest hint returned, in characters; a name longer than this is not a title. */
|
|
27
|
-
const MAX_HINT_LENGTH = 160;
|
|
28
|
-
|
|
29
|
-
/** `S01E02`, `s03.e01`, `S01E01E02`, `S01E01-E02`, `S01E01-02`. */
|
|
30
|
-
const SEASON_EPISODE = /(?<![a-z0-9])s(\d{1,3})[ ._]?e(\d{1,4})((?:-?e\d{1,4}|-\d{1,4})*)(?![0-9])/i;
|
|
31
|
-
|
|
32
|
-
/** `1x02`; four-digit sides are picture sizes (`1920x1080`) and never match. */
|
|
33
|
-
const CROSS = /(?<![a-z0-9])(\d{1,2})x(\d{2,3})(?![0-9a-z])/i;
|
|
34
|
-
|
|
35
|
-
/** `E05`, `Ep 5`, `Episode.05`, `Серия 5` — an episode number with no season in it. */
|
|
36
|
-
const EPISODE_ONLY = /(?<![\p{L}0-9])(?:e|ep|episode|серия)[ ._-]?(\d{1,4})(?![0-9])/iu;
|
|
37
|
-
|
|
38
|
-
/** `Show - 01`, `Show - 01v2` — the numbering most anime releases use. */
|
|
39
|
-
const DASH_NUMBER = / - (\d{1,4})(?:v\d)?(?=[ .[(]|$)/;
|
|
40
|
-
|
|
41
|
-
/** A folder that names a season: `Season_01`, `Season 1`, `S01`, `Сезон 1`, `1 сезон`. */
|
|
42
|
-
const SEASON_FOLDER = /^(?:season|сезон|s)[ ._-]*(\d{1,3})$|^(\d{1,3})[ ._-]*(?:season|сезон)$/iu;
|
|
43
|
-
|
|
44
|
-
/** `Part.1`, `Pt 2`, `Часть 1`. */
|
|
45
|
-
const PART = /(?<![\p{L}0-9])(?:part|pt|часть)[ ._-]?(\d{1,2})(?![0-9])/iu;
|
|
46
|
-
|
|
47
|
-
/** `Special`, `Specials`, `SP01`. */
|
|
48
|
-
const SPECIAL = /(?<![\p{L}])(?:specials?|sp\d{1,3})(?![\p{L}])/iu;
|
|
49
|
-
|
|
50
|
-
/** Separators a release puts between words, trimmed off the ends of a hint. */
|
|
51
|
-
const EDGE_SEPARATORS = /^[\s._\-–—]+|[\s._\-–—]+$/g;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* An episode marker read from a name.
|
|
55
|
-
*
|
|
56
|
-
* @typedef {object} EpisodeMarker
|
|
57
|
-
* @property {number | null} season - `null` when neither the name nor a folder states one.
|
|
58
|
-
* @property {number[]} episodes - The release's numbers, one or more.
|
|
59
|
-
* @property {number | null} part - Which part of one episode, when the name says so.
|
|
60
|
-
* @property {boolean} special - Season 0, or a name calling itself a special.
|
|
61
|
-
* @property {string} showHint - The name's text before the marker, as written.
|
|
62
|
-
* @property {string} titleHint - The name's text after the marker, part removed, as written.
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Trim a piece of a name to a hint.
|
|
67
|
-
*
|
|
68
|
-
* @param {string} text
|
|
69
|
-
* @returns {string}
|
|
70
|
-
*/
|
|
71
|
-
function hint(text) {
|
|
72
|
-
return String(text ?? "").replace(EDGE_SEPARATORS, "").slice(0, MAX_HINT_LENGTH);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* The numbers after the first episode in `S01E01E02` or `S01E01-03`.
|
|
77
|
-
*
|
|
78
|
-
* A `-` joins a range, a repeated `E` lists another episode.
|
|
79
|
-
*
|
|
80
|
-
* @param {number} first
|
|
81
|
-
* @param {string} rest
|
|
82
|
-
* @returns {number[] | null} `null` when the claim is longer than any file carries.
|
|
83
|
-
*/
|
|
84
|
-
function episodesFrom(first, rest) {
|
|
85
|
-
const episodes = [first];
|
|
86
|
-
for (const token of rest.match(/-?e?\d+/gi) ?? []) {
|
|
87
|
-
const value = Number(token.replace(/[^0-9]/g, ""));
|
|
88
|
-
const last = episodes[episodes.length - 1];
|
|
89
|
-
if (token.startsWith("-") && value > last) {
|
|
90
|
-
for (let next = last + 1; next <= value; next += 1) {
|
|
91
|
-
episodes.push(next);
|
|
92
|
-
if (episodes.length > MAX_EPISODES_PER_FILE) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} else if (value !== last) {
|
|
97
|
-
episodes.push(value);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return episodes.length > MAX_EPISODES_PER_FILE ? null : episodes;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* The season the deepest season-naming folder states, if any does.
|
|
105
|
-
*
|
|
106
|
-
* @param {string[]} folders
|
|
107
|
-
* @returns {number | null}
|
|
108
|
-
*/
|
|
109
|
-
function seasonFromFolders(folders) {
|
|
110
|
-
for (let index = folders.length - 1; index >= 0; index -= 1) {
|
|
111
|
-
const match = SEASON_FOLDER.exec(String(folders[index] ?? "").trim());
|
|
112
|
-
if (match) {
|
|
113
|
-
return Number(match[1] ?? match[2]);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Which episode a picture's name says it is.
|
|
121
|
-
*
|
|
122
|
-
* @param {{ name: string, folders?: string[] }} file - File name and the folders above it.
|
|
123
|
-
* @returns {EpisodeMarker | null}
|
|
124
|
-
*/
|
|
125
|
-
export function readEpisodeMarker({ name, folders = [] }) {
|
|
126
|
-
const stem = stripExtension(String(name ?? ""));
|
|
127
|
-
const folderSeason = seasonFromFolders(Array.isArray(folders) ? folders : []);
|
|
128
|
-
|
|
129
|
-
let season = null;
|
|
130
|
-
let episodes = null;
|
|
131
|
-
let before = "";
|
|
132
|
-
let after = "";
|
|
133
|
-
|
|
134
|
-
const seasonEpisode = SEASON_EPISODE.exec(stem);
|
|
135
|
-
const cross = seasonEpisode ? null : CROSS.exec(stem);
|
|
136
|
-
const episodeOnly = seasonEpisode || cross ? null : EPISODE_ONLY.exec(stem);
|
|
137
|
-
const dash = seasonEpisode || cross || episodeOnly ? null : DASH_NUMBER.exec(stem);
|
|
138
|
-
const match = seasonEpisode ?? cross ?? episodeOnly ?? dash;
|
|
139
|
-
if (!match) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
before = stem.slice(0, match.index);
|
|
143
|
-
after = stem.slice(match.index + match[0].length);
|
|
144
|
-
|
|
145
|
-
if (seasonEpisode) {
|
|
146
|
-
season = Number(seasonEpisode[1]);
|
|
147
|
-
episodes = episodesFrom(Number(seasonEpisode[2]), seasonEpisode[3] ?? "");
|
|
148
|
-
} else if (cross) {
|
|
149
|
-
season = Number(cross[1]);
|
|
150
|
-
episodes = [Number(cross[2])];
|
|
151
|
-
} else {
|
|
152
|
-
// `Show S1 - 01` states its season just before the number; a folder is the
|
|
153
|
-
// other place a release puts it.
|
|
154
|
-
const trailingSeason = /(?<![a-z0-9])s(\d{1,3})[\s._-]*$/i.exec(before);
|
|
155
|
-
season = trailingSeason ? Number(trailingSeason[1]) : folderSeason;
|
|
156
|
-
if (trailingSeason) {
|
|
157
|
-
before = before.slice(0, trailingSeason.index);
|
|
158
|
-
}
|
|
159
|
-
episodes = [Number(match[1])];
|
|
160
|
-
}
|
|
161
|
-
// A year where an episode number would be is a year far more often than an
|
|
162
|
-
// episode: `Film - 2024 [1080p]`, and `WALL-E.2008.mkv` reads as `E` then
|
|
163
|
-
// `2008`. Only the markers without a season can be fooled this way.
|
|
164
|
-
if ((dash || episodeOnly) && /^(?:19|20)\d\d$/.test(match[1])) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
if (!episodes) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const part = PART.exec(after);
|
|
172
|
-
const titleText = part ? `${after.slice(0, part.index)} ${after.slice(part.index + part[0].length)}` : after;
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
season,
|
|
176
|
-
episodes,
|
|
177
|
-
part: part ? Number(part[1]) : null,
|
|
178
|
-
special: season === 0 || SPECIAL.test(stem),
|
|
179
|
-
showHint: hint(before),
|
|
180
|
-
titleHint: hint(titleText)
|
|
181
|
-
};
|
|
182
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file What a picture's NAME says about which episode it is.
|
|
3
|
+
*
|
|
4
|
+
* A season pack states its structure in names — `s01e02_Murder.in.the.Mews.avi`,
|
|
5
|
+
* `Reacher.S04E01.1080p.rus.LostFilm.TV.mkv`, `[HorribleSubs] Drifters - 01
|
|
6
|
+
* [1080p].mkv` under a folder, or an episode number under `Season_01/`. This
|
|
7
|
+
* reads that statement and nothing else: which season, which episode numbers,
|
|
8
|
+
* which part of an episode, whether it is a special, and the text on either
|
|
9
|
+
* side of the marker.
|
|
10
|
+
*
|
|
11
|
+
* The numbers are the RELEASE's numbering. They are often the same as a
|
|
12
|
+
* metadata provider's and sometimes not — one release numbers the two parts of
|
|
13
|
+
* a feature-length episode as two episodes, which shifts every number after
|
|
14
|
+
* them — so nothing here claims to know which episode of the show a file is.
|
|
15
|
+
* That is decided later, against the provider's own list, by whoever holds it.
|
|
16
|
+
*
|
|
17
|
+
* A name without a marker returns `null`: absence of a number is absence of
|
|
18
|
+
* information, not evidence that the file is not an episode.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { stripExtension } from "./naming.js";
|
|
22
|
+
|
|
23
|
+
/** Most episodes one file may claim before the claim is read as noise. */
|
|
24
|
+
const MAX_EPISODES_PER_FILE = 8;
|
|
25
|
+
|
|
26
|
+
/** Longest hint returned, in characters; a name longer than this is not a title. */
|
|
27
|
+
const MAX_HINT_LENGTH = 160;
|
|
28
|
+
|
|
29
|
+
/** `S01E02`, `s03.e01`, `S01E01E02`, `S01E01-E02`, `S01E01-02`. */
|
|
30
|
+
const SEASON_EPISODE = /(?<![a-z0-9])s(\d{1,3})[ ._]?e(\d{1,4})((?:-?e\d{1,4}|-\d{1,4})*)(?![0-9])/i;
|
|
31
|
+
|
|
32
|
+
/** `1x02`; four-digit sides are picture sizes (`1920x1080`) and never match. */
|
|
33
|
+
const CROSS = /(?<![a-z0-9])(\d{1,2})x(\d{2,3})(?![0-9a-z])/i;
|
|
34
|
+
|
|
35
|
+
/** `E05`, `Ep 5`, `Episode.05`, `Серия 5` — an episode number with no season in it. */
|
|
36
|
+
const EPISODE_ONLY = /(?<![\p{L}0-9])(?:e|ep|episode|серия)[ ._-]?(\d{1,4})(?![0-9])/iu;
|
|
37
|
+
|
|
38
|
+
/** `Show - 01`, `Show - 01v2` — the numbering most anime releases use. */
|
|
39
|
+
const DASH_NUMBER = / - (\d{1,4})(?:v\d)?(?=[ .[(]|$)/;
|
|
40
|
+
|
|
41
|
+
/** A folder that names a season: `Season_01`, `Season 1`, `S01`, `Сезон 1`, `1 сезон`. */
|
|
42
|
+
const SEASON_FOLDER = /^(?:season|сезон|s)[ ._-]*(\d{1,3})$|^(\d{1,3})[ ._-]*(?:season|сезон)$/iu;
|
|
43
|
+
|
|
44
|
+
/** `Part.1`, `Pt 2`, `Часть 1`. */
|
|
45
|
+
const PART = /(?<![\p{L}0-9])(?:part|pt|часть)[ ._-]?(\d{1,2})(?![0-9])/iu;
|
|
46
|
+
|
|
47
|
+
/** `Special`, `Specials`, `SP01`. */
|
|
48
|
+
const SPECIAL = /(?<![\p{L}])(?:specials?|sp\d{1,3})(?![\p{L}])/iu;
|
|
49
|
+
|
|
50
|
+
/** Separators a release puts between words, trimmed off the ends of a hint. */
|
|
51
|
+
const EDGE_SEPARATORS = /^[\s._\-–—]+|[\s._\-–—]+$/g;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* An episode marker read from a name.
|
|
55
|
+
*
|
|
56
|
+
* @typedef {object} EpisodeMarker
|
|
57
|
+
* @property {number | null} season - `null` when neither the name nor a folder states one.
|
|
58
|
+
* @property {number[]} episodes - The release's numbers, one or more.
|
|
59
|
+
* @property {number | null} part - Which part of one episode, when the name says so.
|
|
60
|
+
* @property {boolean} special - Season 0, or a name calling itself a special.
|
|
61
|
+
* @property {string} showHint - The name's text before the marker, as written.
|
|
62
|
+
* @property {string} titleHint - The name's text after the marker, part removed, as written.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Trim a piece of a name to a hint.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} text
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
function hint(text) {
|
|
72
|
+
return String(text ?? "").replace(EDGE_SEPARATORS, "").slice(0, MAX_HINT_LENGTH);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The numbers after the first episode in `S01E01E02` or `S01E01-03`.
|
|
77
|
+
*
|
|
78
|
+
* A `-` joins a range, a repeated `E` lists another episode.
|
|
79
|
+
*
|
|
80
|
+
* @param {number} first
|
|
81
|
+
* @param {string} rest
|
|
82
|
+
* @returns {number[] | null} `null` when the claim is longer than any file carries.
|
|
83
|
+
*/
|
|
84
|
+
function episodesFrom(first, rest) {
|
|
85
|
+
const episodes = [first];
|
|
86
|
+
for (const token of rest.match(/-?e?\d+/gi) ?? []) {
|
|
87
|
+
const value = Number(token.replace(/[^0-9]/g, ""));
|
|
88
|
+
const last = episodes[episodes.length - 1];
|
|
89
|
+
if (token.startsWith("-") && value > last) {
|
|
90
|
+
for (let next = last + 1; next <= value; next += 1) {
|
|
91
|
+
episodes.push(next);
|
|
92
|
+
if (episodes.length > MAX_EPISODES_PER_FILE) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if (value !== last) {
|
|
97
|
+
episodes.push(value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return episodes.length > MAX_EPISODES_PER_FILE ? null : episodes;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The season the deepest season-naming folder states, if any does.
|
|
105
|
+
*
|
|
106
|
+
* @param {string[]} folders
|
|
107
|
+
* @returns {number | null}
|
|
108
|
+
*/
|
|
109
|
+
function seasonFromFolders(folders) {
|
|
110
|
+
for (let index = folders.length - 1; index >= 0; index -= 1) {
|
|
111
|
+
const match = SEASON_FOLDER.exec(String(folders[index] ?? "").trim());
|
|
112
|
+
if (match) {
|
|
113
|
+
return Number(match[1] ?? match[2]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Which episode a picture's name says it is.
|
|
121
|
+
*
|
|
122
|
+
* @param {{ name: string, folders?: string[] }} file - File name and the folders above it.
|
|
123
|
+
* @returns {EpisodeMarker | null}
|
|
124
|
+
*/
|
|
125
|
+
export function readEpisodeMarker({ name, folders = [] }) {
|
|
126
|
+
const stem = stripExtension(String(name ?? ""));
|
|
127
|
+
const folderSeason = seasonFromFolders(Array.isArray(folders) ? folders : []);
|
|
128
|
+
|
|
129
|
+
let season = null;
|
|
130
|
+
let episodes = null;
|
|
131
|
+
let before = "";
|
|
132
|
+
let after = "";
|
|
133
|
+
|
|
134
|
+
const seasonEpisode = SEASON_EPISODE.exec(stem);
|
|
135
|
+
const cross = seasonEpisode ? null : CROSS.exec(stem);
|
|
136
|
+
const episodeOnly = seasonEpisode || cross ? null : EPISODE_ONLY.exec(stem);
|
|
137
|
+
const dash = seasonEpisode || cross || episodeOnly ? null : DASH_NUMBER.exec(stem);
|
|
138
|
+
const match = seasonEpisode ?? cross ?? episodeOnly ?? dash;
|
|
139
|
+
if (!match) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
before = stem.slice(0, match.index);
|
|
143
|
+
after = stem.slice(match.index + match[0].length);
|
|
144
|
+
|
|
145
|
+
if (seasonEpisode) {
|
|
146
|
+
season = Number(seasonEpisode[1]);
|
|
147
|
+
episodes = episodesFrom(Number(seasonEpisode[2]), seasonEpisode[3] ?? "");
|
|
148
|
+
} else if (cross) {
|
|
149
|
+
season = Number(cross[1]);
|
|
150
|
+
episodes = [Number(cross[2])];
|
|
151
|
+
} else {
|
|
152
|
+
// `Show S1 - 01` states its season just before the number; a folder is the
|
|
153
|
+
// other place a release puts it.
|
|
154
|
+
const trailingSeason = /(?<![a-z0-9])s(\d{1,3})[\s._-]*$/i.exec(before);
|
|
155
|
+
season = trailingSeason ? Number(trailingSeason[1]) : folderSeason;
|
|
156
|
+
if (trailingSeason) {
|
|
157
|
+
before = before.slice(0, trailingSeason.index);
|
|
158
|
+
}
|
|
159
|
+
episodes = [Number(match[1])];
|
|
160
|
+
}
|
|
161
|
+
// A year where an episode number would be is a year far more often than an
|
|
162
|
+
// episode: `Film - 2024 [1080p]`, and `WALL-E.2008.mkv` reads as `E` then
|
|
163
|
+
// `2008`. Only the markers without a season can be fooled this way.
|
|
164
|
+
if ((dash || episodeOnly) && /^(?:19|20)\d\d$/.test(match[1])) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (!episodes) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const part = PART.exec(after);
|
|
172
|
+
const titleText = part ? `${after.slice(0, part.index)} ${after.slice(part.index + part[0].length)}` : after;
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
season,
|
|
176
|
+
episodes,
|
|
177
|
+
part: part ? Number(part[1]) : null,
|
|
178
|
+
special: season === 0 || SPECIAL.test(stem),
|
|
179
|
+
showHint: hint(before),
|
|
180
|
+
titleHint: hint(titleText)
|
|
181
|
+
};
|
|
182
|
+
}
|