@torrent-tv/proxy 2.80.17 → 2.80.19

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.
@@ -1,135 +0,0 @@
1
- /**
2
- * @file The piece a run had open when it ended.
3
- *
4
- * A fact of a run's output directory, and therefore of the encoding layer. It
5
- * lived in the eleven-thousand-line file that is being taken apart, where it was
6
- * called by the one place that killed a run; stopping is decided in one place
7
- * now and carried out in another, so the cleanup belongs to the layer that owns
8
- * the directories rather than to whoever happened to do the killing.
9
- */
10
-
11
- import { readdir, readFile, stat, unlink } from "node:fs/promises";
12
- import path from "node:path";
13
-
14
- /**
15
- * Remove the piece a run had open when it ended, if that piece is unusable.
16
- *
17
- * The `segment` muxer creates its output file when it OPENS it and writes into
18
- * it until the next cut, so at any instant exactly one file in a run's
19
- * directory is unfinished: the highest-numbered one. A run that reaches the end
20
- * of its work closes that file and it is a good piece; a run killed for a seek
21
- * does not — measured 2026-09-03, ffmpeg exited 19 ms after SIGTERM and left
22
- * `segment-00025.mp4` at zero bytes, which then closed the only hole in the
23
- * numbering and convinced the look-ahead to keep the encoder stopped for having
24
- * "produced" it.
25
- *
26
- * **A piece is whole only if the run PROVED it, and reading it proves nothing.**
27
- *
28
- * The highest-numbered file in a run's stretch is the one it had open. How a
29
- * run ends decides what became of that file, and all three outcomes leave it
30
- * readable-looking: stopped with SIGTERM, ffmpeg writes it out and names it on
31
- * the ready channel exactly as it names a finished one; killed harder, or dying
32
- * on its own, it leaves the bytes it had written with no name at all. In every
33
- * case the file decodes and holds film only up to the instant the run ended,
34
- * under a number whose playlist entry promises a whole span. Field 2026-09-06:
35
- * `segment-00010.mp4` held 3.92 s of its declared 5.589 s — 96 frames — and the
36
- * picture jumped 1.5 s at 1:02; the soundtrack did the same at 17.5 s, 2.8 s
37
- * wide, in the same session. Both decoded, which is how both reached the viewer.
38
- *
39
- * So the question asked here is not what the file contains but whether the run
40
- * named it while it was still running normally. That is a fact the run holds,
41
- * so there is no span to measure and no tolerance to choose.
42
- *
43
- * A piece finished in the moment between the last such name and the end is
44
- * then made a second time. That is the cheaper error: the other is a hole the
45
- * viewer sees.
46
- *
47
- * @param {string | null | undefined} runDirPath
48
- * @param {{ isSegmentFileName: (name: string) => boolean, segmentIndexFromName: (name: string) => number }} segmentFormat
49
- * @param {((raw: Buffer) => boolean) | null} judgeUsable - Whether a non-empty
50
- * piece carries what it should. Null where nothing can say, and then only an
51
- * empty file is removed.
52
- * @param {string | null} [provenName] - The last piece the run named while it
53
- * was running normally. A file beyond it was open when the run ended and goes
54
- * whether or not it reads. Null where the run proved nothing, and then every
55
- * piece it left is unproven.
56
- * @returns {Promise<number | null>} The segment number removed, or null.
57
- */
58
- export async function discardOpenPiece(runDirPath, segmentFormat, within, judgeUsable, provenName = null) {
59
- if (!runDirPath || typeof segmentFormat?.isSegmentFileName !== "function") {
60
- return null;
61
- }
62
- // Only inside the stretch the ended run was given. Every run of an output
63
- // writes into one directory now — they are kept apart by their intervals
64
- // rather than by a directory each — so the highest-numbered file in there may
65
- // belong to a run that is still going, and removing it would take away a
66
- // piece somebody is producing.
67
- const from = Number.isInteger(within?.from) ? within.from : 0;
68
- // THE RUN'S OWN REACH, which is a fact it holds and needs nothing measured.
69
- //
70
- // A run cannot have opened a file above the one just past the last it named:
71
- // ffmpeg names a piece when it closes it and opens the next, so the open piece
72
- // is at most `proven + 1`, and where it named nothing at all the open one is
73
- // the first it was given.
74
- //
75
- // Used only where no end was declared — `to` below `from`, which is how "to
76
- // the end of the track" is written everywhere here. Such a run had no bound at
77
- // all: the search covered the whole directory and took the highest-numbered
78
- // file in it. Every run of an output writes into that one
79
- // directory, so what it took was a piece a LIVE run had just finished. Field
80
- // 2026-09-06: the piece holding 2:47-2:57 went that way, its number is spent
81
- // for good because names only grow, and the picture stood still for 647 s.
82
- const provenIndex = typeof provenName === "string" && segmentFormat.isSegmentFileName(provenName)
83
- ? segmentFormat.segmentIndexFromName(provenName)
84
- : null;
85
- const reach = Number.isInteger(provenIndex) && provenIndex >= from ? provenIndex + 1 : from;
86
- // The declared end is the bound wherever there is one. The run's own reach is
87
- // the bound of LAST RESORT, for a run given none: before it, such a run had no
88
- // bound at all and the search covered the whole directory.
89
- const to = Number.isInteger(within?.to) && within.to >= from ? within.to : reach;
90
- let highest = null;
91
- try {
92
- for (const name of await readdir(runDirPath)) {
93
- if (!segmentFormat.isSegmentFileName(name)) {
94
- continue;
95
- }
96
- const index = segmentFormat.segmentIndexFromName(name);
97
- if (index < from || index > to) {
98
- continue;
99
- }
100
- if (index >= 0 && (highest === null || index > highest.index)) {
101
- highest = { index, name };
102
- }
103
- }
104
- } catch {
105
- return null; // The run wrote nothing, or its directory is already gone.
106
- }
107
- if (highest === null) {
108
- return null;
109
- }
110
- const filePath = path.join(runDirPath, highest.name);
111
- // Proven finished only if the run said so while it was running. Anything
112
- // beyond that name was open when the run ended, and its contents cannot say
113
- // so — it decodes.
114
- const proven = typeof provenName === "string" && provenName === highest.name;
115
- let unusable = !proven;
116
- try {
117
- const info = await stat(filePath);
118
- if (info.size === 0) {
119
- unusable = true;
120
- } else if (!unusable && typeof judgeUsable === "function") {
121
- unusable = !judgeUsable(await readFile(filePath));
122
- }
123
- } catch {
124
- return null; // Gone between the listing and the question.
125
- }
126
- if (!unusable) {
127
- return null;
128
- }
129
- try {
130
- await unlink(filePath);
131
- return highest.index;
132
- } catch {
133
- return null; // Already removed.
134
- }
135
- }
@@ -1,152 +0,0 @@
1
- /**
2
- * @file The piece a run had open when it ended, and what proves one whole.
3
- *
4
- * `-segment_list pipe:3` was taken as proof: ffmpeg names a file when it closes
5
- * it, so a named file is closed. True of the FILE and false of the SPAN. The
6
- * highest-numbered file in a run's stretch is the one it had open, and how the
7
- * run ended decides what became of it — stopped with SIGTERM, ffmpeg writes it
8
- * out and names it exactly as it names a finished one; killed harder, or dying
9
- * on its own, it leaves the bytes it had written. All three outcomes decode,
10
- * and all three hold film only up to the instant the run ended.
11
- *
12
- * Field 2026-09-06, one session, both tracks: `segment-00010.mp4` held 3.92 s
13
- * of its declared 5.589 s (96 frames), and the picture jumped 1.5 s at 1:02;
14
- * the soundtrack's own stopped run left the same shape at 17.5 s, 2.8 s wide.
15
- * Judging such a file by whether it decodes answers yes, which is how both
16
- * reached the viewer.
17
- *
18
- * So what is kept is what the run PROVED it finished: the last piece it named
19
- * while it was still running normally.
20
- */
21
-
22
- import test from "node:test";
23
- import assert from "node:assert/strict";
24
- import { existsSync } from "node:fs";
25
- import { mkdtemp, rm, writeFile, readdir } from "node:fs/promises";
26
- import os from "node:os";
27
- import path from "node:path";
28
- import { discardOpenPiece } from "../services/encode/open-piece.js";
29
-
30
- const format = {
31
- isSegmentFileName: (name) => /^segment-\d{5}\.mp4$/.test(name),
32
- segmentIndexFromName: (name) => Number(name.slice(8, 13))
33
- };
34
-
35
- /** A run directory holding pieces 0..last, every one of them readable. */
36
- async function runDirectory(last) {
37
- const dir = await mkdtemp(path.join(os.tmpdir(), "open-piece-"));
38
- for (let index = 0; index <= last; index += 1) {
39
- await writeFile(path.join(dir, `segment-${String(index).padStart(5, "0")}.mp4`), Buffer.alloc(64, 7));
40
- }
41
- return dir;
42
- }
43
-
44
- test("the piece written out on the way to being stopped goes, though it reads", async () => {
45
- // The field case: ffmpeg named #10 while shutting down, so #9 is the last it
46
- // proved. Everything decodes, and nothing about #10's contents betrays it.
47
- const dir = await runDirectory(10);
48
- try {
49
- const removed = await discardOpenPiece(
50
- dir, format, { from: 0, to: 535 }, () => true, "segment-00009.mp4"
51
- );
52
- assert.equal(removed, 10, "the open piece was kept because it decodes");
53
- const left = await readdir(dir);
54
- assert.ok(!left.includes("segment-00010.mp4"));
55
- assert.ok(left.includes("segment-00009.mp4"), "a piece the run proved was taken too");
56
- } finally {
57
- await rm(dir, { recursive: true, force: true });
58
- }
59
- });
60
-
61
- test("a piece the run never named goes too, however it was killed", async () => {
62
- // Killed harder than SIGTERM, or dead on its own: the open piece is left
63
- // half-written and unnamed. It still decodes, and it is still short.
64
- const dir = await runDirectory(10);
65
- try {
66
- const removed = await discardOpenPiece(
67
- dir, format, { from: 0, to: 535 }, () => true, "segment-00009.mp4"
68
- );
69
- assert.equal(removed, 10);
70
- } finally {
71
- await rm(dir, { recursive: true, force: true });
72
- }
73
- });
74
-
75
- test("the piece the run proved finished stays", async () => {
76
- // A run stopped in the moment after closing #10 and before opening #11
77
- // proved #10. Removing it would mean encoding it a second time for nothing.
78
- const dir = await runDirectory(10);
79
- try {
80
- const removed = await discardOpenPiece(
81
- dir, format, { from: 0, to: 535 }, () => true, "segment-00010.mp4"
82
- );
83
- assert.equal(removed, null);
84
- assert.ok((await readdir(dir)).includes("segment-00010.mp4"));
85
- } finally {
86
- await rm(dir, { recursive: true, force: true });
87
- }
88
- });
89
-
90
- test("a run that proved nothing keeps nothing", async () => {
91
- const dir = await runDirectory(3);
92
- try {
93
- const removed = await discardOpenPiece(dir, format, { from: 0, to: 535 }, () => true, null);
94
- assert.equal(removed, 3, "a run that named nothing had its last piece believed");
95
- } finally {
96
- await rm(dir, { recursive: true, force: true });
97
- }
98
- });
99
-
100
- test("only inside the stretch the ended run was given", async () => {
101
- // Several runs write into one directory, kept apart by their intervals. The
102
- // highest file overall may belong to a run that is still going.
103
- const dir = await runDirectory(20);
104
- try {
105
- const removed = await discardOpenPiece(
106
- dir, format, { from: 0, to: 10 }, () => true, "segment-00009.mp4"
107
- );
108
- assert.equal(removed, 10);
109
- assert.ok((await readdir(dir)).includes("segment-00020.mp4"), "another run's piece was taken");
110
- } finally {
111
- await rm(dir, { recursive: true, force: true });
112
- }
113
- });
114
-
115
- test("a run given no end never reaches past what it made, so a live run's piece is safe", async () => {
116
- // Field 2026-09-06. Three encoders wrote into one directory; the first was
117
- // stopped, its stretch was `#0..#-1` — "to the end of the track" — and the
118
- // cleanup after it took the highest-numbered file anywhere in that directory,
119
- // which a LIVE encoder had just finished. The number is spent for good,
120
- // because names only grow, and the picture stood still for 647 seconds.
121
- const dir = await mkdtemp(path.join(os.tmpdir(), "open-piece-"));
122
- // What the stopped run made: #0 and #1, with #2 left open.
123
- await writeFile(path.join(dir, "segment-00000.mp4"), Buffer.alloc(1000, 1));
124
- await writeFile(path.join(dir, "segment-00001.mp4"), Buffer.alloc(1000, 1));
125
- await writeFile(path.join(dir, "segment-00002.mp4"), Buffer.alloc(0));
126
- // What a live encoder, working further along the same track, has finished.
127
- await writeFile(path.join(dir, "segment-00040.mp4"), Buffer.alloc(9000, 1));
128
-
129
- const removed = await discardOpenPiece(
130
- dir,
131
- format,
132
- { from: 0, to: -1 },
133
- null,
134
- "segment-00001.mp4"
135
- );
136
-
137
- assert.equal(removed, 2, "its own open piece goes");
138
- assert.ok(existsSync(path.join(dir, "segment-00040.mp4")), "the live run's piece stays");
139
- });
140
-
141
- test("a run that named nothing leaves only its own first piece", async () => {
142
- // It opened one file and died — 548 ms after starting, in the field. Nothing
143
- // above that can be its.
144
- const dir = await mkdtemp(path.join(os.tmpdir(), "open-piece-"));
145
- await writeFile(path.join(dir, "segment-00010.mp4"), Buffer.alloc(0));
146
- await writeFile(path.join(dir, "segment-00011.mp4"), Buffer.alloc(9000, 1));
147
-
148
- const removed = await discardOpenPiece(dir, format, { from: 10, to: -1 }, null, null);
149
-
150
- assert.equal(removed, 10, "the one it opened");
151
- assert.ok(existsSync(path.join(dir, "segment-00011.mp4")), "and nothing beyond it");
152
- });