@torrent-tv/proxy 1.0.0 → 2.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "1.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -10,8 +10,11 @@ export async function handleApiTranscodeSessionsPost(req, reply, { hlsSessionMan
10
10
  const sourceKey = typeof payload.sourceKey === "string" ? payload.sourceKey.trim() : "";
11
11
  const fileIndex = Number(payload.fileIndex);
12
12
  const transcodeVideo = payload.transcodeVideo === true;
13
+ const transcodeAudio = payload.transcodeAudio === true;
13
14
  const consumerId = typeof payload.consumerId === "string" ? payload.consumerId.trim() : "";
14
15
  const fileName = typeof payload.fileName === "string" ? payload.fileName.trim() : "";
16
+ const targetWidth = Number(payload.targetWidth);
17
+ const targetHeight = Number(payload.targetHeight);
15
18
 
16
19
  if (!sourceKey || !Number.isInteger(fileIndex) || fileIndex < 0) {
17
20
  return reply.code(400).send({ error: "sourceKey and valid fileIndex are required." });
@@ -22,8 +25,11 @@ export async function handleApiTranscodeSessionsPost(req, reply, { hlsSessionMan
22
25
  sourceKey,
23
26
  fileIndex,
24
27
  transcodeVideo,
28
+ transcodeAudio,
25
29
  consumerId,
26
- fileName
30
+ fileName,
31
+ targetWidth: Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : 0,
32
+ targetHeight: Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : 0
27
33
  });
28
34
  return reply.send({
29
35
  sessionId: session.id,
@@ -13,6 +13,9 @@ const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000;
13
13
  const DEFAULT_STARTUP_WAIT_MS = 5_000;
14
14
  const MICROSECONDS_PER_SECOND = 1_000_000;
15
15
  const PROGRESS_LOG_INTERVAL_MS = 5_000;
16
+ const VIDEO_TRANSCODE_PRESET = "superfast";
17
+ const VIDEO_TRANSCODE_CRF = "24";
18
+ const VIDEO_TRANSCODE_FPS = 24;
16
19
 
17
20
  function delay(ms) {
18
21
  return new Promise((resolve) => {
@@ -200,14 +203,32 @@ export class HlsSessionManager {
200
203
  this.cleanupTimer.unref();
201
204
  }
202
205
 
203
- async createOrGetSession({ sourceKey, fileIndex, transcodeVideo = false, consumerId = "", fileName = "" }) {
206
+ async createOrGetSession({
207
+ sourceKey,
208
+ fileIndex,
209
+ transcodeVideo = false,
210
+ transcodeAudio = false,
211
+ consumerId = "",
212
+ fileName = "",
213
+ targetWidth = 0,
214
+ targetHeight = 0
215
+ }) {
204
216
  if (!this.enabled) {
205
217
  const error = new Error("Audio transcoding is disabled on this proxy.");
206
218
  error.code = "TRANSCODE_DISABLED";
207
219
  throw error;
208
220
  }
209
221
 
210
- const sourceMapKey = `${sourceKey}:${fileIndex}:${transcodeVideo ? "video" : "audio"}`;
222
+ const normalizedTargetWidth = Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : 0;
223
+ const normalizedTargetHeight = Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : 0;
224
+ const sourceMapKey = [
225
+ sourceKey,
226
+ String(fileIndex),
227
+ transcodeVideo ? "video" : "audio",
228
+ transcodeAudio ? "a1" : "a0",
229
+ String(normalizedTargetWidth),
230
+ String(normalizedTargetHeight)
231
+ ].join(":");
211
232
  const existingId = this.sessionIdBySource.get(sourceMapKey);
212
233
  if (existingId) {
213
234
  const existing = this.sessionsById.get(existingId);
@@ -238,8 +259,22 @@ export class HlsSessionManager {
238
259
  const durationSeconds = await probeInputDurationSeconds(this.ffmpegBin, inputUrl.toString());
239
260
 
240
261
  const videoCodecArgs = transcodeVideo
241
- ? ["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"]
262
+ ? [
263
+ "-vf",
264
+ this.#buildVideoFilter(normalizedTargetWidth, normalizedTargetHeight),
265
+ "-c:v",
266
+ "libx264",
267
+ "-preset",
268
+ VIDEO_TRANSCODE_PRESET,
269
+ "-crf",
270
+ VIDEO_TRANSCODE_CRF,
271
+ "-pix_fmt",
272
+ "yuv420p"
273
+ ]
242
274
  : ["-c:v", "copy"];
275
+ const audioCodecArgs = transcodeAudio
276
+ ? ["-c:a", "aac", "-ac", "2", "-b:a", "128k"]
277
+ : ["-c:a", "copy"];
243
278
 
244
279
  const args = [
245
280
  "-hide_banner",
@@ -255,12 +290,7 @@ export class HlsSessionManager {
255
290
  "-map",
256
291
  "0:a:0?",
257
292
  ...videoCodecArgs,
258
- "-c:a",
259
- "aac",
260
- "-ac",
261
- "2",
262
- "-b:a",
263
- "160k",
293
+ ...audioCodecArgs,
264
294
  "-f",
265
295
  "hls",
266
296
  "-hls_time",
@@ -403,6 +433,12 @@ export class HlsSessionManager {
403
433
  }
404
434
  }
405
435
 
436
+ #buildVideoFilter(targetWidth, targetHeight) {
437
+ const safeWidth = Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : 1280;
438
+ const safeHeight = Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : 720;
439
+ return `scale=${safeWidth}:${safeHeight}:force_original_aspect_ratio=decrease:force_divisible_by=2,fps=${VIDEO_TRANSCODE_FPS}`;
440
+ }
441
+
406
442
  async waitUntilReady(session) {
407
443
  const playlistPath = path.join(session.dirPath, PLAYLIST_FILE_NAME);
408
444
  const deadline = Date.now() + this.startupWaitMs;