From 98694d54862a9791580f9bb340ff2ab0cfcc0460 Mon Sep 17 00:00:00 2001 From: Peifan Li Date: Sun, 14 Dec 2025 15:48:03 -0500 Subject: [PATCH] refactor: Update merge output format handling --- .../downloaders/BilibiliDownloader.ts | 59 +++++++++++-------- .../services/downloaders/YtDlpDownloader.ts | 57 ++++++++++++------ 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/backend/src/services/downloaders/BilibiliDownloader.ts b/backend/src/services/downloaders/BilibiliDownloader.ts index 83d7ee5..b60aaa1 100644 --- a/backend/src/services/downloaders/BilibiliDownloader.ts +++ b/backend/src/services/downloaders/BilibiliDownloader.ts @@ -5,21 +5,21 @@ import { IMAGES_DIR, SUBTITLES_DIR, VIDEOS_DIR } from "../../config/paths"; import { DownloadCancelledError } from "../../errors/DownloadErrors"; import { bccToVtt } from "../../utils/bccToVtt"; import { - calculateDownloadedSize, - formatBytes, - isCancellationError, - isDownloadActive, - parseSize, + calculateDownloadedSize, + formatBytes, + isCancellationError, + isDownloadActive, + parseSize, } from "../../utils/downloadUtils"; import { - extractBilibiliVideoId, - formatVideoFilename, + extractBilibiliVideoId, + formatVideoFilename, } from "../../utils/helpers"; import { - executeYtDlpJson, - executeYtDlpSpawn, - getNetworkConfigFromUserConfig, - getUserYtDlpConfig, + executeYtDlpJson, + executeYtDlpSpawn, + getNetworkConfigFromUserConfig, + getUserYtDlpConfig, } from "../../utils/ytDlpUtils"; import * as storageService from "../storageService"; import { Collection, Video } from "../storageService"; @@ -214,7 +214,7 @@ export class BilibiliDownloader { }> { try { const videoUrl = `https://www.bilibili.com/video/${videoId}`; - + // Get user config for network options const userConfig = getUserYtDlpConfig(videoUrl); const networkConfig = getNetworkConfigFromUserConfig(userConfig); @@ -331,30 +331,39 @@ export class BilibiliDownloader { ); } - // Prepare base flags from user config (excluding certain overridden options) + // Prepare base flags from user config (excluding output options we manage) const { output: _output, o: _o, - writeSubs: _writeSubs, - writeAutoSubs: _writeAutoSubs, - convertSubs: _convertSubs, f: _f, format: _format, S: _S, formatSort: _formatSort, + // Extract user subtitle preferences (use them if provided) + writeSubs: userWriteSubs, + writeAutoSubs: userWriteAutoSubs, + convertSubs: userConvertSubs, + // Extract user merge output format (use it if provided) + mergeOutputFormat: userMergeOutputFormat, ...safeUserConfig } = userConfig; + // Determine merge output format: use user's choice or default to mp4 + const mergeOutputFormat = userMergeOutputFormat || "mp4"; + console.log(`Using merge output format: ${mergeOutputFormat}`); + // Prepare flags for yt-dlp - merge user config with required settings const flags: Record = { ...networkConfig, // Apply network settings ...safeUserConfig, // Apply other user config output: outputTemplate, format: downloadFormat, - mergeOutputFormat: "mp4", - writeSubs: true, - writeAutoSubs: true, - convertSubs: "vtt", + // Use user preferences if provided, otherwise use defaults + mergeOutputFormat: mergeOutputFormat, + writeSubs: userWriteSubs !== undefined ? userWriteSubs : true, + writeAutoSubs: + userWriteAutoSubs !== undefined ? userWriteAutoSubs : true, + convertSubs: userConvertSubs !== undefined ? userConvertSubs : "vtt", ignoreErrors: true, // Continue even if subtitle download fails noWarnings: false, // Show warnings for debugging }; @@ -857,12 +866,16 @@ export class BilibiliDownloader { `Downloading Bilibili part ${partNumber}/${totalParts}: ${url}` ); + // Get user's yt-dlp configuration for merge output format + const userConfig = getUserYtDlpConfig(url); + const mergeOutputFormat = userConfig.mergeOutputFormat || "mp4"; + // Create a safe base filename (without extension) const timestamp = Date.now(); const safeBaseFilename = `video_${timestamp}`; - // Add extensions for video and thumbnail - const videoFilename = `${safeBaseFilename}.mp4`; + // Add extensions for video and thumbnail (use user's format preference) + const videoFilename = `${safeBaseFilename}.${mergeOutputFormat}`; const thumbnailFilename = `${safeBaseFilename}.jpg`; // Set full paths for video and thumbnail @@ -926,7 +939,7 @@ export class BilibiliDownloader { videoAuthor, videoDate ); - const newVideoFilename = `${newSafeBaseFilename}.mp4`; + const newVideoFilename = `${newSafeBaseFilename}.${mergeOutputFormat}`; const newThumbnailFilename = `${newSafeBaseFilename}.jpg`; // Rename the files diff --git a/backend/src/services/downloaders/YtDlpDownloader.ts b/backend/src/services/downloaders/YtDlpDownloader.ts index bd6a2e4..f99cb2a 100644 --- a/backend/src/services/downloaders/YtDlpDownloader.ts +++ b/backend/src/services/downloaders/YtDlpDownloader.ts @@ -321,8 +321,8 @@ export class YtDlpDownloader { const newVideoPath = path.join(VIDEOS_DIR, finalVideoFilename); const newThumbnailPath = path.join(IMAGES_DIR, finalThumbnailFilename); - // Download the video - console.log("Downloading video to:", newVideoPath); + // Note: newVideoPath will be updated below based on merge output format + console.log("Preparing video download path:", newVideoPath); if (downloadId) { storageService.updateActiveDownload(downloadId, { @@ -348,32 +348,55 @@ export class YtDlpDownloader { console.log("Using user-specified format:", userFormat); } - // Prepare base flags from user config (excluding certain overridden options) + // Prepare base flags from user config (excluding output options we manage) const { output: _output, // Ignore user output template (we manage this) o: _o, - writeSubs: _writeSubs, // We always enable subtitles - writeAutoSubs: _writeAutoSubs, - convertSubs: _convertSubs, f: _f, // Format is handled specially above format: _format, S: userFormatSort, // Format sort is handled specially formatSort: userFormatSort2, + // Extract user subtitle preferences (use them if provided) + writeSubs: userWriteSubs, + writeAutoSubs: userWriteAutoSubs, + convertSubs: userConvertSubs, + // Extract user merge output format (use it if provided) + mergeOutputFormat: userMergeOutputFormat, ...safeUserConfig } = userConfig; // Get format sort option if user specified it const formatSortValue = userFormatSort || userFormatSort2; - // Prepare flags - user config first, then our required overrides + // Determine merge output format: use user's choice or default to mp4 + const mergeOutputFormat = userMergeOutputFormat || "mp4"; + + // Update the video path to use the correct extension based on merge format + const videoExtension = mergeOutputFormat; + const newVideoPathWithFormat = newVideoPath.replace( + /\.mp4$/, + `.${videoExtension}` + ); + finalVideoFilename = finalVideoFilename.replace( + /\.mp4$/, + `.${videoExtension}` + ); + + console.log( + `Using merge output format: ${mergeOutputFormat}, downloading to: ${newVideoPathWithFormat}` + ); + + // Prepare flags - defaults first, then user config to allow overrides const flags: Record = { - ...safeUserConfig, // Apply user config first - output: newVideoPath, // Always use our output path + ...safeUserConfig, // Apply user config + output: newVideoPathWithFormat, // Always use our output path with correct extension format: defaultFormat, - mergeOutputFormat: "mp4", - writeSubs: true, - writeAutoSubs: true, - convertSubs: "vtt", + // Use user preferences if provided, otherwise use defaults + mergeOutputFormat: mergeOutputFormat, + writeSubs: userWriteSubs !== undefined ? userWriteSubs : true, + writeAutoSubs: + userWriteAutoSubs !== undefined ? userWriteAutoSubs : true, + convertSubs: userConvertSubs !== undefined ? userConvertSubs : "vtt", // Only add PO token provider if configured ...(PROVIDER_SCRIPT ? { @@ -427,7 +450,7 @@ export class YtDlpDownloader { // Clean up partial files console.log("Cleaning up partial files..."); - cleanupPartialVideoFiles(newVideoPath); + cleanupPartialVideoFiles(newVideoPathWithFormat); cleanupPartialVideoFiles(newThumbnailPath); cleanupSubtitleFiles(newSafeBaseFilename); }); @@ -487,7 +510,7 @@ export class YtDlpDownloader { } catch (error: any) { if (isCancellationError(error)) { console.log("Download was cancelled"); - cleanupPartialVideoFiles(newVideoPath); + cleanupPartialVideoFiles(newVideoPathWithFormat); cleanupSubtitleFiles(newSafeBaseFilename); throw DownloadCancelledError.create(); } @@ -501,7 +524,7 @@ export class YtDlpDownloader { if (isSubtitleError) { // Check if video file was successfully downloaded - if (fs.existsSync(newVideoPath)) { + if (fs.existsSync(newVideoPathWithFormat)) { console.warn( "Subtitle download failed, but video was downloaded successfully. Continuing...", error.message @@ -524,7 +547,7 @@ export class YtDlpDownloader { // Check if download was cancelled (it might have been removed from active downloads) if (!isDownloadActive(downloadId)) { console.log("Download was cancelled (no longer in active downloads)"); - cleanupPartialVideoFiles(newVideoPath); + cleanupPartialVideoFiles(newVideoPathWithFormat); cleanupSubtitleFiles(newSafeBaseFilename); throw DownloadCancelledError.create(); }