@torrent-tv/proxy 2.55.14 → 2.57.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/CHANGELOG.md +1169 -1150
- package/bin/cli.js +494 -478
- package/package.json +1 -1
- package/routes/api/subtitles/get.js +13 -4
- package/services/container-index/matroska-subtitles.js +6 -0
- package/services/container-index/mp4-subtitles.js +25 -1
- package/services/data-channel-handler.js +1133 -949
- package/services/delivery-probe.js +338 -255
- package/services/packet-witness.js +784 -406
- package/services/torrent-worker/client.js +2 -1
- package/services/torrent-worker/subtitle-cues.js +182 -21
- package/services/torrent-worker/worker.js +28 -2
- package/test/delivery-probe.test.js +124 -78
- package/test/packet-witness-ring.test.js +236 -0
- package/test/packet-witness.test.js +148 -120
- package/test/subtitle-track-numbering.test.js +262 -0
- package/test/wedge-certainty.test.js +131 -0
package/package.json
CHANGED
|
@@ -268,9 +268,10 @@ function readFileFully(file, maxBytes) {
|
|
|
268
268
|
* The cues of a track from clusters already downloaded, as WebVTT.
|
|
269
269
|
*
|
|
270
270
|
* `trackIndex` is the browser's number for the subtitle stream — its position
|
|
271
|
-
* among the subtitle streams, as
|
|
272
|
-
* carry the file's own track number. The plan lists the
|
|
273
|
-
*
|
|
271
|
+
* among ALL the subtitle streams, as ffmpeg lists them — while Matroska blocks
|
|
272
|
+
* carry the file's own track number. The plan lists only the tracks that can
|
|
273
|
+
* become text, so it is matched on `declaredIndex`, which each track carries
|
|
274
|
+
* for exactly this: its place in the file's full list of subtitle tracks.
|
|
274
275
|
*
|
|
275
276
|
* @param {object} torrentPool
|
|
276
277
|
* @param {object} torrent
|
|
@@ -289,7 +290,15 @@ async function cuesFromDownloadedClusters(torrentPool, torrent, fileIndex, track
|
|
|
289
290
|
} catch {
|
|
290
291
|
return null;
|
|
291
292
|
}
|
|
292
|
-
|
|
293
|
+
// BY the browser's number, not by position in this list. The list holds only
|
|
294
|
+
// the tracks that can become WebVTT, while the browser counts every subtitle
|
|
295
|
+
// stream ffmpeg lists — so on a file carrying a PGS or VobSub track the two
|
|
296
|
+
// ran one apart, and the request either found the wrong track or found none
|
|
297
|
+
// and fell through to the ffmpeg extraction below, which reads the whole film
|
|
298
|
+
// (752 s measured, 2026-08-19) for cues already in hand.
|
|
299
|
+
const track = Array.isArray(tracks)
|
|
300
|
+
? tracks.find((candidate) => candidate.declaredIndex === trackIndex) ?? null
|
|
301
|
+
: null;
|
|
293
302
|
if (!track) {
|
|
294
303
|
return null;
|
|
295
304
|
}
|
|
@@ -63,6 +63,11 @@ const TEXT_CODECS = new Set(["S_TEXT/UTF8", "S_TEXT/ASS", "S_TEXT/SSA"]);
|
|
|
63
63
|
/**
|
|
64
64
|
* @typedef {object} SubtitleTrackPlan
|
|
65
65
|
* @property {number} trackNumber - As the blocks name it.
|
|
66
|
+
* @property {number} declaredIndex - Its position among ALL of the file's
|
|
67
|
+
* subtitle tracks, picture-based ones included — which is the number ffmpeg
|
|
68
|
+
* gives the same stream in `0:s:N`, and therefore the only number the browser
|
|
69
|
+
* ever names. Text tracks alone are not a numbering: a file whose PGS track
|
|
70
|
+
* comes first would have every text track one lower here than in the browser.
|
|
66
71
|
* @property {string} codecId
|
|
67
72
|
* @property {string} language - The three-letter code the file declares.
|
|
68
73
|
* @property {string} name - What the file calls the track, if anything.
|
|
@@ -178,6 +183,7 @@ export async function readSubtitlePlan(readRange, fileSize) {
|
|
|
178
183
|
}
|
|
179
184
|
tracks.push({
|
|
180
185
|
trackNumber,
|
|
186
|
+
declaredIndex: declared.length - 1,
|
|
181
187
|
codecId,
|
|
182
188
|
language,
|
|
183
189
|
name,
|
|
@@ -29,6 +29,15 @@ const MAX_MOOV_BYTES = 32 * 1024 * 1024;
|
|
|
29
29
|
|
|
30
30
|
/** Handlers that mean "this track is text on screen". */
|
|
31
31
|
const TEXT_HANDLERS = new Set(["text", "sbtl", "subt"]);
|
|
32
|
+
/**
|
|
33
|
+
* Every handler ffmpeg's mov demuxer turns into a SUBTITLE stream, whether or
|
|
34
|
+
* not this file can read it — `subp` is a DVD subpicture and `clcp` closed
|
|
35
|
+
* captions, both pictures or caption data rather than text. They are counted
|
|
36
|
+
* because `declaredIndex` has to equal ffmpeg's `0:s:N`, and a track left out
|
|
37
|
+
* of the count shifts every text track after it, which is the very defect
|
|
38
|
+
* `declaredIndex` exists to remove.
|
|
39
|
+
*/
|
|
40
|
+
const SUBTITLE_HANDLERS = new Set([...TEXT_HANDLERS, "subp", "clcp"]);
|
|
32
41
|
/** Sample formats this can turn into cues. `stpp` (TTML) is XML and is not one. */
|
|
33
42
|
const TEXT_FORMATS = new Set(["tx3g", "text", "wvtt"]);
|
|
34
43
|
|
|
@@ -218,6 +227,11 @@ function sampleOffsets(moov, stsc, chunkOffsets, sizes) {
|
|
|
218
227
|
/**
|
|
219
228
|
* @typedef {object} Mp4SubtitleTrack
|
|
220
229
|
* @property {number} trackId
|
|
230
|
+
* @property {number} declaredIndex - Its position among ALL of the file's
|
|
231
|
+
* subtitle tracks, including the ones whose sample format this cannot turn
|
|
232
|
+
* into cues (`stpp` TTML). That is the number ffmpeg gives the same stream in
|
|
233
|
+
* `0:s:N`, which is the number the browser names; counting only the readable
|
|
234
|
+
* ones would shift every track after a TTML one.
|
|
221
235
|
* @property {string} format - `tx3g`, `text` or `wvtt`.
|
|
222
236
|
* @property {string} language - Three letters, as the file declares them.
|
|
223
237
|
* @property {Mp4SubtitleSample[]} samples - In time order.
|
|
@@ -246,6 +260,9 @@ export async function readMp4SubtitlePlan(readRange, fileSize) {
|
|
|
246
260
|
|
|
247
261
|
/** @type {Mp4SubtitleTrack[]} */
|
|
248
262
|
const tracks = [];
|
|
263
|
+
// Counts every subtitle track the file has, whether or not this can read it,
|
|
264
|
+
// so the number handed out matches ffmpeg's `0:s:N`. See `declaredIndex`.
|
|
265
|
+
let declaredIndex = -1;
|
|
249
266
|
for (const trak of childrenOf(moov, moovBox.dataOffset, moov.length, "trak")) {
|
|
250
267
|
const mdia = childOf(moov, trak.dataOffset, trak.end, "mdia");
|
|
251
268
|
if (!mdia) {
|
|
@@ -253,6 +270,13 @@ export async function readMp4SubtitlePlan(readRange, fileSize) {
|
|
|
253
270
|
}
|
|
254
271
|
const hdlr = childOf(moov, mdia.dataOffset, mdia.end, "hdlr");
|
|
255
272
|
const handler = hdlr ? moov.toString("latin1", hdlr.dataOffset + 8, hdlr.dataOffset + 12) : "";
|
|
273
|
+
if (!SUBTITLE_HANDLERS.has(handler)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// Counted before the readability checks below, and before the handler is
|
|
277
|
+
// narrowed to the text ones: this number is the track's place in the file,
|
|
278
|
+
// not its place among the tracks this code can turn into cues.
|
|
279
|
+
declaredIndex += 1;
|
|
256
280
|
if (!TEXT_HANDLERS.has(handler)) {
|
|
257
281
|
continue;
|
|
258
282
|
}
|
|
@@ -337,7 +361,7 @@ export async function readMp4SubtitlePlan(readRange, fileSize) {
|
|
|
337
361
|
});
|
|
338
362
|
}
|
|
339
363
|
}
|
|
340
|
-
tracks.push({ trackId, format, language, samples });
|
|
364
|
+
tracks.push({ trackId, declaredIndex, format, language, samples });
|
|
341
365
|
}
|
|
342
366
|
return { tracks };
|
|
343
367
|
}
|