Como gerar e adicionar legendas a vídeos usando Python, OpenAI Whisper e FFmpeg

Introdução

Neste tutorial, você irá construir uma aplicação Python capaz de extrair áudio de um vídeo de entrada, transcrever o áudio extraído, gerar um arquivo de legenda baseado na transcrição e, em seguida, adicionar a legenda a uma cópia do vídeo de entrada.

Para construir esta aplicação, você irá utilizar o FFmpeg para extrair áudio de um vídeo de entrada. Você irá utilizar o Whisper da OpenAI para gerar uma transcrição para o áudio extraído e, em seguida, utilizar essa transcrição para gerar um arquivo de legenda. Além disso, você irá utilizar o FFmpeg para adicionar o arquivo de legenda gerado a uma cópia do vídeo de entrada.

O FFmpeg é uma suíte de software poderosa e de código aberto para lidar com dados multimídia, incluindo tarefas de processamento de áudio e vídeo. Ele fornece uma ferramenta de linha de comando que permite aos usuários converter, editar e manipular arquivos multimídia com uma ampla gama de formatos e codecs.

O Whisper da OpenAI é um sistema de reconhecimento automático de fala (ASR) projetado para converter linguagem falada em texto escrito. Treinado em uma vasta quantidade de dados supervisionados multilíngues e multitarefas, ele se destaca na transcrição de conteúdo de áudio diverso com alta precisão.

Ao final deste tutorial, você terá uma aplicação capaz de adicionar legendas a um vídeo:

Pré-requisitos

Para seguir este tutorial, o leitor precisará das seguintes ferramentas:

Passo 1 — Criando o Diretório Raiz do Projeto

Nesta seção, você criará o diretório do projeto, baixará o vídeo de entrada, criará e ativará um ambiente virtual e instalará os pacotes Python necessários.

Abra uma janela de terminal e navegue até um local adequado para o seu projeto. Execute o seguinte comando para criar o diretório do projeto:

  1. mkdir generate-subtitle

Navegue até o diretório do projeto:

  1. cd generate-subtitle

Baixe este vídeo editado e armazene-o no diretório raiz do seu projeto como input.mp4. O vídeo mostra uma criança chamada Rushawn cantando “Beautiful Day” de Jermaine Edward. O vídeo editado que você vai usar neste tutorial foi retirado do seguinte vídeo do YouTube:

Crie um novo ambiente virtual e nomeie-o como env:

  1. python3 -m venv env

Ative o ambiente virtual:

  1. source env/bin/activate

Agora, use o seguinte comando para instalar os pacotes necessários para construir esta aplicação:

  1. pip3 install faster-whisper ffmpeg-python

Com o comando acima, você instalou as seguintes bibliotecas:

  • faster-whisper: é uma versão redesenhada do modelo Whisper da OpenAI que utiliza CTranslate2, uma engine de inferência de alto desempenho para modelos Transformer. Essa implementação alcança até quatro vezes mais velocidade do que openai/whisper com precisão comparável, tudo isso consumindo menos memória.

  • ffmpeg-python: é uma biblioteca Python que fornece uma interface em torno da ferramenta FFmpeg, permitindo aos usuários interagir com as funcionalidades do FFmpeg em scripts Python facilmente. Através de uma interface pythonica, ela permite tarefas de processamento de vídeo e áudio, como edição, conversão e manipulação.

Execute o seguinte comando para salvar os pacotes que foram instalados usando pip no ambiente virtual em um arquivo chamado requirements.txt:

  1. pip3 freeze > requirements.txt

O arquivo requirements.txt deve se parecer com o seguinte:

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

Nesta seção, você criou o diretório do projeto, baixou o vídeo de entrada que será usado neste tutorial, configurou um ambiente virtual, ativou-o e instalou os pacotes Python necessários. Na próxima seção, você irá gerar uma transcrição para o vídeo de entrada.

Passo 2 — Gerando a transcrição do vídeo

