import os import tempfile import gradio as gr import numpy as np # (필요하다면) import imageio from PIL import Image import moviepy.editor as me # MoviePy 전체 임포트 LOGS = [] def log_step(message: str): LOGS.append(message) print(message) return "\n".join(LOGS) def parse_time_str(time_str: str): try: parts = time_str.strip().split(":") if len(parts) == 3: h, m, s = parts return int(h)*3600 + int(m)*60 + float(s) elif len(parts) == 2: m, s = parts return int(m)*60 + float(s) elif len(parts) == 1: return float(parts[0]) else: return 0.0 except: return 0.0 def upload_video(video_file): if video_file is None: return None, "업로드된 파일이 없습니다.", None, log_step("[단계 1] 업로드된 파일이 없습니다.") temp_dir = tempfile.mkdtemp() temp_video_path = os.path.join(temp_dir, video_file.name) with open(temp_video_path, "wb") as f: f.write(video_file.read()) try: clip = me.VideoFileClip(temp_video_path) duration_sec = clip.duration msg = f"재생 시간: {duration_sec:.2f}초" log_out = log_step("[단계 1] 비디오 업로드 및 재생 확인 완료") return temp_video_path, msg, duration_sec, log_out except Exception as e: err_log = log_step(f"[단계 1] 업로드 에러: {e}") return None, f"영상 처리 에러: {e}", None, err_log def generate_thumbnails(video_path, start_time_str, end_time_str, log_history): log_step("[단계 4] 시작/끝 시간 입력 완료") start_s = parse_time_str(start_time_str) end_s = parse_time_str(end_time_str) if not video_path: return None, None, log_step("[단계 5] 비디오 미업로드 → 썸네일 불가") try: clip = me.VideoFileClip(video_path) duration = clip.duration if start_s < 0: start_s = 0 if end_s > duration: end_s = duration if start_s > end_s: start_s, end_s = end_s, start_s thumb1 = clip.get_frame(start_s) thumb2 = clip.get_frame(end_s) thumb1_img = Image.fromarray(thumb1) thumb2_img = Image.fromarray(thumb2) log_out = log_step("[단계 5] 썸네일 생성 완료") return thumb1_img, thumb2_img, log_out except Exception as e: err_log = log_step(f"[단계 5] 썸네일 생성 에러: {e}") return None, None, err_log def create_gif(video_path, start_time_str, end_time_str, resolution, fps, speed, loop_count, log_history): if not video_path: return None, None, log_step("[단계 10] GIF 생성 실패: 비디오가 업로드되지 않음.") start_s = parse_time_str(start_time_str) end_s = parse_time_str(end_time_str) try: clip = me.VideoFileClip(video_path) duration = clip.duration if start_s < 0: start_s = 0 if end_s > duration: end_s = duration if start_s > end_s: start_s, end_s = end_s, start_s subclip = clip.subclip(start_s, end_s) if speed != 1.0: subclip = subclip.fx(me.vfx.speedx, speed) # 해상도 조절(기본 100%) w, h = subclip.size if resolution < 100: scale = resolution / 100.0 subclip = subclip.resize((int(w * scale), int(h * scale))) # FPS fps = int(fps) if fps > 0 else None # 반복 횟수 loop = loop_count # 0=무한 temp_dir = tempfile.mkdtemp() gif_path = os.path.join(temp_dir, "output.gif") subclip.write_gif(gif_path, fps=fps, loop=loop) log_out1 = log_step("[단계 10] 'GIF 생성' 버튼 클릭됨") log_out2 = log_step("[단계 11] GIF 미리보기 생성 완료") log_out3 = log_step("[단계 12] GIF 다운로드 준비 완료") preview_img = Image.open(gif_path) return preview_img, gif_path, "\n".join([log_out1, log_out2, log_out3]) except Exception as e: err_log = log_step(f"[단계 10~12] GIF 생성 에러: {e}") return None, None, err_log def build_interface(): with gr.Blocks() as demo: gr.Markdown("## 동영상을 GIF로 변환하기") video_upload = gr.File(label="비디오 업로드", file_types=["video"]) video_player = gr.Video(label="업로드된 영상 미리보기") video_info = gr.Textbox(label="업로드된 영상 정보", interactive=False) video_path_state = gr.State() video_duration_state = gr.State() log_box = gr.Textbox(label="로그", interactive=False, lines=10) with gr.Row(): start_time = gr.Textbox(label="시작 시간 (HH:MM:SS/ MM:SS)", value="0:00") end_time = gr.Textbox(label="끝 시간 (HH:MM:SS/ MM:SS)", value="0:10") with gr.Row(): thumb1 = gr.Image(label="시작 지점 썸네일") thumb2 = gr.Image(label="끝 지점 썸네일") resolution_slider = gr.Slider(1, 100, 100, step=1, label="해상도(%)") fps_slider = gr.Slider(1, 60, 30, step=1, label="FPS") speed_slider = gr.Slider(0.1, 3.0, 1.0, step=0.1, label="재생 속도(배)") loop_slider = gr.Slider(0, 10, 0, step=1, label="GIF 반복 횟수 (0=무한)") generate_gif_btn = gr.Button("GIF 생성") gif_preview = gr.Image(label="GIF 미리보기") gif_download = gr.File(label="GIF 다운로드(클릭하여 저장)") # 이벤트 바인딩 video_upload.change( fn=upload_video, inputs=video_upload, outputs=[video_path_state, video_info, video_duration_state, log_box] ) start_time.change( fn=generate_thumbnails, inputs=[video_path_state, start_time, end_time, log_box], outputs=[thumb1, thumb2, log_box] ) end_time.change( fn=generate_thumbnails, inputs=[video_path_state, start_time, end_time, log_box], outputs=[thumb1, thumb2, log_box] ) generate_gif_btn.click( fn=create_gif, inputs=[video_path_state, start_time, end_time, resolution_slider, fps_slider, speed_slider, loop_slider, log_box], outputs=[gif_preview, gif_download, log_box] ) return demo if __name__ == "__main__": interface = build_interface() interface.launch()