Update app.py
Browse files
app.py
CHANGED
@@ -1,7 +1,6 @@
|
|
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,11 +10,16 @@ import gradio as gr
|
|
11 |
global_logs = []
|
12 |
|
13 |
def add_log(message: str):
|
14 |
-
|
|
|
|
|
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,6 +28,9 @@ def parse_time_to_seconds(time_str: str):
|
|
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,8 +42,12 @@ def generate_thumbnail(video_clip, time_point):
|
|
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)":
|
@@ -53,6 +64,7 @@ def adjust_aspect_ratio(clip, option):
|
|
53 |
width, height = clip.size
|
54 |
current_ratio = width / height
|
55 |
|
|
|
56 |
if current_ratio > target_ratio:
|
57 |
new_width = int(height * target_ratio)
|
58 |
new_height = height
|
@@ -60,28 +72,25 @@ def adjust_aspect_ratio(clip, option):
|
|
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):
|
78 |
"""
|
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,6 +107,7 @@ def process_video(video,
|
|
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:
|
@@ -111,22 +121,27 @@ def process_video(video,
|
|
111 |
add_log("[LOG 6] ์์ ์๋ฅด๊ธฐ ์์
์งํ...")
|
112 |
clip = input_video.subclip(start_sec, end_sec)
|
113 |
|
|
|
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)
|
124 |
|
|
|
125 |
original_fps = clip.fps
|
126 |
target_fps = original_fps * frame_rate_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,17 +149,21 @@ def process_video(video,
|
|
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 |
-
|
138 |
-
|
139 |
-
add_log(f"[LOG 12] GIF ์์ฑ ์๋ฃ! ํ์ผ๋ช
: {output_filename}, loop={loop_param}")
|
140 |
except Exception as e:
|
141 |
add_log(f"[ERROR] GIF ์์ฑ ์คํจ: {e}")
|
142 |
return None, None, "\n".join(global_logs)
|
143 |
|
|
|
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,219 +184,72 @@ def update_thumbnails(video, start_time_str, end_time_str):
|
|
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(
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
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 |
-
|
339 |
-
|
340 |
-
gr.
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=[
|
375 |
-
gif_preview_output,
|
376 |
-
download_output,
|
377 |
-
logs_output
|
378 |
-
]
|
379 |
-
)
|
380 |
-
|
381 |
-
|
382 |
if __name__ == "__main__":
|
383 |
-
demo.launch()
|
|
|
1 |
import os
|
2 |
import uuid
|
3 |
import moviepy.editor as mp
|
|
|
4 |
from PIL import Image
|
5 |
import gradio as gr
|
6 |
|
|
|
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 |
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 |
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)":
|
|
|
64 |
width, height = clip.size
|
65 |
current_ratio = width / height
|
66 |
|
67 |
+
# ์์์ด ๋๋ฌด ๋๋ค๋ฉด ๊ฐ๋ก๋ฅผ, ๋๋ฌด ๋๋ค๋ฉด ์ธ๋ก๋ฅผ ํฌ๋กญํฉ๋๋ค.
|
68 |
if current_ratio > target_ratio:
|
69 |
new_width = int(height * target_ratio)
|
70 |
new_height = height
|
|
|
72 |
new_width = width
|
73 |
new_height = int(width / target_ratio)
|
74 |
|
75 |
+
return clip.crop(x_center=width / 2, y_center=height / 2, width=new_width, height=new_height)
|
|
|
|
|
|
|
|
|
|
|
76 |
|
77 |
def process_video(video,
|
78 |
start_time_str,
|
79 |
end_time_str,
|
80 |
+
platform_option, # ํ๋ซํผ๋ณ ๋น์จ ์ ํ (Radio)
|
81 |
+
frame_rate_factor, # ํ๋ ์ ๋ ์ดํธ ์ถ์ ๋ฐฐ์จ (1.0์ด๋ฉด ์๋ณธ)
|
82 |
speed_factor,
|
83 |
repeat_count,
|
84 |
+
resolution_scale): # ์ถ๋ ฅ ํด์๋ ์ถ์ ๋น์จ (1.0์ด๋ฉด ์๋ณธ)
|
85 |
"""
|
86 |
๋์์์ GIF๋ก ๋ณํํ๋ ํจ์
|
87 |
"""
|
88 |
global global_logs
|
89 |
+
global_logs = [] # ํธ์ถ ์๋ง๋ค ๋ก๊ทธ ์ด๊ธฐํ
|
90 |
+
|
91 |
add_log("[LOG 1] ๋น๋์ค ์
๋ก๋ ๋ฐ ์ฒ๋ฆฌ ์์")
|
92 |
|
93 |
+
# video ํ๋ผ๋ฏธํฐ์์ ํ์ผ ๊ฒฝ๋ก ์ถ์ถ (Gradio์์ ๋ฐํํ๋ ๊ฐ์ด ๋ฌธ์์ด์ผ ์ ์์)
|
94 |
video_path = video if isinstance(video, str) else video.name
|
95 |
|
96 |
add_log("[LOG 2] ๋น๋์ค ๋ก๋ ์ค...")
|
|
|
107 |
start_sec = parse_time_to_seconds(start_time_str)
|
108 |
end_sec = parse_time_to_seconds(end_time_str)
|
109 |
|
110 |
+
# ์๊ฐ ๋ฒ์ ๋ณด์
|
111 |
if start_sec < 0:
|
112 |
start_sec = 0
|
113 |
if end_sec <= 0 or end_sec > duration:
|
|
|
121 |
add_log("[LOG 6] ์์ ์๋ฅด๊ธฐ ์์
์งํ...")
|
122 |
clip = input_video.subclip(start_sec, end_sec)
|
123 |
|
124 |
+
# ํ๋ซํผ๋ณ ํด์๋(๋น์จ) ์กฐ์
|
125 |
add_log(f"[LOG 7] ํ๋ซํผ ๋น์จ ์กฐ์ : {platform_option}")
|
126 |
clip = adjust_aspect_ratio(clip, platform_option)
|
127 |
|
128 |
+
# ์ถ๋ ฅ ํด์๋ ์ถ์ ์ต์
์ ์ฉ
|
129 |
if abs(resolution_scale - 1.0) > 1e-3:
|
130 |
add_log(f"[LOG 7-1] ์ถ๋ ฅ ํด์๋ ์ถ์: {resolution_scale*100:.0f}%")
|
131 |
clip = clip.resize(resolution_scale)
|
132 |
|
133 |
+
# ์ฌ์์๋ ์กฐ์ : speedx๋ฅผ ์ ์ฉํ๋ฉด clip.duration์ ์ค์ด๋ค์ง๋ง fps๋ ๊ทธ๋๋ก ์ ์ง๋จ.
|
134 |
if abs(speed_factor - 1.0) > 1e-3:
|
135 |
add_log(f"[LOG 8] ์ฌ์์๋ {speed_factor}๋ฐฐ๋ก ์กฐ์ ์ค...")
|
136 |
clip = clip.fx(mp.vfx.speedx, speed_factor)
|
137 |
|
138 |
+
# FPS ์กฐ์ : ์ต์ข
์ถ๋ ฅ FPS๋ ํ๋ ์ ๋ ์ดํธ ๋ฐฐ์จ๋ง ๋ฐ์ (speed_factor๋ ์ด๋ฏธ duration์ ๋ฐ์๋จ)
|
139 |
original_fps = clip.fps
|
140 |
target_fps = original_fps * frame_rate_factor
|
141 |
add_log(f"[LOG 9] ์ต์ข
์ถ๋ ฅ FPS: {target_fps:.2f} (์๋ณธ FPS: {original_fps})")
|
142 |
clip = clip.set_fps(target_fps)
|
143 |
|
144 |
+
# ๋ฐ๋ณต ํ์: 0์ด๋ฉด ๋ฌดํ๋ฐ๋ณต, 1~10์ด๋ฉด ํด๋น ํ์๋งํผ ๋ฐ๋ณต (GIF์ loop ๋ฉํ๋ฐ์ดํฐ ์ฌ์ฉ)
|
145 |
add_log(f"[LOG 10] GIF ๋ฐ๋ณต ํ์ ์ค์ : {repeat_count} (0์ด๋ฉด ๋ฌดํ๋ฐ๋ณต)")
|
146 |
final_clip = clip
|
147 |
|
|
|
149 |
output_filename = f"temp_{uuid.uuid4().hex}.gif"
|
150 |
try:
|
151 |
loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
|
152 |
+
final_clip.write_gif(output_filename, fps=target_fps, program='ffmpeg', loop=loop_param)
|
153 |
+
add_log("[LOG 12] GIF ์์ฑ ์๋ฃ! ํ์ผ๋ช
: " + output_filename)
|
|
|
154 |
except Exception as e:
|
155 |
add_log(f"[ERROR] GIF ์์ฑ ์คํจ: {e}")
|
156 |
return None, None, "\n".join(global_logs)
|
157 |
|
158 |
+
# ๋ฏธ๋ฆฌ๋ณด๊ธฐ: ์์ฑ๋ GIF ํ์ผ ๊ฒฝ๋ก๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๋ฉด gr.Image์์ ์ ๋๋ฉ์ด์
๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
|
159 |
return output_filename, output_filename, "\n".join(global_logs)
|
160 |
|
161 |
def update_thumbnails(video, start_time_str, end_time_str):
|
162 |
+
"""
|
163 |
+
์์/๋ ์ธ๋ค์ผ์ ์
๋ฐ์ดํธํ๋ ํจ์
|
164 |
+
"""
|
165 |
video_path = video if isinstance(video, str) else video.name
|
166 |
+
|
167 |
try:
|
168 |
input_video = mp.VideoFileClip(video_path)
|
169 |
except Exception as e:
|
|
|
184 |
|
185 |
start_thumb = generate_thumbnail(input_video, start_sec)
|
186 |
end_thumb = generate_thumbnail(input_video, end_sec)
|
|
|
187 |
|
188 |
+
return start_thumb, end_thumb
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
|
190 |
# ------------------------------
|
191 |
# Gradio ์ธํฐํ์ด์ค ๊ตฌ์ฑ (Blocks)
|
192 |
# ------------------------------
|
193 |
+
with gr.Blocks() as demo:
|
194 |
+
gr.Markdown("## ๋์์์ GIF๋ก ๋ณํํ๊ธฐ ๋ฐ๋ชจ")
|
195 |
+
|
196 |
+
with gr.Tab("GIF ๋ณํ"):
|
197 |
+
# ๋์์ ์
๋ก๋
|
198 |
+
video_input = gr.Video(label="๋์์ ์
๋ก๋")
|
199 |
+
# ์์/๋ ์๊ฐ ์
๋ ฅ
|
200 |
+
start_time = gr.Textbox(label="์์ ์๊ฐ (์: 00:00:05)", value="00:00:00")
|
201 |
+
end_time = gr.Textbox(label="์ข
๋ฃ ์๊ฐ (์: 00:00:10)", value="00:00:05")
|
202 |
+
# ์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
|
203 |
+
start_thumb_output = gr.Image(label="์์ ์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
204 |
+
end_thumb_output = gr.Image(label="์ข
๋ฃ ์ธ๋ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
205 |
+
# ํ๋ซํผ๋ณ ํด์๋(๋น์จ) ์ ํ (Radio ๋ฒํผ)
|
206 |
+
platform_option = gr.Radio(
|
207 |
+
label="ํด์๋/๋น์จ ์ ํ",
|
208 |
+
choices=["์๋ณธ ์ ์ง", "์ ํ๋ธ (16:9)", "์ผ์ธ /๋ฆด์ค (9:16)", "์ ์ฌ๊ฐํ (1:1)", "์ธ์คํ๊ทธ๋จ (4:5)", "ํด๋์ (4:3)"],
|
209 |
+
value="์๋ณธ ์ ์ง"
|
210 |
+
)
|
211 |
+
# ์ถ๋ ฅ ํด์๋ ์ถ์ ์ต์
(1.0์ด๋ฉด ์๋ณธ, 0.1 ~ 1.0 ๋ฒ์)
|
212 |
+
resolution_scale_slider = gr.Slider(label="์ถ๋ ฅ ํด์๋ ์ถ์ ๋น์จ (0.1 ~ 1.0)",
|
213 |
+
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
214 |
+
# ํ๋ ์ ๋ ์ดํธ, ์ฌ์ ์๋, ๋ฐ๋ณต ํ์ ์กฐ์
|
215 |
+
frame_rate_slider = gr.Slider(label="ํ๋ ์ ๋ ์ดํธ ๋ฐฐ์จ ์กฐ์ (0.1 ~ 1.0)",
|
216 |
+
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
217 |
+
speed_slider = gr.Slider(label="์ฌ์ ์๋ ์กฐ์ (0.5 ~ 5.0)",
|
218 |
+
minimum=0.5, maximum=5.0, step=0.1, value=1.0)
|
219 |
+
repeat_slider = gr.Slider(label="GIF ๋ฐ๋ณต ํ์ (0: ๋ฌดํ๋ฐ๋ณต, 1~10: ๋ฐ๋ณต ํ์)",
|
220 |
+
minimum=0, maximum=10, step=1, value=0)
|
221 |
+
# GIF ์์ฑ ๋ฒํผ ๋ฐ ๊ฒฐ๊ณผ ์ถ๋ ฅ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
222 |
generate_button = gr.Button("GIF ์์ฑํ๊ธฐ")
|
223 |
+
gif_preview_output = gr.Image(label="์์ฑ๋ GIF ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
224 |
+
download_output = gr.File(label="GIF ๋ค์ด๋ก๋ ๋งํฌ")
|
225 |
+
logs_output = gr.Textbox(label="๋ก๊ทธ ์ถ๋ ฅ", lines=10)
|
226 |
+
|
227 |
+
# ์ธ๋ค์ผ ์
๋ฐ์ดํธ ์ด๋ฒคํธ (๋์์ ๋๋ ์๊ฐ ์
๋ ฅ ๋ณ๊ฒฝ ์)
|
228 |
+
start_time.change(fn=update_thumbnails,
|
229 |
+
inputs=[video_input, start_time, end_time],
|
230 |
+
outputs=[start_thumb_output, end_thumb_output])
|
231 |
+
end_time.change(fn=update_thumbnails,
|
232 |
+
inputs=[video_input, start_time, end_time],
|
233 |
+
outputs=[start_thumb_output, end_thumb_output])
|
234 |
+
video_input.change(fn=update_thumbnails,
|
235 |
+
inputs=[video_input, start_time, end_time],
|
236 |
+
outputs=[start_thumb_output, end_thumb_output])
|
237 |
+
# GIF ์์ฑ ๋ฒํผ ์ด๋ฒคํธ
|
238 |
+
generate_button.click(fn=process_video,
|
239 |
+
inputs=[video_input, start_time, end_time,
|
240 |
+
platform_option, frame_rate_slider, speed_slider, repeat_slider,
|
241 |
+
resolution_scale_slider],
|
242 |
+
outputs=[gif_preview_output, download_output, logs_output])
|
243 |
+
|
244 |
+
gr.Markdown(
|
245 |
+
"### [์ฌ์ฉ ๊ฐ์ด๋]\n"
|
246 |
+
"1. ๋์์์ ์
๋ก๋ํ์ธ์.\n"
|
247 |
+
"2. ์์/๋ ์๊ฐ์ ์
๋ ฅํ์ธ์.\n"
|
248 |
+
"3. ํ๋ซํผ๋ณ ๊ถ์ฅ ํด์๋/๋น์จ(์๋ณธ ์ ์ง, ์ ํ๋ธ, ์ผ์ธ /๋ฆด์ค, ์ ์ฌ๊ฐํ, ์ธ์คํ๊ทธ๋จ, ํด๋์)๊ณผ ์ถ๋ ฅ ํด์๋ ์ถ์ ๋น์จ์ ์ ํํ๊ณ ,\n"
|
249 |
+
" ํ๋ ์ ๋ ์ดํธ ๋ฐฐ์จ, ์ฌ์ ์๋, ๋ฐ๋ณต ํ์๋ฅผ ์กฐ์ ํ ํ `GIF ์์ฑํ๊ธฐ` ๋ฒํผ์ ๋๋ฅด๋ฉด GIF๊ฐ ์์ฑ๋ฉ๋๋ค.\n"
|
250 |
+
" - ๋ฐ๋ณต ํ์: 0์ ์ ํํ๋ฉด ๋ฌดํ๋ฐ๋ณต, 1~10์ ํด๋น ํ์๋งํผ ๋ฐ๋ณต๋ฉ๋๋ค.\n"
|
251 |
+
"4. ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ ๋ค์ด๋ก๋ ๋งํฌ๋ฅผ ํตํด GIF๋ฅผ ํ์ธํ ์ ์์ต๋๋ค."
|
252 |
)
|
253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
if __name__ == "__main__":
|
255 |
+
demo.launch()
|