Utiliser l'IA pour traduire les sous-titres des films

Cet article explique comment utiliser Python pour appeler ffmpeg et Gemini afin de traduire les sous-titres de films. L'effet peut être trouvé dans la section "Affichage des effets".

arrière-plan

J'ai quitté ma dernière entreprise il n'y a pas longtemps et je suis redevenu indépendant. J'ai alterné entre les emplois précédents de manière presque fluide, sans trop réfléchir. Cette fois, j'ai décidé d'y réfléchir attentivement, de me détendre et de travailler sur quelque chose que je trouve amusant. J'ai acheté un NAS et j'ai découvert que mes compétences informatiques au travail étaient enfin utilisées dans ma vie, la première concernait les sous-titres chinois pour les films.

La première étape pour obtenir un NAS est de commencer à télécharger frénétiquement des films 4K. Ces films sont tous sous-titrés, mais certains n'ont pas de sous-titres chinois ou ne sont pas bien traduits. De plus, le logiciel NAS que j'ai acheté n'est pas entièrement fonctionnel et le téléchargement des sous-titres chinois est gênant, j'espère donc avoir une solution automatisée. Après évaluation, je pense que nous pouvons utiliser les IA actuelles telles que ChatGPT et Gemini pour traduire les sous-titres anglais, ce qui devrait donner de bons résultats.

Utiliser la poésie pour gérer des projets

Je n'ai pas réalisé beaucoup de projets Python ces dernières années, mais j'ai vu certains projets utilisant la poésie , j'ai donc décidé de l'utiliser dans ce projet. L'expérience d'essai est très bonne, bien meilleure que le pipenv que j'ai utilisé auparavant.

Le contenu de mon fichier pyproject.toml est le suivant :

