Использование ИИ для перевода субтитров фильмов

В этой статье рассказывается, как использовать Python для вызова ffmpeg и Gemini для перевода субтитров фильмов. Эффект можно найти в разделе «Отображение эффектов».

фон

Не так давно я покинул свою предыдущую компанию и снова стал независимым. Я переключался между предыдущими работами почти плавно, не особо задумываясь. На этот раз я решил хорошенько обдумать это, расслабиться и поработать над тем, что мне нравится. Я купил NAS и обнаружил, что мои ИТ-навыки на работе наконец-то нашли применение в моей жизни, первое из которых касалось китайских субтитров для фильмов.

Первый шаг к приобретению NAS — начать лихорадочно загружать фильмы в формате 4K. Все эти фильмы имеют субтитры, но некоторые из них не имеют китайских субтитров или плохо переведены. Кроме того, купленное мной программное обеспечение NAS не полностью функционально, а загрузка китайских субтитров затруднительна, поэтому я надеюсь, что у меня будет автоматизированное решение. Я думаю, что после оценки мы сможем использовать текущий искусственный интеллект, такой как ChatGPT и Gemini, для перевода английских субтитров, что должно дать хорошие результаты.

Используйте Poetry для управления проектами

За последние несколько лет я не реализовал много проектов Python, но видел некоторые проекты, использующие поэзию , поэтому решил использовать ее в этом проекте. Пробная версия очень хорошая, намного лучше, чем у Pipenv, который я использовал раньше.

Содержимое моего файла pyproject.toml следующее:

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

Я не буду здесь вдаваться в подробности использования поэзии, вы можете научиться этому сами. Здесь указана библиотека упаковки ffmpeg (в пути требуется команда ffmpeg), а также llama-index и соответствующая библиотека Gemini. На самом деле нет большой разницы между использованием llama-index и нет. не используйте слишком много функций llama-index; наконец, это библиотека обработки субтитров pysubs2. Однажды я подумал, стоит ли анализировать субтитры напрямую, но позже я обнаружил, что использование pysubs2 все еще может сэкономить много времени.

извлечение английских субтитров

С помощью ffmpeg легко извлечь субтитры, встроенные в видео. Просто выполните следующую команду:

ffmpeg -i my_file.mkv outfile.vtt

Но на самом деле в видео будет несколько субтитров, что неверно, поэтому вам все равно придется подтвердить это. Я все еще рассматриваю возможность использования библиотеки ffmpeg, то есть ffmpeg-python . Код для использования этой библиотеки для извлечения английских субтитров выглядит следующим образом:

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)

Был добавлен метод _guess_eng_subtitle_indexопределения индекса английских субтитров. Это связано с тем, что, хотя теги субтитров большинства видео относительно стандартизированы, действительно есть некоторые видео, субтитры которых вообще не имеют тегов, поэтому я могу только предполагать. некоторые из них все еще существуют на практике. Другие ситуации можно решать только на основе реальной ситуации.

обработка английских субтитров

Сначала я думал, что достаточно просто закинуть субтитры в Gemini и сохранить результаты, но на самом деле это не сработало. Возникло несколько проблем:

  1. Во многих английских субтитрах есть много тегов, что повлияет на эффект при переводе.
  2. Если субтитр слишком велик, Gemini не сможет с ним справиться, а если контекст слишком длинный, могут возникнуть проблемы.
  3. Временная метка в субтитрах слишком длинная, поэтому подсказка становится слишком длинной.

По этой причине мне пришлось добавить класс субтитров UpSubs, чтобы решить вышеуказанные проблемы:

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

cleanЭтот метод позволяет просто очистить субтитры; метод save можно использовать для сохранения субтитров; merge_dualего можно использовать для объединения двуязычных субтитров; Они относительно просты, и на обработке текста субтитров мы сосредоточимся позже.

Исходный формат файла srt выглядит следующим образом:

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.

Метод станет 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.

Это сделано для уменьшения количества слов и фрагментов. Более того, количество каждого субтитра по-прежнему можно отслеживать. С помощью fillметода мы можем восстановить субтитры из переведенного текста.

