diff options
| author | Fuwn <[email protected]> | 2026-02-12 07:17:13 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-12 07:17:13 -0800 |
| commit | 9a389a504727f367a1157e8209b45b00aeda3c07 (patch) | |
| tree | 1bc34c927736ddd728f3732d77b364b54340ad78 /src | |
| parent | perf(fonts): Move font loading to HTML for faster FCP (diff) | |
| download | due.moe-9a389a504727f367a1157e8209b45b00aeda3c07.tar.xz due.moe-9a389a504727f367a1157e8209b45b00aeda3c07.zip | |
perf(schedule): Reduce redundant work in title matching
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/Media/Anime/Airing/Subtitled/match.ts | 102 |
1 files changed, 59 insertions, 43 deletions
diff --git a/src/lib/Media/Anime/Airing/Subtitled/match.ts b/src/lib/Media/Anime/Airing/Subtitled/match.ts index dd2f45e6..fb494539 100644 --- a/src/lib/Media/Anime/Airing/Subtitled/match.ts +++ b/src/lib/Media/Anime/Airing/Subtitled/match.ts @@ -60,35 +60,36 @@ const calculateWeightedSimilarity = (title1: string, title2: string): number => export const findClosestMatch = (times: Time[], anime: Media): Time | null => { if (excludeMatch.includes(anime.id)) return null; + const airingDay = new Date((anime.nextAiringEpisode?.airingAt || 0) * 1000).toLocaleString( + 'en-US', + { weekday: 'long' } + ); + const dayTimes = times.filter((time) => time.day === airingDay); + + if (dayTimes.length === 0) return null; + + const preprocessedTimes = dayTimes.map((time) => ({ + time, + normalized: preprocessTitle(time.title) + })); let bestMatch: Time | null = null; let bestScore = 0; + const searchTitles = [anime.title.romaji, anime.title.english, ...anime.synonyms].filter(Boolean); - [anime.title.romaji, anime.title.english, ...anime.synonyms] - .filter(Boolean) - .forEach((searchTitle) => { - if (searchTitle.includes('OVA') || searchTitle.includes('Special')) return; - - const normalizedSearchTitle = preprocessTitle(searchTitle); - - times.forEach((time) => { - const normalizedTimeTitle = preprocessTitle(time.title); - const similarityScore = calculateWeightedSimilarity( - normalizedSearchTitle, - normalizedTimeTitle - ); - - if ( - similarityScore > bestScore && - time.day === - new Date((anime.nextAiringEpisode?.airingAt || 0) * 1000).toLocaleString('en-US', { - weekday: 'long' - }) - ) { - bestScore = similarityScore; - bestMatch = time; - } - }); - }); + for (const searchTitle of searchTitles) { + if (searchTitle.includes('OVA') || searchTitle.includes('Special')) continue; + + const normalizedSearchTitle = preprocessTitle(searchTitle); + + for (const { time, normalized } of preprocessedTimes) { + const similarityScore = calculateWeightedSimilarity(normalizedSearchTitle, normalized); + + if (similarityScore > bestScore) { + bestScore = similarityScore; + bestMatch = time; + } + } + } return bestMatch; }; @@ -100,13 +101,21 @@ const normalizeTitle = (title: string | null) => .replace(/[\W_]+/g, ' ') .trim(); +const findClosestMediaCache = new Map<string, Media | null>(); + export const findClosestMedia = (media: Media[], matchFor: string) => { if (!matchFor) return null; + const cached = findClosestMediaCache.get(matchFor); + + if (cached !== undefined) return cached; + + const normalisedMatchFor = normalizeTitle(matchFor); + const matchForWords = normalisedMatchFor.split(' '); let bestFitMedia: Media | null = null; - let smallestDistance = -Infinity; + let bestDistance = -Infinity; - media.forEach((m) => { + for (const m of media) { const titles = [m.title.romaji, m.title.english, ...m.synonyms].filter(Boolean); if ( @@ -114,30 +123,37 @@ export const findClosestMedia = (media: Media[], matchFor: string) => { (title) => title.toLowerCase().includes('special') || title.toLowerCase().includes('ova') ) ) - return; + continue; + + const normalisedTitles = titles.map(normalizeTitle); - titles.forEach((title) => { - const normalisedTitle = normalizeTitle(title); - const normalisedMatchFor = normalizeTitle(matchFor); + for (const normalisedTitle of normalisedTitles) { const distance = stringSimilarity.compareTwoStrings(normalisedMatchFor, normalisedTitle); - if ( - distance > smallestDistance && - (normalisedMatchFor - .split(' ') - .filter((word) => titles.some((title) => normalizeTitle(title).includes(word))).length >= - normalisedMatchFor.split(' ').length || - titles.some((title) => normalizeTitle(title).includes(normalisedMatchFor))) - ) { - smallestDistance = distance; + if (distance <= bestDistance) continue; + + const wordMatch = + matchForWords.every((word) => normalisedTitles.some((t) => t.includes(word))) || + normalisedTitles.some((t) => t.includes(normalisedMatchFor)); + + if (wordMatch) { + bestDistance = distance; bestFitMedia = m; + + if (distance === 1) break; } - }); - }); + } + + if (bestDistance === 1) break; + } + + findClosestMediaCache.set(matchFor, bestFitMedia); return bestFitMedia as Media | null; }; +export const clearClosestMediaCache = () => findClosestMediaCache.clear(); + export const injectAiringTime = (anime: Media, subsPlease: SubsPlease | null) => { if (season() !== anime.season) return anime; |