diff --git a/apps/api-daemon/src/capture/streamIngest.ts b/apps/api-daemon/src/capture/streamIngest.ts index 143b5a2..2e46b05 100644 --- a/apps/api-daemon/src/capture/streamIngest.ts +++ b/apps/api-daemon/src/capture/streamIngest.ts @@ -10,6 +10,20 @@ import { ytdlpAntiBotArgs } from "../ytdlpCookies"; const SEGMENT_LIST_POLL_MS = 5_000; +/** + * Coolify's /logs endpoint can't reliably isolate one docker-compose + * service's stdout, so the last capture attempt's yt-dlp/ffmpeg stderr tail + * is kept here and surfaced via GET /status for debugging. + */ +export const lastCaptureDebug = new Map(); + +function appendCaptureDebug(sessionId: string, line: string) { + const lines = lastCaptureDebug.get(sessionId) ?? []; + lines.push(line); + if (lines.length > 40) lines.shift(); + lastCaptureDebug.set(sessionId, lines); +} + interface SegmentListEntry { fileName: string; startSec: number; @@ -97,9 +111,16 @@ async function runCapture(job: Job): Promise { ); ytdlp.stdout.pipe(ffmpeg.stdin); - ytdlp.stderr.on("data", (chunk) => console.error(`[yt-dlp:${sessionId}]`, chunk.toString().trim())); - ffmpeg.stderr.on("data", () => { - /* ffmpeg logs are extremely verbose; only surface on error below */ + ytdlp.on("error", (err) => appendCaptureDebug(sessionId, `[yt-dlp spawn error] ${err.message}`)); + ytdlp.stderr.on("data", (chunk) => { + const text = chunk.toString().trim(); + console.error(`[yt-dlp:${sessionId}]`, text); + appendCaptureDebug(sessionId, `[yt-dlp] ${text}`); + }); + ffmpeg.on("error", (err) => appendCaptureDebug(sessionId, `[ffmpeg spawn error] ${err.message}`)); + ffmpeg.stderr.on("data", (chunk) => { + /* ffmpeg logs are extremely verbose; keep only the tail for debugging */ + appendCaptureDebug(sessionId, `[ffmpeg] ${chunk.toString().trim().split("\n").pop()}`); }); let lastProcessedIndex = 0; diff --git a/apps/api-daemon/src/server.ts b/apps/api-daemon/src/server.ts index 18c6d4c..451a6b8 100644 --- a/apps/api-daemon/src/server.ts +++ b/apps/api-daemon/src/server.ts @@ -2,6 +2,7 @@ import express from "express"; import { prisma } from "@streamclipper/db"; import { env } from "./env"; import { lastPollErrors } from "./youtubePolling"; +import { lastCaptureDebug } from "./capture/streamIngest"; export function startServer() { const app = express(); @@ -32,6 +33,10 @@ export function startServer() { }); }); + app.get("/debug/capture/:sessionId", (req, res) => { + res.json({ lines: lastCaptureDebug.get(req.params.sessionId) ?? [] }); + }); + app.listen(env.port, () => { console.log(`[server] api-daemon listening on :${env.port}`); });