|
@ -0,0 +1,94 @@
|
|||
# 코드를 작성하기 전에 전체적인 구조를 계획해 보겠습니다.
|
||||
|
||||
# 1. 필요한 라이브러리를 불러옵니다.
|
||||
import os
|
||||
import datetime
|
||||
import openai # OpenAI API를 사용하기 위한 라이브러리
|
||||
|
||||
# 2. ChatGPT와의 소통을 담당할 함수를 정의합니다.
|
||||
def ask_chatgpt(question, api_key):
|
||||
openai.api_key = api_key
|
||||
response = openai.Completion.create(
|
||||
model="gpt-3.5-turbo", # 이 모델은 최신 버전에서 사용됩니다.
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": question}
|
||||
]
|
||||
)
|
||||
# 응답 형식에 따라 적절한 필드를 반환해야 합니다.
|
||||
# 새로운 API에 따라, 응답 형식이 달라질 수 있으므로, 정확한 필드를 확인해야 합니다.
|
||||
return response.choices[0].text.strip() # 'message' 대신 'text'를 사용
|
||||
|
||||
|
||||
|
||||
# 3. 응답을 분석하여 파일 제목을 결정하는 함수를 정의합니다.
|
||||
def determine_filename(response):
|
||||
"""
|
||||
응답 텍스트를 분석하여 파일 제목을 결정하는 함수.
|
||||
:param response: ChatGPT로부터 받은 응답 텍스트.
|
||||
:return: 결정된 파일 제목.
|
||||
"""
|
||||
# "제목"으로 시작하는 문장을 찾습니다.
|
||||
for sentence in response.split('.'):
|
||||
if sentence.strip().lower().startswith("제목"):
|
||||
return sentence.strip() + ".txt" # 제목이 있는 경우 파일 이름을 결정합니다.
|
||||
|
||||
# 제목이 없는 경우 현재 날짜로 파일 이름을 결정합니다.
|
||||
current_date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
return f"{current_date}-Response.txt"
|
||||
|
||||
# 4. 응답과 이미지(있는 경우)를 저장하는 함수를 정의합니다.
|
||||
def save_response_and_images(response, folder_path, filename):
|
||||
"""
|
||||
응답 텍스트와 이미지(있는 경우)를 파일에 저장하는 함수.
|
||||
:param response: ChatGPT로부터 받은 응답 텍스트.
|
||||
:param folder_path: 저장할 폴더의 경로.
|
||||
:param filename: 파일 제목.
|
||||
"""
|
||||
# GPT_Response 폴더가 없으면 생성합니다.
|
||||
if not os.path.exists(folder_path):
|
||||
os.makedirs(folder_path)
|
||||
|
||||
# 응답을 텍스트 파일로 저장합니다.
|
||||
file_path = os.path.join(folder_path, filename)
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(response)
|
||||
|
||||
# TODO: 이미지 처리 로직은 사용자의 추가 요구 사항에 따라 결정되어야 합니다.
|
||||
|
||||
# 주석 처리된 함수 호출들을 제외하고, 이 구조를 기반으로 구체적인 구현을 진행하겠습니다.
|
||||
# 참고: 실제 코드 실행은 주석 처리되어 있으므로, 여기에서는 실행되지 않습니다.
|
||||
|
||||
|
||||
# 파이썬 코드 수정: 사용자로부터 질문을 입력 받는 기능 추가
|
||||
|
||||
# 함수 정의 부분은 그대로 둡니다. 주요 변경은 스크립트 실행 부분에 있습니다.
|
||||
|
||||
# 사용자로부터 질문을 입력 받는 코드를 추가합니다.
|
||||
def main(api_key):
|
||||
# 사용자로부터 질문을 입력 받습니다.
|
||||
question = input("ChatGPT에게 물어볼 질문을 입력하세요: ")
|
||||
|
||||
# ChatGPT에게 질문을 하고 응답을 받습니다.
|
||||
response = ask_chatgpt(question, api_key)
|
||||
|
||||
# 응답으로부터 파일 제목을 결정합니다.
|
||||
filename = determine_filename(response)
|
||||
|
||||
# 응답과 이미지(있는 경우)를 저장합니다.
|
||||
folder_path = os.path.join(os.getcwd(), "GPT_Response")
|
||||
save_response_and_images(response, folder_path, filename)
|
||||
|
||||
# 사용자에게 응답이 저장된 위치를 알려줍니다.
|
||||
print(f"응답이 {folder_path} 폴더에 저장되었습니다, 파일명: {filename}")
|
||||
|
||||
# 이제 main 함수를 실행할 준비가 되었습니다.
|
||||
# 참고: 실제 환경에서 API 키와 함께 main 함수를 호출해야 합니다.
|
||||
# 예: main('your_openai_api_key_here')
|
||||
|
||||
# 주석 처리된 함수 호출들을 제외하고, 이 구조를 기반으로 구체적인 구현을 진행하겠습니다.
|
||||
# 참고: 실제 코드 실행은 주석 처리되어 있으므로, 여기에서는 실행되지 않습니다.
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main("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")
|
|
@ -0,0 +1,164 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, simpledialog, Label
|
||||
from gtts import gTTS
|
||||
from moviepy.editor import *
|
||||
from PIL import Image, ImageTk
|
||||
import os
|
||||
from datetime import datetime
|
||||
from tkinter import messagebox
|
||||
from moviepy.config import change_settings
|
||||
change_settings({"IMAGEMAGICK_BINARY": r"C:\Program Files\ImageMagick-7.1.1-Q16-HDRI\magick.exe"})
|
||||
|
||||
class VideoCreatorApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.setup_ui()
|
||||
self.text_image_pairs = []
|
||||
self.bgm_path = ''
|
||||
self.save_path = "C:/Users/thdtl/Desktop/python/AutoYoutubeProject/Result" # 초기 저장 경로는 비워둡니다.
|
||||
self.selected_item_index = None
|
||||
|
||||
def setup_ui(self):
|
||||
self.root.title("YouTube Shorts Video Creator")
|
||||
self.root.geometry("800x600") # Adjust the size of the window
|
||||
self.listbox = tk.Listbox(self.root, width=70, height=20)
|
||||
self.listbox.pack()
|
||||
tk.Button(self.root, text="Add Text and Image", command=self.add_text_image_sequence).pack()
|
||||
tk.Button(self.root, text="Edit Selected Text and Image", command=self.edit_selected_sequence).pack()
|
||||
tk.Button(self.root, text="Delete Selected Text and Image", command=self.delete_selected_sequence).pack()
|
||||
tk.Button(self.root, text="Set BGM", command=self.set_bgm).pack()
|
||||
tk.Button(self.root, text="Remove BGM", command=self.remove_bgm).pack()
|
||||
tk.Button(self.root, text="Set Save Path", command=self.set_save_path).pack()
|
||||
tk.Button(self.root, text="Create Video", command=self.create_video).pack()
|
||||
|
||||
self.save_path_label = tk.Label(self.root, text="No path set")
|
||||
self.save_path_label.pack()
|
||||
|
||||
self.image_label = Label(self.root)
|
||||
self.image_label.pack()
|
||||
|
||||
self.progress = ttk.Progressbar(self.root, length=300, mode='determinate')
|
||||
self.progress.pack()
|
||||
|
||||
self.listbox.bind('<<ListboxSelect>>', self.on_select)
|
||||
|
||||
def set_save_path(self):
|
||||
# 사용자가 비디오를 저장할 경로를 선택할 수 있게 합니다.
|
||||
path = filedialog.askdirectory() # 폴더 선택 다이얼로그를 엽니다.
|
||||
if path:
|
||||
self.save_path = path
|
||||
self.save_path_label.config(text=path) # 선택된 경로를 레이블에 표시합니다.
|
||||
|
||||
def remove_bgm(self):
|
||||
self.bgm_path = '' # 배경음악 경로를 빈 문자열로 설정
|
||||
print("Background music removed") # 콘솔에 배경음악 제거 메시지 출력
|
||||
|
||||
def add_text_image_sequence(self):
|
||||
text = simpledialog.askstring("Input", "Enter your text:", parent=self.root)
|
||||
if text: # 텍스트가 입력되었다면
|
||||
image_path = filedialog.askopenfilename(
|
||||
title="Select related image",
|
||||
filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif")], # 이미지 파일 형식을 모두 허용
|
||||
parent=self.root
|
||||
)
|
||||
# 이미지 선택을 취소하거나 비워둔 경우 None 또는 빈 문자열('')이 될 수 있습니다.
|
||||
# 이 경우에도 텍스트는 저장되지만, 이미지 경로는 빈 문자열로 처리됩니다.
|
||||
self.text_image_pairs.append((text, image_path if image_path else ""))
|
||||
self.listbox.insert(tk.END, text)
|
||||
else:
|
||||
messagebox.showwarning("Warning", "No text was entered.")
|
||||
|
||||
|
||||
def edit_selected_sequence(self):
|
||||
if self.selected_item_index is not None:
|
||||
new_text = simpledialog.askstring("Edit text", "Enter new text:", parent=self.root, initialvalue=self.text_image_pairs[self.selected_item_index][0])
|
||||
if new_text:
|
||||
new_image_path = filedialog.askopenfilename(title="Select new related image", filetypes=[("Image files", "*.jpg *.jpeg *.png")])
|
||||
if new_image_path:
|
||||
self.text_image_pairs[self.selected_item_index] = (new_text, new_image_path)
|
||||
self.listbox.delete(self.selected_item_index)
|
||||
self.listbox.insert(self.selected_item_index, new_text)
|
||||
|
||||
def delete_selected_sequence(self):
|
||||
if self.selected_item_index is not None:
|
||||
del self.text_image_pairs[self.selected_item_index]
|
||||
self.listbox.delete(self.selected_item_index)
|
||||
|
||||
def on_select(self, event):
|
||||
widget = event.widget
|
||||
if widget.curselection():
|
||||
index = int(widget.curselection()[0])
|
||||
self.selected_item_index = index
|
||||
# 선택된 항목에 대한 이미지 미리보기 표시
|
||||
img_path = self.text_image_pairs[index][1]
|
||||
self.display_image(img_path)
|
||||
|
||||
def set_bgm(self):
|
||||
self.bgm_path = filedialog.askopenfilename(title="Select BGM file", filetypes=[("Audio files", "*.mp3 *.wav")])
|
||||
|
||||
def create_video(self):
|
||||
self.progress['value'] = 0
|
||||
total_steps = len(self.text_image_pairs)
|
||||
current_step = 0
|
||||
|
||||
video_clips = [] # 최종 비디오 클립들을 저장할 리스트
|
||||
|
||||
for text, img_path in self.text_image_pairs:
|
||||
# TTS 생성 및 오디오 클립 저장
|
||||
tts = gTTS(text=text, lang='ko')
|
||||
tts_file = f"temp_audio_{current_step}.mp3"
|
||||
tts.save(tts_file)
|
||||
tts_audio_clip = AudioFileClip(tts_file).volumex(2.0)
|
||||
|
||||
# 이미지 경로가 없는 경우, 투명한 배경의 클립 생성
|
||||
if not img_path:
|
||||
img_clip = ColorClip(size=(1080, 1920), color=(0, 0, 0, 0), duration=tts_audio_clip.duration)
|
||||
else:
|
||||
img_clip = ImageClip(img_path).set_duration(tts_audio_clip.duration).resize(newsize=(1080, 1920)).set_position('center')
|
||||
|
||||
# 자막 클립 생성
|
||||
txt_clip = TextClip(text, font='Arial', fontsize=24, color='white', bg_color=None).set_pos('bottom').set_duration(tts_audio_clip.duration)
|
||||
|
||||
# 이미지 클립과 자막 클립을 합성
|
||||
video_clip = CompositeVideoClip([img_clip, txt_clip], size=(1080, 1920)).set_duration(tts_audio_clip.duration).set_audio(tts_audio_clip)
|
||||
video_clips.append(video_clip)
|
||||
|
||||
os.remove(tts_file)
|
||||
current_step += 1
|
||||
self.progress['value'] = (current_step / total_steps) * 100
|
||||
self.root.update_idletasks()
|
||||
|
||||
# 모든 비디오 클립을 순차적으로 결합
|
||||
final_video_clip = concatenate_videoclips(video_clips, method="compose")
|
||||
|
||||
# 배경음악 설정
|
||||
if self.bgm_path:
|
||||
bgm = AudioFileClip(self.bgm_path).volumex(0.5)
|
||||
final_video_clip = final_video_clip.set_audio(CompositeAudioClip([final_video_clip.audio, bgm.set_duration(final_video_clip.duration)]))
|
||||
|
||||
# 최종 비디오 저장 경로 설정
|
||||
if not self.save_path:
|
||||
messagebox.showerror("Error", "Please set a save path first.")
|
||||
return
|
||||
|
||||
if not os.path.exists("Result"):
|
||||
os.makedirs("Result")
|
||||
current_time = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
final_video_file = os.path.join(self.save_path, f"{current_time}.mp4")
|
||||
final_video_clip.write_videofile(final_video_file, fps=24, codec="libx264", audio_codec="aac")
|
||||
|
||||
self.progress['value'] = 100
|
||||
messagebox.showinfo("Video Creator", f"Video creation completed successfully! Saved to {final_video_file}")
|
||||
|
||||
def display_image(self, img_path):
|
||||
img = Image.open(img_path)
|
||||
img = img.resize((200, 200), Image.Resampling.LANCZOS) # 이미지 크기 조정
|
||||
img_tk = ImageTk.PhotoImage(img)
|
||||
self.image_label.configure(image=img_tk)
|
||||
self.image_label.image = img_tk # 참조 추가
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = VideoCreatorApp(root)
|
||||
root.mainloop()
|
|
@ -0,0 +1,115 @@
|
|||
from tkinter import Tk, Label, Button, Entry, filedialog, messagebox, Listbox, simpledialog, OptionMenu, StringVar
|
||||
import os
|
||||
from gtts import gTTS
|
||||
from moviepy.editor import concatenate_videoclips, AudioFileClip, ImageClip, CompositeVideoClip, TextClip
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
class VideoProject:
|
||||
def __init__(self):
|
||||
self.texts = []
|
||||
self.images = []
|
||||
self.background_music = None
|
||||
self.video_type = "참교육"
|
||||
|
||||
def add_text(self, text):
|
||||
self.texts.append(text)
|
||||
|
||||
def add_image(self, image_path):
|
||||
self.images.append(image_path)
|
||||
|
||||
def set_background_music(self, music_path):
|
||||
self.background_music = music_path
|
||||
|
||||
def set_video_type(self, video_type):
|
||||
self.video_type = video_type
|
||||
|
||||
class VideoMakerApp:
|
||||
def __init__(self, master):
|
||||
self.master = master
|
||||
master.title("유튜브 쇼츠 제작기")
|
||||
|
||||
self.project = VideoProject()
|
||||
|
||||
self.label = Label(master, text="유튜브 쇼츠 제작기에 오신 것을 환영합니다!")
|
||||
self.label.pack()
|
||||
|
||||
self.text_entry = Entry(master)
|
||||
self.text_entry.pack()
|
||||
|
||||
self.add_text_button = Button(master, text="텍스트 추가", command=self.add_text)
|
||||
self.add_text_button.pack()
|
||||
|
||||
self.add_image_button = Button(master, text="이미지 추가", command=self.add_image)
|
||||
self.add_image_button.pack()
|
||||
|
||||
self.add_music_button = Button(master, text="배경음 추가", command=self.add_background_music)
|
||||
self.add_music_button.pack()
|
||||
|
||||
self.type_var = StringVar(master)
|
||||
self.type_var.set("참교육")
|
||||
self.type_dropdown = OptionMenu(master, self.type_var, "참교육", "야썰", "어이없음", "감동", command=self.project.set_video_type)
|
||||
self.type_dropdown.pack()
|
||||
|
||||
self.create_video_button = Button(master, text="동영상 만들기", command=self.create_video_with_speech)
|
||||
self.create_video_button.pack()
|
||||
|
||||
self.item_listbox = Listbox(master)
|
||||
self.item_listbox.pack()
|
||||
|
||||
def add_text(self):
|
||||
text = self.text_entry.get()
|
||||
if text:
|
||||
self.project.add_text(text)
|
||||
self.item_listbox.insert('end', f"텍스트: {text}")
|
||||
messagebox.showinfo("성공", "텍스트가 추가되었습니다.")
|
||||
|
||||
def add_image(self):
|
||||
file_path = filedialog.askopenfilename()
|
||||
if file_path:
|
||||
self.project.add_image(file_path)
|
||||
self.item_listbox.insert('end', f"이미지: {os.path.basename(file_path)}")
|
||||
messagebox.showinfo("성공", "이미지가 추가되었습니다.")
|
||||
|
||||
def add_background_music(self):
|
||||
file_path = filedialog.askopenfilename()
|
||||
if file_path:
|
||||
self.project.set_background_music(file_path)
|
||||
messagebox.showinfo("성공", "배경음이 설정되었습니다.")
|
||||
|
||||
def create_video_with_speech(self):
|
||||
clips = []
|
||||
for text, image_path in zip(self.project.texts, self.project.images):
|
||||
tts = gTTS(text=text, lang='ko')
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
tts.save(temp_file.name)
|
||||
temp_file.close()
|
||||
|
||||
audio_clip = AudioFileClip(temp_file.name)
|
||||
image_clip = ImageClip(image_path).set_duration(audio_clip.duration).set_fps(24)
|
||||
text_clip = TextClip(text, fontsize=24, color='white').set_position('bottom').set_duration(audio_clip.duration)
|
||||
composite_clip = CompositeVideoClip([image_clip, text_clip]).set_duration(audio_clip.duration).set_fps(24).set_audio(audio_clip)
|
||||
clips.append(composite_clip)
|
||||
|
||||
os.unlink(temp_file.name)
|
||||
|
||||
final_clip = concatenate_videoclips(clips)
|
||||
|
||||
if self.project.background_music:
|
||||
background_audio = AudioFileClip(self.project.background_music).set_duration(final_clip.duration).set_fps(24)
|
||||
final_clip = final_clip.set_audio(background_audio)
|
||||
|
||||
result_dir = "Result"
|
||||
if not os.path.exists(result_dir):
|
||||
os.makedirs(result_dir)
|
||||
today = datetime.now().strftime("%Y%m%d")
|
||||
file_name = f"{today}_{self.project.video_type}.mp4"
|
||||
final_path = os.path.join(result_dir, file_name)
|
||||
|
||||
final_clip.write_videofile(final_path, fps=24)
|
||||
|
||||
messagebox.showinfo("완료", "동영상이 성공적으로 생성되었습니다.")
|
||||
|
||||
root = Tk()
|
||||
my_gui = VideoMakerApp(root)
|
||||
root.mainloop()
|
|
@ -0,0 +1,127 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, simpledialog
|
||||
from gtts import gTTS
|
||||
from moviepy.editor import *
|
||||
import os
|
||||
from datetime import datetime
|
||||
from PIL import Image
|
||||
|
||||
class VideoCreatorApp:
|
||||
def __init__(self, master):
|
||||
self.master = master
|
||||
self.master.title('Video Creator')
|
||||
self.init_ui()
|
||||
|
||||
# 데이터 관리를 위한 리스트
|
||||
self.scripts = []
|
||||
self.images = []
|
||||
self.actions = []
|
||||
self.background_music = None
|
||||
|
||||
# 임시 및 결과 폴더 확인
|
||||
self.temp_folder = "temp"
|
||||
self.result_folder = "Result"
|
||||
for folder in [self.temp_folder, self.result_folder]:
|
||||
if not os.path.exists(folder):
|
||||
os.makedirs(folder)
|
||||
|
||||
def init_ui(self):
|
||||
# UI 구성 요소
|
||||
self.main_frame = ttk.Frame(self.master)
|
||||
self.main_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 스크립트, 이미지, 액션 목록 및 추가 버튼
|
||||
self.list_frame = ttk.Frame(self.main_frame)
|
||||
self.list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
self.script_listbox = tk.Listbox(self.list_frame)
|
||||
self.script_listbox.pack(fill=tk.X)
|
||||
ttk.Button(self.list_frame, text='Add Script', command=self.add_script).pack(fill=tk.X)
|
||||
|
||||
self.image_listbox = tk.Listbox(self.list_frame)
|
||||
self.image_listbox.pack(fill=tk.X)
|
||||
ttk.Button(self.list_frame, text='Add Image', command=self.add_image).pack(fill=tk.X)
|
||||
|
||||
# 액션 추가 예시는 생략
|
||||
# self.action_listbox = tk.Listbox(self.list_frame)
|
||||
# self.action_listbox.pack(fill=tk.X)
|
||||
# ttk.Button(self.list_frame, text='Add Action', command=self.add_action).pack(fill=tk.X)
|
||||
|
||||
self.preview_frame = ttk.Frame(self.main_frame, width=200)
|
||||
self.preview_frame.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
self.preview_frame.pack_propagate(False)
|
||||
|
||||
ttk.Button(self.preview_frame, text='Set Background Music', command=self.set_background_music).pack(fill=tk.X)
|
||||
ttk.Button(self.preview_frame, text='Create Video', command=self.create_video).pack(fill=tk.X)
|
||||
|
||||
def add_script(self):
|
||||
script_text = simpledialog.askstring("Input", "Enter your script:")
|
||||
if script_text:
|
||||
self.scripts.append(script_text)
|
||||
self.script_listbox.insert(tk.END, script_text)
|
||||
|
||||
def add_image(self):
|
||||
file_path = filedialog.askopenfilename(filetypes=(("Image files", "*.jpg;*.png;*.gif;*.webp"), ("All files", "*.*")))
|
||||
if file_path:
|
||||
self.images.append(file_path)
|
||||
self.image_listbox.insert(tk.END, file_path)
|
||||
|
||||
# 배경음악 설정 및 동영상 생성 기능은 위에서 제공한 설명에 따라 구현됩니다.
|
||||
# 이 부분의 코드는 이전에 제공된 `create_video_clips` 및 `create_video` 메서드 내용을 참고하여 구현할 수 있습니다.
|
||||
|
||||
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_video(self):
|
||||
clips = []
|
||||
|
||||
# 스크립트를 TTS로 변환하고 비디오 클립으로 추가
|
||||
for script in self.scripts:
|
||||
tts = gTTS(script, lang='ko')
|
||||
tts_file = os.path.join(self.temp_folder, "tts.mp3")
|
||||
tts.save(tts_file)
|
||||
|
||||
audioclip = AudioFileClip(tts_file)
|
||||
txt_clip = TextClip(script, fontsize=24, color='white', bg_color='black', size=audioclip.size)
|
||||
txt_clip = txt_clip.set_duration(audioclip.duration)
|
||||
clip = CompositeVideoClip([txt_clip.set_pos('center')], size=audioclip.size).set_audio(audioclip)
|
||||
clips.append(clip)
|
||||
|
||||
# 이미지를 비디오 클립으로 추가
|
||||
for image in self.images:
|
||||
img_clip = ImageClip(image).set_duration(3) # 예시로 3초 동안 표시
|
||||
img_clip = resize(img_clip, height=720) # 이미지 크기 조정
|
||||
clips.append(img_clip)
|
||||
|
||||
# 모든 클립을 하나의 비디오로 결합
|
||||
final_clip = concatenate_videoclips(clips, method="compose")
|
||||
|
||||
# 배경음악 추가
|
||||
if self.background_music:
|
||||
background_music = AudioFileClip(self.background_music)
|
||||
final_clip = final_clip.set_audio(background_music)
|
||||
|
||||
# Result 폴더 내 오늘 날짜로 시작하는 파일들의 개수를 세어 n 구하기
|
||||
today_str = datetime.now().strftime("%Y-%m-%d")
|
||||
existing_files = glob.glob(os.path.join(self.result_folder, f"{today_str}_*.mp4"))
|
||||
file_number = len(existing_files) + 1
|
||||
|
||||
# 최종 비디오 파일 이름 설정
|
||||
output_filename = f"{today_str}_{file_number:03d}.mp4"
|
||||
output_path = os.path.join(self.result_folder, output_filename)
|
||||
|
||||
# 비디오 파일 저장
|
||||
final_clip.write_videofile(output_path, fps=24)
|
||||
|
||||
messagebox.showinfo("Success", f"Video created successfully: {output_filename}")
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = VideoCreatorApp(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,76 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, simpledialog
|
||||
|
||||
class VideoCreatorApp:
|
||||
def __init__(self, master):
|
||||
self.master = master
|
||||
self.master.title('Video Creator')
|
||||
self.init_ui()
|
||||
|
||||
# 시퀀스 데이터 관리
|
||||
self.sequence = [] # 스크립트, 이미지, 액션을 포함하는 시퀀스
|
||||
|
||||
def init_ui(self):
|
||||
self.sequence_frame = ttk.Frame(self.master)
|
||||
self.sequence_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.add_script_button = ttk.Button(self.sequence_frame, text="Insert Script", command=self.insert_script)
|
||||
self.add_script_button.pack(fill=tk.X)
|
||||
|
||||
self.add_image_button = ttk.Button(self.sequence_frame, text="Insert Image", command=self.insert_image)
|
||||
self.add_image_button.pack(fill=tk.X)
|
||||
|
||||
self.add_action_button = ttk.Button(self.sequence_frame, text="Insert Action", command=self.insert_action)
|
||||
self.add_action_button.pack(fill=tk.X)
|
||||
|
||||
self.create_video_button = ttk.Button(self.sequence_frame, text="Create Video", command=self.create_video)
|
||||
self.create_video_button.pack(fill=tk.X)
|
||||
|
||||
self.sequence_listbox = tk.Listbox(self.sequence_frame)
|
||||
self.sequence_listbox.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
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_type = simpledialog.askstring("Input", "Enter action type (e.g., 'sleep', 'clean'):")
|
||||
if action_type == "sleep":
|
||||
duration = simpledialog.askinteger("Input", "Enter duration (seconds):")
|
||||
self.sequence.append({'type': 'action', 'action': action_type, 'duration': duration})
|
||||
elif action_type == "clean":
|
||||
self.sequence.append({'type': 'action', 'action': action_type})
|
||||
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'][:50]}...")
|
||||
elif item['type'] == 'image':
|
||||
self.sequence_listbox.insert(tk.END, f"image {item['content']}")
|
||||
elif item['type'] == 'action':
|
||||
if item['action'] == 'sleep':
|
||||
self.sequence_listbox.insert(tk.END, f"action sleep {item['duration']}")
|
||||
elif item['action'] == 'clean':
|
||||
self.sequence_listbox.insert(tk.END, "action clean screen")
|
||||
|
||||
def create_video(self):
|
||||
# 여기에 비디오 생성 로직을 구현
|
||||
pass
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = VideoCreatorApp(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,245 @@
|
|||
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()
|
After Width: | Height: | Size: 650 KiB |
After Width: | Height: | Size: 606 KiB |
After Width: | Height: | Size: 547 KiB |
After Width: | Height: | Size: 514 KiB |
After Width: | Height: | Size: 423 KiB |
After Width: | Height: | Size: 537 KiB |
After Width: | Height: | Size: 486 KiB |
After Width: | Height: | Size: 8.9 MiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 292 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 1013 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 562 KiB |
After Width: | Height: | Size: 751 KiB |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 308 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 584 KiB |
After Width: | Height: | Size: 484 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 646 KiB |
After Width: | Height: | Size: 638 KiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 169 KiB |
After Width: | Height: | Size: 485 KiB |
After Width: | Height: | Size: 501 KiB |
After Width: | Height: | Size: 693 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 371 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 475 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 914 KiB |
After Width: | Height: | Size: 341 KiB |
After Width: | Height: | Size: 580 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 580 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 256 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 356 KiB |
After Width: | Height: | Size: 415 KiB |
After Width: | Height: | Size: 853 KiB |
After Width: | Height: | Size: 637 KiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 438 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 468 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 848 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 906 KiB |
After Width: | Height: | Size: 1.6 MiB |