Fix issue with videos getting stuck due to other videos failing

This commit is contained in:
Ryan Whytsell 2025-10-21 11:51:07 -04:00
parent d2f9045084
commit 0ba2d85006
Signed by: Epithium
GPG Key ID: 940AC18C08E925EA
1 changed files with 52 additions and 45 deletions

View File

@ -1,7 +1,11 @@
import cron from 'node-cron'; import cron from "node-cron";
import { videoOperations, configOperations, channelOperations } from '../db/database'; import {
import { downloadVideo, getChannelVideos } from './ytdlp'; videoOperations,
import { DownloadProgress } from '../types'; configOperations,
channelOperations,
} from "../db/database";
import { downloadVideo, getChannelVideos } from "./ytdlp";
import { DownloadProgress } from "../types";
let schedulerTask: cron.ScheduledTask | null = null; let schedulerTask: cron.ScheduledTask | null = null;
let isDownloading = false; let isDownloading = false;
@ -11,7 +15,10 @@ function getRandomVariance(varianceMinutes: number): number {
return Math.floor(Math.random() * varianceMinutes * 2) - varianceMinutes; return Math.floor(Math.random() * varianceMinutes * 2) - varianceMinutes;
} }
function calculateNextRun(intervalMinutes: number, varianceMinutes: number): Date { function calculateNextRun(
intervalMinutes: number,
varianceMinutes: number,
): Date {
const variance = getRandomVariance(varianceMinutes); const variance = getRandomVariance(varianceMinutes);
const totalMinutes = intervalMinutes + variance; const totalMinutes = intervalMinutes + variance;
@ -22,22 +29,22 @@ function calculateNextRun(intervalMinutes: number, varianceMinutes: number): Dat
async function downloadNextVideo() { async function downloadNextVideo() {
if (isDownloading) { if (isDownloading) {
console.log('Already downloading a video, skipping...'); console.log("Already downloading a video, skipping...");
return; return;
} }
try {
isDownloading = true; isDownloading = true;
currentProgress = null; currentProgress = null;
const video = videoOperations.getNextPending(); const video = videoOperations.getNextPending();
if (!video) { if (!video) {
console.log('No pending videos to download'); console.log("No pending videos to download");
return; return;
} }
try {
console.log(`Starting download: ${video.title} (${video.videoId})`); console.log(`Starting download: ${video.title} (${video.videoId})`);
videoOperations.updateStatus(video.id, 'downloading'); videoOperations.updateStatus(video.id, "downloading");
const result = await downloadVideo(video.videoId, video.url, { const result = await downloadVideo(video.videoId, video.url, {
onProgress: (progress, speed, eta) => { onProgress: (progress, speed, eta) => {
@ -45,23 +52,21 @@ async function downloadNextVideo() {
videoId: video.videoId, videoId: video.videoId,
progress, progress,
speed, speed,
eta eta,
}; };
} },
}); });
videoOperations.markCompleted(video.id, result.filePath, result.fileSize); videoOperations.markCompleted(video.id, result.filePath, result.fileSize);
console.log(`Download completed: ${video.title}`); console.log(`Download completed: ${video.title}`);
currentProgress = null; currentProgress = null;
} catch (error) { } catch (error) {
console.error('Download error:', error); console.error("Download error:", error);
const video = videoOperations.getNextPending();
if (video) { if (video) {
videoOperations.updateStatus( videoOperations.updateStatus(
video.id, video.id,
'failed', "failed",
error instanceof Error ? error.message : 'Unknown error' error instanceof Error ? error.message : "Unknown error",
); );
} }
currentProgress = null; currentProgress = null;
@ -71,7 +76,7 @@ async function downloadNextVideo() {
} }
async function checkChannelsForNewVideos() { async function checkChannelsForNewVideos() {
const channels = channelOperations.getAll().filter(c => c.active); const channels = channelOperations.getAll().filter((c) => c.active);
for (const channel of channels) { for (const channel of channels) {
try { try {
@ -81,7 +86,7 @@ async function checkChannelsForNewVideos() {
for (const videoInfo of videos) { for (const videoInfo of videos) {
// Check if video already exists // Check if video already exists
const existingVideos = videoOperations.getAll(); const existingVideos = videoOperations.getAll();
const exists = existingVideos.some(v => v.videoId === videoInfo.id); const exists = existingVideos.some((v) => v.videoId === videoInfo.id);
if (!exists) { if (!exists) {
console.log(`Found new video: ${videoInfo.title}`); console.log(`Found new video: ${videoInfo.title}`);
@ -94,10 +99,10 @@ async function checkChannelsForNewVideos() {
duration: videoInfo.duration, duration: videoInfo.duration,
thumbnail: videoInfo.thumbnail, thumbnail: videoInfo.thumbnail,
uploadDate: videoInfo.uploadDate, uploadDate: videoInfo.uploadDate,
status: 'pending', status: "pending",
filePath: null, filePath: null,
fileSize: null, fileSize: null,
error: null error: null,
}); });
} }
} }
@ -111,36 +116,38 @@ async function checkChannelsForNewVideos() {
export function startScheduler() { export function startScheduler() {
if (schedulerTask) { if (schedulerTask) {
console.log('Scheduler already running'); console.log("Scheduler already running");
return; return;
} }
const config = configOperations.getAll(); const config = configOperations.getAll();
const enabled = config.enabled === 'true'; const enabled = config.enabled === "true";
if (!enabled) { if (!enabled) {
console.log('Scheduler is disabled'); console.log("Scheduler is disabled");
return; return;
} }
const intervalMinutes = parseFloat(config.intervalMinutes || '180'); // Default 180 minutes (3 hours) const intervalMinutes = parseFloat(config.intervalMinutes || "180"); // Default 180 minutes (3 hours)
const varianceMinutes = parseFloat(config.varianceMinutes || '30'); const varianceMinutes = parseFloat(config.varianceMinutes || "30");
console.log(`Starting scheduler: ${intervalMinutes}min ±${varianceMinutes}min`); console.log(
`Starting scheduler: ${intervalMinutes}min ±${varianceMinutes}min`,
);
// Check for new videos every hour // Check for new videos every hour
schedulerTask = cron.schedule('0 * * * *', async () => { schedulerTask = cron.schedule("0 * * * *", async () => {
console.log('Checking channels for new videos...'); console.log("Checking channels for new videos...");
await checkChannelsForNewVideos(); await checkChannelsForNewVideos();
}); });
// Download next video with variable interval // Download next video with variable interval
async function scheduleNextDownload() { async function scheduleNextDownload() {
const config = configOperations.getAll(); const config = configOperations.getAll();
const enabled = config.enabled === 'true'; const enabled = config.enabled === "true";
if (!enabled) { if (!enabled) {
console.log('Scheduler disabled, stopping...'); console.log("Scheduler disabled, stopping...");
return; return;
} }
@ -148,8 +155,8 @@ export function startScheduler() {
await downloadNextVideo(); await downloadNextVideo();
// AFTER download completes, calculate next run // AFTER download completes, calculate next run
const intervalMinutes = parseFloat(config.intervalMinutes || '180'); // Default 180 minutes (3 hours) const intervalMinutes = parseFloat(config.intervalMinutes || "180"); // Default 180 minutes (3 hours)
const varianceMinutes = parseFloat(config.varianceMinutes || '30'); const varianceMinutes = parseFloat(config.varianceMinutes || "30");
const nextRun = calculateNextRun(intervalMinutes, varianceMinutes); const nextRun = calculateNextRun(intervalMinutes, varianceMinutes);
console.log(`Next download scheduled for: ${nextRun.toLocaleString()}`); console.log(`Next download scheduled for: ${nextRun.toLocaleString()}`);
@ -161,14 +168,14 @@ export function startScheduler() {
// Start the first download cycle // Start the first download cycle
scheduleNextDownload(); scheduleNextDownload();
console.log('Scheduler started'); console.log("Scheduler started");
} }
export function stopScheduler() { export function stopScheduler() {
if (schedulerTask) { if (schedulerTask) {
schedulerTask.stop(); schedulerTask.stop();
schedulerTask = null; schedulerTask = null;
console.log('Scheduler stopped'); console.log("Scheduler stopped");
} }
} }
@ -181,11 +188,11 @@ export function getSchedulerStatus() {
const config = configOperations.getAll(); const config = configOperations.getAll();
return { return {
running: schedulerTask !== null, running: schedulerTask !== null,
enabled: config.enabled === 'true', enabled: config.enabled === "true",
intervalMinutes: parseFloat(config.intervalMinutes || '180'), intervalMinutes: parseFloat(config.intervalMinutes || "180"),
varianceMinutes: parseFloat(config.varianceMinutes || '30'), varianceMinutes: parseFloat(config.varianceMinutes || "30"),
isDownloading, isDownloading,
currentProgress currentProgress,
}; };
} }