download job: also write a reference sidecar for FAILED items
A failed download has no media file, so its description was lost — no way to tell which video was missed. Now a failure writes a `<title>.txt` (or `<videoId>.txt` when untitled) holding the URL + description, so every missed item leaves a recoverable reference. Always written (even with an empty description — the URL is the reference); skipped only on a deliberate Stop, not a genuine error. Verified: successful item → "<media base>.txt" (description); failed item → "<title>.txt" (url + description); spaces preserved in both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,26 @@ async function writeDescriptionSidecar(dir: string, mediaFilename: string, descr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FS_ILLEGAL = /[/\\:*?"<>|\u0000-\u001f]/g;
|
||||||
|
const safeName = (s: string) => s.replace(FS_ILLEGAL, '_').replace(/\s+/g, ' ').trim().slice(0, 180);
|
||||||
|
|
||||||
|
// On a FAILED download there's no media file to pair with — still drop a reference so the missed video is
|
||||||
|
// recoverable: a `<title>.txt` (or `<videoId>.txt` when untitled) holding the URL + the description. Always
|
||||||
|
// written (even with an empty description — the URL is the reference).
|
||||||
|
async function writeMissedSidecar(
|
||||||
|
dir: string,
|
||||||
|
item: { url: string; title: string; description: string },
|
||||||
|
): Promise<void> {
|
||||||
|
const vid = item.url.match(/[?&]v=([\w-]+)/)?.[1];
|
||||||
|
const name = safeName(item.title) || vid || 'missed';
|
||||||
|
const content = [item.url, item.description.trim()].filter(Boolean).join('\n\n');
|
||||||
|
try {
|
||||||
|
await writeFile(join(dir, `${name}.txt`), content, 'utf8');
|
||||||
|
} catch {
|
||||||
|
/* best-effort */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The download-job executor — pure scripting, no agent. Two phases:
|
// The download-job executor — pure scripting, no agent. Two phases:
|
||||||
// 1. metadata — fetch each item's info (title + validity); keep the ones that resolve, skip the errors
|
// 1. metadata — fetch each item's info (title + validity); keep the ones that resolve, skip the errors
|
||||||
// (private / deleted / unavailable). The title is required: ReClip names the output file
|
// (private / deleted / unavailable). The title is required: ReClip names the output file
|
||||||
@@ -132,6 +152,7 @@ export async function executeDownload(params: ExecuteDownloadParams): Promise<vo
|
|||||||
} catch {
|
} catch {
|
||||||
checkAbort(); // an abort surfaces as a throw here — re-check so it stops instead of counting as a skip
|
checkAbort(); // an abort surfaces as a throw here — re-check so it stops instead of counting as a skip
|
||||||
progress.dl.failed++;
|
progress.dl.failed++;
|
||||||
|
void writeMissedSidecar(absDir, item); // no media file — leave a reference to what we missed
|
||||||
}
|
}
|
||||||
emit();
|
emit();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user