Nesta seção, você criará o script Python onde a aplicação irá residir. Dentro deste script, você usará a biblioteca ffmpeg-python para extrair a faixa de áudio do vídeo de entrada baixado na seção anterior e salvá-lo como um arquivo WAV. Em seguida, você usará a biblioteca faster-whisper para gerar uma transcrição para o áudio extraído.

No diretório raiz do seu projeto, crie um arquivo chamado main.py e adicione o seguinte código a ele:

import time
import math
import ffmpeg

from faster_whisper import WhisperModel

input_video = "input.mp4"
input_video_name = input_video.replace(".mp4", "")

Aqui, o código começa importando várias bibliotecas e módulos, incluindo time, math, ffmpeg de ffmpeg-python, e um módulo personalizado chamado WhisperModel de faster_whisper. Essas bibliotecas serão usadas para processamento de vídeo e áudio, transcrição e geração de legendas.

Em seguida, o código define o nome do arquivo de vídeo de entrada, armazena-o em uma constante chamada input_video, e depois armazena o nome do arquivo de vídeo sem a extensão .mp4 em uma constante chamada input_video_name. Definir o nome do arquivo de entrada aqui permitirá que você trabalhe com vários vídeos de entrada sem sobrescrever os arquivos de legenda e vídeo de saída gerados para eles.

Adicione o seguinte código ao final do seu 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

O código acima define uma função chamada extract_audio() que é responsável por extrair o áudio do vídeo de entrada.

Primeiro, define o nome do áudio que será extraído como um nome formado anexando audio- ao nome base do vídeo de entrada com a extensão .wav, e armazena este nome em uma constante chamada extracted_audio.

Em seguida, o código chama o método ffmpeg.input() da biblioteca ffmpeg para abrir o vídeo de entrada e cria um objeto de fluxo de entrada chamado stream.

O código então chama o método ffmpeg.output() para criar um objeto de fluxo de saída com o fluxo de entrada e o nome do arquivo de áudio extraído definido.

Após configurar o fluxo de saída, o código chama o método ffmpeg.run(), passando o fluxo de saída como parâmetro para iniciar o processo de extração de áudio e salvar o arquivo de áudio extraído no diretório raiz do seu projeto. Adicionalmente, um parâmetro booleano, overwrite_output=True, é incluído para substituir qualquer arquivo de saída pré-existente pelo recém-gerado, caso tal arquivo já exista.

Por fim, o código retorna o nome do arquivo de áudio extraído.

Adicione o seguinte código abaixo da função extract_audio():


def run():

    extracted_audio = extract_audio()
run()

Aqui, o código define uma função chamada run() e então a chama. Esta função chama todas as funções necessárias para gerar e adicionar legendas a um vídeo.

Dentro da função, o código chama a função extract_audio() para extrair o áudio de um vídeo e depois armazena o nome do arquivo de áudio retornado em uma variável chamada extracted_audio.

Volte ao seu terminal e execute o seguinte comando para rodar o script main.py:

  1. python3 main.py

Depois de executar o comando acima, a saída do FFmpeg será exibida no terminal, e um arquivo chamado audio-input.wav contendo o áudio extraído do vídeo de entrada será armazenado no diretório raiz do seu projeto.

Volte para o seu arquivo main.py e adicione o seguinte código entre as funções extract_audio() e 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

O código acima define uma função chamada transcribe responsável por transcrever o arquivo de áudio extraído do vídeo de entrada.

Primeiro, o código cria uma instância do objeto WhisperModel e define o tipo de modelo como small. O Whisper da OpenAI tem os seguintes tipos de modelo: tiny, base, small, medium e large. O modelo tiny é o menor e mais rápido e o modelo large é o maior e mais lento, mas mais preciso.

Em seguida, o código chama o método model.transcribe() com o áudio extraído como argumento para recuperar a função de segmentos e as informações de áudio e armazená-las nas variáveis info e segments, respectivamente. A função de segmentos é um gerador Python, então a transcrição só começará quando o código iterar sobre ela. A transcrição pode ser executada até o final reunindo os segmentos em uma list ou um for loop.

Em seguida, o código armazena o idioma detectado no áudio em uma constante chamada info e o imprime no console.

