Update app.py
Browse files
app.py
CHANGED
@@ -4,174 +4,119 @@ import moviepy.editor as mp
|
|
4 |
from PIL import Image
|
5 |
import gradio as gr
|
6 |
|
7 |
-
#
|
8 |
-
#
|
9 |
-
#
|
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 |
-
|
26 |
-
return total_seconds
|
27 |
except Exception:
|
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 |
-
|
37 |
-
return thumbnail_img
|
38 |
except Exception as e:
|
39 |
add_log(f"[ERROR] ์ธ๋ค์ผ ์์ฑ ์คํจ: {e}")
|
40 |
frame = video_clip.get_frame(0)
|
41 |
-
|
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
|
53 |
elif option == "์ผ์ธ /๋ฆด์ค (9:16)":
|
54 |
-
target_ratio = 9
|
55 |
elif option == "์ ์ฌ๊ฐํ (1:1)":
|
56 |
target_ratio = 1.0
|
57 |
elif option == "์ธ์คํ๊ทธ๋จ (4:5)":
|
58 |
-
target_ratio = 4
|
59 |
elif option == "ํด๋์ (4:3)":
|
60 |
-
target_ratio = 4
|
61 |
else:
|
62 |
return clip
|
63 |
|
64 |
width, height = clip.size
|
65 |
-
current_ratio = width
|
66 |
-
|
67 |
-
# ์์์ด ๋๋ฌด ๋๋ค๋ฉด ๊ฐ๋ก๋ฅผ, ๋๋ฌด ๋๋ค๋ฉด ์ธ๋ก๋ฅผ ํฌ๋กญํฉ๋๋ค.
|
68 |
if current_ratio > target_ratio:
|
69 |
new_width = int(height * target_ratio)
|
70 |
new_height = height
|
71 |
else:
|
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,
|
81 |
-
frame_rate_factor,
|
82 |
speed_factor,
|
83 |
repeat_count,
|
84 |
-
resolution_scale):
|
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] ๋น๋์ค ๋ก๋ ์ค...")
|
97 |
try:
|
98 |
input_video = mp.VideoFileClip(video_path)
|
99 |
except Exception as e:
|
100 |
add_log(f"[ERROR] ๋น๋์ค ๋ก๋ ์คํจ: {e}")
|
101 |
return None, None, "\n".join(global_logs)
|
102 |
-
|
103 |
duration = input_video.duration
|
104 |
-
add_log(f"[LOG 3]
|
105 |
-
|
106 |
-
add_log("[LOG 4] ์์/๋ ์๊ฐ ํ์ฑ ์ค...")
|
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:
|
114 |
end_sec = duration
|
115 |
if start_sec >= end_sec:
|
116 |
-
start_sec = 0
|
117 |
-
|
118 |
-
|
119 |
-
add_log(f"[LOG 5] ์ ์ฉ๋ ์์ ์๊ฐ: {start_sec}์ด, ์ข
๋ฃ ์๊ฐ: {end_sec}์ด")
|
120 |
-
|
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]
|
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]
|
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 |
-
|
148 |
-
add_log("[LOG 11] GIF ์์ฑ ์ค...")
|
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,
|
153 |
-
add_log("[LOG 12] GIF ์์ฑ
|
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:
|
170 |
add_log(f"[ERROR] ๋น๋์ค ๋ก๋ ์คํจ: {e}")
|
171 |
return None, None
|
172 |
-
|
173 |
duration = input_video.duration
|
174 |
-
|
175 |
start_sec = parse_time_to_seconds(start_time_str)
|
176 |
end_sec = parse_time_to_seconds(end_time_str)
|
177 |
if start_sec < 0:
|
@@ -179,77 +124,125 @@ def update_thumbnails(video, start_time_str, end_time_str):
|
|
179 |
if end_sec <= 0 or end_sec > duration:
|
180 |
end_sec = duration
|
181 |
if start_sec >= end_sec:
|
182 |
-
start_sec = 0
|
183 |
-
end_sec = duration
|
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 |
-
#
|
192 |
-
#
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
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 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
from PIL import Image
|
5 |
import gradio as gr
|
6 |
|
7 |
+
# -------------------------------
|
8 |
+
# ๋ด๋ถ ํจ์๋ค (๋์์ ์ฒ๋ฆฌ ๋ฑ)
|
9 |
+
# -------------------------------
|
10 |
global_logs = []
|
11 |
|
12 |
def add_log(message: str):
|
|
|
|
|
|
|
13 |
global_logs.append(message)
|
14 |
print(message)
|
15 |
|
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 |
|
23 |
def generate_thumbnail(video_clip, time_point):
|
|
|
|
|
|
|
24 |
try:
|
25 |
frame = video_clip.get_frame(time_point)
|
26 |
+
return Image.fromarray(frame)
|
|
|
27 |
except Exception as e:
|
28 |
add_log(f"[ERROR] ์ธ๋ค์ผ ์์ฑ ์คํจ: {e}")
|
29 |
frame = video_clip.get_frame(0)
|
30 |
+
return Image.fromarray(frame)
|
|
|
31 |
|
32 |
def adjust_aspect_ratio(clip, option):
|
|
|
|
|
|
|
33 |
if option == "์๋ณธ ์ ์ง":
|
34 |
return clip
|
|
|
35 |
if option == "์ ํ๋ธ (16:9)":
|
36 |
+
target_ratio = 16/9
|
37 |
elif option == "์ผ์ธ /๋ฆด์ค (9:16)":
|
38 |
+
target_ratio = 9/16
|
39 |
elif option == "์ ์ฌ๊ฐํ (1:1)":
|
40 |
target_ratio = 1.0
|
41 |
elif option == "์ธ์คํ๊ทธ๋จ (4:5)":
|
42 |
+
target_ratio = 4/5
|
43 |
elif option == "ํด๋์ (4:3)":
|
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:
|
51 |
new_width = int(height * target_ratio)
|
52 |
new_height = height
|
53 |
else:
|
54 |
new_width = width
|
55 |
new_height = int(width / target_ratio)
|
56 |
+
return clip.crop(x_center=width/2, y_center=height/2, width=new_width, height=new_height)
|
|
|
57 |
|
58 |
def process_video(video,
|
59 |
start_time_str,
|
60 |
end_time_str,
|
61 |
+
platform_option,
|
62 |
+
frame_rate_factor,
|
63 |
speed_factor,
|
64 |
repeat_count,
|
65 |
+
resolution_scale):
|
|
|
|
|
|
|
66 |
global global_logs
|
67 |
+
global_logs = []
|
68 |
+
add_log("๐ฅ [LOG 1] ๋น๋์ค ์
๋ก๋ ๋ฐ ์ฒ๋ฆฌ ์์")
|
|
|
|
|
|
|
69 |
video_path = video if isinstance(video, str) else video.name
|
|
|
|
|
70 |
try:
|
71 |
input_video = mp.VideoFileClip(video_path)
|
72 |
except Exception as e:
|
73 |
add_log(f"[ERROR] ๋น๋์ค ๋ก๋ ์คํจ: {e}")
|
74 |
return None, None, "\n".join(global_logs)
|
|
|
75 |
duration = input_video.duration
|
76 |
+
add_log(f"[LOG 3] ์์ ์ฌ์์๊ฐ: {duration:.2f}์ด")
|
|
|
|
|
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:
|
80 |
start_sec = 0
|
81 |
if end_sec <= 0 or end_sec > duration:
|
82 |
end_sec = duration
|
83 |
if start_sec >= end_sec:
|
84 |
+
start_sec, end_sec = 0, duration
|
85 |
+
add_log(f"[LOG 5] ์๊ฐ ์ค์ : {start_sec}์ด ~ {end_sec}์ด")
|
|
|
|
|
|
|
|
|
86 |
clip = input_video.subclip(start_sec, end_sec)
|
87 |
+
add_log(f"[LOG 7] ํ๋ซํผ ๋น์จ ์ ์ฉ: {platform_option}")
|
|
|
|
|
88 |
clip = adjust_aspect_ratio(clip, platform_option)
|
|
|
|
|
89 |
if abs(resolution_scale - 1.0) > 1e-3:
|
90 |
+
add_log(f"[LOG 7-1] ํด์๋ ์ถ์: {resolution_scale*100:.0f}%")
|
91 |
clip = clip.resize(resolution_scale)
|
|
|
|
|
92 |
if abs(speed_factor - 1.0) > 1e-3:
|
93 |
+
add_log(f"[LOG 8] ์ฌ์์๋ {speed_factor}๋ฐฐ ์ ์ฉ")
|
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 9] FPS: {target_fps:.2f} (์๋ณธ {original_fps})")
|
98 |
clip = clip.set_fps(target_fps)
|
99 |
+
add_log(f"[LOG 10] GIF ๋ฐ๋ณต ํ์: {repeat_count} (0: ๋ฌดํ)")
|
|
|
|
|
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 |
+
final_clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
|
106 |
+
add_log(f"[LOG 12] GIF ์์ฑ ์๋ฃ: {output_filename}, loop={loop_param}")
|
107 |
except Exception as e:
|
108 |
add_log(f"[ERROR] GIF ์์ฑ ์คํจ: {e}")
|
109 |
return None, None, "\n".join(global_logs)
|
|
|
|
|
110 |
return output_filename, output_filename, "\n".join(global_logs)
|
111 |
|
112 |
def update_thumbnails(video, start_time_str, end_time_str):
|
|
|
|
|
|
|
113 |
video_path = video if isinstance(video, str) else video.name
|
|
|
114 |
try:
|
115 |
input_video = mp.VideoFileClip(video_path)
|
116 |
except Exception as e:
|
117 |
add_log(f"[ERROR] ๋น๋์ค ๋ก๋ ์คํจ: {e}")
|
118 |
return None, None
|
|
|
119 |
duration = input_video.duration
|
|
|
120 |
start_sec = parse_time_to_seconds(start_time_str)
|
121 |
end_sec = parse_time_to_seconds(end_time_str)
|
122 |
if start_sec < 0:
|
|
|
124 |
if end_sec <= 0 or end_sec > duration:
|
125 |
end_sec = duration
|
126 |
if start_sec >= end_sec:
|
127 |
+
start_sec, end_sec = 0, duration
|
|
|
|
|
128 |
start_thumb = generate_thumbnail(input_video, start_sec)
|
129 |
end_thumb = generate_thumbnail(input_video, end_sec)
|
|
|
130 |
return start_thumb, end_thumb
|
131 |
|
132 |
+
# -------------------------------
|
133 |
+
# Custom CSS (HTML/CSS ๊ธฐ๋ฐ ์ UI)
|
134 |
+
# -------------------------------
|
135 |
+
custom_css = """
|
136 |
+
body {
|
137 |
+
font-family: 'Arial', sans-serif;
|
138 |
+
background-color: #eef2f7;
|
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 |
+
text-align: center;
|
150 |
+
margin-bottom: 30px;
|
151 |
+
color: #34495e;
|
152 |
+
}
|
153 |
+
.frame {
|
154 |
+
border: 2px solid #888;
|
155 |
+
border-radius: 20px;
|
156 |
+
padding: 20px;
|
157 |
+
background-color: #fff;
|
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 |
+
.full-width {
|
170 |
+
width: 100%;
|
171 |
+
margin: 10px 0;
|
172 |
+
}
|
173 |
+
input, button, select {
|
174 |
+
font-size: 1em;
|
175 |
+
}
|
176 |
+
.emoji {
|
177 |
+
font-size: 1.2em;
|
178 |
+
margin-right: 5px;
|
179 |
+
}
|
180 |
+
"""
|
181 |
+
|
182 |
+
# -------------------------------
|
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๋ณํํ๊ธฐ ๐๏ธ</div>")
|
188 |
+
gr.HTML("<div class='custom-user-guide'>๐ ์ฌ์ฉ๊ฐ์ด๋: ์ข์ธก '์
๋ ฅ๋ถ'์์ ์์์ ์
๋ก๋ํ๊ณ ์ต์
์ ์ ํํ ํ,<br>"
|
189 |
+
"ํ๋จ '์์
์ธ๋ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ'์์ ์์/์ข
๋ฃ ์๊ฐ์ ์ค์ ํ๊ณ ์ธ๋ค์ผ์ ํ์ธํ์ธ์. <br>"
|
190 |
+
"์ฐ์ธก '์ถ๋ ฅ๋ถ'์์ ์์ฑ๋ GIF๋ฅผ ๋ฏธ๋ฆฌ ๋ณด๊ณ ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค. ๐</div>")
|
191 |
+
|
192 |
+
# ์ฒซ๋ฒ์งธ ํ: ์
๋ ฅ๋ถ (์ข์ธก) / ์ถ๋ ฅ๋ถ (์ฐ์ธก)
|
193 |
+
with gr.Row(elem_classes="row-container"):
|
194 |
+
with gr.Column(elem_classes="column"):
|
195 |
+
with gr.Group(elem_classes="frame"):
|
196 |
+
gr.Markdown("### ๐ฅ ์
๋ ฅ๋ถ")
|
197 |
+
video_input = gr.Video(label="์์ ์
๋ก๋", show_label=True)
|
198 |
+
platform_option = gr.Radio(
|
199 |
+
label="ํด์๋/๋น์จ ์ ํ",
|
200 |
+
choices=["์๋ณธ ์ ์ง", "์ ํ๋ธ (16:9)", "์ผ์ธ /๋ฆด์ค (9:16)", "์ ์ฌ๊ฐํ (1:1)", "์ธ์คํ๊ทธ๋จ (4:5)", "ํด๋์ (4:3)"],
|
201 |
+
value="์๋ณธ ์ ์ง"
|
202 |
+
)
|
203 |
+
resolution_scale_slider = gr.Slider(label="์ถ๋ ฅ ํด์๋ ์ถ์ ๋น์จ (0.1 ~ 1.0)",
|
204 |
+
minimum=0.1, maximum=1.0, step=0.1, value=1.0)
|
205 |
+
frame_rate_slider = gr.Slider(label="ํ๋ ์ ๋ ์ดํธ ๋ฐฐ์จ ์กฐ์ (0.1 ~ 1.0)",
|
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: ๋ฌดํ๋ฐ๋ณต, 1~10)",
|
210 |
+
minimum=0, maximum=10, step=1, value=0)
|
211 |
+
with gr.Column(elem_classes="column"):
|
212 |
+
with gr.Group(elem_classes="frame"):
|
213 |
+
gr.Markdown("### ๐ค ์ถ๋ ฅ๋ถ")
|
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():
|
221 |
+
start_time_tb = gr.Textbox(label="์์ ์๊ฐ (์: 00:00:05)", value="00:00:00")
|
222 |
+
end_time_tb = gr.Textbox(label="์ข
๋ฃ ์๊ฐ (์: 00:00:10)", value="00:00:05")
|
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])
|
235 |
+
end_time_tb.change(fn=update_thumbnails,
|
236 |
+
inputs=[video_input, start_time_tb, end_time_tb],
|
237 |
+
outputs=[start_thumb_output, end_thumb_output])
|
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, logs_output])
|
247 |
+
|
248 |
+
demo.launch()
|