Side by Sideの立体視連番を動画にするスクリプト(Python)をChatGPTでつくってみた
UEでサイドバイサイドで書き出した立体視の連番(PNG)を、mp4に変換するのが手間、、ってことでPythonで自動化。
●機能1(動画1作成)
PNGをつなげただけのシンプル機能
●機能2(動画2作成)
サイドバイサイドの片側だけをSingleViewとする。
※想定フォーマットは、1920x1200のサイドバイサイド。投影時は1200x1920の縦長。
Python GUI
FFmpegすっごいなー。
UEのMovieRenderQueueのジョブ完了後に、実行されるようにしたい。
以上。
構想からChatGPTとやりとして3時間で完成。よき。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#立体視のサイドバイサイド連番から動画作成 | |
# | |
#20240221 v1.0.0 png、1920x1200 サイドバイサイド。からシンプル動画変換と、SingleViewに変換。 | |
# v1.0.1 GUIレイアウト調整。動画上書き保存。 | |
# | |
# 想定命名規則 例1) rend/rend_r/s001/s001_v001_vr/s001_v001_vr_00001.png | |
# 想定命名規則 例2) rend/rend_r/s301/s301_v034_vr/s301_v034_vr_06482.png | |
# | |
import tkinter as tk | |
from tkinter import filedialog, ttk, messagebox | |
import subprocess | |
import os | |
import re | |
from glob import glob | |
def browse_folder(): | |
folder = filedialog.askdirectory() | |
if folder: # フォルダが選択された場合のみ処理を実行 | |
folder_path.set(folder) | |
auto_detect_filenames(folder) | |
detect_png_pattern(folder) | |
detect_smallest_number(folder) | |
# ユーザーが「キャンセル」を選択した場合は、何もしない(folder_pathを更新しない) | |
def auto_detect_filenames(folder): | |
if folder: | |
folder_name = os.path.basename(folder) | |
output1_name = f"{folder_name}.mp4" | |
output2_name = f"{folder_name.replace('vr', 'sv')}.mp4" | |
output1.set(os.path.join(output_folder.get(), output1_name)) | |
output2.set(os.path.join(output_folder.get(), output2_name)) | |
def detect_png_pattern(folder): | |
png_files = glob(os.path.join(folder, "*.png")) | |
if png_files: | |
sample_file = os.path.basename(png_files[0]) | |
match = re.match(r"(.+?)(\d+).png", sample_file) | |
if match: | |
prefix = match.group(1) | |
digits = len(match.group(2)) | |
pattern = f"{prefix}%0{digits}d.png" | |
png_pattern.set(pattern) | |
def detect_smallest_number(folder): | |
png_files = glob(os.path.join(folder, "*.png")) | |
numbers = [int(re.search(r"(\d+).png", os.path.basename(file)).group(1)) for file in png_files if re.search(r"(\d+).png", os.path.basename(file))] | |
if numbers: | |
smallest_number.set(min(numbers)) | |
else: | |
smallest_number.set(0) # ファイルが見つからない場合 | |
def convert_videos(): | |
folder = folder_path.get() | |
framerate = framerate_entry.get() | |
output1_path = output1.get() | |
output2_path = output2.get() | |
resolution_percentage = resolution_percentage_entry.get() | |
pattern = png_pattern.get() | |
start_number = smallest_number.get() | |
bitrate_value = bitrate_entry.get() + "M" # Mbpsを付加 | |
if generate_video1.get(): | |
# 元の解像度での動画生成コマンド | |
command1 = f'ffmpeg -y -framerate {framerate} -start_number {start_number} -i "{folder}/{pattern}" -b:v {bitrate_value} -c:v libx264 -profile:v main -pix_fmt yuv420p -movflags +faststart "{output1_path}"' | |
subprocess.run(command1, shell=True) | |
if generate_video2.get(): | |
# 解像度を計算 | |
resolution_percentage_float = float(resolution_percentage) / 100 | |
command2 = f'ffmpeg -y -framerate {framerate} -start_number {start_number} -i "{folder}/{pattern}" -vf "crop=iw/2:ih:iw/2:0,scale=iw*2*{resolution_percentage_float}:ih*{resolution_percentage_float},transpose=1" -b:v {bitrate_value} -c:v libx264 -profile:v main -pix_fmt yuv420p -movflags +faststart "{output2_path}"' | |
subprocess.run(command2, shell=True) | |
messagebox.showinfo("完了", "動画の変換が完了しました。") # 完了通知のポップアップ | |
app = tk.Tk() | |
app.title('img2video SideBySide (v1.0.1) [FFmpeg]') | |
# 変数の設定 | |
folder_path = tk.StringVar() | |
framerate_entry = tk.StringVar(value="30") | |
output_folder = tk.StringVar() | |
output1 = tk.StringVar() | |
output2 = tk.StringVar() | |
png_pattern = tk.StringVar() | |
smallest_number = tk.IntVar() | |
resolution_percentage_entry = tk.StringVar(value="50") | |
generate_video1 = tk.BooleanVar(value=True) | |
generate_video2 = tk.BooleanVar(value=True) | |
bitrate_entry = tk.StringVar(value="10") # ビットレートのデフォルト値 | |
# GUIの構築 | |
tk.Label(app, text="Output Folder:").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=output_folder).pack(fill='x', padx=5, expand=True) | |
tk.Button(app, text="Browse", command=lambda: output_folder.set(filedialog.askdirectory())).pack(padx=5, anchor=tk.W) | |
tk.Label(app, text="PNG Folder:").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=folder_path).pack(fill='x', padx=5, expand=True) | |
tk.Button(app, text="Browse", command=browse_folder).pack(padx=5, anchor=tk.W) | |
tk.Label(app, text="Frame Rate (fps):").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=framerate_entry).pack(padx=5, anchor=tk.W) | |
tk.Label(app, text="Bitrate (Mbps):").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=bitrate_entry).pack(padx=5, anchor=tk.W) | |
tk.Label(app, text="Output Video 1 Path:").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=output1).pack(fill='x', padx=5, expand=True) | |
tk.Label(app, text="Output Video 2 Path:").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=output2).pack(fill='x', padx=5, expand=True) | |
tk.Label(app, text="Resolution Percentage for Video 2 (%):").pack(padx=5, anchor=tk.W) | |
tk.Entry(app, textvariable=resolution_percentage_entry).pack(padx=5, anchor=tk.W) | |
ttk.Checkbutton(app, text="Generate Video 1", variable=generate_video1).pack(padx=5, anchor=tk.W) | |
ttk.Checkbutton(app, text="Generate Video 2", variable=generate_video2).pack(padx=5, anchor=tk.W) | |
tk.Button(app, text="Convert", command=convert_videos).pack() | |
app.mainloop() |
0 コメント:
コメントを投稿