shorts_factory/MakeVideo5.py

246 lines
11 KiB
Python

import os
import glob
from moviepy.editor import ImageClip, AudioFileClip, concatenate_videoclips, TextClip, CompositeVideoClip
import moviepy.editor as mpe
from gtts import gTTS
from datetime import datetime
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
from PIL import Image, ImageTk
import re
class VideoCreatorApp:
def __init__(self, master):
self.master = master
self.master.title('Video Creator')
self.sequence = []
self.image_thumbnails = {}
self.current_image = None
self.background_music = None
self.animation_after_id = None # 이 부분을 추가
self.init_ui()
def init_ui(self):
self.list_frame = ttk.Frame(self.master)
self.list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.control_frame = ttk.Frame(self.master)
self.control_frame.pack(side=tk.RIGHT, fill=tk.Y)
self.sequence_listbox = tk.Listbox(self.list_frame, width=50, height=20)
self.sequence_listbox.pack(padx=10, pady=10)
self.sequence_listbox.bind('<<ListboxSelect>>', self.show_image_preview)
ttk.Button(self.control_frame, text="Insert Script", command=self.insert_script).pack(pady=5)
ttk.Button(self.control_frame, text="Insert Image", command=self.insert_image).pack(pady=5)
ttk.Button(self.control_frame, text="Insert Action", command=self.insert_action).pack(pady=5)
ttk.Button(self.control_frame, text="Set Background Music", command=self.set_background_music).pack(pady=5)
ttk.Button(self.control_frame, text="Create Video", command=self.create_video).pack(pady=5)
# 'Update' 버튼 추가
ttk.Button(self.control_frame, text="Update Item", command=self.update_item).pack(pady=5)
# 'Delete' 버튼 추가
ttk.Button(self.control_frame, text="Delete Item", command=self.delete_item).pack(pady=5)
self.preview_canvas = tk.Canvas(self.list_frame, width=100, height=100)
self.preview_canvas.pack(padx=10, pady=10)
def update_item(self):
selection = self.sequence_listbox.curselection()
if selection:
index = selection[0]
item = self.sequence[index]
# 스크립트나 이미지에 따라 다른 입력 요청
if item['type'] == 'script':
new_text = simpledialog.askstring("Update Script", "Enter new script:", initialvalue=item['content'])
if new_text is not None:
self.sequence[index]['content'] = new_text
elif item['type'] == 'image':
new_path = filedialog.askopenfilename(filetypes=(("Image files", "*.jpg;*.png;*.gif;*.webp"), ("All files", "*.*")), initialdir="/", title="Select file")
if new_path:
self.sequence[index]['content'] = new_path
self.update_sequence_listbox() # 리스트 박스 업데이트
def delete_item(self):
selection = self.sequence_listbox.curselection()
if selection:
index = selection[0]
del self.sequence[index] # 선택된 항목 삭제
self.update_sequence_listbox() # 리스트 박스 업데이트
def update_sequence_listbox(self):
self.sequence_listbox.delete(0, tk.END)
for item in self.sequence:
if item['type'] == 'script':
self.sequence_listbox.insert(tk.END, f"Script: {item['content'][:30]}")
elif item['type'] == 'image':
# 파일 이름만 표시
file_name = os.path.basename(item['content'])
self.sequence_listbox.insert(tk.END, f"Image: {file_name}")
def insert_script(self):
script_text = simpledialog.askstring("Input", "Enter your script:")
if script_text:
self.sequence.append({'type': 'script', 'content': script_text})
self.update_sequence_listbox()
def insert_image(self):
file_path = filedialog.askopenfilename(filetypes=(("Image files", "*.jpg;*.png;*.gif;*.webp"), ("All files", "*.*")))
if file_path:
self.sequence.append({'type': 'image', 'content': file_path})
self.update_sequence_listbox()
def insert_action(self):
action = simpledialog.askstring("Insert Action", "Enter action (clean or sleep):")
if action == "sleep":
duration = simpledialog.askinteger("Sleep Duration", "Enter duration in seconds:", parent=self.master)
if duration is not None:
self.sequence.append({'type': 'action', 'action': action, 'duration': duration})
elif action == "clean":
self.sequence.append({'type': 'action', 'action': action})
self.update_sequence_listbox()
def set_background_music(self):
file_path = filedialog.askopenfilename(filetypes=(("Audio files", "*.mp3;*.wav"), ("All files", "*.*")))
if file_path:
self.background_music = file_path
messagebox.showinfo("Background Music Set", f"Background music set to: {file_path}")
def create_tts_audio(self, text, filename):
# 자음만 있는 한글 문자열 패턴 확인 및 건너뜀
if re.fullmatch(r'[ㄱ-ㅎ]+', text):
print("자음만 있는 문자열은 TTS에서 제외됩니다:", text)
return False
# TTS 생성
try:
tts = gTTS(text=text, lang='ko', slow=False)
tts.save(filename)
return True
except Exception as e:
print(f"TTS 생성 중 오류 발생: {e}")
return False
def create_video(self):
clips = []
last_clip_end_time = 0 # 마지막 클립의 종료 시간을 추적합니다.
for index, item in enumerate(self.sequence):
if item['type'] == 'script':
tts_filename = f'temp_{datetime.now().strftime("%Y%m%d%H%M%S")}.mp3'
if self.create_tts_audio(item['content'], tts_filename):
audio_clip = AudioFileClip(tts_filename).set_start(last_clip_end_time)
txt_clip = TextClip(item['content'], fontsize=24, color='white', bg_color='black', align='South')\
.set_position(('center', 'bottom'))\
.set_duration(audio_clip.duration)\
.set_start(last_clip_end_time)
# 자막이 항상 최상위 레이어에 오도록 설정합니다.
video_clip = CompositeVideoClip([txt_clip], size=(1080,1920)).set_duration(audio_clip.duration).set_start(last_clip_end_time).set_audio(audio_clip)
clips.append(video_clip)
last_clip_end_time += audio_clip.duration
os.remove(tts_filename)
elif item['type'] == 'image':
if index > 0 and self.sequence[index - 1]['type'] == 'script':
# 이미지 클립의 지속 시간을 다음 스크립트 시작까지로 설정
next_script_start_time = last_clip_end_time
img_clip = ImageClip(item['content']).set_start(last_clip_end_time).set_duration(next_script_start_time - last_clip_end_time).resize(newsize=(1080, 1920))
clips.append(img_clip)
if clips:
final_clip = concatenate_videoclips(clips, method="chain", padding=-1)
output_path = self.generate_output_path()
final_clip.write_videofile(output_path, fps=24)
messagebox.showinfo("Video Creation", f"Video created successfully: {output_path}")
else:
messagebox.showerror("Video Creation", "No clips to create a video.")
def generate_output_path(self):
# 파일 경로 생성 로직 (변경 없음)
date_str = datetime.now().strftime("%Y-%m-%d")
output_dir = os.path.join("Result", date_str)
os.makedirs(output_dir, exist_ok=True)
file_count = len(os.listdir(output_dir)) # 이미 있는 파일의 개수를 기준으로 파일 이름 결정
return os.path.join(output_dir, f"{date_str}_{file_count + 1:03d}.mp4")
def update_sequence_listbox(self):
self.sequence_listbox.delete(0, tk.END)
for item in self.sequence:
item_desc = f"{item['type']}: {item.get('action', '')} {item.get('content', '')[:50]}"
self.sequence_listbox.insert(tk.END, item_desc)
def show_image_preview(self, event=None):
selection = self.sequence_listbox.curselection()
if selection:
selected_index = selection[0]
selected_item = self.sequence[selected_index]
if selected_item['type'] == 'image':
self.display_image_thumbnail(selected_item['content'])
def display_image_thumbnail(self, image_path):
self.preview_canvas.delete("all") # 이전 이미지 삭제
if self.animation_after_id:
self.master.after_cancel(self.animation_after_id) # 기존 애니메이션 타이머 취소
self.animation_after_id = None # 타이머 ID 초기화
if image_path.lower().endswith('.gif'): # 애니메이션 GIF 처리
self.process_gif(image_path)
else: # 정적 이미지 처리
img = Image.open(image_path)
img.thumbnail((100, 100), getattr(Image, 'Resampling', Image).LANCZOS)
photo = ImageTk.PhotoImage(img)
self.preview_canvas.create_image(50, 50, image=photo, anchor=tk.CENTER)
self.image_thumbnails[image_path] = photo # 참조 유지
def process_gif(self, image_path):
gif = Image.open(image_path)
self.gif_frames = []
try:
while True:
self.gif_frames.append(ImageTk.PhotoImage(gif.copy()))
gif.seek(len(self.gif_frames)) # 다음 프레임으로 이동
except EOFError:
pass # 모든 프레임을 처리했음
self.current_frame = 0
self.update_gif_frame() # GIF 애니메이션 시작
def update_gif_frame(self):
if self.gif_frames:
frame = self.gif_frames[self.current_frame]
self.preview_canvas.create_image(50, 50, image=frame, anchor=tk.CENTER)
self.current_frame = (self.current_frame + 1) % len(self.gif_frames)
self.animation_after_id = self.master.after(100, self.update_gif_frame)
def preview_gif(self, img, image_path, frame_number=0):
try:
img.seek(frame_number)
frame = ImageTk.PhotoImage(img)
self.preview_canvas.delete("all")
self.preview_canvas.create_image(50, 50, image=frame, anchor=tk.CENTER)
self.image_thumbnails[image_path] = frame # 참조 유지
self.current_image = image_path # 현재 GIF 이미지 경로 저장
frame_number += 1
delay = img.info.get('duration', 100) # 다음 프레임까지의 지연 시간, 기본값 100ms
self.master.after(delay, self.preview_gif, img, image_path, frame_number)
except EOFError: # 마지막 프레임에 도달하면 다시 시작
if self.current_image == image_path: # 현재 이미지가 여전히 같은 GIF인 경우에만 재시작
self.preview_gif(img, image_path, 0)
def main():
root = tk.Tk()
app = VideoCreatorApp(root)
root.mainloop()
if __name__ == '__main__':
main()