diff --git a/backend/src/db/database.ts b/backend/src/db/database.ts index be52f89..c77522d 100644 --- a/backend/src/db/database.ts +++ b/backend/src/db/database.ts @@ -89,7 +89,8 @@ export function initDatabase() { intervalMinutes: '180', // 180 minutes = 3 hours varianceMinutes: '30', maxConcurrentDownloads: '1', - enabled: 'true' + enabled: 'true', + downloadsPath: path.join(__dirname, '../../downloads') }; const insertConfig = db.prepare( diff --git a/backend/src/server.ts b/backend/src/server.ts index 1d0398f..ce05767 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,7 +1,7 @@ import express from 'express'; import cors from 'cors'; import path from 'path'; -import { initDatabase } from './db/database'; +import { initDatabase, configOperations } from './db/database'; import { startScheduler } from './services/scheduler'; import channelsRouter from './routes/channels'; import playlistsRouter from './routes/playlists'; @@ -24,8 +24,9 @@ app.use('/api/playlists', playlistsRouter); app.use('/api/videos', videosRouter); app.use('/api/config', configRouter); -// Serve downloaded videos -app.use('/downloads', express.static(path.join(__dirname, '../downloads'))); +// Serve downloaded videos from configured path +const downloadsPath = configOperations.get('downloadsPath') || path.join(__dirname, '../downloads'); +app.use('/downloads', express.static(downloadsPath)); // Health check app.get('/api/health', (req, res) => { diff --git a/backend/src/services/ytdlp.ts b/backend/src/services/ytdlp.ts index e14f485..ce8dde3 100644 --- a/backend/src/services/ytdlp.ts +++ b/backend/src/services/ytdlp.ts @@ -1,12 +1,19 @@ import { spawn } from 'child_process'; import path from 'path'; import fs from 'fs'; +import { configOperations } from '../db/database'; -const DOWNLOADS_DIR = path.join(__dirname, '../../downloads'); +// Get downloads directory from config +function getDownloadsDir(): string { + const downloadsPath = configOperations.get('downloadsPath'); + const dir = downloadsPath || path.join(__dirname, '../../downloads'); -// Ensure downloads directory exists -if (!fs.existsSync(DOWNLOADS_DIR)) { - fs.mkdirSync(DOWNLOADS_DIR, { recursive: true }); + // Ensure downloads directory exists + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + return dir; } // Path to cookies file (optional, for additional authentication) @@ -296,7 +303,8 @@ export async function downloadVideo( options: DownloadOptions = {} ): Promise<{ filePath: string; fileSize: number }> { return new Promise((resolve, reject) => { - const outputTemplate = path.join(DOWNLOADS_DIR, `${videoId}.%(ext)s`); + const downloadsDir = getDownloadsDir(); + const outputTemplate = path.join(downloadsDir, `${videoId}.%(ext)s`); const args = [ ...getCommonArgs(), @@ -360,10 +368,10 @@ export async function downloadVideo( // Find the downloaded file if (!outputPath) { - const files = fs.readdirSync(DOWNLOADS_DIR); + const files = fs.readdirSync(downloadsDir); const videoFile = files.find(f => f.startsWith(videoId) && f.endsWith('.mp4')); if (videoFile) { - outputPath = path.join(DOWNLOADS_DIR, videoFile); + outputPath = path.join(downloadsDir, videoFile); } } diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 3f47cf7..519c0cd 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -224,6 +224,22 @@ function SettingsPage() { Number of videos to download simultaneously (recommended: 1)

+ +
+ + handleChange('downloadsPath', e.target.value)} + className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm focus:border-blue-500 focus:ring-blue-500" + placeholder="/var/www/vidrip/downloads" + /> +

+ Absolute path where downloaded videos will be saved. Can point to a mounted WebDAV directory. +

+
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index ae9e1b5..79897ab 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -41,6 +41,7 @@ export interface Config { varianceMinutes: string; maxConcurrentDownloads: string; enabled: string; + downloadsPath: string; } export interface SchedulerStatus {