Update app.py
Browse files
app.py
CHANGED
@@ -5,7 +5,7 @@ from PIL import Image
|
|
5 |
import gradio as gr
|
6 |
|
7 |
# -------------------------------
|
8 |
-
# ๋ด๋ถ
|
9 |
# -------------------------------
|
10 |
global_logs = []
|
11 |
|
@@ -16,7 +16,7 @@ def add_log(message: str):
|
|
16 |
def parse_time_to_seconds(time_str: str):
|
17 |
try:
|
18 |
h, m, s = time_str.split(":")
|
19 |
-
return int(h)
|
20 |
except Exception:
|
21 |
return 0.0
|
22 |
|
@@ -44,7 +44,6 @@ def adjust_aspect_ratio(clip, option):
|
|
44 |
target_ratio = 4/3
|
45 |
else:
|
46 |
return clip
|
47 |
-
|
48 |
width, height = clip.size
|
49 |
current_ratio = width/height
|
50 |
if current_ratio > target_ratio:
|
@@ -65,7 +64,7 @@ def process_video(video,
|
|
65 |
resolution_scale):
|
66 |
global global_logs
|
67 |
global_logs = []
|
68 |
-
add_log("๐ฅ [LOG
|
69 |
video_path = video if isinstance(video, str) else video.name
|
70 |
try:
|
71 |
input_video = mp.VideoFileClip(video_path)
|
@@ -73,7 +72,7 @@ def process_video(video,
|
|
73 |
add_log(f"[ERROR] ๋น๋์ค ๋ก๋ ์คํจ: {e}")
|
74 |
return None, None, "\n".join(global_logs)
|
75 |
duration = input_video.duration
|
76 |
-
add_log(f"[LOG
|
77 |
start_sec = parse_time_to_seconds(start_time_str)
|
78 |
end_sec = parse_time_to_seconds(end_time_str)
|
79 |
if start_sec < 0:
|
@@ -82,28 +81,26 @@ def process_video(video,
|
|
82 |
end_sec = duration
|
83 |
if start_sec >= end_sec:
|
84 |
start_sec, end_sec = 0, duration
|
85 |
-
add_log(f"[LOG
|
86 |
clip = input_video.subclip(start_sec, end_sec)
|
87 |
-
add_log(f"[LOG
|
88 |
clip = adjust_aspect_ratio(clip, platform_option)
|
89 |
if abs(resolution_scale - 1.0) > 1e-3:
|
90 |
-
add_log(f"[LOG
|
91 |
clip = clip.resize(resolution_scale)
|
92 |
if abs(speed_factor - 1.0) > 1e-3:
|
93 |
-
add_log(f"[LOG
|
94 |
clip = clip.fx(mp.vfx.speedx, speed_factor)
|
95 |
original_fps = clip.fps
|
96 |
target_fps = original_fps * frame_rate_factor
|
97 |
-
add_log(f"[LOG
|
98 |
clip = clip.set_fps(target_fps)
|
99 |
-
add_log(f"[LOG
|
100 |
-
final_clip = clip
|
101 |
output_filename = f"temp_{uuid.uuid4().hex}.gif"
|
102 |
try:
|
103 |
-
# ImageMagick์ผ๋ก ์์ฑ ์ loop ํ๋ผ๋ฏธํฐ๊ฐ ์ ์ฉ๋จ.
|
104 |
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
|
105 |
-
|
106 |
-
add_log(f"[LOG
|
107 |
except Exception as e:
|
108 |
add_log(f"[ERROR] GIF ์์ฑ ์คํจ: {e}")
|
109 |
return None, None, "\n".join(global_logs)
|
@@ -130,26 +127,41 @@ def update_thumbnails(video, start_time_str, end_time_str):
|
|
130 |
return start_thumb, end_thumb
|
131 |
|
132 |
# -------------------------------
|
133 |
-
# Custom CSS
|
134 |
# -------------------------------
|
135 |
custom_css = """
|
136 |
-
|
137 |
-
|
138 |
-
|
|
|
139 |
}
|
|
|
|
|
140 |
.custom-title {
|
141 |
font-size: 2.8em;
|
142 |
-
text-align: center;
|
143 |
font-weight: bold;
|
144 |
margin: 20px 0;
|
145 |
color: #2c3e50;
|
|
|
146 |
}
|
147 |
.custom-user-guide {
|
148 |
font-size: 1.2em;
|
149 |
-
|
150 |
-
margin-bottom: 30px;
|
151 |
color: #34495e;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
}
|
|
|
|
|
153 |
.frame {
|
154 |
border: 2px solid #888;
|
155 |
border-radius: 20px;
|
@@ -158,24 +170,44 @@ body {
|
|
158 |
margin: 10px;
|
159 |
box-shadow: 3px 3px 10px rgba(0,0,0,0.1);
|
160 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
.row-container {
|
162 |
display: flex;
|
163 |
justify-content: space-between;
|
164 |
}
|
|
|
|
|
165 |
.column {
|
166 |
flex: 1;
|
167 |
margin: 10px;
|
168 |
}
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
172 |
}
|
173 |
-
|
174 |
-
|
|
|
|
|
175 |
}
|
176 |
-
|
|
|
|
|
|
|
|
|
177 |
font-size: 1.2em;
|
178 |
-
margin-
|
179 |
}
|
180 |
"""
|
181 |
|
@@ -183,11 +215,12 @@ input, button, select {
|
|
183 |
# Gradio UI ๊ตฌ์ฑ (HTML/CSS ์ปค์คํฐ๋ง์ด์ง)
|
184 |
# -------------------------------
|
185 |
with gr.Blocks(css=custom_css, title="์์ -> GIF ๋ณํ ์๋น์ค") as demo:
|
186 |
-
#
|
187 |
-
gr.HTML("<div class='custom-title'>๐ฌ ์์์ GIF
|
188 |
-
gr.HTML("<div class='
|
189 |
-
"
|
190 |
-
"
|
|
|
191 |
|
192 |
# ์ฒซ๋ฒ์งธ ํ: ์
๋ ฅ๋ถ (์ข์ธก) / ์ถ๋ ฅ๋ถ (์ฐ์ธก)
|
193 |
with gr.Row(elem_classes="row-container"):
|
@@ -206,7 +239,7 @@ with gr.Blocks(css=custom_css, title="์์ -> GIF ๋ณํ ์๋น์ค") as demo:
|
|
206 |
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
207 |
speed_slider = gr.Slider(label="์ฌ์ ์๋ ์กฐ์ (0.5 ~ 5.0)",
|
208 |
minimum=0.5, maximum=5.0, step=0.1, value=1.0)
|
209 |
-
repeat_slider = gr.Slider(label="GIF ๋ฐ๋ณต ํ์ (0:
|
210 |
minimum=0, maximum=10, step=1, value=0)
|
211 |
with gr.Column(elem_classes="column"):
|
212 |
with gr.Group(elem_classes="frame"):
|
@@ -214,7 +247,7 @@ with gr.Blocks(css=custom_css, title="์์ -> GIF ๋ณํ ์๋น์ค") as demo:
|
|
214 |
gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
215 |
download_output = gr.File(label="GIF ๋ค์ด๋ก๋")
|
216 |
|
217 |
-
# ๋๋ฒ์งธ ํ: ์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ
|
218 |
with gr.Group(elem_classes="frame full-width"):
|
219 |
gr.Markdown("### ๐ผ๏ธ ์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
220 |
with gr.Row():
|
@@ -223,12 +256,9 @@ with gr.Blocks(css=custom_css, title="์์ -> GIF ๋ณํ ์๋น์ค") as demo:
|
|
223 |
with gr.Row():
|
224 |
start_thumb_output = gr.Image(label="์์ ์ธ๋ค์ผ")
|
225 |
end_thumb_output = gr.Image(label="์ข
๋ฃ ์ธ๋ค์ผ")
|
226 |
-
generate_button = gr.Button("โจ GIF ์์ฑํ๊ธฐ")
|
227 |
-
|
228 |
-
logs_output = gr.Textbox(label="๐ ์์
๋ก๊ทธ", lines=10)
|
229 |
-
|
230 |
# ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ
|
231 |
-
# ์์ ์
๋ก๋, ์์/์ข
๋ฃ ์๊ฐ ๋ณ๊ฒฝ ์ ์ธ๋ค์ผ ์
๋ฐ์ดํธ
|
232 |
start_time_tb.change(fn=update_thumbnails,
|
233 |
inputs=[video_input, start_time_tb, end_time_tb],
|
234 |
outputs=[start_thumb_output, end_thumb_output])
|
@@ -238,11 +268,10 @@ with gr.Blocks(css=custom_css, title="์์ -> GIF ๋ณํ ์๋น์ค") as demo:
|
|
238 |
video_input.change(fn=update_thumbnails,
|
239 |
inputs=[video_input, start_time_tb, end_time_tb],
|
240 |
outputs=[start_thumb_output, end_thumb_output])
|
241 |
-
# GIF ์์ฑ ๋ฒํผ ํด๋ฆญ ์ ์ ์ฒด ์ฒ๋ฆฌ (์
๋ ฅ๋ถ + ์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ)
|
242 |
generate_button.click(fn=process_video,
|
243 |
inputs=[video_input, start_time_tb, end_time_tb,
|
244 |
platform_option, frame_rate_slider, speed_slider, repeat_slider,
|
245 |
resolution_scale_slider],
|
246 |
-
outputs=[gif_preview_output, download_output
|
247 |
-
|
248 |
demo.launch()
|
|
|
5 |
import gradio as gr
|
6 |
|
7 |
# -------------------------------
|
8 |
+
# ๋ด๋ถ ์ฒ๋ฆฌ ํจ์๋ค
|
9 |
# -------------------------------
|
10 |
global_logs = []
|
11 |
|
|
|
16 |
def parse_time_to_seconds(time_str: str):
|
17 |
try:
|
18 |
h, m, s = time_str.split(":")
|
19 |
+
return int(h)*3600 + int(m)*60 + float(s)
|
20 |
except Exception:
|
21 |
return 0.0
|
22 |
|
|
|
44 |
target_ratio = 4/3
|
45 |
else:
|
46 |
return clip
|
|
|
47 |
width, height = clip.size
|
48 |
current_ratio = width/height
|
49 |
if current_ratio > target_ratio:
|
|
|
64 |
resolution_scale):
|
65 |
global global_logs
|
66 |
global_logs = []
|
67 |
+
add_log("๐ฅ [LOG] ์์ ์ฒ๋ฆฌ ์์")
|
68 |
video_path = video if isinstance(video, str) else video.name
|
69 |
try:
|
70 |
input_video = mp.VideoFileClip(video_path)
|
|
|
72 |
add_log(f"[ERROR] ๋น๋์ค ๋ก๋ ์คํจ: {e}")
|
73 |
return None, None, "\n".join(global_logs)
|
74 |
duration = input_video.duration
|
75 |
+
add_log(f"[LOG] ์์ ์ฌ์์๊ฐ: {duration:.2f}์ด")
|
76 |
start_sec = parse_time_to_seconds(start_time_str)
|
77 |
end_sec = parse_time_to_seconds(end_time_str)
|
78 |
if start_sec < 0:
|
|
|
81 |
end_sec = duration
|
82 |
if start_sec >= end_sec:
|
83 |
start_sec, end_sec = 0, duration
|
84 |
+
add_log(f"[LOG] ์๊ฐ ์ค์ : {start_sec}์ด ~ {end_sec}์ด")
|
85 |
clip = input_video.subclip(start_sec, end_sec)
|
86 |
+
add_log(f"[LOG] ํ๋ซํผ ๋น์จ ์ ์ฉ: {platform_option}")
|
87 |
clip = adjust_aspect_ratio(clip, platform_option)
|
88 |
if abs(resolution_scale - 1.0) > 1e-3:
|
89 |
+
add_log(f"[LOG] ํด์๋ ์ถ์: {resolution_scale*100:.0f}%")
|
90 |
clip = clip.resize(resolution_scale)
|
91 |
if abs(speed_factor - 1.0) > 1e-3:
|
92 |
+
add_log(f"[LOG] ์ฌ์์๋ {speed_factor}๋ฐฐ ์ ์ฉ")
|
93 |
clip = clip.fx(mp.vfx.speedx, speed_factor)
|
94 |
original_fps = clip.fps
|
95 |
target_fps = original_fps * frame_rate_factor
|
96 |
+
add_log(f"[LOG] FPS: {target_fps:.2f} (์๋ณธ {original_fps})")
|
97 |
clip = clip.set_fps(target_fps)
|
98 |
+
add_log(f"[LOG] GIF ๋ฐ๋ณต ํ์: {repeat_count} (0: ๋ฌดํ)")
|
|
|
99 |
output_filename = f"temp_{uuid.uuid4().hex}.gif"
|
100 |
try:
|
|
|
101 |
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
|
102 |
+
clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
|
103 |
+
add_log(f"[LOG] GIF ์์ฑ ์๋ฃ: {output_filename}, loop={loop_param}")
|
104 |
except Exception as e:
|
105 |
add_log(f"[ERROR] GIF ์์ฑ ์คํจ: {e}")
|
106 |
return None, None, "\n".join(global_logs)
|
|
|
127 |
return start_thumb, end_thumb
|
128 |
|
129 |
# -------------------------------
|
130 |
+
# Custom CSS
|
131 |
# -------------------------------
|
132 |
custom_css = """
|
133 |
+
/* ์ ์ฒด ์ปจํ
์ด๋ ํญ์ 70%๋ก ์ค์ */
|
134 |
+
.gradio-container {
|
135 |
+
width: 70% !important;
|
136 |
+
margin: 0 auto;
|
137 |
}
|
138 |
+
|
139 |
+
/* ์ ๋ชฉ ๋ฐ ์ฌ์ฉ๊ฐ์ด๋ ์ผ์ชฝ ์ ๋ ฌ ๋ฐ ํฌ๊ธฐ ์ฆ๊ฐ */
|
140 |
.custom-title {
|
141 |
font-size: 2.8em;
|
|
|
142 |
font-weight: bold;
|
143 |
margin: 20px 0;
|
144 |
color: #2c3e50;
|
145 |
+
text-align: left;
|
146 |
}
|
147 |
.custom-user-guide {
|
148 |
font-size: 1.2em;
|
149 |
+
margin-bottom: 20px;
|
|
|
150 |
color: #34495e;
|
151 |
+
text-align: left;
|
152 |
+
}
|
153 |
+
|
154 |
+
/* ์ฌ์ฉ๊ฐ์ด๋ ๋ฐ์ค ์คํ์ผ */
|
155 |
+
.guide-box {
|
156 |
+
border: 2px solid #3498db;
|
157 |
+
border-radius: 10px;
|
158 |
+
padding: 15px;
|
159 |
+
background-color: #d6eaf8;
|
160 |
+
margin: 20px 0;
|
161 |
+
text-align: left;
|
162 |
}
|
163 |
+
|
164 |
+
/* ํ๋ ์ ์คํ์ผ (์
๋ ฅ๋ถ, ์ถ๋ ฅ๋ถ, ์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ) */
|
165 |
.frame {
|
166 |
border: 2px solid #888;
|
167 |
border-radius: 20px;
|
|
|
170 |
margin: 10px;
|
171 |
box-shadow: 3px 3px 10px rgba(0,0,0,0.1);
|
172 |
}
|
173 |
+
|
174 |
+
/* ๊ฐ ํ๋ ์ ์ ๋ชฉ ํฌ๊ฒ */
|
175 |
+
.frame h3 {
|
176 |
+
font-size: 1.8em;
|
177 |
+
margin-bottom: 15px;
|
178 |
+
text-align: left;
|
179 |
+
}
|
180 |
+
|
181 |
+
/* ํ ๋ ์ด์์ */
|
182 |
.row-container {
|
183 |
display: flex;
|
184 |
justify-content: space-between;
|
185 |
}
|
186 |
+
|
187 |
+
/* ์ปฌ๋ผ ๋ ์ด์์ */
|
188 |
.column {
|
189 |
flex: 1;
|
190 |
margin: 10px;
|
191 |
}
|
192 |
+
|
193 |
+
/* ๋ฒํผ ์ฌ๋ฐฑ */
|
194 |
+
.gif-button {
|
195 |
+
margin-top: 30px;
|
196 |
+
padding: 10px 20px;
|
197 |
+
font-size: 1.2em;
|
198 |
}
|
199 |
+
|
200 |
+
/* ์ฌ๋ผ์ด๋ ์คํ์ผ (๊ธ์์ ๋ฐ ํฌ๊ฒ) */
|
201 |
+
input[type=range] {
|
202 |
+
height: 20px;
|
203 |
}
|
204 |
+
input[type=range]::-webkit-slider-thumb {
|
205 |
+
height: 20px;
|
206 |
+
width: 20px;
|
207 |
+
}
|
208 |
+
.slider-label {
|
209 |
font-size: 1.2em;
|
210 |
+
margin-bottom: 5px;
|
211 |
}
|
212 |
"""
|
213 |
|
|
|
215 |
# Gradio UI ๊ตฌ์ฑ (HTML/CSS ์ปค์คํฐ๋ง์ด์ง)
|
216 |
# -------------------------------
|
217 |
with gr.Blocks(css=custom_css, title="์์ -> GIF ๋ณํ ์๋น์ค") as demo:
|
218 |
+
# ์ ๋ชฉ๊ณผ ์ฌ์ฉ๊ฐ์ด๋(์ผ์ชฝ ์ ๋ ฌ, ๋ฐ์คํํ)
|
219 |
+
gr.HTML("<div class='custom-title'>๐ฌ ์์์ GIF๋ณํํ๊ธฐ</div>")
|
220 |
+
gr.HTML("<div class='guide-box'><strong>์ฌ์ฉ๊ฐ์ด๋:</strong><br>"
|
221 |
+
"1. ์ข์ธก ์
๋ ฅ๋ถ์์ ์์์ ์
๋ก๋ํ๊ณ ์ต์
์ ์ ํํ์ธ์.<br>"
|
222 |
+
"2. ํ๋จ ์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ์์ ์์/์ข
๋ฃ ์๊ฐ์ ์
๋ ฅํด ์ธ๋ค์ผ์ ํ์ธํ์ธ์.<br>"
|
223 |
+
"3. โจ GIF ์์ฑํ๊ธฐ ๋ฒํผ์ ๋๋ฌ GIF๋ฅผ ์์ฑํ๊ณ , ์ฐ์ธก ์ถ๋ ฅ๋ถ์์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธ ๋ฐ ๋ค์ด๋ก๋ํ์ธ์. ๐</div>")
|
224 |
|
225 |
# ์ฒซ๋ฒ์งธ ํ: ์
๋ ฅ๋ถ (์ข์ธก) / ์ถ๋ ฅ๋ถ (์ฐ์ธก)
|
226 |
with gr.Row(elem_classes="row-container"):
|
|
|
239 |
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
240 |
speed_slider = gr.Slider(label="์ฌ์ ์๋ ์กฐ์ (0.5 ~ 5.0)",
|
241 |
minimum=0.5, maximum=5.0, step=0.1, value=1.0)
|
242 |
+
repeat_slider = gr.Slider(label="GIF ๋ฐ๋ณต ํ์ (0: ๋ฌดํ, 1~10)",
|
243 |
minimum=0, maximum=10, step=1, value=0)
|
244 |
with gr.Column(elem_classes="column"):
|
245 |
with gr.Group(elem_classes="frame"):
|
|
|
247 |
gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
248 |
download_output = gr.File(label="GIF ๋ค์ด๋ก๋")
|
249 |
|
250 |
+
# ๋๋ฒ์งธ ํ: ์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ ์ฒด ๋๋น)
|
251 |
with gr.Group(elem_classes="frame full-width"):
|
252 |
gr.Markdown("### ๐ผ๏ธ ์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
253 |
with gr.Row():
|
|
|
256 |
with gr.Row():
|
257 |
start_thumb_output = gr.Image(label="์์ ์ธ๋ค์ผ")
|
258 |
end_thumb_output = gr.Image(label="์ข
๋ฃ ์ธ๋ค์ผ")
|
259 |
+
generate_button = gr.Button("โจ GIF ์์ฑํ๊ธฐ", elem_classes="gif-button")
|
260 |
+
|
|
|
|
|
261 |
# ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ
|
|
|
262 |
start_time_tb.change(fn=update_thumbnails,
|
263 |
inputs=[video_input, start_time_tb, end_time_tb],
|
264 |
outputs=[start_thumb_output, end_thumb_output])
|
|
|
268 |
video_input.change(fn=update_thumbnails,
|
269 |
inputs=[video_input, start_time_tb, end_time_tb],
|
270 |
outputs=[start_thumb_output, end_thumb_output])
|
|
|
271 |
generate_button.click(fn=process_video,
|
272 |
inputs=[video_input, start_time_tb, end_time_tb,
|
273 |
platform_option, frame_rate_slider, speed_slider, repeat_slider,
|
274 |
resolution_scale_slider],
|
275 |
+
outputs=[gif_preview_output, download_output])
|
276 |
+
|
277 |
demo.launch()
|