Позвоните Близнецам

При вызове Gemini есть несколько проблем:

  • требуется ключ доступа
  • Для внутренних визитов требуется подходящий агент.
  • Должен иметь определенную отказоустойчивость
  • Существует также необходимость обойти механизм безопасности Gemini.

Поэтому completeдля решения этих проблем был написан специальный метод:

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_settingsОчень важно, что в субтитрах к фильмам часто встречаются какие-то особенно чувствительные выражения, и Близнецам необходимо сообщить, что они должны как можно больше терпеть это. Хотя по документации это умеют только платные аккаунты BLOCK_NONE, но с вышеописанной настройкой, похоже, при переводе фильмов у меня не возникало особых проблем, иногда сталкивались с некоторыми, но они исчезали, если бы я попробовал еще раз.

Затем добавляются 3 повтора. Иногда вызов завершается неудачей. Повторная попытка может решить некоторые проблемы.

Наконец, ключ API можно получить через Google AI Studio . Затем добавьте в проект файл .env:

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

Программа может считывать ключ API и настройки прокси.

Процесс вызова

Давайте сначала посмотрим на самый внешний tran_subtitlesметод

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

Логика относительно проста. Прочитайте английские субтитры, используйте get_subtitle_text_with_indexметод для преобразования их в текст для перевода, а затем выполните методprocess_text для завершения перевода. Шаблон слова приглашения Prompt_tpl напрямую ссылается на MOVIE_TRAN_PROMPT_TPL, который содержит:

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

{content}

# [中文字幕]:"""

Вы можете видеть, что эта подсказка довольно проста.

Тогда можно обратить внимание на следующие process_textспособы:

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)

Разбейте текст субтитров на несколько кусков с помощью _split_subtitlesметода, а затем перекиньте их в метод, упомянутый выше complete.

Показать результаты

Поначалу я не возлагал особых надежд на перевод субтитров, но итоговый эффект оказался на удивление хорош. На примере «Кунг-фу Панды 4» приведу сравнение некоторых переводов:

Английские субтитры:

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?

Китайский субтитр:

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

Результаты были на удивление хорошими. Мой запрос не предоставил дополнительного контекста, но Gemini предоставила достоверный перевод.

Подведем итог

Для фильмов приведенный выше код работает относительно стабильно. Но когда сталкиваешься с некоторыми субтитрами, которые сами по себе не очень хороши, результаты перевода тоже не очень хорошие, и есть много аномалий, поэтому необходимо внести много улучшений. Недавно мой видеоаккаунт (Yunyunzhongshengs) поделился некоторыми техническими видеороликами, реализованными с использованием улучшенного кода, и я поделюсь ими с вами позже.

Я чувствую, что это здорово — иметь возможность использовать технологии, чтобы изменить жизнь. Я надеюсь, что появится больше возможностей для улучшения. Каждый может уделять больше внимания и общаться.

Эта статья была впервые опубликована на Yunyunzhongsheng ( https://yylives.cc/ ), приглашаем всех посетить ее.

Я решил отказаться от открытого исходного кода Hongmeng Ван Чэнлу, отец Hongmeng с открытым исходным кодом: Hongmeng с открытым исходным кодом — единственное мероприятие в области промышленного программного обеспечения, посвященное архитектурным инновациям в области базового программного обеспечения в Китае: выпущен OGG 1.0, Huawei предоставляет весь исходный код. Google Reader уничтожен «горой кодового дерьма» Официально выпущена Ubuntu 24.04 LTS Перед официальным выпуском Fedora Linux 40 разработчики Microsoft: производительность Windows 11 «смехотворно плоха», Ма Хуатэн и Чжоу Хунъи пожимают друг другу руки, «устраняя обиды» Известные игровые компании издали новые правила: свадебные подарки сотрудникам не должны превышать 100 000 юаней. Pinduoduo был осужден за недобросовестную конкуренцию. Компенсация в размере 5 миллионов юаней.
{{o.name}}
{{м.имя}}

рекомендация

отmy.oschina.net/u/6919515/blog/11054239