| 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_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}") |
|
|
| |
| 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 ์์ฑ ์๋ฃ") |
|
|
| |
| 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() |
|
|