|
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, |
|
frame_rate_factor, |
|
speed_factor, |
|
repeat_count, |
|
resolution_scale): |
|
""" |
|
๋์์์ GIF๋ก ๋ณํํ๋ ํจ์ |
|
""" |
|
global global_logs |
|
global_logs = [] |
|
|
|
add_log("[LOG 1] ๋น๋์ค ์
๋ก๋ ๋ฐ ์ฒ๋ฆฌ ์์") |
|
|
|
|
|
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) |
|
|
|
|
|
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} (์๋ณธ FPS: {original_fps})") |
|
clip = clip.set_fps(target_fps) |
|
|
|
|
|
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: |
|
|
|
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.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) |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
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="์ข
๋ฃ ์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ") |
|
|
|
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) |
|
|
|
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]) |
|
|
|
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, |
|
download_output, |
|
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() |
|
|