aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-12 07:17:13 -0800
committerFuwn <[email protected]>2026-02-12 07:17:13 -0800
commit9a389a504727f367a1157e8209b45b00aeda3c07 (patch)
tree1bc34c927736ddd728f3732d77b364b54340ad78
parentperf(fonts): Move font loading to HTML for faster FCP (diff)
downloaddue.moe-9a389a504727f367a1157e8209b45b00aeda3c07.tar.xz
due.moe-9a389a504727f367a1157e8209b45b00aeda3c07.zip
perf(schedule): Reduce redundant work in title matching
-rw-r--r--src/lib/Media/Anime/Airing/Subtitled/match.ts102
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;