Usando IA para traducir subtítulos de películas

Este artículo presenta cómo usar Python para llamar a ffmpeg y Gemini para traducir subtítulos de películas. El efecto se puede encontrar en la sección "Visualización de efectos".

fondo

Dejé mi última empresa no hace mucho y volví a ser independiente. Cambié entre los trabajos anteriores casi sin problemas, sin pensar mucho. Esta vez decidí pensarlo detenidamente y relajarme y trabajar en algo que me parece divertido. Compré un NAS y descubrí que mis habilidades de TI en el trabajo finalmente se utilizaron en mi vida, la primera de las cuales fue sobre subtítulos en chino para películas.

El primer paso para conseguir un NAS es empezar a descargar frenéticamente películas 4K. Todas estas películas vienen con subtítulos, pero algunas no tienen subtítulos en chino o no están bien traducidas. Además, el software NAS que compré no es completamente funcional y descargar subtítulos en chino es problemático, así que espero tener una solución automatizada. Después de la evaluación, creo que podemos usar la IA actual como ChatGPT y Gemini para traducir subtítulos en inglés, lo que debería tener buenos resultados.

Utilice la poesía para gestionar proyectos

No he realizado muchos proyectos de Python en los últimos años, pero vi algunos proyectos usando poesía , así que decidí usarlo en este proyecto. La experiencia de prueba es muy buena, mucho mejor que el pipenv que he usado antes.

El contenido de mi archivo pyproject.toml es el siguiente:

[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"

No entraré en detalles sobre el uso de la poesía aquí, puedes aprenderla tú mismo. La biblioteca de empaquetado de ffmpeg se cita aquí (se requiere el comando ffmpeg en la ruta); luego están llama-index y la biblioteca Gemini correspondiente. De hecho, no hay mucha diferencia entre usar llama-index y no. no use demasiadas funciones de llama-index finalmente Es la biblioteca de procesamiento de subtítulos pysubs2. Una vez consideré si analizar los subtítulos directamente, pero luego descubrí que usar pysubs2 aún puede ahorrar mucho tiempo.

Extracción de subtítulos en inglés.

Es fácil extraer subtítulos incrustados en un video a través de ffmpeg. Simplemente ejecute el siguiente comando:

ffmpeg -i my_file.mkv outfile.vtt

Pero, de hecho, habrá varios subtítulos en un video, lo cual no es exacto, por lo que aún debes confirmar. Todavía considero usar una biblioteca ffmpeg, es decir, ffmpeg-python . El código para usar esta biblioteca para extraer subtítulos en inglés es el siguiente:

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)

Se ha agregado un método _guess_eng_subtitle_indexpara determinar el índice de subtítulos en inglés. Esto se debe a que, aunque las etiquetas de subtítulos de la mayoría de los videos están relativamente estandarizadas, hay algunos videos cuyos subtítulos no tienen ninguna etiqueta, así que solo puedo adivinar. Todavía hay algunas en la práctica. Otras situaciones solo pueden abordarse en función de la situación real.

Procesamiento de subtítulos en inglés

Al principio, pensé que sería suficiente simplemente enviar los subtítulos a Gemini y guardar los resultados, pero en realidad no funcionó. Hubo varios problemas:

  1. Hay muchas etiquetas en muchos subtítulos en inglés, lo que afectará el efecto al traducir.
  2. Si un subtítulo es demasiado grande, Gemini no puede manejarlo y si el contexto es demasiado largo, pueden surgir problemas.
  3. La marca de tiempo en los subtítulos es demasiado larga, lo que hace que el mensaje sea demasiado largo.

Por esta razón, tuve que agregar una clase de subtítulos UpSubs para solucionar los problemas anteriores:

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

cleanEl método puede simplemente limpiar subtítulos; el método guardar se puede usar para guardar subtítulos y merge_dualse puede usar para fusionar subtítulos bilingües. Son relativamente simples y nos centraremos en el procesamiento del texto de los subtítulos más adelante.

El formato de archivo srt original es el siguiente:

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.

El método será 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.

