6-1_gif / app.py
Kims12's picture
Update app.py
8e9820c verified
raw
history blame
11.8 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):
"""
์‹œ:๋ถ„:์ดˆ ํ˜•ํƒœ์˜ ๋ฌธ์ž์—ด(์˜ˆ: '00:00:10' ๋˜๋Š” '00:01:02')์„ ์ดˆ ๋‹จ์œ„(float)๋กœ ๋ณ€ํ™˜
"""
try:
h, m, s = time_str.split(":")
total_seconds = int(h) * 3600 + int(m) * 60 + float(s)
return total_seconds
except Exception:
return 0.0
def generate_thumbnail(video_clip, time_point):
"""
ํŠน์ • ์‹œ์ ์˜ ํ”„๋ ˆ์ž„์„ ์ธ๋„ค์ผ๋กœ ์ƒ์„ฑํ•˜์—ฌ PIL.Image๋กœ ๋ฐ˜ํ™˜
"""
try:
frame = video_clip.get_frame(time_point)
thumbnail_img = Image.fromarray(frame)
return thumbnail_img
except Exception as e:
add_log(f"[ERROR] ์ธ๋„ค์ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
frame = video_clip.get_frame(0)
thumbnail_img = Image.fromarray(frame)
return thumbnail_img
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, # ํ”Œ๋žซํผ๋ณ„ ๋น„์œจ ์„ ํƒ (Radio)
frame_rate_factor, # ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ์ถ•์†Œ ๋ฐฐ์œจ (1.0์ด๋ฉด ์›๋ณธ)
speed_factor,
repeat_count,
resolution_scale): # ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ (1.0์ด๋ฉด ์›๋ณธ)
"""
๋™์˜์ƒ์„ GIF๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
"""
global global_logs
global_logs = [] # ํ˜ธ์ถœ ์‹œ๋งˆ๋‹ค ๋กœ๊ทธ ์ดˆ๊ธฐํ™”
add_log("[LOG 1] ๋น„๋””์˜ค ์—…๋กœ๋“œ ๋ฐ ์ฒ˜๋ฆฌ ์‹œ์ž‘")
# video ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ํŒŒ์ผ ๊ฒฝ๋กœ ์ถ”์ถœ (Gradio์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด ๋ฌธ์ž์—ด์ผ ์ˆ˜ ์žˆ์Œ)
video_path = video if isinstance(video, str) else video.name
add_log("[LOG 2] ๋น„๋””์˜ค ๋กœ๋“œ ์ค‘...")
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}์ดˆ")
add_log("[LOG 4] ์‹œ์ž‘/๋ ์‹œ๊ฐ„ ํŒŒ์‹ฑ ์ค‘...")
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 = 0
end_sec = duration
add_log(f"[LOG 5] ์ ์šฉ๋œ ์‹œ์ž‘ ์‹œ๊ฐ„: {start_sec}์ดˆ, ์ข…๋ฃŒ ์‹œ๊ฐ„: {end_sec}์ดˆ")
add_log("[LOG 6] ์˜์ƒ ์ž๋ฅด๊ธฐ ์ž‘์—… ์ง„ํ–‰...")
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)
# ์žฌ์ƒ์†๋„ ์กฐ์ ˆ: speedx๋ฅผ ์ ์šฉํ•˜๋ฉด clip.duration์€ ์ค„์–ด๋“ค์ง€๋งŒ fps๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€๋จ.
if abs(speed_factor - 1.0) > 1e-3:
add_log(f"[LOG 8] ์žฌ์ƒ์†๋„ {speed_factor}๋ฐฐ๋กœ ์กฐ์ ˆ ์ค‘...")
clip = clip.fx(mp.vfx.speedx, speed_factor)
# FPS ์กฐ์ ˆ: ์ตœ์ข… ์ถœ๋ ฅ FPS๋Š” ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ๋งŒ ๋ฐ˜์˜ (speed_factor๋Š” ์ด๋ฏธ duration์— ๋ฐ˜์˜๋จ)
original_fps = clip.fps
target_fps = original_fps * frame_rate_factor
add_log(f"[LOG 9] ์ตœ์ข… ์ถœ๋ ฅ FPS: {target_fps:.2f} (์›๋ณธ FPS: {original_fps})")
clip = clip.set_fps(target_fps)
# ๋ฐ˜๋ณต ํšŸ์ˆ˜: 0์ด๋ฉด ๋ฌดํ•œ๋ฐ˜๋ณต, 1~10์ด๋ฉด ํ•ด๋‹น ํšŸ์ˆ˜๋งŒํผ ๋ฐ˜๋ณต (GIF์˜ loop ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ฌ์šฉ)
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
add_log(f"[LOG 10] GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜ ์„ค์ •: {repeat_count} (0์ด๋ฉด ๋ฌดํ•œ๋ฐ˜๋ณต, ์ ์šฉ๊ฐ’: {loop_param})")
add_log("[LOG 11] GIF ์ƒ์„ฑ ์ค‘...")
output_filename = f"temp_{uuid.uuid4().hex}.gif"
try:
# MoviePy์˜ write_gif ๋Œ€์‹  ์ง์ ‘ imageio๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ GIF ์ƒ์„ฑ
import imageio
# ํ”„๋ ˆ์ž„ ์ถ”์ถœ
frames = []
for t in range(int(clip.duration * target_fps)):
frame_time = t / target_fps
frame = clip.get_frame(frame_time)
frames.append(frame)
# imageio๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ GIF ์ €์žฅ (loop ํŒŒ๋ผ๋ฏธํ„ฐ ์ ์šฉ)
imageio.mimsave(
output_filename,
frames,
fps=target_fps,
loop=loop_param # ์—ฌ๊ธฐ์„œ ๋ฐ˜๋ณต ํšŸ์ˆ˜ ์„ค์ •
)
add_log("[LOG 12] GIF ์ƒ์„ฑ ์™„๋ฃŒ! ํŒŒ์ผ๋ช…: " + output_filename)
except Exception as e:
add_log(f"[ERROR] GIF ์ƒ์„ฑ ์‹คํŒจ: {e}")
return None, None, "\n".join(global_logs)
# ๋ฏธ๋ฆฌ๋ณด๊ธฐ: ์™„์„ฑ๋œ GIF ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉด gr.Image์—์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
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 = 0
end_sec = duration
start_thumb = generate_thumbnail(input_video, start_sec)
end_thumb = generate_thumbnail(input_video, end_sec)
return start_thumb, end_thumb
# ------------------------------
# Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ (Blocks)
# ------------------------------
with gr.Blocks() as demo:
gr.Markdown("## ๋™์˜์ƒ์„ GIF๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ๋ฐ๋ชจ")
with gr.Tab("GIF ๋ณ€ํ™˜"):
# ๋™์˜์ƒ ์—…๋กœ๋“œ
video_input = gr.Video(label="๋™์˜์ƒ ์—…๋กœ๋“œ")
# ์‹œ์ž‘/๋ ์‹œ๊ฐ„ ์ž…๋ ฅ
start_time = gr.Textbox(label="์‹œ์ž‘ ์‹œ๊ฐ„ (์˜ˆ: 00:00:05)", value="00:00:00")
end_time = gr.Textbox(label="์ข…๋ฃŒ ์‹œ๊ฐ„ (์˜ˆ: 00:00:10)", value="00:00:05")
# ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
start_thumb_output = gr.Image(label="์‹œ์ž‘ ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
end_thumb_output = gr.Image(label="์ข…๋ฃŒ ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
# ํ”Œ๋žซํผ๋ณ„ ํ•ด์ƒ๋„(๋น„์œจ) ์„ ํƒ (Radio ๋ฒ„ํŠผ)
platform_option = gr.Radio(
label="ํ•ด์ƒ๋„/๋น„์œจ ์„ ํƒ",
choices=["์›๋ณธ ์œ ์ง€", "์œ ํŠœ๋ธŒ (16:9)", "์‡ผ์ธ /๋ฆด์Šค (9:16)", "์ •์‚ฌ๊ฐํ˜• (1:1)", "์ธ์Šคํƒ€๊ทธ๋žจ (4:5)", "ํด๋ž˜์‹ (4:3)"],
value="์›๋ณธ ์œ ์ง€"
)
# ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ ์˜ต์…˜ (1.0์ด๋ฉด ์›๋ณธ, 0.1 ~ 1.0 ๋ฒ”์œ„)
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)
# GIF ์ƒ์„ฑ ๋ฒ„ํŠผ ๋ฐ ๊ฒฐ๊ณผ ์ถœ๋ ฅ
generate_button = gr.Button("GIF ์ƒ์„ฑํ•˜๊ธฐ")
gif_preview_output = gr.Image(label="์™„์„ฑ๋œ GIF ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
download_output = gr.File(label="GIF ๋‹ค์šด๋กœ๋“œ ๋งํฌ")
logs_output = gr.Textbox(label="๋กœ๊ทธ ์ถœ๋ ฅ", lines=10)
# ์ธ๋„ค์ผ ์—…๋ฐ์ดํŠธ ์ด๋ฒคํŠธ (๋™์˜์ƒ ๋˜๋Š” ์‹œ๊ฐ„ ์ž…๋ ฅ ๋ณ€๊ฒฝ ์‹œ)
start_time.change(fn=update_thumbnails,
inputs=[video_input, start_time, end_time],
outputs=[start_thumb_output, end_thumb_output])
end_time.change(fn=update_thumbnails,
inputs=[video_input, start_time, end_time],
outputs=[start_thumb_output, end_thumb_output])
video_input.change(fn=update_thumbnails,
inputs=[video_input, start_time, end_time],
outputs=[start_thumb_output, end_thumb_output])
# GIF ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
generate_button.click(
fn=process_video,
inputs=[
video_input, # ๋™์˜์ƒ ์—…๋กœ๋“œ
start_time, # ์‹œ์ž‘ ์‹œ๊ฐ„
end_time, # ์ข…๋ฃŒ ์‹œ๊ฐ„
platform_option, # ํ”Œ๋žซํผ๋ณ„ ๋น„์œจ
frame_rate_slider, # ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ
speed_slider, # ์žฌ์ƒ ์†๋„
repeat_slider, # ๋ฐ˜๋ณต ํšŸ์ˆ˜
resolution_scale_slider # ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ
],
outputs=[
gif_preview_output, # ์™„์„ฑ๋œ GIF ๋ฏธ๋ฆฌ๋ณด๊ธฐ
download_output, # GIF ๋‹ค์šด๋กœ๋“œ ๋งํฌ
logs_output # ๋กœ๊ทธ ์ถœ๋ ฅ
]
)
gr.Markdown(
"### [์‚ฌ์šฉ ๊ฐ€์ด๋“œ]\n"
"1. ๋™์˜์ƒ์„ ์—…๋กœ๋“œํ•˜์„ธ์š”.\n"
"2. ์‹œ์ž‘/๋ ์‹œ๊ฐ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.\n"
"3. ํ”Œ๋žซํผ๋ณ„ ๊ถŒ์žฅ ํ•ด์ƒ๋„/๋น„์œจ(์›๋ณธ ์œ ์ง€, ์œ ํŠœ๋ธŒ, ์‡ผ์ธ /๋ฆด์Šค, ์ •์‚ฌ๊ฐํ˜•, ์ธ์Šคํƒ€๊ทธ๋žจ, ํด๋ž˜์‹)๊ณผ ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ์„ ์„ ํƒํ•˜๊ณ ,\n"
" ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ, ์žฌ์ƒ ์†๋„, ๋ฐ˜๋ณต ํšŸ์ˆ˜๋ฅผ ์กฐ์ ˆํ•œ ํ›„ `GIF ์ƒ์„ฑํ•˜๊ธฐ` ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด GIF๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.\n"
" - ๋ฐ˜๋ณต ํšŸ์ˆ˜(0: ๋ฌดํ•œ๋ฐ˜๋ณต, 1~10: ์ง€์ • ํšŸ์ˆ˜). ์˜ˆ) 1์ด๋ฉด 1ํšŒ ์žฌ์ƒ ํ›„ ๋ฉˆ์ถค, 2์ด๋ฉด 2ํšŒ ์žฌ์ƒ, 0์ด๋ฉด ๋ฌดํ•œ๋ฐ˜๋ณต.\n"
"4. ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ ๋‹ค์šด๋กœ๋“œ ๋งํฌ๋ฅผ ํ†ตํ•ด GIF๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n"
"โ€ป ๋ฌดํ•œ๋ฐ˜๋ณต์ด ์•„๋‹Œ ํŠน์ • ๋ฐ˜๋ณต ํšŸ์ˆ˜๋ฅผ ์›ํ•  ๊ฒฝ์šฐ, ImageMagick์ด ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
)
if __name__ == "__main__":
demo.launch()