[tool.poetry]
name = "upbox"
version = "0.1.0"
description = ""
authors = ["rocksun <[email protected]>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
ffmpeg-python = "^0.2.0"
llama-index = "^0.10.25"
llama-index-llms-gemini = "^0.1.6"
pysubs2 = "^1.6.1"
# yt-dlp = "^2024.4.9"
# typer = "^0.12.3"
# faster-whisper = "^1.0.1"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Je n’entrerai pas ici dans les détails de l’utilisation de la poésie, vous pouvez l’apprendre par vous-même. La bibliothèque d'empaquetage de ffmpeg est citée ici (la commande ffmpeg est requise dans le chemin) ; il y a ensuite llama-index et la bibliothèque Gemini correspondante. En fait, il n'y a pas beaucoup de différence entre utiliser llama-index et non. ne pas utiliser trop de fonctions de lama-index ; enfin C'est la bibliothèque de traitement des sous-titres pysubs2. J'ai déjà réfléchi à l'opportunité d'analyser les sous-titres directement, mais j'ai découvert plus tard que l'utilisation de pysubs2 pouvait encore gagner beaucoup de temps.

Extraction des sous-titres anglais

Il est facile d'extraire les sous-titres intégrés dans une vidéo via ffmpeg. Exécutez simplement la commande suivante :

ffmpeg -i my_file.mkv outfile.vtt

Mais en fait, il y aura plusieurs sous-titres dans une vidéo, ce qui n’est pas exact, vous devez donc quand même confirmer. J'envisage toujours d'utiliser une bibliothèque ffmpeg, c'est-à-dire ffmpeg-python . Le code pour utiliser cette bibliothèque pour extraire les sous-titres anglais est le suivant :

def _guess_eng_subtitle_index(video_path):
    probe = ffmpeg.probe(video_path)
    streams = probe['streams']
    for index, stream in enumerate(streams):
        if stream.get('codec_type') == 'subtitle' and stream.get('tags', {}).get('language') == 'eng':
            return index
    for index, stream in enumerate(streams):
        if stream['codec_type'] == 'subtitle' and stream.get('tags', {}).get('title', "").lower().find("english")!=-1 :
            return index
    return -1

def _extract_subtitle_by_index(video_path, output_path, index):
    return ffmpeg.input(video_path).output(output_path, map='0:'+str(index)).run()

def extract_subtitle(video_path, en_subtitle_path):
    # get the streams from video with ffprobe
    index = _guess_eng_subtitle_index(video_path)
    if index == -1:
        return -1
    
    return _extract_subtitle_by_index(video_path, en_subtitle_path, index)

Une méthode a été ajoutée _guess_eng_subtitle_indexpour déterminer l'index des sous-titres anglais. En effet, bien que les balises de sous-titres de la plupart des vidéos soient relativement standardisées, il existe en effet certaines vidéos dont les sous-titres n'ont pas de balises du tout, donc je ne peux que deviner. Il y en a encore dans la pratique. D'autres situations ne peuvent être traitées qu'en fonction de la situation réelle.

Traitement des sous-titres anglais

Au début, je pensais qu'il suffirait de simplement lancer les sous-titres sur Gemini et de sauvegarder les résultats, mais cela n'a pas fonctionné. Il y a eu plusieurs problèmes :

  1. Il existe de nombreuses balises dans de nombreux sous-titres anglais, ce qui affectera l'effet lors de la traduction.
  2. Si un sous-titre est trop gros, Gemini ne peut pas le gérer, et si le contexte est trop long, des problèmes peuvent survenir.
  3. L'horodatage des sous-titres est trop long, ce qui rend l'invite trop longue.

Pour cette raison, j'ai dû ajouter une classe de sous-titres UpSubs pour résoudre les problèmes ci-dessus :

class UpSubs:
    def __init__(self, subs_path):
        self.subs = pysubs2.load(subs_path)

    def get_subtitle_text(self):
        text = ""
        for sub in self.subs:
            text += sub.text + "\n\n"
        return text

    def get_subtitle_text_with_index(self):
        text = ""
        for i, sub in enumerate(self.subs):
            text += "chunk-"+str(i) + ":\n" + sub.text.replace("\\N", " ") + "\n\n"
        return text
    
    def save(self, output_path):
        self.subs.save(output_path)

    def clean(self):
        indexes = []
        for i, sub in enumerate(self.subs):
            # remove xml tag and line change in sub text
            sub.text = re.sub(r"<[^>]+>", "", sub.text)
            sub.text = sub.text.replace("\\N", " ")

    def fill(self, text):
        text = text.strip()
        pattern = r"\n\s*\n"
        paragraphs = re.split(pattern, text)
        for para in paragraphs:
            try:
                firtline = para.split("\n")[0]
                countstr = firtline[6:len(firtline)-1]
                # print(countstr)
                index = int(countstr)
                p = "\n".join(para.split("\n")[1:])
                self.subs[index].text = p
            except Exception as e:
                print(f"Error merge paragraph : \n {para} \n with exception: \n {e}")
                raise(e)
    
    def merge_dual(self, subspath):
        second_subs = pysubs2.load(subspath)
        merged_subs = SSAFile()
        if len(self.subs.events) == len(second_subs.events):            
            for i, first_event in enumerate(self.subs.events):
                second_event = second_subs[i]
                if first_event.text == second_event.text:
                    merged_event = SSAEvent(first_event.start, first_event.end, first_event.text)
                else:
                    merged_event = SSAEvent(first_event.start, first_event.end, first_event.text + '\n' + second_event.text)
                merged_subs.append(merged_event)
            return merged_subs
        
        return None

cleanLa méthode peut simplement nettoyer les sous-titres ; la méthode de sauvegarde peut être utilisée pour enregistrer des sous-titres ; merge_dualelle peut être utilisée pour fusionner des sous-titres bilingues. Celles-ci sont relativement simples et nous nous concentrerons plus tard sur le traitement du texte des sous-titres.

Le format de fichier srt d'origine est le suivant :

12
00:02:30,776 --> 00:02:34,780
Not even the great Dragon Warrior.

13
00:02:43,830 --> 00:02:45,749
Oh, where is Po?

14
00:02:45,749 --> 00:02:48,502
He was supposed to be here hours ago.

La méthode deviendra get_subtitle_text_with_index:

chunk-12
Not even the great Dragon Warrior.

chunk-13
Oh, where is Po?

chunk-14
He was supposed to be here hours ago.

Ceci est fait pour réduire le nombre de mots et de morceaux. De plus, le numéro de chaque sous-titre peut toujours être suivi. Grâce à fillcette méthode, nous pouvons restaurer les sous-titres à partir du texte traduit.

Appeler les Gémeaux

Il y a plusieurs problèmes liés à l’appel de Gemini :

  • clé d'accès requise
  • Les visites nationales nécessitent un agent approprié
  • Doit avoir une certaine tolérance aux pannes
  • Il est également nécessaire de contourner le mécanisme de sécurité de Gemini.

Par conséquent, une completeméthode spéciale a été écrite pour résoudre ces problèmes :

def complete(prompt, max_tokens=32760):
    prompt = prompt.strip()
    if not prompt:
        return ""
    
    safety_settings = [
        {
            "category": "HARM_CATEGORY_HARASSMENT",
            "threshold": "BLOCK_NONE"
        },
        {
            "category": "HARM_CATEGORY_HATE_SPEECH",
            "threshold": "BLOCK_NONE"
        },
        {
            "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
            "threshold": "BLOCK_NONE"
        },
        {
            "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
            "threshold": "BLOCK_NONE"
        },
    ]

    retries = 3
    for _ in range(retries):
        try:
            return Gemini(max_tokens=max_tokens, safety_settings=safety_settings, temperature = 0.01).complete(prompt).text
        except Exception as e:
            print(f"Error completing prompt: {prompt} \n with error: \n ")
            traceback.print_exc()
    return ""

safety_settingsIl est très important qu'un langage particulièrement sensible apparaisse souvent dans les sous-titres des films, et les Gémeaux doivent être informés pour le tolérer autant que possible. Bien que, selon la documentation, seuls les comptes payants puissent le faire BLOCK_NONE, il semble que je n'ai pas rencontré beaucoup de problèmes avec la configuration ci-dessus lors de la traduction de films. J'en rencontrais occasionnellement, mais ils disparaissaient si j'essayais à nouveau.

Ensuite, 3 tentatives sont ajoutées. L'appel échouera occasionnellement. Une nouvelle tentative peut résoudre certains problèmes.

Enfin, la clé API peut être obtenue via Google AI Studio . Ajoutez ensuite un fichier .env au projet :

http_proxy=http://192.168.0.107:7890
https_proxy=http://192.168.0.107:7890
GOOGLE_API_KEY=[your-api-key]

Le programme peut lire la clé API et les paramètres du proxy.

Processus d'appel

Examinons d'abord la tran_subtitlesméthode la plus externe

def tran_subtitles(fixed_subtitle, zh_subtitle=None, cncf = False, chunk_size=3000):
    subtitle_base = os.path.splitext(fixed_subtitle)[0]
    video_base = os.path.splitext(subtitle_base)[0]
    if zh_subtitle is None:
        zh_subtitle = video_base + ".zh-fixed.vtt"
    if os.path.exists(zh_subtitle):
        print(f"zh subtitle {zh_subtitle} already translated, skip to translate.")
        return 1

    prompt_tpl = MOVIE_TRAN_PROMPT_TPL
    opts = { }

    srtp = UpSubs(fixed_subtitle)
    text = srtp.get_subtitle_text_with_index()

    process_text(srtp, text, prompt_tpl, opts, chunk_size = chunk_size)
    srtp.save(zh_subtitle)

    return zh_subtitle

La logique est relativement simple. Lisez les sous-titres anglais, utilisez get_subtitle_text_with_indexla méthode pour les convertir en texte à traduire, puis exécutez la méthode process_text pour terminer la traduction. Le modèle de mot d'invite prompt_tpl fait directement référence à MOVIE_TRAN_PROMPT_TPL, qui contient :

MOVIE_TRAN_PROMPT_TPL = """你是个专业电影字幕翻译,你需要将一份英文字幕翻译成中文。
[需要翻译的英文字幕]:

{content}

# [中文字幕]:"""

Vous pouvez voir que cette invite est assez simple.

Ensuite, vous pouvez faire attention aux process_textméthodes suivantes :

def process_text(subs, text, prompt_tpl, opts, chunk_size=2500):
    # ret = ""
    chunks = _split_subtitles(text, chunk_size)
    for(i, chunk) in enumerate(chunks):
        print("process chunk ({}/{})".format(i+1,len(chunks)))
        # if i==4:
        #     break
        # format string with all the field in a dict 
        opts["content"] = chunk
        prompt = prompt_tpl.format(**opts)

        print(prompt)
        out = complete(prompt, max_tokens=32760)
        subs.fill(out)
        print(out)

Divisez le texte du sous-titre en plusieurs morceaux via _split_subtitlesla méthode, puis lancez-les dans la méthode mentionnée ci-dessus complete.

Montrer les résultats

Au début, je n'avais pas beaucoup d'attentes concernant la traduction des sous-titres, mais l'effet final était étonnamment bon. En prenant Kung Fu Panda 4 comme exemple, voici une comparaison de quelques traductions :

Sous-titres anglais:

10
00:02:22,184 --> 00:02:27,606
Let it be known from the highest mountain
to the lowest valley that Tai Lung lives,

11
00:02:27,606 --> 00:02:30,776
and no one will stand in his way.

12
00:02:30,776 --> 00:02:34,780
Not even the great Dragon Warrior.

13
00:02:43,830 --> 00:02:45,749
Oh, where is Po?

Sous-titres chinois:

10
00:02:22,184 --> 00:02:27,606
让最高的山峰和最低的山谷都知道,泰隆还活着,

11
00:02:27,606 --> 00:02:30,776
没人能阻挡他。

12
00:02:30,776 --> 00:02:34,780
即使是伟大的神龙大侠也不行。

13
00:02:43,830 --> 00:02:45,749
哦,阿宝在哪儿?

Les résultats ont été étonnamment bons. Mon message n'a pas fourni plus de contexte, mais Gemini a donné une traduction authentique.

Résumer

Pour les films, le code ci-dessus fonctionne de manière relativement stable. Mais face à certains sous-titres qui ne sont pas très bons en eux-mêmes, les résultats de traduction ne sont pas très bons non plus, et il y a de nombreuses anomalies, donc beaucoup d'améliorations doivent être apportées. Récemment, mon compte vidéo (Yunyunzhongshengs) a partagé des vidéos techniques, qui ont été implémentées avec un code amélioré, et je les partagerai avec vous plus tard.

Je pense que c’est formidable de pouvoir utiliser la technologie pour changer des vies. J’espère qu’il y aura plus d’opportunités d’amélioration. Tout le monde est invité à prêter plus d’attention et à communiquer.

Cet article a été publié pour la première fois sur Yunyunzhongsheng ( https://yylives.cc/ ), tout le monde est invité à le visiter.

J'ai décidé d'abandonner l'open source Hongmeng Wang Chenglu, le père de l'open source Hongmeng : L'open source Hongmeng est le seul événement logiciel industriel d'innovation architecturale dans le domaine des logiciels de base en Chine - OGG 1.0 est publié, Huawei contribue à tout le code source. Google Reader est tué par la "montagne de merde de code" Ubuntu 24.04 LTS est officiellement publié Avant la sortie officielle de Fedora Linux 40, les développeurs Microsoft : les performances de Windows 11 sont "ridiculement mauvaises", Ma Huateng et Zhou Hongyi se serrent la main, "éliminant les rancunes" Des sociétés de jeux bien connues ont publié de nouvelles réglementations : les cadeaux de mariage des employés ne doivent pas dépasser 100 000 yuans. Pinduoduo a été condamné pour concurrence déloyale. Indemnisation de 5 millions de yuans.
{{o.name}}
{{m.nom}}

Je suppose que tu aimes

Origine my.oschina.net/u/6919515/blog/11054239
conseillé
Classement