gif-api / app.py
Kims12's picture
Update app.py
9a66b35 verified
import gradio as gr
import subprocess
import os
from datetime import datetime
from PIL import Image
import tempfile
import logging
# ๋กœ๊น… ์„ค์ •
logging.basicConfig(
filename='app.log',
filemode='a',
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.DEBUG
)
def on_video_upload(video):
logging.info("on_video_upload ํ•จ์ˆ˜ ํ˜ธ์ถœ")
try:
# ๋น„๋””์˜ค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
if isinstance(video, str):
video_path = video
logging.debug(f"์—…๋กœ๋“œ๋œ ๋น„๋””์˜ค ๊ฒฝ๋กœ (str): {video_path}")
elif isinstance(video, dict) and 'name' in video:
video_path = video['name']
logging.debug(f"์—…๋กœ๋“œ๋œ ๋น„๋””์˜ค ๊ฒฝ๋กœ (dict): {video_path}")
else:
logging.error("๋น„๋””์˜ค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
raise Exception("๋น„๋””์˜ค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
if not os.path.exists(video_path):
logging.error(f"๋น„๋””์˜ค ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {video_path}")
raise Exception("๋น„๋””์˜ค ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
# ์˜์ƒ ๊ธธ์ด ์ถ”์ถœ
probe_duration = subprocess.run(
["ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of",
"default=noprint_wrappers=1:nokey=1", video_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if probe_duration.returncode != 0:
logging.error(f"ffprobe duration ์—๋Ÿฌ: {probe_duration.stderr}")
raise Exception("์˜์ƒ ๊ธธ์ด ์ถ”์ถœ ์‹คํŒจ")
duration = float(probe_duration.stdout.strip())
duration_str = str(datetime.utcfromtimestamp(duration).strftime('%H:%M:%S'))
logging.debug(f"์˜์ƒ ๊ธธ์ด: {duration_str}")
# ํ•ด์ƒ๋„ ์ถ”์ถœ
probe_resolution = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height",
"-of", "csv=s=x:p=0", video_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if probe_resolution.returncode != 0:
logging.error(f"ffprobe resolution ์—๋Ÿฌ: {probe_resolution.stderr}")
raise Exception("์˜์ƒ ํ•ด์ƒ๋„ ์ถ”์ถœ ์‹คํŒจ")
resolution = probe_resolution.stdout.strip()
logging.debug(f"์˜์ƒ ํ•ด์ƒ๋„: {resolution}")
# ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ๋ ์‹œ๊ฐ„ ์ดˆ๊ธฐํ™”
start_time = "00:00:00"
end_time = duration_str
# ์บก์ณ๋ณธ ์ƒ์„ฑ
start_capture = capture_frame(video_path, start_time)
end_capture = capture_frame(video_path, end_time)
return duration_str, resolution, start_time, end_time, start_capture, end_capture
except Exception as e:
logging.exception("on_video_upload ํ•จ์ˆ˜์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ")
return "์—๋Ÿฌ ๋ฐœ์ƒ", "์—๋Ÿฌ ๋ฐœ์ƒ", "์—๋Ÿฌ", "์—๋Ÿฌ", None, None
def on_any_change(video, start_time, end_time):
logging.info("on_any_change ํ•จ์ˆ˜ ํ˜ธ์ถœ")
try:
# ๋น„๋””์˜ค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
if isinstance(video, str):
video_path = video
logging.debug(f"๋น„๋””์˜ค ๊ฒฝ๋กœ (str): {video_path}")
elif isinstance(video, dict) and 'name' in video:
video_path = video['name']
logging.debug(f"๋น„๋””์˜ค ๊ฒฝ๋กœ (dict): {video_path}")
else:
logging.error("๋น„๋””์˜ค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
raise Exception("๋น„๋””์˜ค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
if not os.path.exists(video_path):
logging.error(f"๋น„๋””์˜ค ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {video_path}")
raise Exception("๋น„๋””์˜ค ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
logging.debug(f"์‹œ์ž‘ ์‹œ๊ฐ„: {start_time}, ๋ ์‹œ๊ฐ„: {end_time}")
start_capture = capture_frame(video_path, start_time)
end_capture = capture_frame(video_path, end_time)
return start_capture, end_capture
except Exception as e:
logging.exception("on_any_change ํ•จ์ˆ˜์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ")
return None, None
def on_generate_click(video, start_time, end_time, fps, resize_factor, speed_factor):
logging.info("on_generate_click ํ•จ์ˆ˜ ํ˜ธ์ถœ")
try:
# ๋น„๋””์˜ค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
if isinstance(video, str):
video_path = video
logging.debug(f"๋น„๋””์˜ค ๊ฒฝ๋กœ (str): {video_path}")
elif isinstance(video, dict) and 'name' in video:
video_path = video['name']
logging.debug(f"๋น„๋””์˜ค ๊ฒฝ๋กœ (dict): {video_path}")
else:
logging.error("๋น„๋””์˜ค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
raise Exception("๋น„๋””์˜ค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
if not os.path.exists(video_path):
logging.error(f"๋น„๋””์˜ค ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {video_path}")
raise Exception("๋น„๋””์˜ค ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
logging.debug(f"์‹œ์ž‘ ์‹œ๊ฐ„: {start_time}, ๋ ์‹œ๊ฐ„: {end_time}")
logging.debug(f"FPS: {fps}, ํ•ด์ƒ๋„ ๋ฐฐ์œจ: {resize_factor}, ๋ฐฐ์†: {speed_factor}")
# ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ๋ ์‹œ๊ฐ„์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
start_seconds = time_str_to_seconds(start_time)
end_seconds = time_str_to_seconds(end_time)
if start_seconds >= end_seconds:
logging.error("์‹œ์ž‘ ์‹œ๊ฐ„์ด ๋ ์‹œ๊ฐ„๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์Šต๋‹ˆ๋‹ค.")
return None, "์‹œ์ž‘ ์‹œ๊ฐ„์ด ๋ ์‹œ๊ฐ„๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์Šต๋‹ˆ๋‹ค.", None
# ์˜์ƒ ๊ธธ์ด์™€ ์ผ์น˜ ์—ฌ๋ถ€ ํ™•์ธ
probe_duration = subprocess.run(
["ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of",
"default=noprint_wrappers=1:nokey=1", video_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if probe_duration.returncode != 0:
logging.error(f"ffprobe duration ์—๋Ÿฌ: {probe_duration.stderr}")
raise Exception("์˜์ƒ ๊ธธ์ด ์ถ”์ถœ ์‹คํŒจ")
duration_video = float(probe_duration.stdout.strip())
if end_seconds > duration_video:
logging.error("๋ ์‹œ๊ฐ„์ด ์˜์ƒ ๊ธธ์ด๋ฅผ ์ดˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.")
return None, "๋ ์‹œ๊ฐ„์ด ์˜์ƒ ๊ธธ์ด๋ฅผ ์ดˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.", None
# GIF์˜ ์ง€์† ์‹œ๊ฐ„ ๊ณ„์‚ฐ (end_time - start_time)
gif_duration_seconds = end_seconds - start_seconds
gif_duration_str = str(datetime.utcfromtimestamp(gif_duration_seconds).strftime('%H:%M:%S'))
logging.debug(f"GIF ์ง€์† ์‹œ๊ฐ„: {gif_duration_str} (์ดˆ: {gif_duration_seconds})")
# ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as tmp_gif:
gif_path = tmp_gif.name
logging.debug(f"์ƒ์„ฑ๋œ GIF ์ž„์‹œ ๊ฒฝ๋กœ: {gif_path}")
# GIF ์ƒ์„ฑ ๋ช…๋ น์–ด
cmd = [
"ffmpeg",
"-y", # ๊ธฐ์กด ํŒŒ์ผ ๋ฎ์–ด์“ฐ๊ธฐ
"-ss", start_time,
"-to", end_time,
"-i", video_path,
"-vf", f"fps={fps},scale=iw*{resize_factor}:ih*{resize_factor}",
gif_path
]
logging.debug(f"์‹คํ–‰ํ•  ffmpeg ๋ช…๋ น์–ด: {' '.join(cmd)}")
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
logging.debug(f"ffmpeg stdout: {result.stdout}")
logging.debug(f"ffmpeg stderr: {result.stderr}")
logging.info("GIF ์ƒ์„ฑ ์™„๋ฃŒ")
# GIF ์†๋„ ์กฐ์ ˆ (speed_factor)
if speed_factor != 1:
gif_fast = gif_path.replace('.gif', '_fast.gif')
cmd_speed = [
"ffmpeg",
"-y", # ๊ธฐ์กด ํŒŒ์ผ ๋ฎ์–ด์“ฐ๊ธฐ
"-i", gif_path,
"-filter_complex", f"[0:v]setpts={1/speed_factor}*PTS[v]",
"-map", "[v]",
gif_fast
]
logging.debug(f"์‹คํ–‰ํ•  ์†๋„ ์กฐ์ ˆ ffmpeg ๋ช…๋ น์–ด: {' '.join(cmd_speed)}")
result_speed = subprocess.run(cmd_speed, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
logging.debug(f"ffmpeg speed ์กฐ์ ˆ stdout: {result_speed.stdout}")
logging.debug(f"ffmpeg speed ์กฐ์ ˆ stderr: {result_speed.stderr}")
os.remove(gif_path)
gif_path = gif_fast
logging.info(f"GIF ์†๋„ ์กฐ์ ˆ ์™„๋ฃŒ: {gif_path}")
# ํŒŒ์ผ ํฌ๊ธฐ ์ธก์ •
file_size = os.path.getsize(gif_path)
logging.debug(f"์ƒ์„ฑ๋œ GIF ํŒŒ์ผ ํฌ๊ธฐ: {file_size} bytes")
# ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํŒŒ์ผ ๊ฒฝ๋กœ
gif_download = gif_path
return gif_path, str(file_size), gif_download
except subprocess.CalledProcessError as e:
logging.error(f"subprocess ์—๋Ÿฌ: {e.stderr}")
return None, f"GIF ์ƒ์„ฑ ์ค‘ subprocess ์—๋Ÿฌ ๋ฐœ์ƒ: {e.stderr}", None
except Exception as e:
logging.exception("on_generate_click ํ•จ์ˆ˜์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ")
return None, f"์—๋Ÿฌ ๋ฐœ์ƒ: {str(e)}", None
def capture_frame(video_path, timestamp):
logging.info(f"capture_frame ํ•จ์ˆ˜ ํ˜ธ์ถœ: {video_path} at {timestamp}")
try:
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_img:
img_path = tmp_img.name
logging.debug(f"์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์ž„์‹œ ๊ฒฝ๋กœ: {img_path}")
cmd = [
"ffmpeg",
"-y", # ๊ธฐ์กด ํŒŒ์ผ ๋ฎ์–ด์“ฐ๊ธฐ
"-ss", timestamp,
"-i", video_path,
"-vframes", "1",
"-q:v", "2",
img_path
]
logging.debug(f"์‹คํ–‰ํ•  ffmpeg ์บก์ณ ๋ช…๋ น์–ด: {' '.join(cmd)}")
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
logging.debug(f"ffmpeg ์บก์ณ stdout: {result.stdout}")
logging.debug(f"ffmpeg ์บก์ณ stderr: {result.stderr}")
logging.info(f"ํ”„๋ ˆ์ž„ ์บก์ณ ์™„๋ฃŒ: {img_path}")
return img_path
except subprocess.CalledProcessError as e:
logging.error(f"ffmpeg ์บก์ณ ์—๋Ÿฌ: {e.stderr}")
return None
except Exception as e:
logging.exception("capture_frame ํ•จ์ˆ˜์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ")
return None
def time_str_to_seconds(time_str):
"""HH:MM:SS ํฌ๋งท์˜ ์‹œ๊ฐ„์„ ์ดˆ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜"""
try:
parts = time_str.split(':')
if len(parts) != 3:
raise ValueError("์‹œ๊ฐ„ ํฌ๋งท์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
hours, minutes, seconds = map(int, parts)
return hours * 3600 + minutes * 60 + seconds
except Exception as e:
logging.error(f"time_str_to_seconds ์—๋Ÿฌ: {e}")
raise
with gr.Blocks() as demo:
gr.Markdown("# ์˜์ƒ ์ฒ˜๋ฆฌ ์ปจํŠธ๋กค ํƒ€์›Œ")
with gr.Row():
video_input = gr.Video(label="์˜์ƒ ์—…๋กœ๋“œ")
with gr.Row():
duration_text = gr.Textbox(label="์˜์ƒ ๊ธธ์ด", interactive=False)
resolution_text = gr.Textbox(label="ํ•ด์ƒ๋„", interactive=False)
with gr.Row():
start_time = gr.Textbox(label="์‹œ์ž‘ ์‹œ๊ฐ„ (HH:MM:SS)", value="00:00:00")
end_time = gr.Textbox(label="๋ ์‹œ๊ฐ„ (HH:MM:SS)", value="00:00:00")
with gr.Row():
start_image = gr.Image(label="์‹œ์ž‘ ์ง€์  ์บก์ณ๋ณธ")
end_image = gr.Image(label="๋ ์ง€์  ์บก์ณ๋ณธ")
with gr.Row():
fps_slider = gr.Slider(label="FPS", minimum=1, maximum=60, step=1, value=10)
resize_slider = gr.Slider(label="ํ•ด์ƒ๋„ ๋ฐฐ์œจ", minimum=0.5, maximum=2.0, step=0.1, value=1.0)
speed_slider = gr.Slider(label="๋ฐฐ์†", minimum=0.5, maximum=2.0, step=0.1, value=1.0)
with gr.Row():
generate_button = gr.Button("GIF ์ƒ์„ฑ")
with gr.Row():
gif_output = gr.Image(label="๊ฒฐ๊ณผ GIF")
file_size_output = gr.Textbox(label="ํŒŒ์ผ ์šฉ๋Ÿ‰")
download_output = gr.File(label="GIF ๋‹ค์šด๋กœ๋“œ")
# ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
video_input.upload(on_video_upload, inputs=video_input, outputs=[duration_text, resolution_text, start_time, end_time, start_image, end_image])
start_time.change(on_any_change, inputs=[video_input, start_time, end_time], outputs=[start_image, end_image])
end_time.change(on_any_change, inputs=[video_input, start_time, end_time], outputs=[start_image, end_image])
generate_button.click(on_generate_click, inputs=[video_input, start_time, end_time, fps_slider, resize_slider, speed_slider], outputs=[gif_output, file_size_output, download_output])
demo.launch()