見出し画像

ショート映像大量生産 - Stable DiffusionとPythonの画像処理と自動化(6)

YouTube のショート動画、みなさんはどうやって作ってるんでしょう。
Tokyo Rock Girl はこの度、過去の素材からショート動画を作ろうと思ったんですが、いちいち1つずつは作ってられないので、Python で自動で大量生産しちゃいました。
Tokyo Rock Girl は、ともかく同じ作業を繰り返さないのです。

大量?生産したショート動画。上げたばかりなので再生回数 0 ですが、増えるんでしょうか

ノイズいれたりと、まあまあ細かいことはしてみたんですが、Tokyo Rock Girl に特化したものを公開してもしょうがないので、ちょっといじって汎用性が高いやつつくりました。
皆さんよければショート動画の大量生産しちゃってください。
YouTube に動画を上げてて、素材にしていた画像と音声はいっぱい眠ってるけど、ショートはあまり作ってないなーみたいな方、よければどうぞ。

とりあえず画像が2枚、こんなのがあるとします。

音声ファイルはそれぞれに mp3 形式であるとして、画像と音声がディスク内にあるなら、CSV ファイルを準備すれば、こんな感じのショート動画がたくさんズバッとできます。
CSV ファイルには、以下を書いていってください。
なお、1行目は無視されるので注意、というか列名を好きな感じで。

  1. png ファイルへのパス

  2. mp3 ファイルへのパス

  3. mp3 ファイルで切り取る時間の開始と終了を「分:秒」で指定

  4. 上部のタイトル

  5. 下部のサブタイトル

  6. 保存先フォルダ

  7. ファイル名 ( mp4 になるので拡張子なし )

CSV ファイルを Google スプレッドシートで準備
こうやってダウンロード

あと、フォントですね。
Google Fonts に行って、好きなフォントをダウンロードしてください。

で、Python のコードはこちら。

import csv
import os
from PIL import Image, ImageDraw, ImageFont
from moviepy.editor import AudioFileClip, ImageClip, CompositeVideoClip
import numpy as np

def hex_to_rgb(hex_color):
    return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

