@torrent-tv/proxy 2.97.31 → 2.98.0
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/bin/cli.js +2 -0
- package/package.json +1 -1
- package/server.js +4 -0
- package/services/encode/Renditions.js +17 -2
- package/services/encode/hwaccel.js +3 -2
- package/services/encode/quality/EncodeCost.js +27 -8
- package/services/encode/quality/output-format.js +2 -1
- package/services/server/wire-outputs.js +22 -1
package/bin/cli.js
CHANGED
|
@@ -88,6 +88,7 @@ program
|
|
|
88
88
|
.option("--no-transcode-audio", "Disable optional HLS AAC audio transcoding")
|
|
89
89
|
.option("--no-port-mapping", "Disable automatic UPnP/NAT-PMP port mapping")
|
|
90
90
|
.option("--delivery-sink", "Serve /api/delivery-sink, a torrent-free byte stream for delivery testing")
|
|
91
|
+
.option("--pin-preset <name>", "Encode every software picture at this libx264 preset only, for runs that must be compared at one quality (off by default)")
|
|
91
92
|
.option("--profile-start <seconds>", "Profile the main thread's CPU for this many seconds from the first source opened, written beside the state (off by default)")
|
|
92
93
|
.option(
|
|
93
94
|
"--send-chunk-bytes <bytes>",
|
|
@@ -394,6 +395,7 @@ try {
|
|
|
394
395
|
logFile: options.logFile ?? "",
|
|
395
396
|
deliverySink: options.deliverySink === true,
|
|
396
397
|
profileStartSeconds: Number(options.profileStart) || 0,
|
|
398
|
+
pinnedPreset: typeof options.pinPreset === "string" && options.pinPreset.trim() ? options.pinPreset.trim() : null,
|
|
397
399
|
// Late-bound the same way `webRtcManager` is below: the torrent pool is
|
|
398
400
|
// built inside `startProxyServer`, before `dataChannelHandler` — the
|
|
399
401
|
// thing that actually owns a channel to push down — exists.
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -127,6 +127,9 @@ export async function startProxyServer({
|
|
|
127
127
|
deliverySink = false,
|
|
128
128
|
// Seconds of main-thread CPU profile from the first source opened; 0 is off.
|
|
129
129
|
profileStartSeconds = 0,
|
|
130
|
+
// The one software preset every picture is encoded at, for runs compared at
|
|
131
|
+
// one quality; null leaves the choice to the machine.
|
|
132
|
+
pinnedPreset = null,
|
|
130
133
|
// What viewers' connections carry beyond the film, per byte of film; the
|
|
131
134
|
// transport counts it and is built after this server, so it is late-bound.
|
|
132
135
|
serviceShare = () => null,
|
|
@@ -798,6 +801,7 @@ export async function startProxyServer({
|
|
|
798
801
|
});
|
|
799
802
|
let sourcePreparation;
|
|
800
803
|
const outputParts = wireOutputs({
|
|
804
|
+
pinnedPreset,
|
|
801
805
|
indexMemory,
|
|
802
806
|
serviceShare,
|
|
803
807
|
readMetadataActivity: () => containerOrchestrator.activity(),
|
|
@@ -1358,8 +1358,23 @@ export class Renditions {
|
|
|
1358
1358
|
if (!base) {
|
|
1359
1359
|
return null;
|
|
1360
1360
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1361
|
+
const offered = this.#host.qualityOffer.offeredHeightsFor(base);
|
|
1362
|
+
if (!offered.includes(height)) {
|
|
1363
|
+
if (!this.#host.outputs.splicableHeights(base).includes(height)) {
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
// A STEP THE MASTER LISTS AND THE OFFER NO LONGER HOLDS. The offer moves
|
|
1367
|
+
// with what the machine measures, and a page preparing a step it was
|
|
1368
|
+
// asked for can arrive after the ask was withdrawn: field r28, 360p
|
|
1369
|
+
// asked at 21:40:44.308, the offer down to 400p at 21:40:46.143, the
|
|
1370
|
+
// warm-up at 46.170 answered "no such variant" (torrent-tv/meta#178).
|
|
1371
|
+
// The step exists; it is not available now, and the page keeps what it
|
|
1372
|
+
// plays and follows the next ask.
|
|
1373
|
+
this.#host.logger.info(`transcode ${base.id} warming ${height}p for ${consumerId}: not offered now (offering ${offered.map(one => `${one}p`).join(" ") || "nothing"})`);
|
|
1374
|
+
const error = new Error(`${height}p is not offered now.`);
|
|
1375
|
+
error.code = OUTPUT_UNAVAILABLE;
|
|
1376
|
+
error.details = { reason: `${height}p is not offered on this machine now`, figures: { height, offeredHeights: offered } };
|
|
1377
|
+
throw error;
|
|
1363
1378
|
}
|
|
1364
1379
|
const index = this.#host.outputTimes.segmentIndexForTime(base, positionSeconds);
|
|
1365
1380
|
const variant = await this.resolveVariantSession(baseSessionId, height, index, consumerId);
|
|
@@ -1715,7 +1715,8 @@ export function buildResolutionLadder(ceilingWidth, ceilingHeight) {
|
|
|
1715
1715
|
* @param {{ width: number, height: number }} ceiling
|
|
1716
1716
|
* @param {number} outputFps
|
|
1717
1717
|
* @param {{ decodeModel?: object | null, source?: { megapixelsPerSecond: number, megabitsPerSecond: number } | null, requiredSpeed?: number | null }} [cost]
|
|
1718
|
-
* @returns {{ width: number, height: number, preset: string, ladder: Array<{ width: number, height: number }>, rungIndex: number } | null}
|
|
1718
|
+
* @returns {{ width: number, height: number, preset: string, ladder: Array<{ width: number, height: number }>, rungIndex: number, bar: number } | null}
|
|
1719
|
+
* `bar` is the speed the choice had to clear.
|
|
1719
1720
|
*/
|
|
1720
1721
|
export function chooseSoftwareEncodeSettings(benchmark, ceiling, outputFps, cost = {}) {
|
|
1721
1722
|
if (!Array.isArray(benchmark) || benchmark.length === 0) {
|
|
@@ -1750,7 +1751,7 @@ export function chooseSoftwareEncodeSettings(benchmark, ceiling, outputFps, cost
|
|
|
1750
1751
|
}
|
|
1751
1752
|
const chosen = ladder[chosenIndex];
|
|
1752
1753
|
const preset = pickSoftwarePreset(benchmark, chosen.width * chosen.height * fps, cost, chosen);
|
|
1753
|
-
return { width: chosen.width, height: chosen.height, preset, ladder, rungIndex: chosenIndex };
|
|
1754
|
+
return { width: chosen.width, height: chosen.height, preset, ladder, rungIndex: chosenIndex, bar };
|
|
1754
1755
|
}
|
|
1755
1756
|
|
|
1756
1757
|
/**
|
|
@@ -1103,7 +1103,8 @@ export class EncodeCost {
|
|
|
1103
1103
|
sourceHeight,
|
|
1104
1104
|
outputFps,
|
|
1105
1105
|
source = null,
|
|
1106
|
-
requiredSpeed = null
|
|
1106
|
+
requiredSpeed = null,
|
|
1107
|
+
observedDecodeCostSec = null
|
|
1107
1108
|
}) {
|
|
1108
1109
|
if (!transcodeVideo || this.#host().encoderKind !== "software" || !this.#host().benchmark) {
|
|
1109
1110
|
return null;
|
|
@@ -1113,26 +1114,44 @@ export class EncodeCost {
|
|
|
1113
1114
|
return null;
|
|
1114
1115
|
}
|
|
1115
1116
|
const concurrentCostSec = this.#committedMachineCostSec();
|
|
1117
|
+
// THE FILE'S OWN DECODE COST, where its encoder has already been read: the
|
|
1118
|
+
// opening had it and the choice priced decoding from the startup model
|
|
1119
|
+
// instead (review of r28, torrent-tv/meta#178).
|
|
1120
|
+
const decodeCostSec = Number.isFinite(observedDecodeCostSec) && observedDecodeCostSec > 0 ? observedDecodeCostSec : null;
|
|
1121
|
+
/** Every size and preset the choice priced, with the speed it was priced at. */
|
|
1122
|
+
const considered = [];
|
|
1116
1123
|
const speedForPreset = (benchmark, frame, outputPixelsPerSec) => {
|
|
1117
|
-
|
|
1118
|
-
return null;
|
|
1119
|
-
}
|
|
1120
|
-
return this.#speedOnThisMachine({
|
|
1124
|
+
const speed = concurrentCostSec === null ? null : this.#speedOnThisMachine({
|
|
1121
1125
|
benchmark,
|
|
1122
1126
|
source,
|
|
1123
1127
|
width: frame.width,
|
|
1124
1128
|
height: frame.height,
|
|
1125
1129
|
fps: outputPixelsPerSec / (frame.width * frame.height),
|
|
1126
|
-
observedDecodeCostSec:
|
|
1130
|
+
observedDecodeCostSec: decodeCostSec,
|
|
1127
1131
|
concurrentCostSec
|
|
1128
1132
|
});
|
|
1133
|
+
considered.push({ preset: benchmark[0]?.preset, width: frame.width, height: frame.height, speed });
|
|
1134
|
+
return speed;
|
|
1129
1135
|
};
|
|
1130
|
-
|
|
1136
|
+
const choice = chooseSoftwareEncodeSettings(
|
|
1131
1137
|
this.#host().benchmark,
|
|
1132
1138
|
{ width: ceiling.w, height: ceiling.h },
|
|
1133
1139
|
outputFps,
|
|
1134
|
-
{ decodeModel: this.#host().decodeModel, source, requiredSpeed, speedForPreset }
|
|
1140
|
+
{ decodeModel: this.#host().decodeModel, source, requiredSpeed, speedForPreset, observedDecodeCostSec: decodeCostSec }
|
|
1135
1141
|
);
|
|
1142
|
+
// WHAT THE CHOICE WAS MADE FROM, said once per choice: three runs of one
|
|
1143
|
+
// file on one host opened at three presets and nothing said why (r25, r26,
|
|
1144
|
+
// r28, torrent-tv/meta#178).
|
|
1145
|
+
if (choice) {
|
|
1146
|
+
const availability = this.#host().availability;
|
|
1147
|
+
const priced = considered.map(({ preset, width, height, speed }) =>
|
|
1148
|
+
`${width}x${height} ${preset}=${Number.isFinite(speed) ? `${speed.toFixed(2)}x` : "unpriced"}`).join(", ");
|
|
1149
|
+
logger.info(`encode choice: ${choice.width}x${choice.height}@${Number(outputFps).toFixed(3)} ${choice.preset} against ${choice.bar.toFixed(2)}x; ` +
|
|
1150
|
+
`priced ${priced}; decoding ${decodeCostSec === null ? "from the startup model" : `${decodeCostSec.toFixed(4)} s per second, measured on this file`}; ` +
|
|
1151
|
+
`machine ${availability?.known ? `${Math.round(availability.share * 100)}% to spare` : "share unknown"}; ` +
|
|
1152
|
+
`committed work ${concurrentCostSec === null ? "unmeasured" : `${concurrentCostSec.toFixed(3)} s per second`}`);
|
|
1153
|
+
}
|
|
1154
|
+
return choice;
|
|
1136
1155
|
}
|
|
1137
1156
|
}
|
|
1138
1157
|
|
|
@@ -144,7 +144,8 @@ export function decideOutputFormat({
|
|
|
144
144
|
sourceHeight: source.height,
|
|
145
145
|
outputFps: fps,
|
|
146
146
|
source: source.decode,
|
|
147
|
-
requiredSpeed: cost.requiredSpeed
|
|
147
|
+
requiredSpeed: cost.requiredSpeed,
|
|
148
|
+
observedDecodeCostSec: cost.observedDecodeCostSec ?? null
|
|
148
149
|
});
|
|
149
150
|
const width = budget?.width ?? ceiling?.w ?? 0;
|
|
150
151
|
const height = budget?.height ?? ceiling?.h ?? 0;
|
|
@@ -189,6 +189,9 @@ export function wireOutputs({
|
|
|
189
189
|
keyframeTables = new KeyframeTables(),
|
|
190
190
|
videoEncoder = null,
|
|
191
191
|
softwarePresetBenchmark = null,
|
|
192
|
+
// A software preset every picture is encoded at, for runs compared at one
|
|
193
|
+
// quality (`--pin-preset`); null leaves the choice to the machine.
|
|
194
|
+
pinnedPreset = null,
|
|
192
195
|
// What every encoder this host may use was qualified and measured to do at
|
|
193
196
|
// startup, by kind (`encode/calibration.js`). Takes the place of the one
|
|
194
197
|
// array above, which a test may still hand in on its own.
|
|
@@ -771,8 +774,26 @@ export function wireOutputs({
|
|
|
771
774
|
// Null where nothing was measured, and then nothing is re-encoded here.
|
|
772
775
|
parts.calibration = calibration ?? null;
|
|
773
776
|
const givenModes = Array.isArray(softwarePresetBenchmark) ? softwarePresetBenchmark : null;
|
|
777
|
+
// ONE PRESET FOR RUNS COMPARED AT ONE QUALITY. Three runs of one film on one
|
|
778
|
+
// host opened at `faster`, `veryfast` and `fast`, so no change could be
|
|
779
|
+
// compared at the same picture (r25, r26, r28, torrent-tv/meta#178). Pinned
|
|
780
|
+
// here, every decision that prices or chooses a software mode — the choice,
|
|
781
|
+
// the offer, the machine's admission and the command — sees the one mode,
|
|
782
|
+
// measured as calibrated. A preset calibration did not qualify is not
|
|
783
|
+
// pinned: the machine's own list stands, and the log says so.
|
|
784
|
+
let pinWarned = false;
|
|
785
|
+
const pinned = (modes) => {
|
|
786
|
+
if (!pinnedPreset || !Array.isArray(modes) || parts.encoders.current?.kind !== "software") return modes;
|
|
787
|
+
const kept = modes.filter(mode => mode.preset === pinnedPreset);
|
|
788
|
+
if (kept.length > 0) return kept;
|
|
789
|
+
if (!pinWarned) {
|
|
790
|
+
pinWarned = true;
|
|
791
|
+
logger.warn(`encode: --pin-preset ${pinnedPreset} names no calibrated mode (${modes.map(mode => mode.preset).join(", ")}); not pinned`);
|
|
792
|
+
}
|
|
793
|
+
return modes;
|
|
794
|
+
};
|
|
774
795
|
Object.defineProperty(parts, "softwarePresetBenchmark", {
|
|
775
|
-
get: () => (parts.calibration
|
|
796
|
+
get: () => pinned(parts.calibration
|
|
776
797
|
? parts.calibration.modesFor(parts.encoders.current?.kind)
|
|
777
798
|
: givenModes),
|
|
778
799
|
enumerable: true
|