@torrent-tv/proxy 2.104.2 → 2.105.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": "2.104.2",
3
+ "version": "2.105.1",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -155,7 +155,11 @@ export class ContainerOrchestrator {
155
155
  // picture's first input of an AVI waited about 4 s behind the sound's
156
156
  // table of the whole film it does not use (r38, torrent-tv/meta#178).
157
157
  const aside = this.#answersWithoutReading(key, statement);
158
- const previous = aside ? Promise.resolve() : this.#reads.get(key) ?? Promise.resolve();
158
+ // Chapter discovery can walk a whole file when the format has no chapter
159
+ // index. Keep it serial with other chapter reads, but out of the queue used
160
+ // to prepare playback inputs for this file.
161
+ const queueKey = statement === "chapters" ? `${key}:chapters` : key;
162
+ const previous = aside ? Promise.resolve() : this.#reads.get(queueKey) ?? Promise.resolve();
159
163
  const askedAt = performance.now();
160
164
  const reading = previous.catch(() => undefined).then(async () => {
161
165
  if (!isCurrent()) return { result: { kind: "terminal", reason: "request-obsolete", requestId: params.requestId } };
@@ -168,7 +172,7 @@ export class ContainerOrchestrator {
168
172
  readMs: endedAt - startedAt, cached: aside, result: result.kind, reason: result.reason, ranges: result.ranges });
169
173
  return { result, revision };
170
174
  });
171
- if (!aside) this.#reads.set(key, reading);
175
+ if (!aside) this.#reads.set(queueKey, reading);
172
176
  try {
173
177
  const { result, revision } = await reading;
174
178
  if (!isCurrent()) return { kind: "terminal", reason: "request-obsolete", requestId: params.requestId };
@@ -177,7 +181,7 @@ export class ContainerOrchestrator {
177
181
  await params.onReadResult?.(statement, result, revision);
178
182
  return result;
179
183
  } finally {
180
- if (this.#reads.get(key) === reading) this.#reads.delete(key);
184
+ if (this.#reads.get(queueKey) === reading) this.#reads.delete(queueKey);
181
185
  }
182
186
  }
183
187
 
@@ -11,11 +11,12 @@ const TOP_LEVEL = new Set([SEEK_HEAD, CHAPTERS, CLUSTER, 0x1549a966, 0x1654ae6b,
11
11
  async function header(reader, at, limit) {
12
12
  reader.step();
13
13
  const vint = async (position, marker) => {
14
- const first = (await reader.bytes(position, 1))[0];
14
+ const firstBytes = await reader.bytes(position, 1);
15
+ const first = firstBytes[0];
15
16
  let n = 1;
16
17
  while (n <= 8 && !(first & (0x80 >> (n - 1)))) n++;
17
18
  if (n > (marker ? 4 : 8) || position + n > limit) throw new Error("Invalid chapter EBML header");
18
- const bytes = await reader.bytes(position, n);
19
+ const bytes = n === 1 ? firstBytes : Buffer.concat([firstBytes, await reader.bytes(position + 1, n - 1)]);
19
20
  const value = readVint(bytes, 0, marker);
20
21
  const mask = 2 ** (8 - n) - 1;
21
22
  return { ...value, unknown: !marker && (bytes[0] & mask) === mask && bytes.subarray(1).every((byte) => byte === 255) };
@@ -608,12 +608,30 @@ export function sidecarNaming({ folders, fileName, videoName = "" }) {
608
608
  stripExtension(String(videoName ?? ""))
609
609
  );
610
610
  const language = fromName.language ?? languageFromFolderNames(folders);
611
+ const title = titleOf(fromName.title);
612
+ const team = teamFrom({ folders, fileName, videoName });
611
613
  return {
612
614
  code: language?.code ?? null,
613
615
  name: language?.name ?? null,
614
- releaser: titleOf(fromName.title) ?? releaserFrom({ folders, fileName, videoName }),
616
+ // Keep releaser unchanged: subtitle identity uses this value across episodes.
617
+ releaser: title ?? releaserFrom({ folders, fileName, videoName }),
618
+ team,
619
+ title,
615
620
  isForced: fromName.isForced,
616
621
  isHearingImpaired: fromName.isHearingImpaired,
617
622
  isDefault: fromName.isDefault
618
623
  };
619
624
  }
625
+
626
+ /**
627
+ * A team is only a distinct bracketed name. Generic track titles are not teams.
628
+ * @param {{ folders: string[], fileName: string, videoName?: string }} params
629
+ * @returns {string | null}
630
+ */
631
+ function teamFrom({ folders, fileName, videoName = "" }) {
632
+ const team = releaserFrom({ folders, fileName, videoName });
633
+ if (!team || /^(?:full|signs?|commentary)$/iu.test(team.trim())) {
634
+ return null;
635
+ }
636
+ return team;
637
+ }