def check_files(csv_file):
    missing_files = []
    with open(csv_file, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        next(reader)
        for row in reader:
            if len(row) == 0:
                break
            png_path, mp3_path = row[0], row[1]
            if not os.path.exists(png_path):
                missing_files.append(png_path)
            if not os.path.exists(mp3_path):
                missing_files.append(mp3_path)
    return missing_files

def create_video(row, font_path, title_font_size, title_position, author_font_size, author_position, text_color, outline_size, outline_color, background_color):
    if len(row) != 8:
        return None
    
    png_path, mp3_path, start_time, end_time, title, author, output_directory, output_filename = row

    start_time = sum(x * int(t) for x, t in zip([60, 1], start_time.split(":")))
    end_time = sum(x * int(t) for x, t in zip([60, 1], end_time.split(":")))

    audio = AudioFileClip(mp3_path)
    audio_duration = audio.duration

    if start_time >= audio_duration:
        print(f"Warning: Start time {start_time} is greater than or equal to audio duration {audio_duration} for {mp3_path}")
        return None

    end_time = min(end_time, audio_duration)
    duration = end_time - start_time

    img = Image.open(png_path).convert('RGB')

    bg_color = hex_to_rgb(background_color)

    new_img = Image.new('RGB', (608, 1080), color=bg_color)

    if img.width > 608 or img.height > 608:
        img.thumbnail((608, 608), Image.LANCZOS)
    
    paste_x = (new_img.width - img.width) // 2
    paste_y = (new_img.height - img.height) // 2 + 50
    paste_position = (paste_x, paste_y)

    new_img.paste(img, paste_position)

    def draw_text_with_outline(draw, text, font, x, y, color, outline_color, outline_size, align="center", bold_offset=0):
        lines = text.split("\\n")
        y_text = y
        for line in lines:
            bbox = font.getbbox(line)
            line_width = bbox[2] - bbox[0]
            line_height = bbox[3] - bbox[1]
            if align == "center":
                x_text = x - line_width / 2
            elif align == "left":
                x_text = x
            elif align == "right":
                x_text = x - line_width
            
            if outline_size > 0:
                for offset_x in range(-outline_size, outline_size + 1):
                    for offset_y in range(-outline_size, outline_size + 1):
                        draw.text((x_text + offset_x, y_text + offset_y), line, font=font, fill=outline_color)
            
            for offset_x in range(-bold_offset, bold_offset + 1):
                for offset_y in range(-bold_offset, bold_offset + 1):
                    draw.text((x_text + offset_x, y_text + offset_y), line, font=font, fill=color)
            
            y_text += line_height

    text_img = Image.new('RGBA', new_img.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(text_img)
    
    title_font = ImageFont.truetype(font_path, title_font_size, encoding="unic")
    author_font = ImageFont.truetype(font_path, author_font_size, encoding="unic")
    
    title_y = int(new_img.height * title_position)
    author_y = int(new_img.height * author_position)
    
    text_color_rgb = hex_to_rgb(text_color)
    outline_color_rgb = hex_to_rgb(outline_color)
    
    draw_text_with_outline(draw, title, title_font, new_img.width/2, title_y, text_color_rgb, outline_color_rgb, outline_size)
    draw_text_with_outline(draw, author, author_font, new_img.width/2, author_y, text_color_rgb, outline_color_rgb, outline_size)
    
    result = Image.alpha_composite(new_img.convert('RGBA'), text_img)
    
    img_array = np.array(result.convert('RGB'))
    
    clip = ImageClip(img_array).set_duration(duration)

    audio = audio.subclip(start_time, end_time)
    final_clip = CompositeVideoClip([clip.set_audio(audio)])

    os.makedirs(output_directory, exist_ok=True)

    output_path = os.path.join(output_directory, f"{output_filename}.mp4")
    final_clip.write_videofile(output_path, fps=1)

    audio.close()
    final_clip.close()

    return output_path

def main(csv_file, font_path, title_font_size, title_position, author_font_size, author_position, text_color, outline_size, outline_color, background_color):
    missing_files = check_files(csv_file)
    if missing_files:
        print("Error: The following files are missing:")
        for file in missing_files:
            print(file)
        return

    with open(csv_file, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        next(reader)
        for row in reader:
            video_path = create_video(row, font_path, title_font_size, title_position, author_font_size, author_position, text_color, outline_size, outline_color, background_color)
            if video_path != None:
                print(f"Created video: {video_path}")
            else:
                break

if __name__ == "__main__":
    csv_file = "input.csv"
    font_path = "NotoSerifJP-ExtraBold.ttf"
    title_font_size = 42
    title_position = 0.15
    author_font_size = 30
    author_position = 0.8
    text_color = "FFFFFF"
    outline_size = 2
    outline_color = "1D477D"
    background_color = "000000"

    main(csv_file, font_path, title_font_size, title_position, author_font_size, author_position, text_color, outline_size, outline_color, background_color)

画像ファイル、音声ファイル、CSV ファイル、フォントのファイル、Python のコードを以下のように置いてください。

ファイルをこう置く
girl フォルダの中

ちなみに、今回は音声はこちらで作成しましたが、皆さんお好みで。

さあ、Python を実行しましょう。実行の仕方は ChatGPT あたりに聞いたら教えてくれます。
成功すると、こんな感じのショート映像ができます。
画像をショート映像にリンクしてるので、クリックして実物を確認してください。

ヘレナ・ルビンスタインの名言的なショート映像
ブリジット・バルドーの名言的なショート映像

さあこれで、好きな画像を夜な夜な作って、たくさんの好きな男の子女の子に好きな声で言わせてみたいセリフの音声を作ったら、毎晩布団の中でたくさんの彼氏彼女に告白されて寝れますよ。


この記事が気に入ったらサポートをしてみませんか?