3DCG屋さんの活動記録

PROFILE ★★こんな人です

3DCGを活用した映像や没入体験コンテンツの制作をしています。テクノロジーの社会実装に興味があり。テクニカルディレクター。面白いこと新しいことにワクワクする気持ちに『素直』でいつづける。

3DCG屋さんの活動記録

2024年2月22日木曜日

Side by Sideの立体視連番を動画にするスクリプト(Python)をChatGPTでつくってみた


 UEでサイドバイサイドで書き出した立体視の連番(PNG)を、mp4に変換するのが手間、、ってことでPythonで自動化。


●機能1(動画1作成)

  PNGをつなげただけのシンプル機能

●機能2(動画2作成)

  サイドバイサイドの片側だけをSingleViewとする。

※想定フォーマットは、1920x1200のサイドバイサイド。投影時は1200x1920の縦長。


Python GUI



FFmpegすっごいなー。

UEのMovieRenderQueueのジョブ完了後に、実行されるようにしたい。

以上。

構想からChatGPTとやりとして3時間で完成。よき。

#立体視のサイドバイサイド連番から動画作成
#
#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 コメント:

コメントを投稿