Artigo original: How to Download and Trim MP3s from YouTube with Python

Todo mundo é diferente, mas acredito que quase todos nós gostamos de ouvir música.

Se você deseja manter uma versão local dos áudios que costuma ouvir on-line, precisará fazer o download desses arquivos. Às vezes, você também vai querer recortar uma parte desse arquivo de áudio em vez de ter o arquivo todo disponível.

Você pode desenvolver um script em Python para fazer exatamente essas coisas. Você também pode estendê-lo com funcionalidades adicionais, se desejar. Mostrarei a você como fazer isso neste tutorial.

Uma observação sobre direitos autorais

Se você já usou a Internet antes, provavelmente está ciente de que questões de direitos autorais (em inglês, copyrights) podem deixar muitas pessoas chateadas – em ambos os lados de um debate sobre como o conteúdo livre deve ser.

A própria biblioteca que usaremos teve sua parcela de problemas com direitos autorais.

Felizmente, há material livre de direitos autorais disponível para desfrutarmos e brincarmos usando nossos programas. Portanto, usaremos neste tutorial o 4º Movimento da 9ª Sinfonia de Beethoven, que é isento de royalties.

Este guia pressupõe que você também usará os métodos ensinados para baixar material livre de direitos autorais. Não use esta informação para infringir direitos autorais!

O que vamos fazer neste tutorial

Primeiro, vamos instalar a dependência básica, FFMPEG. Em seguida, instalaremos a biblioteca youtube-dl (que também funciona com o Vimeo e muitas outras plataformas) para baixar o áudio de um URL do YouTube e usá-lo no código em Python.

Em seguida, faremos o download da biblioteca pydub para cortar arquivos de áudio e implementar essa funcionalidade em nosso código.

Por fim, criaremos uma interface de usuário amigável para que possamos reutilizar esse script mais tarde sem precisar editar o código.

Tudo isso será executado dentro de uma função main() para que possamos separar funcionalidade, implementação e uso.

Como instalar o pacote do FFMPEG

Este pacote está no centro de muitos programas multimídia (e todos os de código aberto que usei até agora). Vamos precisar dele para ambas as bibliotecas do Python que instalaremos em breve.

Como instalar no Linux

Se você está em uma máquina com o Debian (tal como o Ubuntu ou o Kali), aqui está o comando para instalar o FFMPEG:

sudo apt-get install ffmpeg

Se você está usando outros tipos de distribuições, as instruções de instalação estão aqui (em inglês).

Como instalar no Windows

Primeiro, instale o Chocolatey Package Manager. As instruções de instalação estão aqui.

Depois de instalar corretamente o Chocolatey (caso ainda não o tenha), baixe o seguinte pacote a partir de uma instância administrativa do Powershell:

choco install ffmpeg

Como instalar no Mac

Caso você ainda não o tenha, instale o homebrew. Então, em um terminal, digite:

brew install ffmpeg

Como baixar programaticamente o áudio de URLs do YouTube

Antes de mais nada, baixe o pacote youtube-dl com o pip. É um dos pacotes com mais estrelas no GitHub.

pip install youtube-dl

Vamos importar este módulo e, então, declarar uma função que baixa o áudio em formato mp3 com qualidade razoável de um URL do YouTube.

import youtube_dl # client para muitos portais de multimídia

# baixa yt_url para o mesmo diretório no qual o script é executado
def download_audio(yt_url):
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '192',
        }],
    }
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        ydl.download([yt_url])

def main():
    yt_url = "https://www.youtube.com/watch?v=8OAPLk20epo"
    download_audio(yt_url)

main()

O método .download() baixará gradualmente o fluxo de áudio como um arquivo .webm. Depois de detectar que todo o arquivo está disponível, ele usará o ffmpeg para convertê-lo em um arquivo de áudio MP3. Isso significa que, se algo acontecer, digamos que a conexão com a Internet caia quando o arquivo estiver 90% baixado, ele retomará o download em 90% em vez de desde o início – bem legal.

Observe que, se você já baixou o arquivo mp3, o download será reiniciado e substituirá o arquivo.

