diff --git a/backend/lib/ytdlp.js b/backend/lib/ytdlp.js index 1d2108d..9e63adf 100644 --- a/backend/lib/ytdlp.js +++ b/backend/lib/ytdlp.js @@ -77,11 +77,18 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb } } + // Strip release-type suffixes Spotify appends to track names (e.g. "recognize - Single" → "recognize"). + // Only strip trailing "- word(s)" when those words are release-type qualifiers or bare years, not + // real sub-titles (which tend to be longer phrases or start with "feat"/"ft"). + // Used for BOTH the filename ("01 - Artist - Title", no "- Single") and the YTM search query. + const RELEASE_SUFFIX_RE = /\s*[-–]\s*(?:(?:official\s+)?(?:music\s+)?(?:single|ep|lp|album|ost|soundtrack|remaster(?:ed)?|deluxe(?:\s+edition)?|bonus\s+tracks?|b-?sides?|version|edit)\b.*|(?:20|19)\d{2})\s*$/i; + const cleanTitle = title.replace(RELEASE_SUFFIX_RE, '').trim() || title; + // Folder / filename: romaji of the resolved artist (iTunes name if available — already in English, // otherwise kuromoji). rawArtist (original Japanese) is only used for tags + YTM search. const mainArtistRomaji = romajiForStorage(mainArtist) || mainArtist; const artistRomaji = romajiForStorage(artist) || artist; - const titleRomaji = romajiForStorage(title) || title; + const titleRomaji = romajiForStorage(cleanTitle) || cleanTitle; const albumRomaji = album ? (romajiForStorage(album) || album) : null; const safeArtist = sanitize(mainArtistRomaji.split(/[,;&]/)[0].trim()); const safeAlbum = sanitize(albumRomaji || 'Singles'); @@ -110,11 +117,7 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb const escRe = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - // Strip release-type suffixes Spotify appends to track names (e.g. "recognize - Single" → "recognize"). - // Only strip trailing "- word(s)" when those words are release-type qualifiers or bare years, not - // real sub-titles (which tend to be longer phrases or start with "feat"/"ft"). - const RELEASE_SUFFIX_RE = /\s*[-–]\s*(?:(?:official\s+)?(?:music\s+)?(?:single|ep|lp|album|ost|soundtrack|remaster(?:ed)?|deluxe(?:\s+edition)?|bonus\s+tracks?|b-?sides?|version|edit)\b.*|(?:20|19)\d{2})\s*$/i; - const searchTitle = title.replace(RELEASE_SUFFIX_RE, '').trim() || title; + const searchTitle = cleanTitle; const variantFilter = buildVariantFilter(searchTitle); const titleKws = buildTitleKeywords(searchTitle);