@@ -733,6 +733,7 @@ def scrape(cls, html: str) -> str | None:
733
733
class Translator (RequestHandler ):
734
734
TRANSLATE_URL = "https://api.cognitive.microsofttranslator.com/translate"
735
735
LINE_PARTS_RE = re .compile (r"^(\[\d\d:\d\d.\d\d\]|) *(.*)$" )
736
+ remove_translations = partial (re .compile (r" / [^\n]+" ).sub , "" )
736
737
737
738
_log : beets .logging .Logger
738
739
api_key : str
@@ -800,23 +801,45 @@ def append_translations(self, lines: Iterable[str]) -> list[str]:
800
801
# only add the space between non-empty timestamps and texts
801
802
return [" " .join (filter (None , p )) for p in zip (timestamps , texts )]
802
803
803
- def translate (self , lyrics : str ) -> str :
804
+ def translate (self , new_lyrics : str , old_lyrics : str ) -> str :
804
805
"""Translate the given lyrics to the target language.
805
806
807
+ Check old lyrics for existing translations and return them if their
808
+ original text matches the new lyrics. This is to avoid translating
809
+ the same lyrics multiple times.
810
+
806
811
If the lyrics are already in the target language or not in any of
807
812
of the source languages (if configured), they are returned as is.
808
813
809
814
The footer with the source URL is preserved, if present.
810
815
"""
811
- lyrics_language = langdetect . detect ( lyrics ). upper ()
812
- if lyrics_language == self . to_language or (
813
- self . from_languages and lyrics_language not in self .from_languages
816
+ if (
817
+ " / " in old_lyrics
818
+ and self .remove_translations ( old_lyrics ) == new_lyrics
814
819
):
815
- return lyrics
820
+ self .info ("🔵 Translations already exist" )
821
+ return old_lyrics
822
+
823
+ lyrics_language = langdetect .detect (new_lyrics ).upper ()
824
+ if lyrics_language == self .to_language :
825
+ self .info (
826
+ "🔵 Lyrics are already in the target language {}" ,
827
+ self .to_language ,
828
+ )
829
+ return new_lyrics
830
+
831
+ if self .from_languages and lyrics_language not in self .from_languages :
832
+ self .info (
833
+ "🔵 Configuration {} does not permit translating from {}" ,
834
+ self .from_languages ,
835
+ lyrics_language ,
836
+ )
837
+ return new_lyrics
816
838
817
- lyrics , * url = lyrics .split ("\n \n Source: " )
839
+ lyrics , * url = new_lyrics .split ("\n \n Source: " )
818
840
with self .handle_request ():
819
841
translated_lines = self .append_translations (lyrics .splitlines ())
842
+ self .info ("🟢 Translated lyrics to {}" , self .to_language )
820
843
return "\n \n Source: " .join (["\n " .join (translated_lines ), * url ])
821
844
822
845
@@ -1054,12 +1077,7 @@ def add_item_lyrics(self, item: Item, write: bool) -> None:
1054
1077
if lyrics := self .find_lyrics (item ):
1055
1078
self .info ("🟢 Found lyrics: {0}" , item )
1056
1079
if translator := self .translator :
1057
- initial_lyrics = lyrics
1058
- if (lyrics := translator .translate (lyrics )) != initial_lyrics :
1059
- self .info (
1060
- "🟢 Added translation to {}" ,
1061
- self .config ["translate_to" ].get ().upper (),
1062
- )
1080
+ lyrics = translator .translate (lyrics , item .lyrics )
1063
1081
else :
1064
1082
self .info ("🔴 Lyrics not found: {}" , item )
1065
1083
lyrics = self .config ["fallback" ].get ()
0 commit comments