após imprimir o idioma detectado, o código reúne os segmentos de transcrição em uma lista list para executar a transcrição e armazena os segmentos coletados em uma variável também chamada de segments. O código então percorre a lista de segmentos de transcrição e imprime o tempo de início, o tempo de término e o texto de cada segmento no console.

Finalmente, o código retorna o idioma detectado no áudio e os segmentos de transcrição.

Adicione o seguinte código dentro da função run():

def run():

    extracted_audio = extract_audio()

    language, segments = transcribe(audio=extracted_audio)

O código adicionado chama a função de transcrição com o áudio extraído como argumento e armazena os valores retornados em constantes chamadas language e segments.

Volte ao seu terminal e execute o seguinte comando para rodar o script main.py:

  1. python3 main.py

Na primeira vez que você executar este script, o código fará o download e armazenará em cache o modelo Whisper Small, as execuções subsequentes serão muito mais rápidas.

Após executar o comando acima, você deverá ver a seguinte saída no console:

…
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

A saída acima mostra que o idioma detectado no áudio é o inglês (en). Além disso, mostra o tempo de início e término de cada segmento de transcrição em segundos, juntamente com o texto.

Aviso: Embora o reconhecimento de fala do OpenAI Whisper seja muito preciso, não é 100% preciso, podendo estar sujeito a limitações e erros ocasionais, especialmente em cenários linguísticos ou de áudio desafiadores. Portanto, sempre certifique-se de verificar a transcrição manualmente.

Nesta seção, você criou um script Python para a aplicação. Dentro do script, ffmpeg-python foi usado para extrair o áudio do vídeo baixado e salvá-lo como um arquivo WAV. Em seguida, a biblioteca faster-whisper foi usada para gerar uma transcrição para o áudio extraído. Na próxima seção, você irá gerar um arquivo de legenda com base na transcrição e então irá adicionar a legenda ao vídeo.

Passo 3 — Gerando e adicionando a legenda ao vídeo

Nesta seção, primeiro, você irá entender o que é um arquivo de legenda e como ele é estruturado. Em seguida, você irá usar os segmentos de transcrição gerados na seção anterior para criar um arquivo de legenda. Após criar o arquivo de legenda, você irá usar a biblioteca ffmpeg-python para adicionar o arquivo de legenda a uma cópia do vídeo de entrada.

Entendendo Legendas: Estrutura e Tipos

A subtitle file is a text file that contains timed text information corresponding to spoken or written content in a video or film. It typically includes information about when each subtitle should appear and disappear on the screen. There are many subtitle formats, however, in this tutorial, we will focus on the widely used format named SubRip (SRT).

A subtitle file is organized into a series of subtitle entries, each typically following a specific format. The common structure of a subtitle entry includes:

  1. Índice da Legenda: Um número sequencial que indica a ordem da legenda no arquivo.

  2. Temporizações: Marcadores de início e fim que especificam quando o texto de legenda deve ser exibido. As temporizações geralmente são formatadas como HH:MM:SS,sss (horas, minutos, segundos, milissegundos).

  3. Texto da Legenda: O texto real da entrada de legenda, representando conteúdo falado ou escrito. Esse texto é exibido na tela durante o intervalo de tempo especificado.

Por exemplo, uma entrada de legenda em um arquivo SRT pode se parecer com isso:

1
00:00:10,500 --> 00:00:15,000
This is an example subtitle.

Neste exemplo, o índice é 1, as temporizações indicam que a legenda deve ser exibida de 10.5 segundos a 15 segundos, e o texto da legenda é Este é um exemplo de legenda.

Legendas podem ser divididas em dois tipos principais:

  • Legendas suaves: Também conhecidas como legendas fechadas, são armazenadas externamente como arquivos separados (como SRT) e podem ser adicionadas ou removidas independentemente do vídeo. Elas proporcionam flexibilidade ao espectador, permitindo alternância, troca de idioma e personalização de configurações. No entanto, sua eficácia depende do suporte do reprodutor de vídeo, e nem todos os reprodutores acomodam universalmente legendas suaves.

  • **Legendas Fixas:** São incorporadas permanentemente nos quadros do vídeo durante a edição ou codificação, permanecendo uma parte fixa do vídeo. Embora garantam visibilidade constante, mesmo em players que não suportam arquivos de legendas externos, fazer modificações ou desativá-las requer a recodificação de todo o vídeo, limitando o controle do usuário.

