Как создавать и добавлять субтитры к видео с помощью Python, OpenAI Whisper и FFmpeg
Автор выбрал Open Source Initiative для получения пожертвования в рамках программы Write for DOnations.
Введение
В этом руководстве вы создадите приложение Python, способное извлекать звук из входного видео, расшифровывать извлеченный звук, генерировать файл субтитров на основе транскрипции, а затем добавлять субтитры к копии входного видео.
Чтобы создать это приложение, вы будете использовать FFmpeg для извлечения звука из входного видео. Вы будете использовать Whisper OpenAI для создания расшифровки извлеченного аудио, а затем использовать эту расшифровку для создания файла субтитров. Кроме того, вы будете использовать FFmpeg для добавления сгенерированного файла субтитров в копию входного видео.
FFmpeg — это мощный пакет программного обеспечения с открытым исходным кодом для обработки мультимедийных данных, включая задачи обработки аудио и видео. Он предоставляет инструмент командной строки, который позволяет пользователям конвертировать, редактировать и манипулировать мультимедийными файлами с широким спектром форматов и кодеков.
Whisper от OpenAI — это система автоматического распознавания речи (ASR), предназначенная для преобразования устной речи в письменный текст. Обученный на огромном количестве многоязычных и многозадачных контролируемых данных, он превосходно расшифровывает разнообразный аудиоконтент с высокой точностью.
К концу этого урока у вас будет приложение, способное добавлять субтитры к видео:
Предварительные условия
Чтобы следовать этому руководству, читателю потребуются следующие инструменты:
Установлен Python версии 3.9+ и соответствующий venv.
FFmpeg установлен.
Базовое понимание Python. Вы можете следовать этой серии руководств, чтобы научиться программировать на Python.
Шаг 1 — Создание корневого каталога проекта
В этом разделе вы создадите каталог проекта, загрузите входное видео, создадите и активируете виртуальную среду и установите необходимые пакеты Python.
Откройте окно терминала и перейдите в подходящее место для вашего проекта. Выполните следующую команду, чтобы создать каталог проекта:
mkdir generate-subtitle
Перейдите в каталог проекта:
cd generate-subtitle
Загрузите это отредактированное видео и сохраните его в корневом каталоге вашего проекта как input.mp4
. На видео видно, как ребенок по имени Рашон поет «Beautiful Day» Джермейна Эдварда. Отредактированное видео, которое вы собираетесь использовать в этом уроке, было взято из следующего видео на YouTube:
Создайте новую виртуальную среду и назовите ее env
:
python3 -m venv env
Активируйте виртуальную среду:
source env/bin/activate
Теперь используйте следующую команду для установки пакетов, необходимых для сборки этого приложения:
pip3 install faster-whisper ffmpeg-python
С помощью приведенной выше команды вы установили следующие библиотеки:
faster-whisper
: это переработанная версия модели OpenAI Whisper, в которой используется CTranslate2, высокопроизводительный механизм вывода для моделей Transformer. Эта реализация обеспечивает в четыре раза большую скорость, чем openai/whisper, с сопоставимой точностью, и при этом потребляет меньше памяти.ffmpeg-python
: библиотека Python, предоставляющая оболочку для инструмента FFmpeg, позволяющая пользователям легко взаимодействовать с функциями FFmpeg в сценариях Python. Через интерфейс Pythonic он позволяет решать задачи обработки видео и аудио, такие как редактирование, преобразование и манипулирование.
Выполните следующую команду, чтобы сохранить пакеты, которые были установлены с помощью pip
в виртуальной среде, в файле с именем requirements.txt
:
pip3 freeze > requirements.txt
Файл requirements.txt
должен выглядеть примерно так:
av==10.0.0
certifi==2023.7.22
charset-normalizer==3.3.2
coloredlogs==15.0.1
ctranslate2==3.20.0
faster-whisper==0.9.0
ffmpeg-python==0.2.0
filelock==3.13.1
flatbuffers==23.5.26
fsspec==2023.10.0
future==0.18.3
huggingface-hub==0.17.3
humanfriendly==10.0
idna==3.4
mpmath==1.3.0
numpy==1.26.1
onnxruntime==1.16.1
packaging==23.2
protobuf==4.25.0
PyYAML==6.0.1
requests==2.31.0
sympy==1.12
tokenizers==0.14.1
tqdm==4.66.1
typing_extensions==4.8.0
urllib3==2.0.7
В этом разделе вы создали каталог проекта, загрузили входное видео, которое будет использоваться в этом руководстве, настроили виртуальную среду, активировали ее и установили необходимые пакеты Python. В следующем разделе вы создадите расшифровку входного видео.
Шаг 2 — Создание расшифровки видео
В этом разделе вы создадите сценарий Python, в котором будет размещаться приложение. Внутри этого скрипта вы будете использовать библиотеку ffmpeg-python
для извлечения звуковой дорожки из входного видео, загруженного в предыдущем разделе, и сохранения ее как файла WAV. Далее вы будете использовать библиотеку faster-whisper
для создания расшифровки извлеченного аудио.
В корневом каталоге вашего проекта создайте файл с именем main.py
и добавьте в него следующий код:
import time
import math
import ffmpeg
from faster_whisper import WhisperModel
input_video = "input.mp4"
input_video_name = input_video.replace(".mp4", "")
Здесь код начинается с импорта различных библиотек и модулей, включая time
, math
, ffmpeg
из ffmpeg-python
и пользовательский модуль с именем WhisperModel
из faster_whisper
. Эти библиотеки будут использоваться для обработки видео и аудио, транскрипции и создания субтитров.
Затем код задает имя входного видеофайла, сохраняет его в константе с именем input_video
, а затем сохраняет имя видеофайла без расширения .mp4
в константе с именем <имя_входного_видео. Установка здесь имени входного файла позволит вам работать с несколькими входными видео без перезаписи субтитров и выходных видеофайлов, созданных для них.
Добавьте следующий код в конец вашего main.py
:
def extract_audio():
extracted_audio = f"audio-{input_video_name}.wav"
stream = ffmpeg.input(input_video)
stream = ffmpeg.output(stream, extracted_audio)
ffmpeg.run(stream, overwrite_output=True)
return extracted_audio
В приведенном выше коде определяется функция с именем extract_audio()
, которая отвечает за извлечение звука из входного видео.
Во-первых, он устанавливает имя аудио, которое будет извлечено, как имя, образованное добавлением audio-
к базовому имени входного видео с расширением .wav
, и сохраняет это имя в константе с именем extracted_audio
.
Затем код вызывает метод ffmpeg.input()
библиотеки ffmpeg
, чтобы открыть входное видео, и создает объект входного потока с именем stream
.
Затем код вызывает метод ffmpeg.output()
для создания объекта выходного потока с входным потоком и определенным именем извлеченного аудиофайла.
После установки выходного потока код вызывает метод ffmpeg.run()
, передавая выходной поток в качестве параметра, чтобы инициировать процесс извлечения аудио и сохранить извлеченный аудиофайл в корневом каталоге вашего проекта. Кроме того, включен логический параметр overwrite_output=True
для замены любого ранее существовавшего выходного файла вновь созданным, если такой файл уже существует.
Наконец, код возвращает имя извлеченного аудиофайла.
Добавьте следующий код под функцией extract_audio()
:
def run():
extracted_audio = extract_audio()
run()
Здесь код определяет функцию с именем run()
и затем вызывает ее. Эта функция вызывает все функции, необходимые для создания и добавления субтитров к видео.
Внутри функции код вызывает функцию extract_audio()
для извлечения звука из видео, а затем сохраняет имя возвращенного аудиофайла в переменной с именем extracted_audio
.
Вернитесь к своему терминалу и выполните следующую команду, чтобы запустить скрипт main.py
:
python3 main.py
После выполнения приведенной выше команды вывод FFmpeg будет показан в терминале, а файл с именем audio-input.wav
, содержащий аудио, извлеченный из входного видео, будет сохранен в корневом каталоге вашего проекта.
Вернитесь к файлу main.py
и добавьте следующий код между функциями extract_audio()
и run()
:
def transcribe(audio):
model = WhisperModel("small")
segments, info = model.transcribe(audio)
language = info[0]
print("Transcription language", info[0])
segments = list(segments)
for segment in segments:
# print(segment)
print("[%.2fs -> %.2fs] %s" %
(segment.start, segment.end, segment.text))
return language, segments
В приведенном выше коде определяется функция с именем transcribe
, отвечающая за расшифровку аудиофайла, извлеченного из входного видео.
Сначала код создает экземпляр объекта WhisperModel
и устанавливает тип модели small
. OpenAI’s Whisper имеет следующие типы моделей: крошечные, базовые, маленькие, средние и большие. Крошечная модель — самая маленькая и быстрая, а большая — самая большая и медленная, но наиболее точная.
Затем код вызывает метод model.transcribe()
с извлеченным аудио в качестве аргумента для получения функции сегментов и аудиоинформации и сохраняет их в переменных с именами info
и сегменты
соответственно. Функция сегментов — это генератор Python, поэтому транскрипция начнется только тогда, когда код ее обработает. Транскрипцию можно завершить, собрав сегменты в цикле list
или for
.
Затем код сохраняет язык, обнаруженный в аудио, в константе с именем info
и выводит его на консоль.
После печати обнаруженного языка код собирает сегменты транскрипции в список
для запуска транскрипции и сохраняет собранные сегменты в переменной, также называемой сегменты
. Затем код перебирает список сегментов транскрипции и выводит время начала, время окончания и текст каждого сегмента на консоль.
Наконец, код возвращает язык, обнаруженный в сегментах аудио и транскрипции.
Добавьте следующий код в функцию run()
:
def run():
extracted_audio = extract_audio()
language, segments = transcribe(audio=extracted_audio)
Добавленный код вызывает функцию транскрипции с извлеченным аудио в качестве аргумента и сохраняет возвращаемые значения в константах с именами language
и segments
.
Вернитесь к своему терминалу и выполните следующую команду, чтобы запустить скрипт main.py
:
python3 main.py
При первом запуске этого сценария код сначала загрузит и кэширует модель Whisper Small, последующие запуски будут намного быстрее.
После выполнения приведенной выше команды вы должны увидеть в консоли следующий вывод:
…
Transcription language en
[0.00s -> 4.00s] This morning I wake up and I look in the mirror
[4.00s -> 8.00s] Every part of my body was in the place many people lie
[8.00s -> 11.00s] I don't wanna act too high and mighty
[11.00s -> 15.00s] Cause tomorrow I may fall down on my face
[15.00s -> 17.00s] Lord I thank You for sunshine
[17.00s -> 19.00s] Thank You for rain
[19.00s -> 20.00s] Thank You for joy
[20.00s -> 22.00s] Thank You for pain
[22.00s -> 25.00s] It's a beautiful day
[25.00s -> 28.00s] It's a beautiful day
Вывод выше показывает, что язык, обнаруженный в аудио, — английский (en
). Кроме того, он показывает время начала и окончания каждого сегмента транскрипции в секундах и тексте.
Предупреждение! Хотя распознавание речи OpenAI Whisper очень точное, оно не на 100 % точное, оно может иметь ограничения и случайные ошибки, особенно в сложных лингвистических или аудиосценариях. Поэтому всегда проверяйте транскрипцию вручную.
В этом разделе вы создали сценарий Python для приложения. Внутри скрипта ffmpeg-python
использовался для извлечения звука из загруженного видео и сохранения его в виде файла WAV. Затем библиотека faster-whisper
использовалась для создания расшифровки извлеченного аудио. В следующем разделе вы создадите файл субтитров на основе стенограммы, а затем добавите субтитры к видео.
Шаг 3 — Создание и добавление субтитров к видео
В этом разделе сначала вы поймете, что такое файл субтитров и как он структурирован. Далее вы будете использовать сегменты транскрипции, созданные в предыдущем разделе, для создания файла субтитров. После создания файла субтитров вы будете использовать библиотеку ffmpeg-python
, чтобы добавить файл субтитров к копии входного видео.
Понимание субтитров: структура и типы
Файл субтитров — это текстовый файл, который содержит синхронизированную текстовую информацию, соответствующую устному или письменному содержимому видео или фильма. Обычно он включает информацию о том, когда каждый субтитр должен появиться и исчезнуть на экране. Существует множество форматов субтитров, однако в этом уроке мы сосредоточимся на широко используемом формате под названием SubRip (SRT).
Файл субтитров состоит из серии записей субтитров, каждая из которых обычно соответствует определенному формату. Общая структура записи субтитров включает в себя:
Индекс субтитров: порядковый номер, указывающий порядок субтитров в файле.
Таймкоды: маркеры времени начала и окончания, которые определяют, когда должен отображаться текст субтитров. Тайм-коды обычно имеют формат
ЧЧ:ММ:СС,сс
(часы, минуты, секунды, миллисекунды).Текст субтитров: фактический текст записи субтитров, представляющий устный или письменный контент. Этот текст отображается на экране в течение заданного интервала времени.
Например, запись субтитров в файле SRT может выглядеть так:
1
00:00:10,500 --> 00:00:15,000
This is an example subtitle.
В этом примере индекс равен 1
, тайм-коды указывают, что субтитры должны отображаться от 10,5
секунд до 15
секунд, а текст субтитров is Это пример подзаголовка.
Субтитры можно разделить на два основных типа:
Мягкие субтитры: также известные как скрытые субтитры, хранятся снаружи в виде отдельных файлов (например, SRT) и могут быть добавлены или удалены независимо от видео. Они обеспечивают гибкость просмотра, позволяя переключать языки, переключать языки и настраивать параметры. Однако их эффективность зависит от поддержки видеоплеера, и не все проигрыватели всегда поддерживают мягкие субтитры.
Жесткие субтитры: постоянно внедряются в видеокадры во время редактирования или кодирования, оставаясь фиксированной частью видео. Несмотря на то, что они обеспечивают постоянную видимость, даже в проигрывателях, не поддерживающих внешние файлы субтитров, их модификация или отключение требуют перекодирования всего видео, что ограничивает контроль пользователя.
Создание файла субтитров
Вернитесь к файлу main.py
и добавьте следующий код между функциями transcribe()
и run()
:
def format_time(seconds):
hours = math.floor(seconds / 3600)
seconds %= 3600
minutes = math.floor(seconds / 60)
seconds %= 60
milliseconds = round((seconds - math.floor(seconds)) * 1000)
seconds = math.floor(seconds)
formatted_time = f"{hours:02d}:{minutes:02d}:{seconds:01d},{milliseconds:03d}"
return formatted_time
Здесь код определяет функцию с именем format_time()
, которая отвечает за преобразование времени начала и окончания данного сегмента транскрипции в секундах в формат времени, совместимый с субтитрами, который отображает часы, минуты, секунды и миллисекунды. (ЧЧ:ММ:СС,сс
).
Код сначала вычисляет часы, минуты, секунды и миллисекунды на основе заданного времени в секундах, форматирует их соответствующим образом, а затем возвращает отформатированное время.
Добавьте следующий код между функциями format_time()
и run()
:
def generate_subtitle_file(language, segments):
subtitle_file = f"sub-{input_video_name}.{language}.srt"
text = ""
for index, segment in enumerate(segments):
segment_start = format_time(segment.start)
segment_end = format_time(segment.end)
text += f"{str(index+1)} \n"
text += f"{segment_start} --> {segment_end} \n"
text += f"{segment.text} \n"
text += "\n"
f = open(subtitle_file, "w")
f.write(text)
f.close()
return subtitle_file
Добавленный код определяет функцию с именем generate_subtitle_file()
, которая принимает в качестве параметров язык, обнаруженный в извлеченном аудио и сегментах транскрипции. Эта функция отвечает за создание файла субтитров SRT на основе языка и сегментов транскрипции.
Сначала код присваивает имени файла субтитров имя, образованное добавлением sub-
и обнаруженного языка к базовому имени входного видео с расширением «.srt», и сохраняет это имя в файле. константа с именем subtitle_file
. Кроме того, в коде определяется переменная с именем text
, в которой вы будете хранить записи субтитров.
Затем код перебирает записанные сегменты, форматирует время начала и окончания с помощью функции format_time()
, использует эти форматированные значения вместе с индексом сегмента и текстом для создания записи подзаголовка, а затем добавляет пустая строка отделяет каждую запись субтитров.
Наконец, код создает файл субтитров в корневом каталоге вашего проекта с заданным ранее именем, добавляет записи субтитров в файл и возвращает имя файла субтитров.
Добавьте следующий код в конец функции run()
:
def run():
extracted_audio = extract_audio()
language, segments = transcribe(audio=extracted_audio)
subtitle_file = generate_subtitle_file(
language=language,
segments=segments
)
Добавленный код вызывает функцию generate_subtitle_file()
с обнаруженным языком и сегментами транскрипции в качестве аргументов и сохраняет файл субтитров с возвращаемым именем в константе с именем subtitle_file
.
Вернитесь к своему терминалу и выполните следующую команду, чтобы запустить скрипт main.py
:
python3 main.py
После выполнения приведенной выше команды файл субтитров с именем sub-input.en.srt
будет сохранен в корневом каталоге вашего проекта.
Откройте файл субтитров sub-input.en.srt
, и вы увидите что-то похожее на следующее:
1
00:00:0,000 --> 00:00:4,000
This morning I wake up and I look in the mirror
2
00:00:4,000 --> 00:00:8,000
Every part of my body was in the place many people lie
3
00:00:8,000 --> 00:00:11,000
I don't wanna act too high and mighty
4
00:00:11,000 --> 00:00:15,000
Cause tomorrow I may fall down on my face
5
00:00:15,000 --> 00:00:17,000
Lord I thank You for sunshine
6
00:00:17,000 --> 00:00:19,000
Thank You for rain
7
00:00:19,000 --> 00:00:20,000
Thank You for joy
8
00:00:20,000 --> 00:00:22,000
Thank You for pain
9
00:00:22,000 --> 00:00:25,000
It's a beautiful day
10
00:00:25,000 --> 00:00:28,000
It's a beautiful day
Добавление субтитров к видео
Добавьте следующий код между функциями generate_subtitle_file()
и run()
:
def add_subtitle_to_video(soft_subtitle, subtitle_file, subtitle_language):
video_input_stream = ffmpeg.input(input_video)
subtitle_input_stream = ffmpeg.input(subtitle_file)
output_video = f"output-{input_video_name}.mp4"
subtitle_track_title = subtitle_file.replace(".srt", "")
if soft_subtitle:
stream = ffmpeg.output(
video_input_stream, subtitle_input_stream, output_video, **{"c": "copy", "c:s": "mov_text"},
**{"metadata:s:s:0": f"language={subtitle_language}",
"metadata:s:s:0": f"title={subtitle_track_title}"}
)
ffmpeg.run(stream, overwrite_output=True)
Здесь код определяет функцию с именем add_subtitle_to_video()
, которая принимает в качестве параметров логическое значение, используемое для определения того, следует ли добавлять мягкий субтитр или жесткий субтитр, имя файла субтитров и язык, обнаруженный в транскрипция. Эта функция отвечает за добавление мягких или жестких субтитров к копии входного видео.
Во-первых, код использует метод ffmpeg.input()
с входным файлом видео и субтитров для создания объектов входного потока для входного видео и файла субтитров и сохраняет их в константах с именем video_input_stream
и subtitle_input_stream
соответственно.
После создания входных потоков код присваивает имени выходного видеофайла имя, образованное добавлением output-
к базовому имени входного видео с расширением «.mp4», и сохраняет это имя в константа с именем output_video
. Кроме того, задает имя дорожки субтитров как имя файла субтитров без расширения .srt
и сохраняет это имя в константе с именем subtitle_track_title
.
Затем код проверяет, установлено ли для логического значения soft_subtitle
значение True
, что указывает на необходимость добавления мягкого субтитра.
В этом случае код вызывает метод ffmpeg.output()
для создания объекта выходного потока с входными потоками, именем выходного видеофайла и следующими параметрами для выходного видео:
"c": "copy"
: указывает, что видеокодек и другие параметры видео должны быть скопированы непосредственно со входа на выход без повторного кодирования."c:s": "mov_text"
: указывает, что кодек и параметры субтитров также должны копироваться со входа на выход без повторного кодирования.mov_text
— это общий кодек субтитров, используемый в файлах MP4/MOV.”metadata:s:s:0 ”: f"language={subtitle_language}"
: устанавливает метаданные языка для потока субтитров. В качестве языка установлено значение, хранящееся вsubtitle_language
."metadata:s:s:0": f"title={subtitle_track_title}"
: устанавливает метаданные заголовка для потока субтитров. Заголовку присвоено значение, хранящееся вsubtitle_track_title
.
Наконец, код вызывает метод ffmpeg.run()
, передавая выходной поток в качестве параметра для добавления мягких субтитров к видео и сохранения выходного видеофайла в корневом каталоге вашего проекта.
Добавьте следующий код в конец функции add_subtitle_to_video()
:
def add_subtitle_to_video(soft_subtitle, subtitle_file, subtitle_language):
...
if soft_subtitle:
...
else:
stream = ffmpeg.output(video_input_stream, output_video,
vf=f"subtitles={subtitle_file}")
ffmpeg.run(stream, overwrite_output=True)
Выделенный код будет выполняться, если для логического значения soft_subtitle
установлено значение False
, указывающее, что следует добавить жесткий субтитр.
В этом случае сначала код вызывает метод ffmpeg.output()
для создания объекта выходного потока с входным видеопотоком, именем выходного видеофайла и vf= параметр f"subtitles={subtitle_file}"
. vf
означает «видеофильтр» и используется для применения фильтра к видеопотоку. В этом случае применяемый фильтр — это добавление субтитров.
Наконец, код вызывает метод ffmpeg.run()
, передавая выходной поток в качестве параметра для добавления жестких субтитров к видео и сохранения выходного видеофайла в корневом каталоге вашего проекта.
Добавьте следующий выделенный код в функцию run()
:
def run():
extracted_audio = extract_audio()
language, segments = transcribe(audio=extracted_audio)
subtitle_file = generate_subtitle_file(
language=language,
segments=segments
)
add_subtitle_to_video(
soft_subtitle=True,
subtitle_file=subtitle_file,
subtitle_language=language
)
Выделенный код вызывает функцию add_subtitle_to_video()
с параметром soft_subtitle
, установленным в значение True
, именем файла субтитров и языком субтитров для добавления программного субтитры к копии входного видео.
Вернитесь к своему терминалу и выполните следующую команду, чтобы запустить скрипт main.py
:
python3 main.py
После выполнения приведенной выше команды выходной видеофайл с именем output-input.mp4
будет сохранен в корневом каталоге вашего проекта.
Откройте видео с помощью предпочитаемого видеопроигрывателя, выберите субтитры для видео и обратите внимание, что субтитры не будут отображаться, пока вы их не выберете:
Вернитесь к файлу main.py
, перейдите к функции run()
и в вызове функции add_subtitle_to_video()
установите soft_subtitle
для False
:
def run():
…
add_subtitle_to_video(
soft_subtitle=False,
subtitle_file=subtitle_file,
subtitle_language=language
)
Здесь вы устанавливаете для параметра soft_subtitle
значение False
, чтобы добавить к видео жесткие субтитры.
Вернитесь к своему терминалу и выполните следующую команду, чтобы запустить скрипт main.py
:
python3 main.py
После выполнения приведенной выше команды видеофайл output-input.mp4
, расположенный в корневом каталоге вашего проекта, будет перезаписан.
Откройте видео с помощью предпочитаемого вами видеопроигрывателя, попробуйте выбрать субтитры для видео и обратите внимание, что он недоступен, но субтитры отображаются:
В этом разделе вы получили представление о структуре файла субтитров SRT и использовали сегменты транскрипции из предыдущего раздела для его создания. После этого была использована библиотека ffmpeg-python
для добавления сгенерированного файла субтитров к видео.
Заключение
В этом руководстве вы использовали библиотеки Python ffmpeg-python
и faster-whisper
для создания приложения, способного извлекать аудио из входного видео, транскрибировать извлеченное аудио, генерировать файл субтитров на основе транскрипции и добавление субтитров к копии входного видео.