Normalize YouTube Shorts links to watch URLs

This commit is contained in:
Fisto
2026-09-17 13:34:10 +02:00
parent 52049bf186
commit 8c72def863
6 changed files with 197 additions and 8 deletions
+81 -1
View File
@@ -7,7 +7,7 @@ import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "n
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { fileURLToPath } from "node:url";
import { extractMedia } from "./extract.js";
import { extractMedia, normalizeYouTubeUrl } from "./extract.js";
import { aspectCondition, resolutionCondition } from "./filter-presets.js";
import { emptyMediaCounts, type CoreMessage, type MediaKind, type ScrapeMeta } from "./types.js";
@@ -281,6 +281,86 @@ export class ScrapeStore {
this.db.exec("ALTER TABLE media ADD COLUMN title_attempts INTEGER NOT NULL DEFAULT 0");
this.db.exec("ALTER TABLE media ADD COLUMN title_error TEXT");
}
this.migrateYouTubeShorts();
}
/**
* One-time rewrite of youtube.com/shorts links to watch URLs. The embed player needs `v=`,
* so old rows with a shorts URL would never play. `PRAGMA user_version` makes it idempotent
* per scrape database.
*/
private migrateYouTubeShorts(): void {
const version = this.db.prepare("PRAGMA user_version").get() as { user_version?: number } | undefined;
if ((version?.user_version ?? 0) >= 1) return;
const rows = this.db
.prepare(
"SELECT * FROM media WHERE url LIKE '%youtube.com/shorts/%' OR play_url LIKE '%youtube.com/shorts/%'"
)
.all() as unknown as MediaRow[];
if (rows.length) {
const update = this.db.prepare("UPDATE media SET url = ?, play_url = ? WHERE id = ?");
const findTarget = this.db.prepare("SELECT id FROM media WHERE url = ? AND id != ?");
const merge = this.db.prepare(
`UPDATE media SET
preview_hash = COALESCE(preview_hash, ?),
preview_bytes = COALESCE(preview_bytes, ?),
preview_ext = COALESCE(preview_ext, ?),
file_path = COALESCE(file_path, ?),
file_hash = COALESCE(file_hash, ?),
file_bytes = COALESCE(file_bytes, ?),
downloaded = MAX(downloaded, ?),
title = COALESCE(title, ?),
title_key = COALESCE(title_key, ?),
play_url = COALESCE(play_url, ?)
WHERE id = ?`
);
const relink = this.db.prepare(
"UPDATE OR IGNORE media_sources SET media_id = ? WHERE media_id = ?"
);
const dropLinks = this.db.prepare("DELETE FROM media_sources WHERE media_id = ?");
const drop = this.db.prepare("DELETE FROM media WHERE id = ?");
this.db.exec("BEGIN");
try {
for (const row of rows) {
const url = normalizeYouTubeUrl(row.url);
const playUrl = row.play_url ? normalizeYouTubeUrl(row.play_url) : null;
const target = findTarget.get(url, row.id) as { id: number } | undefined;
// The same video was already indexed under its watch URL: fold this row in.
if (!target) {
update.run(url, playUrl, row.id);
continue;
}
merge.run(
row.preview_hash,
row.preview_bytes,
row.preview_ext,
row.file_path,
row.file_hash,
row.file_bytes,
row.downloaded,
row.title,
row.title_key,
playUrl,
target.id
);
relink.run(target.id, row.id);
dropLinks.run(row.id);
drop.run(row.id);
}
this.db.exec("COMMIT");
} catch (error) {
this.db.exec("ROLLBACK");
throw error;
}
}
this.db.exec("PRAGMA user_version = 1");
}
close(): void {