refactor: Improve video part skipping and processing

This commit is contained in:
Peifan Li
2025-12-21 13:44:43 -05:00
parent 8982c11e09
commit e4a34ac3ea
3 changed files with 109 additions and 23 deletions

View File

@@ -3,6 +3,7 @@ import { ValidationError } from "../errors/DownloadErrors";
import downloadManager from "../services/downloadManager";
import * as downloadService from "../services/downloadService";
import * as storageService from "../services/storageService";
import { DownloadResult } from "../services/downloaders/bilibili/types";
import {
extractBilibiliVideoId,
extractSourceVideoId,
@@ -342,26 +343,56 @@ export const downloadVideo = async (
const baseUrl = videoUrl.split("?")[0];
const firstPartUrl = `${baseUrl}?p=1`;
// Download the first part
const firstPartResult =
await downloadService.downloadSingleBilibiliPart(
firstPartUrl,
1,
videosNumber,
title || "Bilibili Video",
downloadId,
registerCancel
);
// Check if part 1 already exists
const existingPart1 = storageService.getVideoBySourceUrl(firstPartUrl);
let firstPartResult: DownloadResult;
// Add to collection if needed
if (collectionId && firstPartResult.videoData) {
storageService.atomicUpdateCollection(
collectionId,
(collection) => {
collection.videos.push(firstPartResult.videoData!.id);
return collection;
}
if (existingPart1) {
logger.info(
`Part 1/${videosNumber} already exists, skipping. Video ID: ${existingPart1.id}`
);
firstPartResult = {
success: true,
videoData: existingPart1,
};
// If we have a collection ID, make sure the existing video is in the collection
if (collectionId && existingPart1.id) {
const collection = storageService.getCollectionById(collectionId);
if (collection && !collection.videos.includes(existingPart1.id)) {
storageService.atomicUpdateCollection(
collectionId,
(collection) => {
if (!collection.videos.includes(existingPart1.id)) {
collection.videos.push(existingPart1.id);
}
return collection;
}
);
}
}
} else {
// Download the first part
firstPartResult =
await downloadService.downloadSingleBilibiliPart(
firstPartUrl,
1,
videosNumber,
title || "Bilibili Video",
downloadId,
registerCancel
);
// Add to collection if needed
if (collectionId && firstPartResult.videoData) {
storageService.atomicUpdateCollection(
collectionId,
(collection) => {
collection.videos.push(firstPartResult.videoData!.id);
return collection;
}
);
}
}
// Set up background download for remaining parts

View File

@@ -306,9 +306,51 @@ export async function downloadRemainingParts(
}
let successCount = 0;
let skippedCount = 0;
let failedParts: number[] = [];
let skippedParts: number[] = [];
for (let part = startPart; part <= totalParts; part++) {
// Construct URL for this part
const partUrl = `${baseUrl}?p=${part}`;
// Check if this part already exists
const existingVideo = storageService.getVideoBySourceUrl(partUrl);
if (existingVideo) {
skippedCount++;
skippedParts.push(part);
logger.info(
`Part ${part}/${totalParts} already exists, skipping. Video ID: ${existingVideo.id}`
);
// If we have a collection ID, make sure the existing video is in the collection
if (collectionId && existingVideo.id) {
try {
const collection = storageService.getCollectionById(collectionId);
if (collection && !collection.videos.includes(existingVideo.id)) {
storageService.atomicUpdateCollection(
collectionId,
(collection: Collection) => {
if (!collection.videos.includes(existingVideo.id)) {
collection.videos.push(existingVideo.id);
}
return collection;
}
);
logger.info(
`Added existing part ${part}/${totalParts} to collection ${collectionId}`
);
}
} catch (collectionError) {
logger.error(
`Error adding existing part ${part}/${totalParts} to collection:`,
collectionError
);
}
}
continue;
}
logger.info(`Starting download of part ${part}/${totalParts}`);
// Update status to show which part is being downloaded
if (downloadId) {
@@ -318,9 +360,6 @@ export async function downloadRemainingParts(
);
}
// Construct URL for this part
const partUrl = `${baseUrl}?p=${part}`;
// Download this part
const result = await downloadSinglePart(
partUrl,
@@ -372,13 +411,19 @@ export async function downloadRemainingParts(
// Log appropriate message based on results
const remainingPartsCount = totalParts - startPart + 1;
if (failedParts.length === 0) {
const totalProcessed = successCount + skippedCount;
if (failedParts.length === 0 && skippedParts.length === 0) {
logger.info(
`All remaining parts (${startPart}-${totalParts}) of "${seriesTitle}" downloaded successfully`
);
} else if (failedParts.length === 0) {
logger.info(
`Processed ${totalProcessed}/${remainingPartsCount} remaining parts (${startPart}-${totalParts}) of "${seriesTitle}". Downloaded: ${successCount}, Skipped (already exist): ${skippedCount} (parts: ${skippedParts.join(", ")})`
);
} else {
logger.warn(
`Downloaded ${successCount}/${remainingPartsCount} remaining parts (${startPart}-${totalParts}) of "${seriesTitle}". Failed parts: ${failedParts.join(", ")}`
`Processed ${totalProcessed}/${remainingPartsCount} remaining parts (${startPart}-${totalParts}) of "${seriesTitle}". Downloaded: ${successCount}, Skipped: ${skippedCount} (parts: ${skippedParts.join(", ")}), Failed: ${failedParts.length} (parts: ${failedParts.join(", ")})`
);
}
} catch (error) {

View File

@@ -84,6 +84,16 @@ export function getVideoById(id: string): import("./types").Video | undefined {
}
}
/**
* Check if a video part already exists by sourceUrl
* Returns the existing video if found, undefined otherwise
*/
export function getVideoPartBySourceUrl(
sourceUrl: string
): import("./types").Video | undefined {
return getVideoBySourceUrl(sourceUrl);
}
/**
* Format legacy filenames to the new standard format: Title-Author-YYYY
*/