Criando o arquivo de legenda

Volte para o seu arquivo main.py e adicione o seguinte código entre as funções transcribe() e 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

Aqui, o código define uma função chamada format_time() que é responsável por converter o tempo de início e fim de um segmento de transcrição dado em segundos para um formato de tempo compatível com legendas que exibe horas, minutos, segundos e milissegundos (HH:MM:SS,sss).

O código primeiro calcula horas, minutos, segundos e milissegundos a partir do tempo dado em segundos, formata-os adequadamente e então retorna o tempo formatado.

Adicione o seguinte código entre as funções format_time() e 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

O código adicionado define uma função chamada generate_subtitle_file() que recebe como parâmetros o idioma detectado no áudio extraído e os segmentos de transcrição. Esta função é responsável por gerar um arquivo de legenda SRT baseado no idioma e nos segmentos de transcrição.

Primeiro, o código define o nome do arquivo de legenda como um nome formado anexando sub- e o idioma detectado ao nome base do vídeo de entrada com a extensão “.srt”, e armazena este nome em uma constante chamada subtitle_file. Além disso, o código define uma variável chamada text onde você irá armazenar as entradas de legenda.

Em seguida, o código itera pelos segmentos transcritos, formata os tempos de início e fim usando a função format_time(), utiliza esses valores formatados juntamente com o índice do segmento e o texto para criar uma entrada de legenda, e então adiciona uma linha em branco separando cada entrada de legenda.

Por último, o código cria um arquivo de legenda no diretório raiz do seu projeto com o nome definido anteriormente, adiciona as entradas de legenda ao arquivo e retorna o nome do arquivo de legenda.

Adicione o seguinte código ao final da sua função run():


def run():

    extracted_audio = extract_audio()
    language, segments = transcribe(audio=extracted_audio)
    subtitle_file = generate_subtitle_file(
    language=language,
    segments=segments
    )

O código adicionado chama a função generate_subtitle_file() com o idioma detectado e os segmentos de transcrição como argumentos, e armazena o nome do arquivo de legenda retornado em uma constante chamada subtitle_file.

Volte ao seu terminal e execute o seguinte comando para executar o script main.py:

  1. python3 main.py

Após executar o comando acima, um arquivo de legenda chamado sub-input.en.srt será salvo no diretório raiz do seu projeto.

Abra o arquivo de legenda sub-input.en.srt e você deverá ver algo semelhante ao seguinte:


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

Adicionando legendas a vídeos

Adicione o seguinte código entre a função generate_subtitle_file() e a função 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)

Aqui, o código define uma função chamada add_subtitle_to_video() que recebe como parâmetros um valor booleano usado para determinar se deve adicionar uma legenda suave ou uma legenda fixa, o nome do arquivo de legenda e o idioma detectado na transcrição. Esta função é responsável por adicionar legendas suaves ou fixas a uma cópia do vídeo de entrada.

Primeiro, o código usa o método ffmpeg.input() com o vídeo de entrada e o arquivo de legenda para criar objetos de fluxo de entrada para o vídeo de entrada e o arquivo de legenda, armazenando-os em constantes nomeadas video_input_stream e subtitle_input_stream, respectivamente.

Após criar os fluxos de entrada, o código define o nome do arquivo de vídeo de saída como um nome formado acrescentando output- ao nome base do vídeo de entrada com a extensão “.mp4” e armazena esse nome em uma constante chamada output_video. Além disso, define o nome da faixa de legenda como o nome do arquivo de legenda sem a extensão .srt e armazena esse nome em uma constante chamada subtitle_track_title.

Em seguida, o código verifica se o booleano soft_subtitle está definido como True, indicando que deve adicionar uma legenda suave.

