From 48e3821ed30e46d508d97d458710ca8a10ebc7ff Mon Sep 17 00:00:00 2001 From: Peifan Li Date: Mon, 8 Dec 2025 13:42:49 -0500 Subject: [PATCH] feat: Add downloadId parameter for progress monitoring --- backend/src/controllers/videoController.ts | 6 +- backend/src/services/downloadService.ts | 10 ++- .../downloaders/BilibiliDownloader.ts | 89 +++++++++++++++++-- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/backend/src/controllers/videoController.ts b/backend/src/controllers/videoController.ts index 70c2147..d48c2c6 100644 --- a/backend/src/controllers/videoController.ts +++ b/backend/src/controllers/videoController.ts @@ -198,7 +198,8 @@ export const downloadVideo = async ( firstPartUrl, 1, videosNumber, - title || "Bilibili Video" + title || "Bilibili Video", + downloadId ); // Add to collection if needed @@ -240,7 +241,8 @@ export const downloadVideo = async ( videoUrl, 1, 1, - "" // seriesTitle not used when totalParts is 1 + "", // seriesTitle not used when totalParts is 1 + downloadId ); if (result.success) { diff --git a/backend/src/services/downloadService.ts b/backend/src/services/downloadService.ts index 00df8e7..6e1f8ba 100644 --- a/backend/src/services/downloadService.ts +++ b/backend/src/services/downloadService.ts @@ -21,9 +21,10 @@ export type { export async function downloadBilibiliVideo( url: string, videoPath: string, - thumbnailPath: string + thumbnailPath: string, + downloadId?: string ): Promise { - return BilibiliDownloader.downloadVideo(url, videoPath, thumbnailPath); + return BilibiliDownloader.downloadVideo(url, videoPath, thumbnailPath, downloadId); } // Helper function to check if a Bilibili video has multiple parts @@ -51,9 +52,10 @@ export async function downloadSingleBilibiliPart( url: string, partNumber: number, totalParts: number, - seriesTitle: string + seriesTitle: string, + downloadId?: string ): Promise { - return BilibiliDownloader.downloadSinglePart(url, partNumber, totalParts, seriesTitle); + return BilibiliDownloader.downloadSinglePart(url, partNumber, totalParts, seriesTitle, downloadId); } // Helper function to download all videos from a Bilibili collection or series diff --git a/backend/src/services/downloaders/BilibiliDownloader.ts b/backend/src/services/downloaders/BilibiliDownloader.ts index cc73585..f524609 100644 --- a/backend/src/services/downloaders/BilibiliDownloader.ts +++ b/backend/src/services/downloaders/BilibiliDownloader.ts @@ -88,11 +88,21 @@ export class BilibiliDownloader { } } + // Helper function to format bytes + private static formatBytes(bytes: number): string { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KiB", "MiB", "GiB", "TiB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]; + } + // Helper function to download Bilibili video static async downloadVideo( url: string, videoPath: string, - thumbnailPath: string + thumbnailPath: string, + downloadId?: string ): Promise { const tempDir = path.join(VIDEOS_DIR, `temp_${Date.now()}_${Math.floor(Math.random() * 10000)}`); @@ -102,6 +112,52 @@ export class BilibiliDownloader { console.log("Downloading Bilibili video to temp directory:", tempDir); + // Start monitoring progress + let progressInterval: NodeJS.Timeout | undefined; + if (downloadId) { + let lastSize = 0; + let lastUpdateTime = Date.now(); + + progressInterval = setInterval(() => { + try { + const files = fs.readdirSync(tempDir); + const videoFile = files.find((file: string) => file.endsWith(".mp4")); + + if (videoFile) { + const filePath = path.join(tempDir, videoFile); + if (fs.existsSync(filePath)) { + const stats = fs.statSync(filePath); + const currentSize = stats.size; + const currentTime = Date.now(); + const timeDiff = (currentTime - lastUpdateTime) / 1000; // seconds + + if (timeDiff > 0 && currentSize > lastSize) { + // Calculate speed (bytes per second) + const bytesPerSecond = (currentSize - lastSize) / timeDiff; + + // Format speed + const speedStr = this.formatBytes(bytesPerSecond) + "/s"; + + // Format downloaded size + const downloadedSizeStr = this.formatBytes(currentSize); + + // Update progress + storageService.updateActiveDownload(downloadId, { + downloadedSize: downloadedSizeStr, + speed: speedStr, + }); + } + + lastSize = currentSize; + lastUpdateTime = currentTime; + } + } + } catch (error) { + // Ignore errors during monitoring + } + }, 500); + } + // Download the video using the package await downloadByVedioPath({ url: url, @@ -109,6 +165,11 @@ export class BilibiliDownloader { folder: tempDir, }); + // Stop progress monitoring + if (progressInterval) { + clearInterval(progressInterval); + } + console.log("Download completed, checking for video file"); // Find the downloaded file @@ -123,8 +184,20 @@ export class BilibiliDownloader { console.log("Found video file:", videoFile); - // Move the file to the desired location + // Get final file size for progress update const tempVideoPath = path.join(tempDir, videoFile); + if (downloadId && fs.existsSync(tempVideoPath)) { + const stats = fs.statSync(tempVideoPath); + const finalSize = this.formatBytes(stats.size); + storageService.updateActiveDownload(downloadId, { + downloadedSize: finalSize, + totalSize: finalSize, + progress: 100, + speed: "0 B/s", + }); + } + + // Move the file to the desired location fs.moveSync(tempVideoPath, videoPath, { overwrite: true }); console.log("Moved video file to:", videoPath); @@ -424,7 +497,8 @@ export class BilibiliDownloader { url: string, partNumber: number, totalParts: number, - seriesTitle: string + seriesTitle: string, + downloadId?: string ): Promise { try { console.log( @@ -451,7 +525,8 @@ export class BilibiliDownloader { const bilibiliInfo = await BilibiliDownloader.downloadVideo( url, videoPath, - thumbnailPath + thumbnailPath, + downloadId ); if (!bilibiliInfo) { @@ -642,7 +717,8 @@ export class BilibiliDownloader { videoUrl, videoNumber, videos.length, - title || "Collection" + title || "Collection", + downloadId ); // If download was successful, add to collection @@ -721,7 +797,8 @@ export class BilibiliDownloader { partUrl, part, totalParts, - seriesTitle + seriesTitle, + downloadId ); // If download was successful and we have a collection ID, add to collection