@torrent-tv/proxy 2.95.37 → 2.95.39

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": "2.95.37",
3
+ "version": "2.95.39",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -2,6 +2,8 @@ import { createHash } from "node:crypto";
2
2
  import { matroskaInput, matroskaTrackIdentity } from "./MatroskaInput.js";
3
3
  import { pcmNormalization, normalizePcmSlices } from "./pcm-normalization.js";
4
4
 
5
+ const INPUT_WRITE_BATCH_BYTES = 64 * 1024;
6
+
5
7
  /** Acquire complete source ranges before any process can consume the input. */
6
8
  export async function admitInput({ sources, reserve, readRanges }) {
7
9
  if (!Array.isArray(sources) || sources.length === 0 || typeof reserve !== "function" || typeof readRanges !== "function") {
@@ -106,27 +108,37 @@ export async function writeAdmittedInput(input, stdin, { next = null } = {}) {
106
108
  let current = input, includeHeader = true;
107
109
  try {
108
110
  while (current) {
109
- if (JSON.stringify(current.tracks.map(input => matroskaTrackIdentity(input.track))) !== declarations) {
110
- throw new Error("Consecutive encoder input changed its declared tracks.");
111
- }
112
- for (let index = 0; index < current.tracks.length; index++) {
113
- const end = current.tracks[index].sourceEndSeconds;
114
- if (Number.isFinite(end)) input.tracks[index].sourceEndSeconds = end;
115
- }
116
- for await (const chunk of current.stream({ originSeconds, includeHeader, seen })) {
117
- await new Promise((resolve, reject) => {
118
- const failed = error => { stdin.off("error", failed); reject(error); };
119
- stdin.once("error", failed);
120
- stdin.write(chunk, error => {
121
- stdin.off("error", failed);
122
- if (error) reject(error);
123
- else resolve();
124
- });
125
- });
126
- }
127
- current.release();
128
- current = next ? await next() : null;
129
- includeHeader = false;
111
+ if (JSON.stringify(current.tracks.map(input => matroskaTrackIdentity(input.track))) !== declarations) {
112
+ throw new Error("Consecutive encoder input changed its declared tracks.");
113
+ }
114
+ for (let index = 0; index < current.tracks.length; index++) {
115
+ const end = current.tracks[index].sourceEndSeconds;
116
+ if (Number.isFinite(end)) input.tracks[index].sourceEndSeconds = end;
117
+ }
118
+ let batch = Buffer.allocUnsafe(INPUT_WRITE_BATCH_BYTES);
119
+ let used = 0;
120
+ const flush = async () => {
121
+ if (used === 0) return;
122
+ const chunk = batch.subarray(0, used);
123
+ batch = Buffer.allocUnsafe(INPUT_WRITE_BATCH_BYTES);
124
+ used = 0;
125
+ await writeChunk(stdin, chunk);
126
+ };
127
+ for await (const chunk of current.stream({ originSeconds, includeHeader, seen })) {
128
+ if (!Buffer.isBuffer(chunk)) throw new TypeError("Admitted input stream yielded a non-buffer chunk.");
129
+ let offset = 0;
130
+ while (offset < chunk.length) {
131
+ const copied = Math.min(INPUT_WRITE_BATCH_BYTES - used, chunk.length - offset);
132
+ chunk.copy(batch, used, offset, offset + copied);
133
+ used += copied;
134
+ offset += copied;
135
+ if (used === INPUT_WRITE_BATCH_BYTES) await flush();
136
+ }
137
+ }
138
+ await flush();
139
+ current.release();
140
+ current = next ? await next() : null;
141
+ includeHeader = false;
130
142
  }
131
143
  stdin.end();
132
144
  } finally {
@@ -134,3 +146,15 @@ export async function writeAdmittedInput(input, stdin, { next = null } = {}) {
134
146
  input.release();
135
147
  }
136
148
  }
149
+
150
+ function writeChunk(stdin, chunk) {
151
+ return new Promise((resolve, reject) => {
152
+ const failed = error => { stdin.off("error", failed); reject(error); };
153
+ stdin.once("error", failed);
154
+ stdin.write(chunk, error => {
155
+ stdin.off("error", failed);
156
+ if (error) reject(error);
157
+ else resolve();
158
+ });
159
+ });
160
+ }
@@ -1682,13 +1682,15 @@ export function pickSoftwarePreset(benchmark, pixelsPerSecNeeded, cost = {}, fra
1682
1682
  if (encodePixelsPerSec === null) {
1683
1683
  continue;
1684
1684
  }
1685
- const speed = predictedRealtimeSpeed({
1686
- decodeModel: cost.decodeModel ?? null,
1687
- encodePixelsPerSec,
1688
- outputPixelsPerSec: pixelsPerSecNeeded,
1689
- source: cost.source ?? null,
1690
- observedDecodeCostSec: observed
1691
- });
1685
+ const speed = typeof cost.speedForPreset === "function"
1686
+ ? cost.speedForPreset([entry], frame, pixelsPerSecNeeded)
1687
+ : predictedRealtimeSpeed({
1688
+ decodeModel: cost.decodeModel ?? null,
1689
+ encodePixelsPerSec,
1690
+ outputPixelsPerSec: pixelsPerSecNeeded,
1691
+ source: cost.source ?? null,
1692
+ observedDecodeCostSec: observed
1693
+ });
1692
1694
  if (speed !== null && speed >= bar) {
1693
1695
  return entry.preset;
1694
1696
  }
@@ -1786,12 +1788,15 @@ export function chooseSoftwareEncodeSettings(benchmark, ceiling, outputFps, cost
1786
1788
  if (fastest === null) {
1787
1789
  continue;
1788
1790
  }
1789
- const speed = predictedRealtimeSpeed({
1790
- decodeModel: cost.decodeModel ?? null,
1791
- encodePixelsPerSec: fastest,
1792
- outputPixelsPerSec: ladder[i].width * ladder[i].height * fps,
1793
- source: cost.source ?? null
1794
- });
1791
+ const outputPixelsPerSec = ladder[i].width * ladder[i].height * fps;
1792
+ const speed = typeof cost.speedForPreset === "function"
1793
+ ? cost.speedForPreset([benchmark.at(-1)], ladder[i], outputPixelsPerSec)
1794
+ : predictedRealtimeSpeed({
1795
+ decodeModel: cost.decodeModel ?? null,
1796
+ encodePixelsPerSec: fastest,
1797
+ outputPixelsPerSec,
1798
+ source: cost.source ?? null
1799
+ });
1795
1800
  if (speed !== null && speed >= bar) {
1796
1801
  chosenIndex = i;
1797
1802
  break;
@@ -172,6 +172,21 @@ export class EncodeCost {
172
172
  );
173
173
  }
174
174
 
175
+ #committedMachineCostSec() {
176
+ let cost = 0;
177
+ for (const session of this.#outputs.values()) {
178
+ if (!processCanBeSignalled(this.#stateFor(session))) {
179
+ continue;
180
+ }
181
+ const measured = this.#loadOfSession(session)?.costSec;
182
+ if (!Number.isFinite(measured) || measured <= 0) {
183
+ return null;
184
+ }
185
+ cost += measured;
186
+ }
187
+ return cost;
188
+ }
189
+
175
190
  // The last refusal printed. The offer is recomputed on the path that serves
176
191
  // every playlist, init and segment, and the figures behind it move every few
177
192
  // seconds — so the line is written when the ANSWER changes, not when it is
@@ -1085,11 +1100,26 @@ export class EncodeCost {
1085
1100
  if (!ceiling) {
1086
1101
  return null;
1087
1102
  }
1103
+ const concurrentCostSec = this.#committedMachineCostSec();
1104
+ const speedForPreset = (benchmark, frame, outputPixelsPerSec) => {
1105
+ if (concurrentCostSec === null) {
1106
+ return null;
1107
+ }
1108
+ return this.#speedOnThisMachine({
1109
+ benchmark,
1110
+ source,
1111
+ width: frame.width,
1112
+ height: frame.height,
1113
+ fps: outputPixelsPerSec / (frame.width * frame.height),
1114
+ observedDecodeCostSec: null,
1115
+ concurrentCostSec
1116
+ });
1117
+ };
1088
1118
  return chooseSoftwareEncodeSettings(
1089
1119
  this.#host().benchmark,
1090
1120
  { width: ceiling.w, height: ceiling.h },
1091
1121
  outputFps,
1092
- { decodeModel: this.#host().decodeModel, source, requiredSpeed }
1122
+ { decodeModel: this.#host().decodeModel, source, requiredSpeed, speedForPreset }
1093
1123
  );
1094
1124
  }
1095
1125
  }