6-1_gif / app.py
Kims12's picture
Update app.py
a53e3dc verified
raw
history blame
9.9 kB
import os
import uuid
import moviepy.editor as mp
from PIL import Image
import gradio as gr
# -------------------------------
# ๋‚ด๋ถ€ ํ•จ์ˆ˜๋“ค (๋™์˜์ƒ ์ฒ˜๋ฆฌ ๋“ฑ)
# -------------------------------
global_logs = []
def add_log(message: str):
global_logs.append(message)
print(message)
def parse_time_to_seconds(time_str: str):
try:
h, m, s = time_str.split(":")
return int(h) * 3600 + int(m) * 60 + float(s)
except Exception:
return 0.0
def generate_thumbnail(video_clip, time_point):
try:
frame = video_clip.get_frame(time_point)
return Image.fromarray(frame)
except Exception as e:
add_log(f"[ERROR] ์ธ๋„ค์ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
frame = video_clip.get_frame(0)
return Image.fromarray(frame)
def adjust_aspect_ratio(clip, option):
if option == "์›๋ณธ ์œ ์ง€":
return clip
if option == "์œ ํŠœ๋ธŒ (16:9)":
target_ratio = 16/9
elif option == "์‡ผ์ธ /๋ฆด์Šค (9:16)":
target_ratio = 9/16
elif option == "์ •์‚ฌ๊ฐํ˜• (1:1)":
target_ratio = 1.0
elif option == "์ธ์Šคํƒ€๊ทธ๋žจ (4:5)":
target_ratio = 4/5
elif option == "ํด๋ž˜์‹ (4:3)":
target_ratio = 4/3
else:
return clip
width, height = clip.size
current_ratio = width/height
if current_ratio > target_ratio:
new_width = int(height * target_ratio)
new_height = height
else:
new_width = width
new_height = int(width / target_ratio)
return clip.crop(x_center=width/2, y_center=height/2, width=new_width, height=new_height)
def process_video(video,
start_time_str,
end_time_str,
platform_option,
frame_rate_factor,
speed_factor,
repeat_count,
resolution_scale):
global global_logs
global_logs = []
add_log("๐ŸŽฅ [LOG 1] ๋น„๋””์˜ค ์—…๋กœ๋“œ ๋ฐ ์ฒ˜๋ฆฌ ์‹œ์ž‘")
video_path = video if isinstance(video, str) else video.name
try:
input_video = mp.VideoFileClip(video_path)
except Exception as e:
add_log(f"[ERROR] ๋น„๋””์˜ค ๋กœ๋“œ ์‹คํŒจ: {e}")
return None, None, "\n".join(global_logs)
duration = input_video.duration
add_log(f"[LOG 3] ์˜์ƒ ์žฌ์ƒ์‹œ๊ฐ„: {duration:.2f}์ดˆ")
start_sec = parse_time_to_seconds(start_time_str)
end_sec = parse_time_to_seconds(end_time_str)
if start_sec < 0:
start_sec = 0
if end_sec <= 0 or end_sec > duration:
end_sec = duration
if start_sec >= end_sec:
start_sec, end_sec = 0, duration
add_log(f"[LOG 5] ์‹œ๊ฐ„ ์„ค์ •: {start_sec}์ดˆ ~ {end_sec}์ดˆ")
clip = input_video.subclip(start_sec, end_sec)
add_log(f"[LOG 7] ํ”Œ๋žซํผ ๋น„์œจ ์ ์šฉ: {platform_option}")
clip = adjust_aspect_ratio(clip, platform_option)
if abs(resolution_scale - 1.0) > 1e-3:
add_log(f"[LOG 7-1] ํ•ด์ƒ๋„ ์ถ•์†Œ: {resolution_scale*100:.0f}%")
clip = clip.resize(resolution_scale)
if abs(speed_factor - 1.0) > 1e-3:
add_log(f"[LOG 8] ์žฌ์ƒ์†๋„ {speed_factor}๋ฐฐ ์ ์šฉ")
clip = clip.fx(mp.vfx.speedx, speed_factor)
original_fps = clip.fps
target_fps = original_fps * frame_rate_factor
add_log(f"[LOG 9] FPS: {target_fps:.2f} (์›๋ณธ {original_fps})")
clip = clip.set_fps(target_fps)
add_log(f"[LOG 10] GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜: {repeat_count} (0: ๋ฌดํ•œ)")
final_clip = clip
output_filename = f"temp_{uuid.uuid4().hex}.gif"
try:
# ImageMagick์œผ๋กœ ์ƒ์„ฑ ์‹œ loop ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์ ์šฉ๋จ.
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
final_clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
add_log(f"[LOG 12] GIF ์ƒ์„ฑ ์™„๋ฃŒ: {output_filename}, loop={loop_param}")
except Exception as e:
add_log(f"[ERROR] GIF ์ƒ์„ฑ ์‹คํŒจ: {e}")
return None, None, "\n".join(global_logs)
return output_filename, output_filename, "\n".join(global_logs)
def update_thumbnails(video, start_time_str, end_time_str):
video_path = video if isinstance(video, str) else video.name
try:
input_video = mp.VideoFileClip(video_path)
except Exception as e:
add_log(f"[ERROR] ๋น„๋””์˜ค ๋กœ๋“œ ์‹คํŒจ: {e}")
return None, None
duration = input_video.duration
start_sec = parse_time_to_seconds(start_time_str)
end_sec = parse_time_to_seconds(end_time_str)
if start_sec < 0:
start_sec = 0
if end_sec <= 0 or end_sec > duration:
end_sec = duration
if start_sec >= end_sec:
start_sec, end_sec = 0, duration
start_thumb = generate_thumbnail(input_video, start_sec)
end_thumb = generate_thumbnail(input_video, end_sec)
return start_thumb, end_thumb
# -------------------------------
# Custom CSS (HTML/CSS ๊ธฐ๋ฐ˜ ์ƒˆ UI)
# -------------------------------
custom_css = """
body {
font-family: 'Arial', sans-serif;
background-color: #eef2f7;
}
.custom-title {
font-size: 2.8em;
text-align: center;
font-weight: bold;
margin: 20px 0;
color: #2c3e50;
}
.custom-user-guide {
font-size: 1.2em;
text-align: center;
margin-bottom: 30px;
color: #34495e;
}
.frame {
border: 2px solid #888;
border-radius: 20px;
padding: 20px;
background-color: #fff;
margin: 10px;
box-shadow: 3px 3px 10px rgba(0,0,0,0.1);
}
.row-container {
display: flex;
justify-content: space-between;
}
.column {
flex: 1;
margin: 10px;
}
.full-width {
width: 100%;
margin: 10px 0;
}
input, button, select {
font-size: 1em;
}
.emoji {
font-size: 1.2em;
margin-right: 5px;
}
"""
# -------------------------------
# Gradio UI ๊ตฌ์„ฑ (HTML/CSS ์ปค์Šคํ„ฐ๋งˆ์ด์ง•)
# -------------------------------
with gr.Blocks(css=custom_css, title="์˜์ƒ -> GIF ๋ณ€ํ™˜ ์„œ๋น„์Šค") as demo:
# ์ œ๋ชฉ & ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ
gr.HTML("<div class='custom-title'>๐ŸŽฌ ์˜์ƒ์„ GIF๋ณ€ํ™˜ํ•˜๊ธฐ ๐ŸŽž๏ธ</div>")
gr.HTML("<div class='custom-user-guide'>๐Ÿ‘‰ ์‚ฌ์šฉ๊ฐ€์ด๋“œ: ์ขŒ์ธก '์ž…๋ ฅ๋ถ€'์—์„œ ์˜์ƒ์„ ์—…๋กœ๋“œํ•˜๊ณ  ์˜ต์…˜์„ ์„ ํƒํ•œ ํ›„,<br>"
"ํ•˜๋‹จ '์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ'์—์„œ ์‹œ์ž‘/์ข…๋ฃŒ ์‹œ๊ฐ„์„ ์„ค์ •ํ•˜๊ณ  ์ธ๋„ค์ผ์„ ํ™•์ธํ•˜์„ธ์š”. <br>"
"์šฐ์ธก '์ถœ๋ ฅ๋ถ€'์—์„œ ์ƒ์„ฑ๋œ GIF๋ฅผ ๋ฏธ๋ฆฌ ๋ณด๊ณ  ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Š</div>")
# ์ฒซ๋ฒˆ์งธ ํ–‰: ์ž…๋ ฅ๋ถ€ (์ขŒ์ธก) / ์ถœ๋ ฅ๋ถ€ (์šฐ์ธก)
with gr.Row(elem_classes="row-container"):
with gr.Column(elem_classes="column"):
with gr.Group(elem_classes="frame"):
gr.Markdown("### ๐Ÿ“ฅ ์ž…๋ ฅ๋ถ€")
video_input = gr.Video(label="์˜์ƒ ์—…๋กœ๋“œ", show_label=True)
platform_option = gr.Radio(
label="ํ•ด์ƒ๋„/๋น„์œจ ์„ ํƒ",
choices=["์›๋ณธ ์œ ์ง€", "์œ ํŠœ๋ธŒ (16:9)", "์‡ผ์ธ /๋ฆด์Šค (9:16)", "์ •์‚ฌ๊ฐํ˜• (1:1)", "์ธ์Šคํƒ€๊ทธ๋žจ (4:5)", "ํด๋ž˜์‹ (4:3)"],
value="์›๋ณธ ์œ ์ง€"
)
resolution_scale_slider = gr.Slider(label="์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ (0.1 ~ 1.0)",
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
frame_rate_slider = gr.Slider(label="ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ ์กฐ์ ˆ (0.1 ~ 1.0)",
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
speed_slider = gr.Slider(label="์žฌ์ƒ ์†๋„ ์กฐ์ ˆ (0.5 ~ 5.0)",
minimum=0.5, maximum=5.0, step=0.1, value=1.0)
repeat_slider = gr.Slider(label="GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜ (0: ๋ฌดํ•œ๋ฐ˜๋ณต, 1~10)",
minimum=0, maximum=10, step=1, value=0)
with gr.Column(elem_classes="column"):
with gr.Group(elem_classes="frame"):
gr.Markdown("### ๐Ÿ“ค ์ถœ๋ ฅ๋ถ€")
gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
download_output = gr.File(label="GIF ๋‹ค์šด๋กœ๋“œ")
# ๋‘๋ฒˆ์งธ ํ–‰: ์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ (์ „์ฒด ๋„ˆ๋น„)
with gr.Group(elem_classes="frame full-width"):
gr.Markdown("### ๐Ÿ–ผ๏ธ ์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
with gr.Row():
start_time_tb = gr.Textbox(label="์‹œ์ž‘ ์‹œ๊ฐ„ (์˜ˆ: 00:00:05)", value="00:00:00")
end_time_tb = gr.Textbox(label="์ข…๋ฃŒ ์‹œ๊ฐ„ (์˜ˆ: 00:00:10)", value="00:00:05")
with gr.Row():
start_thumb_output = gr.Image(label="์‹œ์ž‘ ์ธ๋„ค์ผ")
end_thumb_output = gr.Image(label="์ข…๋ฃŒ ์ธ๋„ค์ผ")
generate_button = gr.Button("โœจ GIF ์ƒ์„ฑํ•˜๊ธฐ")
logs_output = gr.Textbox(label="๐Ÿ“ ์ž‘์—… ๋กœ๊ทธ", lines=10)
# ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ
# ์˜์ƒ ์—…๋กœ๋“œ, ์‹œ์ž‘/์ข…๋ฃŒ ์‹œ๊ฐ„ ๋ณ€๊ฒฝ ์‹œ ์ธ๋„ค์ผ ์—…๋ฐ์ดํŠธ
start_time_tb.change(fn=update_thumbnails,
inputs=[video_input, start_time_tb, end_time_tb],
outputs=[start_thumb_output, end_thumb_output])
end_time_tb.change(fn=update_thumbnails,
inputs=[video_input, start_time_tb, end_time_tb],
outputs=[start_thumb_output, end_thumb_output])
video_input.change(fn=update_thumbnails,
inputs=[video_input, start_time_tb, end_time_tb],
outputs=[start_thumb_output, end_thumb_output])
# GIF ์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ „์ฒด ์ฒ˜๋ฆฌ (์ž…๋ ฅ๋ถ€ + ์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ)
generate_button.click(fn=process_video,
inputs=[video_input, start_time_tb, end_time_tb,
platform_option, frame_rate_slider, speed_slider, repeat_slider,
resolution_scale_slider],
outputs=[gif_preview_output, download_output, logs_output])
demo.launch()