Kims12 commited on
Commit
b1d61d9
ยท
verified ยท
1 Parent(s): 47d0ad6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -222
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
- 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,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
- # 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:
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(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(
306
- label="ํ•ด์ƒ๋„/๋น„์œจ ์„ ํƒ",
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],
349
- outputs=[start_thumb_output, end_thumb_output]
350
- )
351
- end_time.change(
352
- fn=update_thumbnails,
353
- inputs=[video_input, start_time, end_time],
354
- outputs=[start_thumb_output, end_thumb_output]
355
- )
356
- video_input.change(
357
- fn=update_thumbnails,
358
- 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=[
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()