@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/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
  })