Execute seu script do Python. O download de áudio para esta interpretação do 4º Movimento da 9ª Sinfonia de Beethoven deve ser um arquivo MP3 de aproximadamente 33 Mb com o título do vídeo disponível localmente. Provavelmente, vai ser um pouco lento, então vá fazer um chá.

Como você pode ver, é possível passar parâmetros opcionais para youtube-dl (que também estará disponível como um programa CLI independente fora do script). Um de seus recursos é baixar uma série de vídeos de um URL de lista de reprodução. Se você estiver mais interessado, pode ler a documentação deles. Vou manter o uso mais trivial ao longo deste tutorial.

Como cortar o arquivo baixado

Com o arquivo baixado, vamos cortá-lo localmente (você deve ter considerado se é possível simplesmente baixar um clipe do YouTube. Todos os métodos confiáveis que encontrei se resumem basicamente a baixar o arquivo inteiro e depois editar localmente). Para isso, usaremos a biblioteca pydub. Você pode instalá-la assim:

pip install pydub

Esta é uma biblioteca muito boa, que permite manipular completamente o áudio, reduzindo ou aumentando o volume em determinados intervalos, repetindo clipes e assim por diante. Por enquanto, estamos interessados apenas em cortar.

Para cortar nosso arquivo baixado, teremos que obter o nome do arquivo de nosso MP3 recém-baixado, converter os pontos inicial e final de nosso intervalo de áudio desejado de "horas:minutos:segundos" para milissegundos e, finalmente, usar o pydub para cortar nosso arquivo de áudio.

Como obter o nome do arquivo

Infelizmente, o método .download() não retornará nosso nome de arquivo gerado, ao qual também não teremos acesso, pois estamos apenas passando o URL como parâmetro. Temos, porém, o Python – e ele é uma ferramenta fantástica.

Sabemos que estamos procurando um arquivo .mp3 que foi gerado logo antes de nossa operação de pesquisa de nome de arquivo (por padrão, o Python executa em thread única e executará o código de forma síncrona). Pegaremos o nome do nosso MP3 mais recente no diretório do script, e esse será o nosso arquivo.

Podemos realizar esta operação listando todos os arquivos .mp3 no diretório local, coletando suas datas e horas como números inteiros (o que significa tempo em milissegundos contados a partir de uma determinada data no passado – ou seja, quanto maior o valor, mais distante no tempo o arquivo foi criado), e obtendo o arquivo com o valor mais alto.

Para isso, vamos precisar dos módulos glob para navegar no diretório e os para obter informações de data e hora, ambos disponíveis nativamente.

import glob
import os

def newest_mp3_filename():
    # lista todos os mp3s no diretório local
    list_of_mp3s = glob.glob('./*.mp3')
    # retorna o mp3 com o valor de data e hora mais alto
    return max(list_of_mp3s, key = os.path.getctime)

Como obter HH:MM:SS como milissegundos

Assim que fatiarmos nosso arquivo, o pydub esperará intervalos de tempo expressos em milissegundos. Para nós, humanos, calcular o momento exato em milissegundos toda vez que queremos cortar um vídeo seria muito chato. Então, pediremos ao computador que faça isso por nós.

Nossa entrada será uma string no formato HH:MM:SS. Isso funciona bem se quisermos cortar um vídeo com mais de uma hora, mas, na maioria das vezes, queremos apenas obter o intervalo de tempo de minuto:segundo para outro. Então, temos que levar isso em consideração também.

Um milissegundo é 1/1000 de segundo. Um minuto são 60 segundos e uma hora são 60 minutos. Portanto, temos que obter o valor para horas, depois para minutos e depois para segundos, realizar a conversão de milissegundos em cada um e depois somar as partes para chegar ao resultado.

def get_video_time_in_ms(video_timestamp):
    vt_split = video_timestamp.split(":")
    if (len(vt_split) == 3): # se estiver no formato HH:MM:SS
        hours = int(vt_split[0]) * 60 * 60 * 1000
        minutes = int(vt_split[1]) * 60 * 1000
        seconds = int(vt_split[2]) * 1000
    else: # formato MM:SS
        hours = 0
        minutes = int(vt_split[0]) * 60 * 1000
        seconds = int(vt_split[1]) * 1000
    # ponto no tempo em milissegundos
    return hours + minutes + seconds

