@torrent-tv/proxy 2.9.27 → 2.9.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/bin/cli.js +9 -1
- package/openspec/changes/disk-cap/.openspec.yaml +2 -0
- package/openspec/changes/disk-cap/proposal.md +37 -0
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -0
- package/openspec/changes/disk-cap/tasks.md +19 -0
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -0
- package/openspec/changes/subtitle-language/proposal.md +50 -0
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -0
- package/openspec/changes/subtitle-language/tasks.md +26 -0
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -0
- package/openspec/changes/transcode-quality/proposal.md +51 -0
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +37 -0
- package/openspec/changes/transcode-quality/tasks.md +34 -0
- package/package.json +2 -1
- package/routes/api/sources/files/get.js +47 -4
- package/routes/api/subtitles/get.js +92 -30
- package/server.js +3 -2
- package/services/hls-session-manager.js +33 -1
- package/services/hwaccel.js +560 -514
- package/services/language-detect.js +73 -0
- package/services/subtitle-convert.js +150 -0
- package/services/torrent-pool.js +131 -3
package/server.js
CHANGED
|
@@ -60,6 +60,7 @@ function buildPortCandidates(startPort, maxAttempts = 51) {
|
|
|
60
60
|
* @property {number} port - Preferred listen port.
|
|
61
61
|
* @property {boolean} transcodeAudio - Whether HLS audio transcoding is enabled.
|
|
62
62
|
* @property {string} ffmpegBin - Path to the ffmpeg executable.
|
|
63
|
+
* @property {number} [maxDiskBytes] - Global disk cap for torrent data (undefined = pool default).
|
|
63
64
|
*/
|
|
64
65
|
|
|
65
66
|
/**
|
|
@@ -68,7 +69,7 @@ function buildPortCandidates(startPort, maxAttempts = 51) {
|
|
|
68
69
|
* @param {ProxyServerOptions} options
|
|
69
70
|
* @returns {Promise<{ app: import("fastify").FastifyInstance, port: number }>}
|
|
70
71
|
*/
|
|
71
|
-
export async function startProxyServer({ host, port, transcodeAudio, ffmpegBin }) {
|
|
72
|
+
export async function startProxyServer({ host, port, transcodeAudio, ffmpegBin, maxDiskBytes }) {
|
|
72
73
|
const app = Fastify({
|
|
73
74
|
// No practical body-size limit — the proxy server is localhost-only and
|
|
74
75
|
// receives torrent source payloads that may be arbitrarily large.
|
|
@@ -94,7 +95,7 @@ export async function startProxyServer({ host, port, transcodeAudio, ffmpegBin }
|
|
|
94
95
|
});
|
|
95
96
|
|
|
96
97
|
const sourceRegistry = createSourceRegistry(200);
|
|
97
|
-
const torrentPool = new TorrentPool();
|
|
98
|
+
const torrentPool = new TorrentPool({ maxDiskBytes });
|
|
98
99
|
const selectedPort = await getPort({
|
|
99
100
|
port: buildPortCandidates(port)
|
|
100
101
|
});
|
|
@@ -15,7 +15,7 @@ import path from "node:path";
|
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
16
|
import { spawn } from "node:child_process";
|
|
17
17
|
import { logger } from "../utils/logger.js";
|
|
18
|
-
import { softwareDescriptor, pickSoftwarePreset, TRANSCODE_FPS } from "./hwaccel.js";
|
|
18
|
+
import { softwareDescriptor, pickSoftwarePreset, TRANSCODE_FPS, chooseOutputFps } from "./hwaccel.js";
|
|
19
19
|
|
|
20
20
|
const PLAYLIST_FILE_NAME = "index.m3u8";
|
|
21
21
|
const SEGMENT_FILE_NAME_PATTERN = /^segment-\d{5}\.ts$/;
|
|
@@ -295,6 +295,29 @@ function parseFfmpegVideoDimensions(stderrText) {
|
|
|
295
295
|
};
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Parse the source frame rate from the ffmpeg "Video:" line
|
|
300
|
+
* (e.g. "… 23.98 fps," / "… 25 fps,"). Returns null when absent.
|
|
301
|
+
*
|
|
302
|
+
* @param {string} stderrText
|
|
303
|
+
* @returns {number | null}
|
|
304
|
+
*/
|
|
305
|
+
function parseFfmpegVideoFps(stderrText) {
|
|
306
|
+
if (typeof stderrText !== "string" || stderrText.length === 0) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const videoLine = stderrText.match(/Video:[^\n]*/i);
|
|
310
|
+
if (!videoLine) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const match = videoLine[0].match(/([\d.]+)\s*fps/i);
|
|
314
|
+
if (!match) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const value = Number(match[1]);
|
|
318
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
319
|
+
}
|
|
320
|
+
|
|
298
321
|
/**
|
|
299
322
|
* Run a short ffmpeg probe to extract the total duration AND video resolution
|
|
300
323
|
* of a stream from the container header. Both are printed almost immediately
|
|
@@ -323,6 +346,7 @@ async function probeInputMediaInfo(ffmpegBin, inputUrl) {
|
|
|
323
346
|
durationSeconds: parseFfmpegDurationSeconds(stderr),
|
|
324
347
|
width: dims.width,
|
|
325
348
|
height: dims.height,
|
|
349
|
+
fps: parseFfmpegVideoFps(stderr),
|
|
326
350
|
startTime: parseFfmpegStartTimeSeconds(stderr)
|
|
327
351
|
});
|
|
328
352
|
};
|
|
@@ -720,6 +744,10 @@ export class HlsSessionManager {
|
|
|
720
744
|
const sourceWidth = mediaInfo.width;
|
|
721
745
|
const sourceHeight = mediaInfo.height;
|
|
722
746
|
const sourceStartTime = Number.isFinite(mediaInfo.startTime) ? mediaInfo.startTime : 0;
|
|
747
|
+
// Output frame rate inherited from the source (integer, capped) so 25/30
|
|
748
|
+
// fps content is not resampled to 24. Fixed-GOP encoders keep the fps↔GOP
|
|
749
|
+
// relationship exact; time-based-keyframe encoders just use it as the rate.
|
|
750
|
+
const outputFps = chooseOutputFps(mediaInfo.fps);
|
|
723
751
|
const hasDuration = Number.isFinite(durationSeconds) && durationSeconds > 0;
|
|
724
752
|
const logName = normalizeLogFileName(fileName, fileIndex);
|
|
725
753
|
if (!hasDuration) {
|
|
@@ -789,6 +817,7 @@ export class HlsSessionManager {
|
|
|
789
817
|
transcodeVideo,
|
|
790
818
|
transcodeAudio,
|
|
791
819
|
audioTrackIndex: normalizedAudioTrack,
|
|
820
|
+
outputFps,
|
|
792
821
|
targetWidth: normalizedTargetWidth,
|
|
793
822
|
targetHeight: normalizedTargetHeight,
|
|
794
823
|
sourceWidth,
|
|
@@ -1001,6 +1030,9 @@ export class HlsSessionManager {
|
|
|
1001
1030
|
targetWidth: session.targetWidth,
|
|
1002
1031
|
targetHeight: session.targetHeight,
|
|
1003
1032
|
segmentDurationSec: this.segmentDurationSec,
|
|
1033
|
+
// Source-inherited output rate (integer, capped); descriptors that
|
|
1034
|
+
// use time-based keyframes just apply it as the frame rate.
|
|
1035
|
+
fps: session.outputFps,
|
|
1004
1036
|
// Software-only; hardware descriptors ignore it.
|
|
1005
1037
|
preset: session.softwarePreset ?? undefined
|
|
1006
1038
|
})
|