@@ -466,13 +466,11 @@ func generateSearchVariations(cleanedName string) []string {
466466 }
467467
468468 // Variation: Remove trailing roman numerals (seasons like II, III, IV)
469- reRoman := regexp .MustCompile (`\s+(?:II|III|IV|V|VI|VII|VIII|IX|X)\s*$` )
470469 if match := reRoman .FindString (cleanedName ); match != "" {
471470 addVariation (strings .TrimSpace (reRoman .ReplaceAllString (cleanedName , "" )))
472471 }
473472
474473 // Variation: Remove trailing numbers that might be season indicators (2, 3, 4, etc.)
475- reTrailingNum := regexp .MustCompile (`\s+\d+\s*$` )
476474 if match := reTrailingNum .FindString (cleanedName ); match != "" {
477475 addVariation (strings .TrimSpace (reTrailingNum .ReplaceAllString (cleanedName , "" )))
478476 }
@@ -491,7 +489,6 @@ func generateSearchVariations(cleanedName string) []string {
491489
492490 // Variation: Remove common PT-BR descriptive suffixes (Clássico, Classic, etc.)
493491 // These are used by Brazilian sites to distinguish series (e.g. "Naruto Clássico" vs "Naruto Shippuden")
494- rePtBRSuffix := regexp .MustCompile (`(?i)\s+(?:cl[aá]ssico|classic|shippuuden|next\s+generations?)\s*$` )
495492 if rePtBRSuffix .MatchString (cleanedName ) {
496493 addVariation (strings .TrimSpace (rePtBRSuffix .ReplaceAllString (cleanedName , "" )))
497494 }
@@ -515,96 +512,110 @@ func generateSearchVariations(cleanedName string) []string {
515512 return variations
516513}
517514
515+ // Compiled once at init: CleanTitle and generateSearchVariations run per search
516+ // result, so recompiling these on every call dominated the search hot path.
517+ var (
518+ // generateSearchVariations patterns
519+ reRoman = regexp .MustCompile (`\s+(?:II|III|IV|V|VI|VII|VIII|IX|X)\s*$` )
520+ reTrailingNum = regexp .MustCompile (`\s+\d+\s*$` )
521+ rePtBRSuffix = regexp .MustCompile (`(?i)\s+(?:cl[aá]ssico|classic|shippuuden|next\s+generations?)\s*$` )
522+
523+ // CleanTitle patterns, applied in order
524+ reMediaTags = regexp .MustCompile (`^\s*\[(?:Movies?(?:/TV)?|TV|Anime|Series|Show)\]\s*` )
525+ reLangTags = regexp .MustCompile (`^\s*\[(?:English|PT-BR|Portuguese|Português|Japonês|Japanese|Multilanguage)\]\s*` )
526+ reSourceTag = regexp .MustCompile (`(?i)[🔥🌐]?\[(?:animefire|allanime|animedrive|9anime)\]\s*` )
527+ reEmDash = regexp .MustCompile (`\s*[–—]\s+.*$` )
528+ // For the regular hyphen ( - ) we cannot strip blindly — it appears inside legitimate
529+ // titles such as "Jujutsu Kaisen: Shimetsu Kaiyuu - Zenpen" / "- Kouhen" (前編/後編,
530+ // Part 1 / Part 2 on AniList). Only strip when the suffix matches known PT-BR noise
531+ // or season/episode markers; otherwise keep the dash and what follows so AniList
532+ // resolves the correct entry.
533+ reSpaceDashNoise = regexp .MustCompile (`(?i)\s+-\s+(?:` +
534+ `dublado|legendado|dual\s*[aá]udio|dub|sub|completo|` +
535+ `todos\s+os\s+epis[oó]dios?|` +
536+ `epis[oó]dios?\s*\d+|ep\s*\d+|` +
537+ `\d+[ªº]?\s*temporada|temporada\s*\d*|` +
538+ `season\s*\d+|\d+(?:st|nd|rd|th)\s*season|` +
539+ `parte\s*\d+|part\s*\d+|` +
540+ `allanime|animefire|animedrive|9anime|goyabu|superflix|flixhq|sflix` +
541+ `).*$` )
542+ reLangParens = regexp .MustCompile (`(?i)\s*\([^)]*(?:dublado|legendado|dub|sub)[^)]*\)` )
543+ reLangSuffix = regexp .MustCompile (`(?i)\s+(?:dublado|legendado|dub|sub|dual\s*[aá]udio)\s*$` )
544+ reTodosEps = regexp .MustCompile (`(?i)[-–—]?\s*todos\s+os\s+epis[oó]dios` )
545+ reCompleto = regexp .MustCompile (`(?i)\s+(?:completo|episodio\s*\d+|ep\s*\d+)\s*$` )
546+ reSeasonPt = regexp .MustCompile (`(?i)\s*[-–—]?\s*(?:\d+[ªº]?\s*temporada|temporada\s*\d+|season\s*\d+|\d+(?:st|nd|rd|th)\s*season)\s*$` )
547+ rePart = regexp .MustCompile (`(?i)\s*[-–—]?\s*(?:parte\s*\d+|part\s*\d+)\s*$` )
548+ reSeasonEpTag = regexp .MustCompile (`\s+\d+(\.\d+)?\s+A\d+\s*$` )
549+ reDecimalSuffix = regexp .MustCompile (`\s+\d+\.\d+\s*$` )
550+ reEpCount = regexp .MustCompile (`(?i)\s*\(\d+\s+(?:episodes?|eps?|epis[oó]dios?)\)` )
551+ re9AnimeEpInfo = regexp .MustCompile (`(?i)\s*\((?:HD\s+)?(?:(?:SUB|DUB)\s+)*Ep\s+\d+/\d+\)` )
552+ reSpecialTitles = regexp .MustCompile (`(?i):\s*(?:Jump Festa \d+|The All Magic Knights|Sword of the Wizard King|Mahou Tei no Ken).*$` )
553+ reNASuffix = regexp .MustCompile (`(?i)\s+N/A\s*$` )
554+ reEmptyParens = regexp .MustCompile (`\s*\(\s*\)` )
555+ reHyphenLetters = regexp .MustCompile (`([a-zA-Z])-([a-zA-Z])` )
556+ reWhitespaceRun = regexp .MustCompile (`\s+` )
557+ )
558+
518559func CleanTitle (title string ) string {
519560 cleaned := title
520561
521562 // Remove media type tags like [Movies/TV], [Anime], [Series], [Movie] at the start
522- reMediaTags := regexp .MustCompile (`^\s*\[(?:Movies?(?:/TV)?|TV|Anime|Series|Show)\]\s*` )
523563 cleaned = strings .TrimSpace (reMediaTags .ReplaceAllString (cleaned , "" ))
524564
525565 // Remove language tags like [English], [PT-BR], [Portuguese], [Português], [Multilanguage] at the start
526- reLangTags := regexp .MustCompile (`^\s*\[(?:English|PT-BR|Portuguese|Português|Japonês|Japanese|Multilanguage)\]\s*` )
527566 cleaned = strings .TrimSpace (reLangTags .ReplaceAllString (cleaned , "" ))
528567
529568 // Remove source tags like 🔥[AnimeFire], 🌐[AllAnime], [AnimeDrive], or [9Anime]
530- re1 := regexp .MustCompile (`(?i)[🔥🌐]?\[(?:animefire|allanime|animedrive|9anime)\]\s*` )
531- cleaned = strings .TrimSpace (re1 .ReplaceAllString (cleaned , "" ))
569+ cleaned = strings .TrimSpace (reSourceTag .ReplaceAllString (cleaned , "" ))
532570
533571 // Remove everything after em-dash or en-dash (typically subtitles like "– Todos os Episódios").
534572 // Em/en dashes are almost always used as noise separators in PT-BR scrapers.
535- reEmDash := regexp .MustCompile (`\s*[–—]\s+.*$` )
536573 cleaned = strings .TrimSpace (reEmDash .ReplaceAllString (cleaned , "" ))
537574
538- // For the regular hyphen ( - ) we cannot strip blindly — it appears inside legitimate
539- // titles such as "Jujutsu Kaisen: Shimetsu Kaiyuu - Zenpen" / "- Kouhen" (前編/後編,
540- // Part 1 / Part 2 on AniList). Only strip when the suffix matches known PT-BR noise
541- // or season/episode markers; otherwise keep the dash and what follows so AniList
542- // resolves the correct entry.
543- reSpaceDashNoise := regexp .MustCompile (`(?i)\s+-\s+(?:` +
544- `dublado|legendado|dual\s*[aá]udio|dub|sub|completo|` +
545- `todos\s+os\s+epis[oó]dios?|` +
546- `epis[oó]dios?\s*\d+|ep\s*\d+|` +
547- `\d+[ªº]?\s*temporada|temporada\s*\d*|` +
548- `season\s*\d+|\d+(?:st|nd|rd|th)\s*season|` +
549- `parte\s*\d+|part\s*\d+|` +
550- `allanime|animefire|animedrive|9anime|goyabu|superflix|flixhq|sflix` +
551- `).*$` )
575+ // Strip " - <noise>" suffixes (see reSpaceDashNoise for why plain hyphens
576+ // can't be stripped blindly).
552577 cleaned = strings .TrimSpace (reSpaceDashNoise .ReplaceAllString (cleaned , "" ))
553578
554579 // Remove content in parentheses if it contains language info (do this BEFORE removing standalone language indicators)
555- re6 := regexp .MustCompile (`(?i)\s*\([^)]*(?:dublado|legendado|dub|sub)[^)]*\)` )
556- cleaned = strings .TrimSpace (re6 .ReplaceAllString (cleaned , "" ))
580+ cleaned = strings .TrimSpace (reLangParens .ReplaceAllString (cleaned , "" ))
557581
558582 // Remove standalone language indicators (not in parentheses) - more comprehensive for Brazilian sources
559- re2 := regexp .MustCompile (`(?i)\s+(?:dublado|legendado|dub|sub|dual\s*[aá]udio)\s*$` )
560- cleaned = strings .TrimSpace (re2 .ReplaceAllString (cleaned , "" ))
583+ cleaned = strings .TrimSpace (reLangSuffix .ReplaceAllString (cleaned , "" ))
561584
562585 // Remove "Todos os Episodios" and similar Brazilian phrases (in case em-dash removal didn't catch it)
563- re3 := regexp .MustCompile (`(?i)[-–—]?\s*todos\s+os\s+epis[oó]dios` )
564- cleaned = strings .TrimSpace (re3 .ReplaceAllString (cleaned , "" ))
586+ cleaned = strings .TrimSpace (reTodosEps .ReplaceAllString (cleaned , "" ))
565587
566588 // Remove "Completo" or "Episodio X" suffixes common in Brazilian sources
567- reCompleto := regexp .MustCompile (`(?i)\s+(?:completo|episodio\s*\d+|ep\s*\d+)\s*$` )
568589 cleaned = strings .TrimSpace (reCompleto .ReplaceAllString (cleaned , "" ))
569590
570591 // Remove season indicators like "X Temporada", "Season X", "Temporada X", "Xª Temporada"
571- reSeasonPt := regexp .MustCompile (`(?i)\s*[-–—]?\s*(?:\d+[ªº]?\s*temporada|temporada\s*\d+|season\s*\d+|\d+(?:st|nd|rd|th)\s*season)\s*$` )
572592 cleaned = strings .TrimSpace (reSeasonPt .ReplaceAllString (cleaned , "" ))
573593
574594 // Remove "Parte X" (Part X) common in Brazilian titles
575- rePart := regexp .MustCompile (`(?i)\s*[-–—]?\s*(?:parte\s*\d+|part\s*\d+)\s*$` )
576595 cleaned = strings .TrimSpace (rePart .ReplaceAllString (cleaned , "" ))
577596
578597 // Remove season/episode indicators like "2.0 A2" at the end (but NOT plain season numbers)
579- re4 := regexp .MustCompile (`\s+\d+(\.\d+)?\s+A\d+\s*$` )
580- cleaned = strings .TrimSpace (re4 .ReplaceAllString (cleaned , "" ))
598+ cleaned = strings .TrimSpace (reSeasonEpTag .ReplaceAllString (cleaned , "" ))
581599
582600 // Remove decimal version numbers at the end like "3.5" (but NOT "Season 2")
583- re5 := regexp .MustCompile (`\s+\d+\.\d+\s*$` )
584- cleaned = strings .TrimSpace (re5 .ReplaceAllString (cleaned , "" ))
601+ cleaned = strings .TrimSpace (reDecimalSuffix .ReplaceAllString (cleaned , "" ))
585602
586603 // Remove episode count like "(171 episodes)" or "(1 eps)" or Portuguese equivalents
587- re7 := regexp .MustCompile (`(?i)\s*\(\d+\s+(?:episodes?|eps?|epis[oó]dios?)\)` )
588- cleaned = strings .TrimSpace (re7 .ReplaceAllString (cleaned , "" ))
604+ cleaned = strings .TrimSpace (reEpCount .ReplaceAllString (cleaned , "" ))
589605
590606 // Remove 9Anime-style episode info like "(HD SUB DUB Ep 170/170)" or "(SUB Ep 12/12)"
591- re9anime := regexp .MustCompile (`(?i)\s*\((?:HD\s+)?(?:(?:SUB|DUB)\s+)*Ep\s+\d+/\d+\)` )
592- cleaned = strings .TrimSpace (re9anime .ReplaceAllString (cleaned , "" ))
607+ cleaned = strings .TrimSpace (re9AnimeEpInfo .ReplaceAllString (cleaned , "" ))
593608
594609 // Remove special titles and additions after colon
595- re8 := regexp .MustCompile (`(?i):\s*(?:Jump Festa \d+|The All Magic Knights|Sword of the Wizard King|Mahou Tei no Ken).*$` )
596- cleaned = strings .TrimSpace (re8 .ReplaceAllString (cleaned , "" ))
610+ cleaned = strings .TrimSpace (reSpecialTitles .ReplaceAllString (cleaned , "" ))
597611
598612 // Remove N/A ratings and similar suffixes
599- re9 := regexp .MustCompile (`(?i)\s+N/A\s*$` )
600- cleaned = strings .TrimSpace (re9 .ReplaceAllString (cleaned , "" ))
613+ cleaned = strings .TrimSpace (reNASuffix .ReplaceAllString (cleaned , "" ))
601614
602615 // Remove rating scores like "7.12" or "8.5" at the end (only decimal numbers)
603- re10 := regexp .MustCompile (`\s+\d+\.\d+\s*$` )
604- cleaned = strings .TrimSpace (re10 .ReplaceAllString (cleaned , "" ))
616+ cleaned = strings .TrimSpace (reDecimalSuffix .ReplaceAllString (cleaned , "" ))
605617
606618 // Remove empty parentheses that may be left after other cleanups
607- reEmptyParens := regexp .MustCompile (`\s*\(\s*\)` )
608619 cleaned = strings .TrimSpace (reEmptyParens .ReplaceAllString (cleaned , "" ))
609620
610621 // Remove trailing colons that may be left after removing season/part info
@@ -618,13 +629,13 @@ func CleanTitle(title string) string {
618629
619630 // Replace hyphens with spaces (for URL-style names like "black-clover")
620631 // But only if surrounded by letters (not em-dashes already handled above)
621- cleaned = regexp . MustCompile ( `([a-zA-Z])-([a-zA-Z])` ) .ReplaceAllString (cleaned , "$1 $2" )
632+ cleaned = reHyphenLetters .ReplaceAllString (cleaned , "$1 $2" )
622633
623634 // Replace underscores with spaces
624635 cleaned = strings .ReplaceAll (cleaned , "_" , " " )
625636
626637 // Normalize whitespace
627- cleaned = regexp . MustCompile ( `\s+` ) .ReplaceAllString (cleaned , " " )
638+ cleaned = reWhitespaceRun .ReplaceAllString (cleaned , " " )
628639 cleaned = strings .TrimSpace (cleaned )
629640
630641 util .Debugf ("CleanTitle: '%s' -> '%s'" , title , cleaned )
0 commit comments