🌟

SeamlessM4Tで英語のYouTube動画の日本語音声を自動生成する

2023/08/26に公開

SeamlessM4Tとは

https://gigazine.net/news/20230823-meta-speech-translation-transcription-seamlessm4t/

環境構築

Linux+CUDAな環境が一番楽です。Google Colab Proが良いでしょう。[1]

https://note.com/npaka/n/ne7874068482f

依存ライブラリ

システム依存については https://huggingface.co/spaces/facebook/seamless_m4t/blob/main/Dockerfile が参考になります。

apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y --no-install-recommends \
    git \
    git-lfs \
    wget \
    curl \
    # python build dependencies \
    build-essential \
    libssl-dev \
    zlib1g-dev \
    libbz2-dev \
    libreadline-dev \
    libsqlite3-dev \
    libncursesw5-dev \
    xz-utils \
    tk-dev \
    libxml2-dev \
    libxmlsec1-dev \
    libffi-dev \
    liblzma-dev \
    # gradio dependencies \
    ffmpeg \
    # fairseq2 dependencies \
    libsndfile-dev && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

pythonパッケージについては

pip install fairseq2 git+https://github.com/facebookresearch/seamless_communication torch torchaudio pytube moviepy pydub

モデルのダウンロード

import torch
from seamless_communication.models.inference.translator import Translator

device = torch.device("cuda:0")
translator = Translator(
    model_name_or_card="seamlessM4T_large",
    vocoder_name_or_card="vocoder_36langs",
    device=device,
    dtype=torch.float16
)

ここでseamlessM4T_largeモデルの10GBのダウンロードが発生します

日本語音声生成と動画合成部分

Steve Jobs' 2005 Stanford Commencement Address を読み込みました。

Translatorは数秒の音声しか受け付けてくれないのでファイル分割して最後に結合しています。

import functools
import re
import torch
import torchaudio
from pytube import YouTube
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip
from pydub import AudioSegment
from pydub.silence import split_on_silence
from seamless_communication.models.inference.translator import Translator
import functools
import torch
import torchaudio
from pytube import YouTube
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip
from pydub import AudioSegment
from pydub.silence import split_on_silence
from seamless_communication.models.inference.translator import Translator


@functools.lru_cache(maxsize=None)
def init_translator():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    translator = Translator(
        model_name_or_card="seamlessM4T_large",
        vocoder_name_or_card="vocoder_36langs",
        device=device,
        dtype=torch.float16
    )
    return translator

def download_and_convert_video(url: str) -> AudioSegment:
    # ビデオをダウンロード
    yt = YouTube(url)
    video = yt.streams.get_highest_resolution()
    video.download(filename='temp_video.mp4')

    # mp4をwavに変換
    video_clip = VideoFileClip('temp_video.mp4')
    audio_clip = video_clip.audio
    audio_clip.write_audiofile('output.wav')

    # wavファイルを読み込む
    audio = AudioSegment.from_wav("output.wav")

    # 無音部分で区切る
    chunks = split_on_silence(audio, 
        # 無音部分とみなす閾値(dB)
        silence_thresh = audio.dBFS - 14,
        # 無音部分の最小長さ(ミリ秒)
        min_silence_len = 500,
        # 音声チャンクの前後に残す無音の長さ(ミリ秒)
        keep_silence = 100
    )

    # 分割した音声を保存
    sample_rate = 22050
    combined = AudioSegment.empty()
    translator = init_translator()
    for i, chunk in enumerate(chunks):
        f = f"chunk_{i}.wav"
        chunk.export(f, format="wav")
        text_out, wav, sr = translator.predict(
            input=f,
            task_str='S2ST',
            tgt_lang='jpn',
            src_lang='eng',
            ngram_filtering=True,
            sample_rate=sample_rate
        )
        f_jpn = f"chunk_{i}.jpn.wav"
        print(text_out)
        torchaudio.save(f_jpn, wav[0].cpu(), sr)
        audio = AudioSegment.from_wav(f_jpn)
        combined += audio

    combined.export("combined.jpn.wav", format="wav")
    # ビデオとオーディオを読み込む
    video = VideoFileClip("temp_video.mp4")
    original_audio = video.audio
    new_audio = AudioFileClip("combined.jpn.wav")

    # original_audioのボリュームを半分にする
    original_audio = original_audio.fx(lambda x: x.volumex(0.5))

    # オーディオの長さを調整
    min_length = min(original_audio.duration, new_audio.duration)
    original_audio = original_audio.subclip(0, min_length)
    new_audio = new_audio.subclip(0, min_length)

    # オーディオをオーバーラップ
    combined_audio = CompositeAudioClip([original_audio, new_audio])

    # オーディオを置き換える
    final_video = video.set_audio(combined_audio)

    # 結合したビデオを保存
    vid = extract_video_id(url)
    output_file = f"{vid}.mp4"
    final_video.write_videofile(output_file, codec="libx264", audio_codec="aac")
    final_video.close()
    return output_file

def extract_video_id(url):
    pattern = r'(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)'
    match = re.search(pattern, url)
    if match:
        return match.group(1)
    return None
    
url = 'https://www.youtube.com/watch?v=UF8uR6Z6KLc'
assert extract_video_id(url)
result = download_and_convert_video(url)
print(result)

できた!? のか

出力されたmp4をダウンロードして再生すると日本語音声が乗っています。

https://twitter.com/laiso/status/1695458374098600086

できてはいない

脚注
  1. https://github.com/facebookresearch/fairseq2/issues/1 ↩︎

Discussion