commit 32b81b9082dd5e1cee5695b54cd547679582f9d8
parent 20466b29e57eab7e474707981b5cb0e68493ef5f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 2 Oct 2026 00:47:21 -0400
common: the live-chat archive reads no raw replay while the media is away
A build re-normalizes a stale cues file from the tiered raw replay only while
the channel's media is ok or in place; otherwise it stages the cues on disk as
they are and logs how many are older than their raw replay.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
1 file changed, 40 insertions(+), 8 deletions(-)
diff --git a/common/controller/archiveLiveChat.ts b/common/controller/archiveLiveChat.ts
@@ -26,7 +26,8 @@ import {
} from "node:fs/promises";
import pLimit from "p-limit";
import { listChannelStatsFromDisk, type ChannelStat } from "./channels";
-import { normalizeLiveChat } from "./normalizeLiveChat";
+import { isLiveChatCuesFresh, normalizeLiveChat } from "./normalizeLiveChat";
+import { inspectChannelMedia } from "../lib/channelMedia";
import {
archiveExtension,
archiveSidecarPath,
@@ -139,9 +140,22 @@ async function stageChannel(
log(`Archive live chat ${ch.slug}: scanning ${videoIds.length} videos`);
+ // THE RAW REPLAY IS MEDIA (release 17): `transcript.live_chat.json` is a
+ // tiered link onto the channel's media drive. A build re-normalizes a stale
+ // cues file from it only while that drive is reachable; otherwise the cues
+ // the corpus disk holds are staged as they are (the freshness check `lstat`s
+ // the link, so asking it costs the drive nothing), and a video with no cues
+ // yet is left out of this build. Never a read of a drive that is unmounted,
+ // stalled or mid-move.
+ const media = await inspectChannelMedia(paths, ch.slug, ch.config, {
+ fresh: true,
+ });
+ const rawReadable = media.status === "ok" || media.status === "in-place";
+
let normalizedCount = 0;
let linkedCount = 0;
let normalizeFailed = 0;
+ let keptStale = 0;
const requiresRewrite = !build.includeMetadata || build.prettyPrint;
@@ -157,13 +171,24 @@ async function stageChannel(
}
let outcome;
try {
- outcome = await normalizeLiveChat({
- videoDir,
- channelSlug: ch.slug,
- configName: ch.config.name,
- // A build moves no media (release 17): the jobs tier, this reads.
- tier: false,
- });
+ if (rawReadable) {
+ outcome = await normalizeLiveChat({
+ videoDir,
+ channelSlug: ch.slug,
+ configName: ch.config.name,
+ // A build moves no media (release 17): the jobs tier, this reads.
+ tier: false,
+ });
+ } else {
+ const { fresh, cuesPath } = await isLiveChatCuesFresh(videoDir);
+ const have = await stat(cuesPath).then(
+ (st) => st.isFile(),
+ () => false,
+ );
+ if (!have) return;
+ if (!fresh) keptStale++;
+ outcome = { status: "fresh" as const, cuesPath };
+ }
} catch (err) {
normalizeFailed++;
log(` ! normalize ${ch.slug}/${id}: ${(err as Error).message}`);
@@ -189,6 +214,13 @@ async function stageChannel(
if (normalizeFailed > 0) {
log(` ${ch.slug}: ${normalizeFailed} videos failed to normalize`);
}
+ if (!rawReadable) {
+ log(
+ ` ${ch.slug}: its media is not reachable (${media.detail ?? media.status}) — ` +
+ `staged the live-chat cues on disk as they are` +
+ (keptStale > 0 ? ` (${keptStale} older than their raw replay)` : ""),
+ );
+ }
if (linkedCount > 0) {
await writeFile(