Como obter o áudio cortado

Agora, vamos ler nosso MP3 como um objeto pydub e fatiar nosso intervalo desejado. A sintaxe é exatamente a mesma das operações de fatiamento em strings e arrays, mas, em vez de um índice para um elemento, usaremos milissegundos para instantes específicos dentro do áudio.

def get_trimmed(mp3_filename, initial, final = ""):
    if (not mp3_filename):
        # levanta um erro para suspender imediatamente a execução do programa
        raise Exception("No MP3 found in local directory.")
    # lê o mp3 como um objeto PyDub
    sound = AudioSegment.from_mp3(mp3_filename)
    t0 = get_video_time_in_ms(initial)
    print("Beginning trimming process for file ", mp3_filename, ".\n")
    print("Starting from ", initial, "...")
    if (len(final) > 0):
        print("...up to ", final, ".\n")
        t1 = get_video_time_in_ms(final)
        return sound[t0:t1] # t0 up to t1
    return sound[t0:] # t0 até o fim

Como juntar tudo

Alle Menschen werden Brüder,
Wo dein sanfter Flügel weilt.
-- Friedrich Schiller

Caso você esteja se perguntando, o fragmento anterior se traduz em "criais-nos um mundo irmão, insuflais nosso Canto". É um fragmento de Hino à alegria (e, inglês, Ode to Joy), um poema de Friedrich Schiller, que serve como a maior parte da letra das partes corais do 4º Movimento.

Este é o fragmento mais famoso do movimento mais famoso da mais famosa sinfonia de Beethoven. É muito provável que você reconheça esta peça, não importando sua origem ou criação.

Vamos juntar tudo o que fizemos. Faremos o download do áudio do YouTube, fatiaremos o coral de Hino à alegria (de 9:51 a 14:04) e salvaremos como <nome_do_arquivo> - TRIM.mp3.

Se você seguiu o tutorial corretamente, atualize sua função main() para executar cada etapa de modo a ficar com o MP3 completo e a versão cortada dele como arquivos disponíveis no diretório em que você executará o script. Não se esqueça de executar a função main() no final do script.

def main():
    yt_url = "https://www.youtube.com/watch?v=8OAPLk20epo"
    download_audio(yt_url)
    initial = "9:51"
    final = "14:04"
    filename = newest_mp3_filename()
    trimmed_file = get_trimmed(filename, initial, final)
    trimmed_filename = "".join([filename.split(".mp3")[0], "- TRIM.mp3"])
    print("Process concluded successfully. Saving trimmed file as ", trimmed_filename)
    # salva o arquivo com o mais novo nome de arquivo
    trimmed_file.export(trimmed_filename, format="mp3")

Como adicionar interação de usuário diretamente da interface de linha de comando

Para esta parte, precisaremos do módulo sys do Python, que lê a entrada passada pela linha de comando (entre outras coisas). Simplesmente atualizaremos as variáveis na função main() para ler a entrada da CLI em vez dos dados atualmente codificados.

O ARGV lê a entrada sequencialmente como um array, começando no índice 1 (0 representa o nome do script Python em execução). Vamos configurá-lo para ler um URL como o primeiro argumento e, em seguida, os instantes de corte inicial e final (opcional).

import sys

def main():
    if (not len(sys.argv) > 1):
        print("Please insert a multimedia-platform URL supported by youtube-dl as your first argument.")
        return
    yt_url = sys.argv[1]
    download_audio(yt_url)
    if (not len(sys.argv > 2)): # exit if no instants as args
        return
    initial = sys.argv[2]
    final = ""
    if (sys.argv[3]):
        final = sys.argv[3]
    filename = newest_mp3_filename()
    trimmed_file = get_trimmed(filename, initial, final)
    trimmed_filename = "".join([filename.split(".mp3")[0], "- TRIM.mp3"])
    print("Process concluded successfully. Saving trimmed file as ", trimmed_filename)
    # salva o arquivo com o mais novo nome de arquivo
    trimmed_file.export(trimmed_filename, format="mp3")

