Update app.py
Browse files
app.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import os
|
2 |
import uuid
|
3 |
import moviepy.editor as mp
|
|
|
4 |
from PIL import Image
|
5 |
import gradio as gr
|
6 |
|
@@ -10,16 +11,11 @@ import gradio as gr
|
|
10 |
global_logs = []
|
11 |
|
12 |
def add_log(message: str):
|
13 |
-
|
14 |
-
๋ก๊ทธ ๋ฉ์์ง๋ฅผ ์ ์ฅํ๋ ํจ์
|
15 |
-
"""
|
16 |
global_logs.append(message)
|
17 |
print(message)
|
18 |
|
19 |
def parse_time_to_seconds(time_str: str):
|
20 |
-
"""
|
21 |
-
์:๋ถ:์ด ํํ์ ๋ฌธ์์ด(์: '00:00:10' ๋๋ '00:01:02')์ ์ด ๋จ์(float)๋ก ๋ณํ
|
22 |
-
"""
|
23 |
try:
|
24 |
h, m, s = time_str.split(":")
|
25 |
total_seconds = int(h) * 3600 + int(m) * 60 + float(s)
|
@@ -28,9 +24,6 @@ def parse_time_to_seconds(time_str: str):
|
|
28 |
return 0.0
|
29 |
|
30 |
def generate_thumbnail(video_clip, time_point):
|
31 |
-
"""
|
32 |
-
ํน์ ์์ ์ ํ๋ ์์ ์ธ๋ค์ผ๋ก ์์ฑํ์ฌ PIL.Image๋ก ๋ฐํ
|
33 |
-
"""
|
34 |
try:
|
35 |
frame = video_clip.get_frame(time_point)
|
36 |
thumbnail_img = Image.fromarray(frame)
|
@@ -42,12 +35,8 @@ def generate_thumbnail(video_clip, time_point):
|
|
42 |
return thumbnail_img
|
43 |
|
44 |
def adjust_aspect_ratio(clip, option):
|
45 |
-
"""
|
46 |
-
์์ ํด๋ฆฝ์ ์ ํํ ํ๋ซํผ์ ๊ถ์ฅ ๋น์จ๋ก ์ค์ ํฌ๋กญํ์ฌ ์กฐ์ ํฉ๋๋ค.
|
47 |
-
"""
|
48 |
if option == "์๋ณธ ์ ์ง":
|
49 |
return clip
|
50 |
-
|
51 |
if option == "์ ํ๋ธ (16:9)":
|
52 |
target_ratio = 16 / 9
|
53 |
elif option == "์ผ์ธ /๋ฆด์ค (9:16)":
|
@@ -71,13 +60,18 @@ def adjust_aspect_ratio(clip, option):
|
|
71 |
new_width = width
|
72 |
new_height = int(width / target_ratio)
|
73 |
|
74 |
-
return clip.crop(
|
|
|
|
|
|
|
|
|
|
|
75 |
|
76 |
def process_video(video,
|
77 |
start_time_str,
|
78 |
end_time_str,
|
79 |
-
platform_option,
|
80 |
-
frame_rate_factor,
|
81 |
speed_factor,
|
82 |
repeat_count,
|
83 |
resolution_scale):
|
@@ -85,11 +79,9 @@ def process_video(video,
|
|
85 |
๋์์์ GIF๋ก ๋ณํํ๋ ํจ์
|
86 |
"""
|
87 |
global global_logs
|
88 |
-
global_logs = []
|
89 |
-
|
90 |
add_log("[LOG 1] ๋น๋์ค ์
๋ก๋ ๋ฐ ์ฒ๋ฆฌ ์์")
|
91 |
|
92 |
-
# video ํ๋ผ๋ฏธํฐ์์ ํ์ผ ๊ฒฝ๋ก ์ถ์ถ
|
93 |
video_path = video if isinstance(video, str) else video.name
|
94 |
|
95 |
add_log("[LOG 2] ๋น๋์ค ๋ก๋ ์ค...")
|
@@ -106,7 +98,6 @@ def process_video(video,
|
|
106 |
start_sec = parse_time_to_seconds(start_time_str)
|
107 |
end_sec = parse_time_to_seconds(end_time_str)
|
108 |
|
109 |
-
# ์๊ฐ ๋ฒ์ ๋ณด์
|
110 |
if start_sec < 0:
|
111 |
start_sec = 0
|
112 |
if end_sec <= 0 or end_sec > duration:
|
@@ -123,12 +114,10 @@ def process_video(video,
|
|
123 |
add_log(f"[LOG 7] ํ๋ซํผ ๋น์จ ์กฐ์ : {platform_option}")
|
124 |
clip = adjust_aspect_ratio(clip, platform_option)
|
125 |
|
126 |
-
# ์ถ๋ ฅ ํด์๋ ์ถ์
|
127 |
if abs(resolution_scale - 1.0) > 1e-3:
|
128 |
add_log(f"[LOG 7-1] ์ถ๋ ฅ ํด์๋ ์ถ์: {resolution_scale*100:.0f}%")
|
129 |
clip = clip.resize(resolution_scale)
|
130 |
|
131 |
-
# ์ฌ์์๋ ์กฐ์
|
132 |
if abs(speed_factor - 1.0) > 1e-3:
|
133 |
add_log(f"[LOG 8] ์ฌ์์๋ {speed_factor}๋ฐฐ๋ก ์กฐ์ ์ค...")
|
134 |
clip = clip.fx(mp.vfx.speedx, speed_factor)
|
@@ -138,7 +127,6 @@ def process_video(video,
|
|
138 |
add_log(f"[LOG 9] ์ต์ข
์ถ๋ ฅ FPS: {target_fps:.2f} (์๋ณธ FPS: {original_fps})")
|
139 |
clip = clip.set_fps(target_fps)
|
140 |
|
141 |
-
# ๋ฐ๋ณต ํ์ ์ค์
|
142 |
add_log(f"[LOG 10] GIF ๋ฐ๋ณต ํ์ ์ค์ : {repeat_count} (0์ด๋ฉด ๋ฌดํ๋ฐ๋ณต)")
|
143 |
final_clip = clip
|
144 |
|
@@ -146,6 +134,7 @@ def process_video(video,
|
|
146 |
output_filename = f"temp_{uuid.uuid4().hex}.gif"
|
147 |
try:
|
148 |
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
|
|
|
149 |
final_clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
|
150 |
add_log(f"[LOG 12] GIF ์์ฑ ์๋ฃ! ํ์ผ๋ช
: {output_filename}, loop={loop_param}")
|
151 |
except Exception as e:
|
@@ -155,11 +144,7 @@ def process_video(video,
|
|
155 |
return output_filename, output_filename, "\n".join(global_logs)
|
156 |
|
157 |
def update_thumbnails(video, start_time_str, end_time_str):
|
158 |
-
"""
|
159 |
-
์์/๋ ์ธ๋ค์ผ์ ์
๋ฐ์ดํธํ๊ธฐ ์ํ ํจ์
|
160 |
-
"""
|
161 |
video_path = video if isinstance(video, str) else video.name
|
162 |
-
|
163 |
try:
|
164 |
input_video = mp.VideoFileClip(video_path)
|
165 |
except Exception as e:
|
@@ -180,129 +165,141 @@ def update_thumbnails(video, start_time_str, end_time_str):
|
|
180 |
|
181 |
start_thumb = generate_thumbnail(input_video, start_sec)
|
182 |
end_thumb = generate_thumbnail(input_video, end_sec)
|
183 |
-
|
184 |
return start_thumb, end_thumb
|
185 |
|
186 |
-
|
187 |
-
#
|
|
|
188 |
custom_css = """
|
189 |
-
/* ์ ์ฒด ๋ฐฐ๊ฒฝ */
|
190 |
body {
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
}
|
196 |
|
197 |
-
/*
|
198 |
-
.
|
199 |
-
|
200 |
-
|
201 |
-
|
|
|
|
|
|
|
202 |
}
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
|
|
|
|
|
|
|
|
207 |
}
|
208 |
-
|
209 |
-
|
|
|
|
|
210 |
}
|
211 |
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
padding: 0 20px;
|
216 |
-
margin-bottom: 20px;
|
217 |
}
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
|
|
|
|
|
|
222 |
}
|
223 |
|
224 |
-
/*
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
|
|
|
|
|
|
231 |
}
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
234 |
}
|
235 |
|
236 |
-
/*
|
237 |
-
|
238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
239 |
}
|
240 |
|
241 |
-
/*
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
gap: 10px;
|
246 |
-
flex-wrap: wrap; /* ํ๋ฉด ์ข์ ๋ ์๋ ์ค๋ฐ๊ฟ */
|
247 |
}
|
248 |
-
|
249 |
-
|
250 |
-
|
|
|
|
|
|
|
|
|
251 |
}
|
252 |
|
253 |
-
|
254 |
-
|
255 |
-
border-radius: 25px !important;
|
256 |
-
background-color: #fa8072 !important;
|
257 |
-
color: #fff !important;
|
258 |
-
font-weight: bold !important;
|
259 |
}
|
260 |
|
261 |
-
/*
|
262 |
-
.
|
263 |
-
|
264 |
-
|
265 |
-
margin-top: 10px;
|
266 |
-
gap: 20px;
|
267 |
-
flex-wrap: wrap; /* ํ๋ฉด ์ข์์ง๋ฉด ์ค๋ฐ๊ฟ */
|
268 |
}
|
269 |
"""
|
270 |
|
271 |
# ------------------------------
|
272 |
# Gradio ์ธํฐํ์ด์ค ๊ตฌ์ฑ (Blocks)
|
273 |
# ------------------------------
|
274 |
-
with gr.Blocks(css=custom_css
|
275 |
# ์๋จ ์ ๋ชฉ
|
276 |
-
|
277 |
-
html_title = """
|
278 |
-
<div class="title-container">
|
279 |
-
<h1><span class="emoji">๐๏ธ</span> ์์์ GIF๋ก ๋ณํํ๊ธฐ <span class="emoji">โจ</span></h1>
|
280 |
-
</div>
|
281 |
-
"""
|
282 |
-
print(html_title) # ์ด ์ถ๋ ฅ์ ์ค์ UI์๋ ์ํฅ ์ฃผ์ง ์์
|
283 |
-
gr.HTML(html_title)
|
284 |
|
285 |
# ์ฌ์ฉ ๊ฐ์ด๋
|
286 |
-
|
287 |
-
|
288 |
-
<div
|
289 |
-
<
|
290 |
-
|
291 |
-
|
|
|
|
|
|
|
292 |
</div>
|
293 |
"""
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
<div class="frame">
|
302 |
-
<h3>์
๋ ฅ๋ถ ๐</h3>
|
303 |
-
</div>
|
304 |
-
"""
|
305 |
-
gr.HTML(custom_input_html)
|
306 |
|
307 |
video_input = gr.Video(label="์์ ์
๋ก๋")
|
308 |
platform_option = gr.Radio(
|
@@ -310,53 +307,42 @@ with gr.Blocks(css=custom_css, theme=None) as demo:
|
|
310 |
choices=["์๋ณธ ์ ์ง", "์ ํ๋ธ (16:9)", "์ผ์ธ /๋ฆด์ค (9:16)", "์ ์ฌ๊ฐํ (1:1)", "์ธ์คํ๊ทธ๋จ (4:5)", "ํด๋์ (4:3)"],
|
311 |
value="์๋ณธ ์ ์ง"
|
312 |
)
|
313 |
-
resolution_scale_slider = gr.Slider(
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
speed_slider = gr.Slider(
|
322 |
-
label="์ฌ์ ์๋ ์กฐ์ (0.5 ~ 5.0)",
|
323 |
-
minimum=0.5, maximum=5.0, step=0.1, value=1.0
|
324 |
-
)
|
325 |
-
repeat_slider = gr.Slider(
|
326 |
-
label="GIF ๋ฐ๋ณต ํ์ (0: ๋ฌดํ๋ฐ๋ณต, 1~10: ๋ฐ๋ณต ํ์)",
|
327 |
-
minimum=0, maximum=10, step=1, value=0
|
328 |
-
)
|
329 |
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
"""
|
336 |
-
gr.HTML(custom_output_html)
|
337 |
|
338 |
gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
339 |
-
download_output = gr.File(label="GIF ๋ค์ด๋ก๋")
|
340 |
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
</div>
|
347 |
-
"""
|
348 |
-
gr.HTML(custom_thumb_html)
|
349 |
|
350 |
start_thumb_output = gr.Image(label="์์ ์ธ๋ค์ผ")
|
351 |
end_thumb_output = gr.Image(label="์ข
๋ฃ ์ธ๋ค์ผ")
|
352 |
-
generate_button = gr.Button("GIF ์์ฑํ๊ธฐ", elem_classes="custom-button")
|
353 |
-
logs_output = gr.Textbox(label="๋ก๊ทธ ์ถ๋ ฅ", lines=8)
|
354 |
|
355 |
-
|
356 |
-
|
357 |
-
|
|
|
|
|
|
|
|
|
|
|
358 |
|
359 |
-
# ์ธ๋ค์ผ ์
๋ฐ์ดํธ
|
360 |
start_time.change(
|
361 |
fn=update_thumbnails,
|
362 |
inputs=[video_input, start_time, end_time],
|
@@ -373,17 +359,16 @@ with gr.Blocks(css=custom_css, theme=None) as demo:
|
|
373 |
outputs=[start_thumb_output, end_thumb_output]
|
374 |
)
|
375 |
|
376 |
-
# GIF ์์ฑ ๋ฒํผ ์ด๋ฒคํธ
|
377 |
generate_button.click(
|
378 |
fn=process_video,
|
379 |
inputs=[
|
380 |
-
video_input,
|
381 |
-
start_time,
|
382 |
end_time,
|
383 |
-
platform_option,
|
384 |
-
frame_rate_slider,
|
385 |
-
speed_slider,
|
386 |
-
repeat_slider,
|
387 |
resolution_scale_slider
|
388 |
],
|
389 |
outputs=[
|
@@ -393,18 +378,6 @@ with gr.Blocks(css=custom_css, theme=None) as demo:
|
|
393 |
]
|
394 |
)
|
395 |
|
396 |
-
# ๊ฐ๋จํ ์ฌ์ฉ ๊ฐ์ด๋ (HTML๋ก ํ์, ์ค์ ์ฑ ๋ด๋ถ์ ํ์ํ๋ ค๋ฉด gr.HTML(...)์ ๋ฃ์ด๋ ๋จ)
|
397 |
-
guide_text = """
|
398 |
-
<h2>๊ฐ๋จ ์ฌ์ฉ ๊ฐ์ด๋ ๐</h2>
|
399 |
-
<ol>
|
400 |
-
<li>์์ ํ์ผ์ ์
๋ก๋ํ์ธ์.</li>
|
401 |
-
<li>์ํ๋ ํด์๋/๋น์จ ์ต์
์ ์ ํํ์ธ์.</li>
|
402 |
-
<li>์ถ๋ ฅ ํด์๋ ์ถ์ ๋น์จ, ํ๋ ์ ๋ ์ดํธ, ์ฌ์ ์๋, GIF ๋ฐ๋ณต ํ์๋ฅผ ์กฐ์ ํ์ธ์.</li>
|
403 |
-
<li><strong>GIF ์์ฑํ๊ธฐ</strong> ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ณํ์ด ์๋ฃ๋ฉ๋๋ค.</li>
|
404 |
-
<li>์ถ๋ ฅ๋ GIF๋ฅผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ก ํ์ธํ๊ณ , ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค.</li>
|
405 |
-
</ol>
|
406 |
-
"""
|
407 |
-
print(guide_text)
|
408 |
|
409 |
if __name__ == "__main__":
|
410 |
demo.launch()
|
|
|
1 |
import os
|
2 |
import uuid
|
3 |
import moviepy.editor as mp
|
4 |
+
import numpy as np
|
5 |
from PIL import Image
|
6 |
import gradio as gr
|
7 |
|
|
|
11 |
global_logs = []
|
12 |
|
13 |
def add_log(message: str):
|
14 |
+
global global_logs
|
|
|
|
|
15 |
global_logs.append(message)
|
16 |
print(message)
|
17 |
|
18 |
def parse_time_to_seconds(time_str: str):
|
|
|
|
|
|
|
19 |
try:
|
20 |
h, m, s = time_str.split(":")
|
21 |
total_seconds = int(h) * 3600 + int(m) * 60 + float(s)
|
|
|
24 |
return 0.0
|
25 |
|
26 |
def generate_thumbnail(video_clip, time_point):
|
|
|
|
|
|
|
27 |
try:
|
28 |
frame = video_clip.get_frame(time_point)
|
29 |
thumbnail_img = Image.fromarray(frame)
|
|
|
35 |
return thumbnail_img
|
36 |
|
37 |
def adjust_aspect_ratio(clip, option):
|
|
|
|
|
|
|
38 |
if option == "์๋ณธ ์ ์ง":
|
39 |
return clip
|
|
|
40 |
if option == "์ ํ๋ธ (16:9)":
|
41 |
target_ratio = 16 / 9
|
42 |
elif option == "์ผ์ธ /๋ฆด์ค (9:16)":
|
|
|
60 |
new_width = width
|
61 |
new_height = int(width / target_ratio)
|
62 |
|
63 |
+
return clip.crop(
|
64 |
+
x_center=width / 2,
|
65 |
+
y_center=height / 2,
|
66 |
+
width=new_width,
|
67 |
+
height=new_height
|
68 |
+
)
|
69 |
|
70 |
def process_video(video,
|
71 |
start_time_str,
|
72 |
end_time_str,
|
73 |
+
platform_option,
|
74 |
+
frame_rate_factor,
|
75 |
speed_factor,
|
76 |
repeat_count,
|
77 |
resolution_scale):
|
|
|
79 |
๋์์์ GIF๋ก ๋ณํํ๋ ํจ์
|
80 |
"""
|
81 |
global global_logs
|
82 |
+
global_logs = []
|
|
|
83 |
add_log("[LOG 1] ๋น๋์ค ์
๋ก๋ ๋ฐ ์ฒ๋ฆฌ ์์")
|
84 |
|
|
|
85 |
video_path = video if isinstance(video, str) else video.name
|
86 |
|
87 |
add_log("[LOG 2] ๋น๋์ค ๋ก๋ ์ค...")
|
|
|
98 |
start_sec = parse_time_to_seconds(start_time_str)
|
99 |
end_sec = parse_time_to_seconds(end_time_str)
|
100 |
|
|
|
101 |
if start_sec < 0:
|
102 |
start_sec = 0
|
103 |
if end_sec <= 0 or end_sec > duration:
|
|
|
114 |
add_log(f"[LOG 7] ํ๋ซํผ ๋น์จ ์กฐ์ : {platform_option}")
|
115 |
clip = adjust_aspect_ratio(clip, platform_option)
|
116 |
|
|
|
117 |
if abs(resolution_scale - 1.0) > 1e-3:
|
118 |
add_log(f"[LOG 7-1] ์ถ๋ ฅ ํด์๋ ์ถ์: {resolution_scale*100:.0f}%")
|
119 |
clip = clip.resize(resolution_scale)
|
120 |
|
|
|
121 |
if abs(speed_factor - 1.0) > 1e-3:
|
122 |
add_log(f"[LOG 8] ์ฌ์์๋ {speed_factor}๋ฐฐ๋ก ์กฐ์ ์ค...")
|
123 |
clip = clip.fx(mp.vfx.speedx, speed_factor)
|
|
|
127 |
add_log(f"[LOG 9] ์ต์ข
์ถ๋ ฅ FPS: {target_fps:.2f} (์๋ณธ FPS: {original_fps})")
|
128 |
clip = clip.set_fps(target_fps)
|
129 |
|
|
|
130 |
add_log(f"[LOG 10] GIF ๋ฐ๋ณต ํ์ ์ค์ : {repeat_count} (0์ด๋ฉด ๋ฌดํ๋ฐ๋ณต)")
|
131 |
final_clip = clip
|
132 |
|
|
|
134 |
output_filename = f"temp_{uuid.uuid4().hex}.gif"
|
135 |
try:
|
136 |
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
|
137 |
+
# ImageMagick๋ก ์์ฑ (๊ธฐ๋ณธ๊ฐ), loop=0 => ๋ฌดํ
|
138 |
final_clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
|
139 |
add_log(f"[LOG 12] GIF ์์ฑ ์๋ฃ! ํ์ผ๋ช
: {output_filename}, loop={loop_param}")
|
140 |
except Exception as e:
|
|
|
144 |
return output_filename, output_filename, "\n".join(global_logs)
|
145 |
|
146 |
def update_thumbnails(video, start_time_str, end_time_str):
|
|
|
|
|
|
|
147 |
video_path = video if isinstance(video, str) else video.name
|
|
|
148 |
try:
|
149 |
input_video = mp.VideoFileClip(video_path)
|
150 |
except Exception as e:
|
|
|
165 |
|
166 |
start_thumb = generate_thumbnail(input_video, start_sec)
|
167 |
end_thumb = generate_thumbnail(input_video, end_sec)
|
|
|
168 |
return start_thumb, end_thumb
|
169 |
|
170 |
+
# ------------------------------
|
171 |
+
# ์ปค์คํ
CSS (Gradio ๊ธฐ๋ณธ ์คํ์ผ ๊ฐ์ถ๊ณ , ๋ชจ์๋ฆฌ๋ฅผ ๋ฅ๊ธ๊ฒ, ๋ฐฐ๊ฒฝ์ ๋ฑ)
|
172 |
+
# ------------------------------
|
173 |
custom_css = """
|
174 |
+
/* ์ ์ฒด ๋ฐฐ๊ฒฝ ๋ฐ ๊ธฐ๋ณธ ๊ธ๏ฟฝ๏ฟฝ๏ฟฝ ์ค์ */
|
175 |
body {
|
176 |
+
background: linear-gradient(145deg, #fcfcfc, #eef4f7);
|
177 |
+
font-family: 'Helvetica', 'Arial', sans-serif;
|
178 |
+
margin: 0;
|
179 |
+
padding: 0;
|
180 |
}
|
181 |
|
182 |
+
/* ์จ๊ฒจ์ผ ํ๋ Gradio์ ๊ธฐ๋ณธ ์์๋ค */
|
183 |
+
.gradio-container {
|
184 |
+
max-width: 1100px; /* ํญ ์กฐ์ */
|
185 |
+
margin: 0 auto;
|
186 |
+
padding: 0px;
|
187 |
+
box-shadow: none !important;
|
188 |
+
border-radius: 0 !important;
|
189 |
+
background-color: transparent !important;
|
190 |
}
|
191 |
+
|
192 |
+
/* ๊ฐ ์น์
์ ๊ฐ์ ์นด๋ ๋์์ธ */
|
193 |
+
.my-frame {
|
194 |
+
background-color: #ffffffcc;
|
195 |
+
border-radius: 25px;
|
196 |
+
padding: 20px;
|
197 |
+
margin: 10px;
|
198 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
199 |
}
|
200 |
+
|
201 |
+
.my-frame h2 {
|
202 |
+
font-size: 1.5em;
|
203 |
+
margin-bottom: 10px;
|
204 |
}
|
205 |
|
206 |
+
.my-frame h3 {
|
207 |
+
font-size: 1.3em;
|
208 |
+
margin-bottom: 8px;
|
|
|
|
|
209 |
}
|
210 |
+
|
211 |
+
/* ์ด๋ชจํฐ์ฝ ํฌํจํ ํ์ดํ */
|
212 |
+
#title {
|
213 |
+
text-align: center;
|
214 |
+
font-size: 2rem;
|
215 |
+
font-weight: bold;
|
216 |
+
margin: 20px 0 10px 0;
|
217 |
}
|
218 |
|
219 |
+
/* ์ฌ์ฉ ๊ฐ์ด๋ */
|
220 |
+
#guide {
|
221 |
+
text-align: center;
|
222 |
+
margin-bottom: 25px;
|
223 |
+
font-size: 1rem;
|
224 |
+
line-height: 1.5;
|
225 |
+
background-color: #fffcf7cc;
|
226 |
+
padding: 15px;
|
227 |
+
border-radius: 15px;
|
228 |
+
display: inline-block;
|
229 |
}
|
230 |
+
|
231 |
+
/* ๋ผ๋ฒจ ํ
์คํธ ์ด์ง ํฌ๊ฒ */
|
232 |
+
label {
|
233 |
+
font-size: 1rem;
|
234 |
+
font-weight: 500;
|
235 |
}
|
236 |
|
237 |
+
/* ๋ฒํผ ๋์์ธ */
|
238 |
+
button {
|
239 |
+
font-size: 1rem;
|
240 |
+
font-weight: bold;
|
241 |
+
padding: 10px 15px;
|
242 |
+
border-radius: 25px;
|
243 |
+
background: linear-gradient(135deg, #81ecec, #74b9ff);
|
244 |
+
color: #fff;
|
245 |
+
border: none;
|
246 |
+
cursor: pointer;
|
247 |
+
}
|
248 |
+
button:hover {
|
249 |
+
opacity: 0.9;
|
250 |
}
|
251 |
|
252 |
+
/* ์ด๋ฏธ์ง(์ธ๋ค์ผ, ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ) ๋์์ธ */
|
253 |
+
img {
|
254 |
+
border-radius: 15px;
|
255 |
+
max-width: 100%;
|
|
|
|
|
256 |
}
|
257 |
+
|
258 |
+
/* ๊ทธ๋ฆฌ๋ ๋ฐฐ์น ์ปจํ
์ด๋ */
|
259 |
+
.row-container {
|
260 |
+
display: flex;
|
261 |
+
flex-direction: row;
|
262 |
+
justify-content: space-between;
|
263 |
+
gap: 10px;
|
264 |
}
|
265 |
|
266 |
+
.column-container {
|
267 |
+
flex: 1;
|
|
|
|
|
|
|
|
|
268 |
}
|
269 |
|
270 |
+
/* ์ด๋ชจํฐ์ฝ์ฉ ๊ฐ์กฐ */
|
271 |
+
.emoji {
|
272 |
+
font-size: 1.4em;
|
273 |
+
margin-right: 8px;
|
|
|
|
|
|
|
274 |
}
|
275 |
"""
|
276 |
|
277 |
# ------------------------------
|
278 |
# Gradio ์ธํฐํ์ด์ค ๊ตฌ์ฑ (Blocks)
|
279 |
# ------------------------------
|
280 |
+
with gr.Blocks(css=custom_css) as demo:
|
281 |
# ์๋จ ์ ๋ชฉ
|
282 |
+
gr.HTML("<div id='title'>๐ ์์์ GIF๋ก ๋ณํํ๊ธฐ ๐</div>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
|
284 |
# ์ฌ์ฉ ๊ฐ์ด๋
|
285 |
+
gr.HTML(
|
286 |
+
"""
|
287 |
+
<div id='guide'>
|
288 |
+
<strong>๐ ์ฌ์ฉ ๊ฐ์ด๋</strong><br><br>
|
289 |
+
1) ์์์ ์
๋ก๋ ํ, ์ํ๋ ํด์๋์ ์ต์
์ ์ค์ ํฉ๋๋ค.<br>
|
290 |
+
2) [GIF ์์ฑํ๊ธฐ] ๋ฒํผ์ ํด๋ฆญํ๋ฉด ๋ณํ์ด ์์๋ฉ๋๋ค.<br>
|
291 |
+
3) ์ธ๋ค์ผ(์์/๋), ์์ฑ๋ GIF ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ๋ค์ด๋ก๋ ๋งํฌ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.<br>
|
292 |
+
<br>
|
293 |
+
๐ Tips: ํ๋ ์ ๋ ์ดํธ/ํด์๋๋ฅผ ๋ฎ์ถ๋ฉด GIF ์ฉ๋์ ์ค์ผ ์ ์์ต๋๋ค.
|
294 |
</div>
|
295 |
"""
|
296 |
+
)
|
297 |
+
|
298 |
+
# 1) ์
๋ ฅ๋ถ + ์ถ๋ ฅ๋ถ (ํ ์ค)
|
299 |
+
with gr.Row():
|
300 |
+
with gr.Column():
|
301 |
+
# ์
๋ ฅ๋ถ
|
302 |
+
gr.HTML("<div class='my-frame'><h2 class='emoji'>๐ป ์
๋ ฅ๋ถ</h2>", elem_id="input-start")
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
video_input = gr.Video(label="์์ ์
๋ก๋")
|
305 |
platform_option = gr.Radio(
|
|
|
307 |
choices=["์๋ณธ ์ ์ง", "์ ํ๋ธ (16:9)", "์ผ์ธ /๋ฆด์ค (9:16)", "์ ์ฌ๊ฐํ (1:1)", "์ธ์คํ๊ทธ๋จ (4:5)", "ํด๋์ (4:3)"],
|
308 |
value="์๋ณธ ์ ์ง"
|
309 |
)
|
310 |
+
resolution_scale_slider = gr.Slider(label="์ถ๋ ฅ ํด์๋ ์ถ์ ๋น์จ (0.1 ~ 1.0)",
|
311 |
+
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
312 |
+
frame_rate_slider = gr.Slider(label="ํ๋ ์ ๋ ์ดํธ ๋ฐฐ์จ ์กฐ์ (0.1 ~ 1.0)",
|
313 |
+
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
314 |
+
speed_slider = gr.Slider(label="์ฌ์ ์๋ ์กฐ์ (0.5 ~ 5.0)",
|
315 |
+
minimum=0.5, maximum=5.0, step=0.1, value=1.0)
|
316 |
+
repeat_slider = gr.Slider(label="GIF ๋ฐ๋ณต ํ์ (0: ๋ฌดํ๋ฐ๋ณต, 1~10: ๋ฐ๋ณต ํ์)",
|
317 |
+
minimum=0, maximum=10, step=1, value=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
318 |
|
319 |
+
gr.HTML("</div>", elem_id="input-end")
|
320 |
+
|
321 |
+
with gr.Column():
|
322 |
+
# ์ถ๋ ฅ๋ถ
|
323 |
+
gr.HTML("<div class='my-frame'><h2 class='emoji'>โ
์ถ๋ ฅ๋ถ</h2>", elem_id="output-start")
|
|
|
|
|
324 |
|
325 |
gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
326 |
+
download_output = gr.File(label="GIF ๋ค์ด๋ก๋ ๋งํฌ")
|
327 |
|
328 |
+
gr.HTML("</div>", elem_id="output-end")
|
329 |
+
|
330 |
+
# 2) ์์
์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (ํ๋จ ํ ์ค)
|
331 |
+
with gr.Column():
|
332 |
+
gr.HTML("<div class='my-frame'><h2 class='emoji'>๐ ์์
์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ</h2>", elem_id="thumb-start")
|
|
|
|
|
|
|
333 |
|
334 |
start_thumb_output = gr.Image(label="์์ ์ธ๋ค์ผ")
|
335 |
end_thumb_output = gr.Image(label="์ข
๋ฃ ์ธ๋ค์ผ")
|
|
|
|
|
336 |
|
337 |
+
generate_button = gr.Button("GIF ์์ฑํ๊ธฐ")
|
338 |
+
logs_output = gr.Textbox(label="์ฒ๋ฆฌ ๋ก๊ทธ", lines=6)
|
339 |
+
|
340 |
+
gr.HTML("</div>", elem_id="thumb-end")
|
341 |
+
|
342 |
+
# ๋ก์ง ์ฐ๊ฒฐ
|
343 |
+
start_time = gr.Textbox(label="", visible=False, value="00:00:00") # ์จ๊น
|
344 |
+
end_time = gr.Textbox(label="", visible=False, value="00:00:05") # ์จ๊น
|
345 |
|
|
|
346 |
start_time.change(
|
347 |
fn=update_thumbnails,
|
348 |
inputs=[video_input, start_time, end_time],
|
|
|
359 |
outputs=[start_thumb_output, end_thumb_output]
|
360 |
)
|
361 |
|
|
|
362 |
generate_button.click(
|
363 |
fn=process_video,
|
364 |
inputs=[
|
365 |
+
video_input,
|
366 |
+
start_time,
|
367 |
end_time,
|
368 |
+
platform_option,
|
369 |
+
frame_rate_slider,
|
370 |
+
speed_slider,
|
371 |
+
repeat_slider,
|
372 |
resolution_scale_slider
|
373 |
],
|
374 |
outputs=[
|
|
|
378 |
]
|
379 |
)
|
380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
381 |
|
382 |
if __name__ == "__main__":
|
383 |
demo.launch()
|