Module split: extract yt-dlp lifecycle helpers to server/ytdlp.js
• checkYtdlp() — version + GitHub-latest check (24h-cached) • autoUpdateYtdlp(dataDir) — multi-strategy update (-U, pip, brew, binary) Module-private memoization (ytdlpVersion, ytdlpLastCheck) now stays inside ytdlp.js where it belongs. autoUpdateYtdlp gained an explicit dataDir parameter — strategy 4 (StartOS binary download to /data/bin/) needs it; passing it in keeps the module pure of caller-side state. server/index.js: 2694 → 2614 lines. Smoke tested: server boots; /api/license-status, /api/health respond. No behavior change.
This commit is contained in:
+5
-85
@@ -25,6 +25,7 @@ import {
|
||||
splitAudioFile,
|
||||
downloadPodcastAudio,
|
||||
} from "./audio.js";
|
||||
import { checkYtdlp, autoUpdateYtdlp } from "./ytdlp.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const app = express();
|
||||
@@ -351,88 +352,7 @@ async function saveToHistory(videoId, url, title, chunks, entries, logs, uploadD
|
||||
app.use(express.static(path.join(__dirname, "..", "public")));
|
||||
app.use("/assets", express.static(path.join(__dirname, "..", "assets")));
|
||||
|
||||
// ── yt-dlp auto-update ─────────────────────────────────────────────────────
|
||||
|
||||
let ytdlpVersion = null;
|
||||
let ytdlpLastCheck = 0;
|
||||
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
async function checkYtdlp() {
|
||||
const info = { installed: false, version: null, updateAvailable: false, latestVersion: null };
|
||||
try {
|
||||
const { stdout } = await execFileAsync("yt-dlp", ["--version"]);
|
||||
info.installed = true;
|
||||
info.version = stdout.trim();
|
||||
ytdlpVersion = info.version;
|
||||
} catch {
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check for updates at most once per 24h
|
||||
const now = Date.now();
|
||||
if (now - ytdlpLastCheck > UPDATE_CHECK_INTERVAL) {
|
||||
ytdlpLastCheck = now;
|
||||
try {
|
||||
const resp = await fetch("https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest", {
|
||||
headers: { "Accept": "application/vnd.github.v3+json" },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
info.latestVersion = data.tag_name?.replace(/^v/, "") || null;
|
||||
if (info.latestVersion && info.version !== info.latestVersion) {
|
||||
info.updateAvailable = true;
|
||||
}
|
||||
}
|
||||
} catch {} // Network errors are fine, just skip the check
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
async function autoUpdateYtdlp() {
|
||||
console.log(" ↻ Updating yt-dlp...");
|
||||
|
||||
// Strategy 1: yt-dlp's built-in self-update
|
||||
try {
|
||||
const { stdout } = await execFileAsync("yt-dlp", ["-U"], { timeout: 60000 });
|
||||
console.log(` ${stdout.trim()}`);
|
||||
return { success: true, message: stdout.trim() };
|
||||
} catch (e1) {
|
||||
console.log(" … Self-update failed, trying pip...");
|
||||
}
|
||||
|
||||
// Strategy 2: pip3 / pip (works in containers and on macOS)
|
||||
for (const pip of ["pip3", "pip"]) {
|
||||
try {
|
||||
await execFileAsync(pip, ["install", "-U", "yt-dlp"], { timeout: 120000 });
|
||||
console.log(` ✓ Updated via ${pip}`);
|
||||
return { success: true, message: `Updated via ${pip}` };
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Strategy 3: Homebrew (macOS local dev only)
|
||||
try {
|
||||
await execFileAsync("brew", ["upgrade", "yt-dlp"], { timeout: 120000 });
|
||||
console.log(" ✓ Updated via Homebrew");
|
||||
return { success: true, message: "Updated via Homebrew" };
|
||||
} catch {}
|
||||
|
||||
// Strategy 4: Direct binary download to persistent storage (StartOS fallback)
|
||||
try {
|
||||
const binDir = path.join(DATA_DIR, "bin");
|
||||
await fs.mkdir(binDir, { recursive: true });
|
||||
const binPath = path.join(binDir, "yt-dlp");
|
||||
console.log(" … Trying direct binary download...");
|
||||
const { stdout } = await execFileAsync("sh", ["-c",
|
||||
`curl -L -o "${binPath}" "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" && chmod +x "${binPath}" && "${binPath}" --version`
|
||||
], { timeout: 120000 });
|
||||
console.log(` ✓ Downloaded yt-dlp binary: ${stdout.trim()}`);
|
||||
return { success: true, message: `Downloaded binary: ${stdout.trim()}` };
|
||||
} catch (e4) {
|
||||
console.log(" ✗ All update strategies failed");
|
||||
return { success: false, message: "Auto-update failed. Try updating manually from the StartOS Actions menu." };
|
||||
}
|
||||
}
|
||||
// checkYtdlp + autoUpdateYtdlp moved to ./ytdlp.js
|
||||
|
||||
// PRICING + calcCost + buildAnalysisPrompt moved to ./gemini-helpers.js
|
||||
// safeText + retryGemini moved to ./util.js
|
||||
@@ -476,7 +396,7 @@ app.post("/api/shutdown", (req, res) => {
|
||||
// ── Manual update endpoint ─────────────────────────────────────────────────
|
||||
|
||||
app.post("/api/update-ytdlp", async (req, res) => {
|
||||
const result = await autoUpdateYtdlp();
|
||||
const result = await autoUpdateYtdlp(DATA_DIR);
|
||||
const info = await checkYtdlp();
|
||||
res.json({ ...result, ...info });
|
||||
});
|
||||
@@ -2120,7 +2040,7 @@ app.post("/api/process", async (req, res) => {
|
||||
if (blocked && attempt === MAX_RETRIES) {
|
||||
// Last resort: try yt-dlp auto-update in case there's a newer version that handles this
|
||||
log(1, "All retries exhausted — attempting yt-dlp auto-update as last resort...");
|
||||
const updateResult = await autoUpdateYtdlp();
|
||||
const updateResult = await autoUpdateYtdlp(DATA_DIR);
|
||||
if (updateResult.success) {
|
||||
log(1, "yt-dlp updated! Final retry...");
|
||||
try {
|
||||
@@ -2646,7 +2566,7 @@ app.listen(PORT, BIND_HOST, async () => {
|
||||
console.log(` ✓ yt-dlp ${info.version} found`);
|
||||
console.log(` ↑ Update available: ${info.latestVersion}`);
|
||||
console.log(` Auto-updating...`);
|
||||
const result = await autoUpdateYtdlp();
|
||||
const result = await autoUpdateYtdlp(DATA_DIR);
|
||||
if (result.success) {
|
||||
const refreshed = await checkYtdlp();
|
||||
console.log(` ✓ yt-dlp updated to ${refreshed.version}\n`);
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// yt-dlp lifecycle helpers: version check, GitHub-latest comparison, and
|
||||
// multi-strategy auto-update. Self-contained — module-private memoization
|
||||
// for the GitHub check (capped to once per 24h to avoid hammering the
|
||||
// API).
|
||||
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
let ytdlpVersion = null;
|
||||
let ytdlpLastCheck = 0;
|
||||
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
// ── Version + update-availability check ─────────────────────────────────────
|
||||
// Returns `{ installed, version, updateAvailable, latestVersion }`.
|
||||
// Suppresses errors — a missing yt-dlp returns `installed: false`, a
|
||||
// failed GitHub call just leaves `latestVersion` as null. The GitHub
|
||||
// fetch is rate-limited to once per 24h.
|
||||
export async function checkYtdlp() {
|
||||
const info = { installed: false, version: null, updateAvailable: false, latestVersion: null };
|
||||
try {
|
||||
const { stdout } = await execFileAsync("yt-dlp", ["--version"]);
|
||||
info.installed = true;
|
||||
info.version = stdout.trim();
|
||||
ytdlpVersion = info.version;
|
||||
} catch {
|
||||
return info;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (now - ytdlpLastCheck > UPDATE_CHECK_INTERVAL) {
|
||||
ytdlpLastCheck = now;
|
||||
try {
|
||||
const resp = await fetch("https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest", {
|
||||
headers: { "Accept": "application/vnd.github.v3+json" },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
info.latestVersion = data.tag_name?.replace(/^v/, "") || null;
|
||||
if (info.latestVersion && info.version !== info.latestVersion) {
|
||||
info.updateAvailable = true;
|
||||
}
|
||||
}
|
||||
} catch {} // Network errors are fine, just skip the check
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
// ── Multi-strategy auto-update ──────────────────────────────────────────────
|
||||
// Tries strategies in order until one works:
|
||||
// 1. yt-dlp -U (built-in self-update, when permitted)
|
||||
// 2. pip3/pip install -U (containers, macOS)
|
||||
// 3. brew upgrade yt-dlp (macOS local dev)
|
||||
// 4. Direct binary download (StartOS fallback to /data/bin/)
|
||||
//
|
||||
// `dataDir` is needed only for strategy 4. Returns `{ success, message }`.
|
||||
export async function autoUpdateYtdlp(dataDir) {
|
||||
console.log(" ↻ Updating yt-dlp...");
|
||||
|
||||
// Strategy 1: yt-dlp's built-in self-update
|
||||
try {
|
||||
const { stdout } = await execFileAsync("yt-dlp", ["-U"], { timeout: 60000 });
|
||||
console.log(` ${stdout.trim()}`);
|
||||
return { success: true, message: stdout.trim() };
|
||||
} catch {
|
||||
console.log(" … Self-update failed, trying pip...");
|
||||
}
|
||||
|
||||
// Strategy 2: pip3 / pip
|
||||
for (const pip of ["pip3", "pip"]) {
|
||||
try {
|
||||
await execFileAsync(pip, ["install", "-U", "yt-dlp"], { timeout: 120000 });
|
||||
console.log(` ✓ Updated via ${pip}`);
|
||||
return { success: true, message: `Updated via ${pip}` };
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Strategy 3: Homebrew (macOS local dev only)
|
||||
try {
|
||||
await execFileAsync("brew", ["upgrade", "yt-dlp"], { timeout: 120000 });
|
||||
console.log(" ✓ Updated via Homebrew");
|
||||
return { success: true, message: "Updated via Homebrew" };
|
||||
} catch {}
|
||||
|
||||
// Strategy 4: Direct binary download to persistent storage (StartOS fallback)
|
||||
try {
|
||||
const binDir = path.join(dataDir, "bin");
|
||||
await fs.mkdir(binDir, { recursive: true });
|
||||
const binPath = path.join(binDir, "yt-dlp");
|
||||
console.log(" … Trying direct binary download...");
|
||||
const { stdout } = await execFileAsync("sh", ["-c",
|
||||
`curl -L -o "${binPath}" "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" && chmod +x "${binPath}" && "${binPath}" --version`
|
||||
], { timeout: 120000 });
|
||||
console.log(` ✓ Downloaded yt-dlp binary: ${stdout.trim()}`);
|
||||
return { success: true, message: `Downloaded binary: ${stdout.trim()}` };
|
||||
} catch {
|
||||
console.log(" ✗ All update strategies failed");
|
||||
return { success: false, message: "Auto-update failed. Try updating manually from the StartOS Actions menu." };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user