Esto se hace para reducir la cantidad de palabras y fragmentos. Además, aún se puede rastrear el número de cada subtítulo. A través filldel método, podemos restaurar los subtítulos del texto traducido.

Llamar a Géminis

Hay varios problemas al llamar a Gemini:

  • clave de acceso requerida
  • Las visitas nacionales requieren un agente adecuado.
  • Debe tener cierta tolerancia a fallos.
  • También existe la necesidad de eludir el mecanismo de seguridad de Gemini.

completePor lo tanto, se escribió un método especial para abordar estos problemas:

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_settingsEs muy importante que en los subtítulos de las películas aparezca a menudo algún lenguaje particularmente sensible, y se debe informar a Géminis para que lo tolere tanto como sea posible. Aunque según la documentación, sólo las cuentas pagas pueden hacerlo BLOCK_NONE, parece que no encontré muchos problemas con la configuración anterior al traducir películas. Ocasionalmente encontraba algunos, pero desaparecían si lo intentaba nuevamente.

Luego se agregan 3 reintentos. La llamada ocasionalmente fallará. Volver a intentarlo puede resolver algunos problemas.

Finalmente, la API Key se puede obtener a través de Google AI Studio . Luego agregue un archivo .env al proyecto:

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

El programa puede leer la clave API y la configuración del proxy.

proceso de llamada

Veamos primero el tran_subtitlesmétodo más externo.

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 lógica es relativamente simple. Lea los subtítulos en inglés, use get_subtitle_text_with_indexel método para convertirlos en texto para traducir y luego ejecute el método Process_Text para completar la traducción. La plantilla de palabra rápida request_tpl hace referencia directa a MOVIE_TRAN_PROMPT_TPL, que contiene:

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

{content}

# [中文字幕]:"""

Puedes ver que este mensaje es bastante simple.

Entonces puedes prestar atención a los siguientes process_textmétodos:

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)

Divida el texto del subtítulo en varios fragmentos mediante _split_subtitlesel método y luego tírelos al método mencionado anteriormente complete.

Mostrar resultados

Al principio no tenía muchas expectativas para la traducción de los subtítulos, pero el efecto final fue sorprendentemente bueno. Tomando como ejemplo Kung Fu Panda 4, esta es una comparación de algunas traducciones:

Subtitulos en ingles:

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?

Subtitulo chino:

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
哦,阿宝在哪儿?

Los resultados fueron sorprendentemente buenos. Mi propuesta no proporcionó más contexto, pero Gemini dio una traducción auténtica.

Resumir

Para películas, el código anterior se ejecuta de manera relativamente estable. Pero ante algunos subtítulos que no son muy buenos en sí mismos, los resultados de la traducción tampoco son muy buenos y hay muchas anomalías, por lo que es necesario hacer muchas mejoras. Recientemente, mi cuenta de video (Yunyunzhongshengs) compartió algunos videos técnicos que se implementaron con código mejorado y los compartiré con ustedes más adelante.

Siento que es fantástico poder utilizar la tecnología para cambiar vidas. Espero que haya más oportunidades de mejora. Todos pueden prestar más atención y comunicarse.

Este artículo se publicó por primera vez en Yunyunzhongsheng ( https://yylives.cc/ ), todos son bienvenidos a visitarlo.

Decidí renunciar al código abierto Hongmeng Wang Chenglu, el padre del código abierto Hongmeng: El código abierto Hongmeng es el único evento de software industrial de innovación arquitectónica en el campo del software básico en China: se lanza OGG 1.0, Huawei contribuye con todo el código fuente. Google Reader es asesinado por la "montaña de mierda de código" Ubuntu 24.04 LTS se lanza oficialmente Antes del lanzamiento oficial de Fedora Linux 40, desarrolladores de Microsoft: el rendimiento de Windows 11 es "ridículamente malo", Ma Huateng y Zhou Hongyi se dan la mano, "eliminando rencores" Conocidas empresas de juegos han emitido nuevas regulaciones: los obsequios de boda de los empleados no deben exceder los 100.000 yuanes Pinduoduo fue sentenciado por competencia desleal Indemnización de 5 millones de yuanes
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/6919515/blog/11054239
Recomendado
Clasificación