diff --git a/scripts/backfill_uuid_filenames.mjs b/scripts/backfill_uuid_filenames.mjs index 1256ac1..ad72faf 100644 --- a/scripts/backfill_uuid_filenames.mjs +++ b/scripts/backfill_uuid_filenames.mjs @@ -83,7 +83,7 @@ async function run() { // 1. Process items console.log('[BACKFILL] Fetching items to process...'); - const items = await db`SELECT id, dest FROM items`; + const items = await db`SELECT id, dest, mime FROM items`; console.log(`[BACKFILL] Found ${items.length} items in DB.`); let itemsProcessed = 0; @@ -93,6 +93,11 @@ async function run() { if (limit && itemsProcessed >= limit) break; itemsProcessed++; + // YouTube embeds store dest as "yt:VIDEO_ID" — not a real file, skip entirely + if (item.mime === 'video/youtube') { + continue; + } + const ext = path.extname(item.dest); const base = path.basename(item.dest, ext); diff --git a/scripts/fix_youtube_dest.mjs b/scripts/fix_youtube_dest.mjs new file mode 100644 index 0000000..2c4dd70 --- /dev/null +++ b/scripts/fix_youtube_dest.mjs @@ -0,0 +1,83 @@ +/** + * fix_youtube_dest.mjs + * + * One-time fix: the backfill_uuid_filenames.mjs script failed to exclude + * YouTube items, which store dest as "yt:VIDEO_ID" rather than a real file. + * As a result, those dest values were replaced with random UUIDs, breaking + * embed URLs like youtube.com/embed/. + * + * This script: + * 1. Finds all items with mime = 'video/youtube' + * 2. For each, re-extracts the video ID from the src column + * (src holds the original YouTube watch URL: https://www.youtube.com/watch?v=VIDEO_ID) + * 3. Restores dest to "yt:VIDEO_ID" + * + * Usage: + * node scripts/fix_youtube_dest.mjs # live run + * node scripts/fix_youtube_dest.mjs --dry-run # preview only, no DB changes + */ + +import db from '../src/inc/sql.mjs'; + +const isDryRun = process.argv.includes('--dry-run'); + +const ytRegex = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\/?\/?\?(?:\S*?&?v=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/i; + +async function run() { + console.log(`[FIX-YT-DEST] Starting${isDryRun ? ' (DRY RUN)' : ''}...`); + + const rows = await db` + SELECT id, dest, src + FROM items + WHERE mime = 'video/youtube' + ORDER BY id + `; + + console.log(`[FIX-YT-DEST] Found ${rows.length} YouTube item(s) to check.`); + + let fixed = 0; + let skipped = 0; + let failed = 0; + + for (const row of rows) { + // Already correct format + if (row.dest && row.dest.startsWith('yt:')) { + skipped++; + continue; + } + + // Try to extract video ID from src URL + const match = row.src && row.src.match(ytRegex); + if (!match) { + console.warn(`[FIX-YT-DEST] [WARN] Item ${row.id}: cannot extract video ID from src="${row.src}", dest="${row.dest}" — skipping`); + failed++; + continue; + } + + const videoId = match[1]; + const newDest = `yt:${videoId}`; + + console.log(`[FIX-YT-DEST] Item ${row.id}: "${row.dest}" → "${newDest}" (src: ${row.src})`); + + if (!isDryRun) { + await db`UPDATE items SET dest = ${newDest} WHERE id = ${row.id}`; + } + fixed++; + } + + console.log(`\n[FIX-YT-DEST] Done.`); + console.log(` Fixed: ${fixed}`); + console.log(` Skipped: ${skipped} (already correct)`); + console.log(` Failed: ${failed} (no video ID recoverable from src)`); + + if (isDryRun) { + console.log('\n[FIX-YT-DEST] DRY RUN — no changes were written to the database.'); + } + + process.exit(0); +} + +run().catch(err => { + console.error('[FIX-YT-DEST] Fatal error:', err); + process.exit(1); +});