|
import os |
|
import tempfile |
|
import gradio as gr |
|
|
|
import numpy as np |
|
import imageio |
|
from PIL import Image |
|
|
|
import moviepy.editor as me |
|
|
|
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) |
|
|
|
|
|
w, h = subclip.size |
|
if resolution < 100: |
|
scale = resolution / 100.0 |
|
subclip = subclip.resize((int(w * scale), int(h * scale))) |
|
|
|
|
|
fps = int(fps) if fps > 0 else None |
|
|
|
|
|
loop = loop_count |
|
|
|
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() |
|
|