Se for o caso, o código chama o método ffmpeg.output() para criar um objeto de fluxo de saída com os fluxos de entrada, o nome do arquivo de vídeo de saída e as seguintes opções para o vídeo de saída:

  • "c": "copy": Especifica que o codec de vídeo e outros parâmetros de vídeo devem ser copiados diretamente da entrada para a saída sem re-codificação.

  • "c:s": "mov_text": Ele especifica que o codec e os parâmetros de legendas também devem ser copiados da entrada para a saída sem re-codificação. mov_text é um codec de legendas comum usado em arquivos MP4/MOV.

  • ”metadata:s:s:0”: f"language={subtitle_language}": Define os metadados de idioma para o fluxo de legendas. O idioma é definido com base no valor armazenado em subtitle_language

  • "metadata:s:s:0": f"title={subtitle_track_title}": Define os metadados de título para o fluxo de legendas. O título é definido com base no valor armazenado em subtitle_track_title

Finalmente, o código chama o método ffmpeg.run(), passando o fluxo de saída como parâmetro para adicionar a legenda suave ao vídeo e salvar o arquivo de vídeo de saída no diretório raiz do seu projeto.

Adicione o seguinte código ao final de sua função 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)

O código destacado será executado se o booleano soft_subtitle estiver definido como Falso, indicando que deve adicionar uma legenda rígida.

Se este for o caso, primeiro, o código chama o método ffmpeg.output() para criar um objeto de fluxo de saída com o fluxo de vídeo de entrada, o nome do arquivo de vídeo de saída e o parâmetro vf=f"subtitles={subtitle_file}". O vf significa “filtro de vídeo” e é usado para aplicar um filtro ao fluxo de vídeo. Neste caso, o filtro sendo aplicado é a adição da legenda.

Por fim, o código chama o método ffmpeg.run(), passando o fluxo de saída como parâmetro para adicionar a legenda rígida ao vídeo e salvar o arquivo de vídeo de saída no diretório raiz do seu projeto.

Adicione o seguinte código destacado à função 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
    )

O código destacado chama o add_subtitle_to_video() com o parâmetro soft_subtitle definido como Verdadeiro, o nome do arquivo de legenda e o idioma da legenda para adicionar uma legenda suave a uma cópia do vídeo de entrada.

Volte ao seu terminal e execute o seguinte comando para executar o script main.py:

  1. python3 main.py

Após executar o comando acima, um arquivo de vídeo de saída nomeado output-input.mp4 será salvo no diretório raiz do seu projeto.

Abra o vídeo usando o seu player de vídeo preferido, selecione uma legenda para o vídeo e observe como a legenda não será exibida até que você a selecione:

Volte para o arquivo main.py, navegue até a função run() e na chamada da função add_subtitle_to_video(), defina o parâmetro soft_subtitle como False:

def run():
    …
    add_subtitle_to_video(
        soft_subtitle=False,
        subtitle_file=subtitle_file,
        subtitle_language=language
    )

Aqui, você define o parâmetro soft_subtitle como False para adicionar legendas rígidas ao vídeo.

Volte ao seu terminal e execute o seguinte comando para executar o script main.py:

  1. python3 main.py

Depois de executar o comando acima, o arquivo de vídeo output-input.mp4 localizado no diretório raiz do seu projeto será sobrescrito.

Abra o vídeo usando seu player de vídeo preferido, tente selecionar uma legenda para o vídeo e observe que uma não está disponível, mas uma legenda está sendo exibida:

Nesta seção, você obteve uma compreensão da estrutura de um arquivo de legenda SRT e utilizou os segmentos de transcrição da seção anterior para criar um. Em seguida, a biblioteca ffmpeg-python foi usada para adicionar o arquivo de legenda gerado ao vídeo.

Conclusão

Neste tutorial, você usou as bibliotecas Python ffmpeg-python e faster-whisper para construir uma aplicação capaz de extrair áudio de um vídeo de entrada, transcrever o áudio extraído, gerar um arquivo de legenda com base na transcrição e adicionar a legenda a uma cópia do vídeo de entrada.

Source:
https://www.digitalocean.com/community/tutorials/how-to-generate-and-add-subtitles-to-videos-using-python-openai-whisper-and-ffmpeg