Execute o arquivo para testá-lo. Lembre-se de atualizar o nome do script para aquele que está em sua máquina.

python ytauddown.py https://www.youtube.com/watch?v=8OAPLk20epo 9:51 14:04

Script final

Esta é a versão final, com tudo junto. Observe que os comentários sobre os módulos estão relacionados apenas ao que estamos usando e que a função main() está sendo invocada na última linha.

import youtube_dl # cliente para baixar a partir de vários portais de multimídia
import glob # operações de diretório
import os # interface para informações fornecidas pelo sistema em arquivos
import sys # interface para a linha de comando
from pydub import AudioSegment # somente operações de áudio

def newest_mp3_filename():
    # lista todos os mp3s no diretório local
    list_of_mp3s = glob.glob('./*.mp3')
    # returna mp3 com o valor de data e hora mais alto
    return max(list_of_mp3s, key = os.path.getctime)

def get_video_time_in_ms(video_timestamp):
    vt_split = video_timestamp.split(":")
    if (len(vt_split) == 3): # se estiver no formato HH:MM:SS
        hours = int(vt_split[0]) * 60 * 60 * 1000
        minutes = int(vt_split[1]) * 60 * 1000
        seconds = int(vt_split[2]) * 1000
    else: # formato MM:SS
        hours = 0
        minutes = int(vt_split[0]) * 60 * 1000
        seconds = int(vt_split[1]) * 1000
    # ponto no tempo em milissegundos
    return hours + minutes + seconds

def get_trimmed(mp3_filename, initial, final = ""):
    if (not mp3_filename):
        # levanta um erro para suspender imediatamente a execução do programa
        raise Exception("No MP3 found in local directory.")
    # lê o mp3 como um objeto PyDub
    sound = AudioSegment.from_mp3(mp3_filename)
    t0 = get_video_time_in_ms(initial)
    print("Beginning trimming process for file ", mp3_filename, ".\n")
    print("Starting from ", initial, "...")
    if (len(final) > 0):
        print("...up to ", final, ".\n")
        t1 = get_video_time_in_ms(final)
        return sound[t0:t1] # t0 up to t1
    return sound[t0:] # t0 up to the end



# baixa yt_url para o mesmo diretório no qual o script é executado
def download_audio(yt_url):
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '192',
        }],
    }
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        ydl.download([yt_url])

def main():
    if (not len(sys.argv) > 1):
        print("Please insert a multimedia-platform URL supported by youtube-dl as your first argument.")
        return
    yt_url = sys.argv[1]
    download_audio(yt_url)
    if (not len(sys.argv > 2)): # sai se não houver os tempos como argumentos
        return
    initial = sys.argv[2]
    final = ""
    if (sys.argv[3]):
        final = sys.argv[3]
    filename = newest_mp3_filename()
    trimmed_file = get_trimmed(filename, initial, final)
    trimmed_filename = "".join([filename.split(".mp3")[0], "- TRIM.mp3"])
    print("Process concluded successfully. Saving trimmed file as ", trimmed_filename)
    # salva o arquivo com o mais novo nome de arquivo
    trimmed_file.export(trimmed_filename, format="mp3")

# exemplo de uso:
# python ytauddown.py https://www.youtube.com/watch?v=8OAPLk20epo 9:51 14:04
main()

Exercícios sugeridos

  1. Detecte se a primeira entrada é um URL válido ou não. Dê uma olhada nas RegEx em Python se não souber por onde começar.
  2. Detecte se a segunda e a terceira entradas estão no formato válido (horas:minutos:segundos OU minutos:segundos).
  3. Adicione uma opção para renomear o arquivo MP3 diretamente da CLI. Lembre-se de que os argumentos ARGV são executados em ordem.
  4. Refatore este script para interagir com sua funcionalidade usando uma GUI. Pode ser um aplicativo da web ou local, sua escolha.

Considerações finais

Espero que você se divirta com este projeto e faça bom uso dele.

Lembre-se de que ganhar a vida como artista é muito difícil, especialmente para a maioria sem apoio corporativo. Lembre-se de apoiar os artistas de cujo trabalho você gosta sempre que puder e lembre-se também de apoiar o software de código aberto.