Module split: extract audio I/O helpers to server/audio.js

• getAudioDuration(path)         — ffprobe wrapper, returns seconds | null
  • splitAudioFile(in, dir, secs)  — ffmpeg -acodec copy chunking
  • downloadPodcastAudio(url, dst) — streams HTTP audio to disk

Also moved fetchUrl into util.js (alongside the other stateless
helpers) — it's a generic HTTP-GET-with-redirects used by RSS parsing
and channel discovery, not strictly audio.

server/index.js: 2758 → 2694 lines.

Smoke tested: server boots; /api/license-status, /api/health, /
respond. No behavior change.
This commit is contained in:
Keysat
2026-05-08 16:53:06 -05:00
parent 1c78e46ebd
commit 4c3cb6a077
3 changed files with 122 additions and 76 deletions
+27 -3
View File
@@ -1,6 +1,9 @@
// Pure helpers — no module-scoped state, no Express, no I/O effects.
// Anything in here is safe to import from any other module without
// worrying about ordering or initialization side effects.
// Stateless helpers — no module-scoped state, no Express, no
// initialization side effects. Anything in here is safe to import from
// any other module without worrying about ordering. A few helpers do
// I/O (fetchUrl) but only when called.
import https from "https";
// ── SSE helper ──────────────────────────────────────────────────────────────
// Writes a single Server-Sent Events frame: `event: X\ndata: Y\n\n`.
@@ -89,6 +92,27 @@ export function safeText(result) {
return "";
}
// ── HTTP GET with redirect following ────────────────────────────────────────
// Returns the response body as a string. Follows HTTP redirects up to a
// reasonable depth (relies on https module's default behavior plus a one-
// level recursion). Used for fetching RSS feeds, channel pages, etc.
//
// For binary downloads (e.g. podcast audio), use audio.downloadPodcastAudio
// — it streams to disk instead of buffering in memory.
export function fetchUrl(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
return fetchUrl(res.headers.location).then(resolve, reject);
}
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => resolve(data));
res.on("error", reject);
}).on("error", reject);
});
}
// ── Retry helper for transient Gemini API errors ────────────────────────────
// Retries on 503/429 and on common transient network errors. Linear backoff
// (delayMs * attempt). The optional `log` callback receives a one-line