Kims12 commited on
Commit
aa66a00
ยท
verified ยท
1 Parent(s): 02446f3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -210
app.py CHANGED
@@ -64,7 +64,6 @@ def adjust_aspect_ratio(clip, option):
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
@@ -77,11 +76,11 @@ def adjust_aspect_ratio(clip, option):
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
  """
@@ -90,7 +89,7 @@ def process_video(video,
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] ๋น„๋””์˜ค ๋กœ๋“œ ์ค‘...")
@@ -121,11 +120,10 @@ def process_video(video,
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)
@@ -135,13 +133,12 @@ def process_video(video,
135
  add_log(f"[LOG 8] ์žฌ์ƒ์†๋„ {speed_factor}๋ฐฐ๋กœ ์กฐ์ ˆ ์ค‘...")
136
  clip = clip.fx(mp.vfx.speedx, speed_factor)
137
 
138
- # FPS ์กฐ์ ˆ
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: ์ง€์ • ํšŸ์ˆ˜)
145
  add_log(f"[LOG 10] GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜ ์„ค์ •: {repeat_count} (0์ด๋ฉด ๋ฌดํ•œ๋ฐ˜๋ณต)")
146
  final_clip = clip
147
 
@@ -149,19 +146,17 @@ def process_video(video,
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
- # ImageMagick๋กœ GIF ์ƒ์„ฑ (loop ์ ์šฉ)
153
  final_clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
154
  add_log(f"[LOG 12] GIF ์ƒ์„ฑ ์™„๋ฃŒ! ํŒŒ์ผ๋ช…: {output_filename}, loop={loop_param}")
155
  except Exception as e:
156
  add_log(f"[ERROR] GIF ์ƒ์„ฑ ์‹คํŒจ: {e}")
157
  return None, None, "\n".join(global_logs)
158
 
159
- # ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ (๋ฏธ๋ฆฌ๋ณด๊ธฐ์šฉ ๊ฒฝ๋กœ, ๋‹ค์šด๋กœ๋“œ์šฉ ๊ฒฝ๋กœ, ๋กœ๊ทธ)
160
  return output_filename, output_filename, "\n".join(global_logs)
161
 
162
  def update_thumbnails(video, start_time_str, end_time_str):
163
  """
164
- ์‹œ์ž‘/๋ ์ธ๋„ค์ผ์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํ•จ์ˆ˜
165
  """
166
  video_path = video if isinstance(video, str) else video.name
167
 
@@ -188,185 +183,180 @@ def update_thumbnails(video, start_time_str, end_time_str):
188
 
189
  return start_thumb, end_thumb
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  # ------------------------------
192
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ (Blocks)
193
  # ------------------------------
194
- with gr.Blocks() as demo:
195
- #
196
- # 1) CSS ์Šคํƒ€์ผ: ์ตœ๋Œ€ํ•œ Gradio ๋А๋‚Œ์„ ์ˆจ๊ธฐ๊ณ , ์ปค์Šคํ…€ ๋””์ž์ธ ์ ์šฉ
197
- #
198
- custom_css = """
199
- <style>
200
- /* ๊ธฐ๋ณธ์ ์ธ ๋ฆฌ์…‹ */
201
- body, h1, h2, h3, h4, p, div, span {
202
- margin: 0; padding: 0; box-sizing: border-box;
203
- font-family: "Noto Sans", sans-serif;
204
- }
205
-
206
- /* ๋ฐฐ๊ฒฝ์ƒ‰, ๋งˆ์ง„ ๋“ฑ ๊ธฐ๋ณธ ์„ค์ • */
207
- body {
208
- background: #fefefe;
209
- color: #333;
210
- padding: 20px;
211
- }
212
-
213
- /* ํฐ ํƒ€์ดํ‹€ */
214
- .main-title {
215
- font-size: 2.0rem;
216
- text-align: center;
217
- margin: 0.5em 0;
218
- color: #e67e22; /* ์˜ค๋ Œ์ง€ ํ†ค */
219
- font-weight: bold;
220
- }
221
-
222
- /* ์•ˆ๋‚ด ๋ฌธ๊ตฌ (์‚ฌ์šฉ ๊ฐ€์ด๋“œ ๋“ฑ) */
223
- .guide-text {
224
- font-size: 1rem;
225
- color: #555;
226
- margin-bottom: 1.5em;
227
- text-align: center;
228
- line-height: 1.4em;
229
- }
230
-
231
- .emoji-title {
232
- font-size: 1.3rem;
233
- margin-bottom: 0.5em;
234
- text-align: center;
235
- color: #2ecc71; /* ๊ทธ๋ฆฐ ํ†ค */
236
- }
237
-
238
- /* ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ขŒ์šฐ๋กœ ๋ฐฐ์น˜ */
239
- .row-container {
240
- display: flex;
241
- flex-direction: row;
242
- justify-content: space-between;
243
- margin-bottom: 1.5em;
244
- gap: 1em;
245
- }
246
-
247
- /* 2๊ฐœ์˜ ๋ฐ•์Šค๋ฅผ ๊ฐ๊ฐ ๋ฐ˜์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋ฐฐ์น˜ */
248
- .box {
249
- width: 50%;
250
- background-color: #ffffff;
251
- border: 2px solid #ddd;
252
- border-radius: 15px;
253
- padding: 1em;
254
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
255
- }
256
-
257
- /* 1๊ฐœ์˜ ๋ฐ•์Šค ์ „์ฒด ๋„ˆ๋น„ */
258
- .single-box {
259
- width: 100%;
260
- background-color: #ffffff;
261
- border: 2px solid #ddd;
262
- border-radius: 15px;
263
- padding: 1em;
264
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
265
- margin-bottom: 1.5em;
266
- }
267
-
268
- /* ์„น์…˜ ๋‚ด๋ถ€ ํ—ค๋” ์Šคํƒ€์ผ */
269
- .section-title {
270
- font-size: 1.2rem;
271
- margin-bottom: 0.5em;
272
- font-weight: bold;
273
- color: #3498db; /* ๋ธ”๋ฃจ ํ†ค */
274
- }
275
-
276
- /* ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์น˜ ์œ„ํ•œ ๋‚ด๋ถ€ ๋ž˜ํผ */
277
- .component-wrapper {
278
- margin-bottom: 0.8em;
279
- }
280
-
281
- /* "GIF ์ƒ์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ ์Šคํƒ€์ผ(์•ฝ๊ฐ„ ํฌ๊ฒŒ) */
282
- .big-button {
283
- display: block;
284
- width: 100%;
285
- padding: 0.8em;
286
- background-color: #e74c3c;
287
- color: #fff;
288
- border: none;
289
- border-radius: 8px;
290
- font-size: 1.1rem;
291
- font-weight: bold;
292
- cursor: pointer;
293
- margin-top: 1em;
294
- }
295
- .big-button:hover {
296
- background-color: #c0392b;
297
- }
298
- </style>
299
- """
300
-
301
- gr.HTML(custom_css) # ์ปค์Šคํ…€ CSS๋ฅผ ์‚ฝ์ž…
302
-
303
- # 2) ์ œ๋ชฉ ๋ฐ ๊ฐ„๋‹จ ์„ค๋ช…
304
- gr.HTML(
305
  """
306
- <h1 class="main-title">๐ŸŽฌ ์˜์ƒ์„ GIF๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ๐Ÿฟ</h1>
307
- <div class="guide-text">
308
- ๐Ÿ“ ๊ฐ„๋‹จ ์•ˆ๋‚ด: ์˜์ƒ์„ ์—…๋กœ๋“œํ•˜๊ณ , ์›ํ•˜๋Š” ํ•ด์ƒ๋„/๋น„์œจ, ํ”„๋ ˆ์ž„์†๋„, ๋ฐ˜๋ณต ํšŸ์ˆ˜๋ฅผ ์„ค์ •ํ•œ ๋’ค GIF ์ƒ์„ฑํ•˜๊ธฐ๋ฅผ ๋ˆŒ๋Ÿฌ๋ณด์„ธ์š”!<br/>
 
 
 
 
 
 
 
309
  </div>
310
  """
311
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
- # 3) ์ฒซ ๋ฒˆ์งธ ์ค„: ์ž…๋ ฅ๋ถ€(์ขŒ์ธก) + ์ถœ๋ ฅ๋ถ€(์šฐ์ธก)
314
- gr.HTML("<div class='row-container'>")
315
- gr.HTML("<div class='box'>") # ์ขŒ์ธก ์ž…๋ ฅ๋ถ€ ๋ฐ•์Šค ์‹œ์ž‘
316
- gr.HTML("<div class='emoji-title'>โš™๏ธ ์ž…๋ ฅ๋ถ€</div>")
317
- # ์˜์ƒ ์—…๋กœ๋“œ
318
- video_input = gr.Video(label="์˜์ƒ ์—…๋กœ๋“œ", elem_id="video_input", show_label=True)
319
- platform_option = gr.Radio(
320
- label="ํ•ด์ƒ๋„/๋น„์œจ ์„ ํƒ",
321
- choices=["์›๋ณธ ์œ ์ง€", "์œ ํŠœ๋ธŒ (16:9)", "์‡ผ์ธ /๋ฆด์Šค (9:16)", "์ •์‚ฌ๊ฐํ˜• (1:1)", "์ธ์Šคํƒ€๊ทธ๋žจ (4:5)", "ํด๋ž˜์‹ (4:3)"],
322
- value="์›๋ณธ ์œ ์ง€",
323
- elem_id="platform_option"
324
- )
325
- resolution_scale_slider = gr.Slider(
326
- label="์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ (0.1 ~ 1.0)",
327
- minimum=0.1, maximum=1.0, step=0.1, value=1.0,
328
- elem_id="resolution_scale"
329
- )
330
- frame_rate_slider = gr.Slider(
331
- label="ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ ์กฐ์ ˆ (0.1 ~ 1.0)",
332
- minimum=0.1, maximum=1.0, step=0.1, value=1.0,
333
- elem_id="frame_rate"
334
- )
335
- speed_slider = gr.Slider(
336
- label="์žฌ์ƒ ์†๋„ ์กฐ์ ˆ (0.5 ~ 5.0)",
337
- minimum=0.5, maximum=5.0, step=0.1, value=1.0,
338
- elem_id="speed_factor"
339
- )
340
- repeat_slider = gr.Slider(
341
- label="GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜ (0: ๋ฌดํ•œ๋ฐ˜๋ณต, 1~10: ๋ฐ˜๋ณต ํšŸ์ˆ˜)",
342
- minimum=0, maximum=10, step=1, value=0,
343
- elem_id="repeat_count"
344
- )
345
- gr.HTML("</div>") # ์ขŒ์ธก ์ž…๋ ฅ๋ถ€ ๋ฐ•์Šค ๋
346
-
347
- gr.HTML("<div class='box'>") # ์šฐ์ธก ์ถœ๋ ฅ๋ถ€ ๋ฐ•์Šค ์‹œ์ž‘
348
- gr.HTML("<div class='emoji-title'>๐Ÿš€ ์ถœ๋ ฅ๋ถ€</div>")
349
- gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ", elem_id="gif_preview")
350
- download_output = gr.File(label="GIF ๋‹ค์šด๋กœ๋“œ", elem_id="gif_download")
351
- gr.HTML("</div>") # ์šฐ์ธก ์ถœ๋ ฅ๋ถ€ ๋ฐ•์Šค ๋
352
- gr.HTML("</div>") # ์ฒซ ๋ฒˆ์งธ ์ค„(row-container) ์ข…๋ฃŒ
353
-
354
- # 4) ๋‘ ๋ฒˆ์งธ ์ค„: ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
355
- gr.HTML("<div class='single-box'>")
356
- gr.HTML("<div class='emoji-title'>๐Ÿ”Ž ์ž‘์—… ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ</div>")
357
- start_thumb_output = gr.Image(label="์‹œ์ž‘ ์ธ๋„ค์ผ", elem_id="start_thumb")
358
- end_thumb_output = gr.Image(label="์ข…๋ฃŒ ์ธ๋„ค์ผ", elem_id="end_thumb")
359
- # GIF ์ƒ์„ฑ ๋ฒ„ํŠผ
360
- generate_button = gr.Button("๐Ÿ’ก GIF ์ƒ์„ฑํ•˜๊ธฐ", elem_id="generate_btn", css="", variant="primary").style(full_width=False)
361
- logs_output = gr.Textbox(label="์ž‘์—… ๋กœ๊ทธ", lines=6)
362
- gr.HTML("</div>")
363
-
364
- # 5) ์ด๋ฒคํŠธ & ๋กœ์ง ์—ฐ๊ฒฐ
365
- start_time = gr.Textbox(label="์‹œ์ž‘ ์‹œ๊ฐ„ (์˜ˆ: 00:00:05)", value="00:00:00")
366
- end_time = gr.Textbox(label="์ข…๋ฃŒ ์‹œ๊ฐ„ (์˜ˆ: 00:00:10)", value="00:00:05")
367
-
368
- # ํ™”๋ฉด์ƒ์—๋Š” ํ‘œ์‹œ๋˜์ง€ ์•Š์ง€๋งŒ, ๋กœ์ง ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์œ„์น˜๋งŒ ์žก์•„๋‘  (elem_id ์—†์ด)
369
- # -> ํ•„์š”ํ•˜๋‹ค๋ฉด style="display:none;"๋กœ ์ˆจ๊ฒจ๋„ ๋จ.
370
  start_time.change(
371
  fn=update_thumbnails,
372
  inputs=[video_input, start_time, end_time],
@@ -383,40 +373,38 @@ with gr.Blocks() as demo:
383
  outputs=[start_thumb_output, end_thumb_output]
384
  )
385
 
 
386
  generate_button.click(
387
  fn=process_video,
388
  inputs=[
389
- video_input, # ๋™์˜์ƒ ์—…๋กœ๋“œ
390
- start_time, # ์‹œ์ž‘ ์‹œ๊ฐ„
391
- end_time, # ์ข…๋ฃŒ ์‹œ๊ฐ„
392
- platform_option, # ํ”Œ๋žซํผ๋ณ„ ๋น„์œจ
393
- frame_rate_slider, # ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ
394
- speed_slider, # ์žฌ์ƒ ์†๋„
395
- repeat_slider, # ๋ฐ˜๋ณต ํšŸ์ˆ˜
396
- resolution_scale_slider # ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ
397
  ],
398
  outputs=[
399
- gif_preview_output, # ์™„์„ฑ๋œ GIF ๋ฏธ๋ฆฌ๋ณด๊ธฐ
400
- download_output, # GIF ๋‹ค์šด๋กœ๋“œ ๋งํฌ
401
- logs_output # ๋กœ๊ทธ ์ถœ๋ ฅ
402
  ]
403
  )
404
 
405
- # 6) ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ(ํ•˜๋‹จ)
406
- gr.HTML(
407
- """
408
- <div class="single-box" style="text-align:center;">
409
- <h2 class="section-title">๐Ÿ“ข ๊ฐ„๋‹จ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ</h2>
410
- <p style="font-size:1rem; margin-bottom:0.5em;">
411
- 1) <strong>์˜์ƒ์„ ์—…๋กœ๋“œ</strong>ํ•ฉ๋‹ˆ๋‹ค.<br/>
412
- 2) <strong>์‹œ์ž‘/๋ ์‹œ๊ฐ„</strong>๊ณผ <strong>ํ•ด์ƒ๋„/๋น„์œจ</strong>์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.<br/>
413
- 3) <strong>์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ, ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ, ์žฌ์ƒ ์†๋„, ๋ฐ˜๋ณต ํšŸ์ˆ˜</strong>๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.<br/>
414
- 4) <strong>GIF ์ƒ์„ฑํ•˜๊ธฐ</strong> ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ณ€ํ™˜์„ ์ง„ํ–‰ํ•˜์„ธ์š”.<br/>
415
- 5) ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ ๋‹ค์šด๋กœ๋“œ๋ฅผ ํ†ตํ•ด GIF๋ฅผ ํ™•์ธํ•˜์„ธ์š”!<br/>
416
- </p>
417
- </div>
418
- """
419
- )
420
 
421
  if __name__ == "__main__":
422
  demo.launch()
 
64
  width, height = clip.size
65
  current_ratio = width / height
66
 
 
67
  if current_ratio > target_ratio:
68
  new_width = int(height * target_ratio)
69
  new_height = height
 
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):
84
  """
85
  ๋™์˜์ƒ์„ GIF๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
86
  """
 
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] ๋น„๋””์˜ค ๋กœ๋“œ ์ค‘...")
 
120
  add_log("[LOG 6] ์˜์ƒ ์ž๋ฅด๊ธฐ ์ž‘์—… ์ง„ํ–‰...")
121
  clip = input_video.subclip(start_sec, end_sec)
122
 
 
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)
 
133
  add_log(f"[LOG 8] ์žฌ์ƒ์†๋„ {speed_factor}๋ฐฐ๋กœ ์กฐ์ ˆ ์ค‘...")
134
  clip = clip.fx(mp.vfx.speedx, speed_factor)
135
 
 
136
  original_fps = clip.fps
137
  target_fps = original_fps * frame_rate_factor
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
  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:
152
  add_log(f"[ERROR] GIF ์ƒ์„ฑ ์‹คํŒจ: {e}")
153
  return None, None, "\n".join(global_logs)
154
 
 
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
 
 
183
 
184
  return start_thumb, end_thumb
185
 
186
+
187
+ # ---------- ์ปค์Šคํ…€ CSS ----------
188
+ custom_css = """
189
+ /* ์ „์ฒด ๋ฐฐ๊ฒฝ */
190
+ body {
191
+ background-color: #fdfdfd;
192
+ margin: 0;
193
+ padding: 0;
194
+ font-family: 'Noto Sans KR', sans-serif;
195
+ }
196
+
197
+ /* ์ƒ๋‹จ ์ œ๋ชฉ ์˜์—ญ */
198
+ .title-container {
199
+ text-align: center;
200
+ margin-top: 20px;
201
+ margin-bottom: 15px;
202
+ }
203
+ .title-container h1 {
204
+ font-size: 2.2rem;
205
+ font-weight: bold;
206
+ color: #333;
207
+ }
208
+ .title-container h1 .emoji {
209
+ font-size: 1.8rem;
210
+ }
211
+
212
+ /* ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์˜์—ญ */
213
+ .guide-container {
214
+ text-align: center;
215
+ padding: 0 20px;
216
+ margin-bottom: 20px;
217
+ }
218
+ .guide-container p {
219
+ font-size: 1rem;
220
+ color: #444;
221
+ line-height: 1.5;
222
+ }
223
+
224
+ /* ํ”„๋ ˆ์ž„(ํ…Œ๋‘๋ฆฌ) ๊ณตํ†ต ์Šคํƒ€์ผ */
225
+ .frame {
226
+ border: 2px solid #ccc;
227
+ border-radius: 15px;
228
+ padding: 15px;
229
+ margin: 10px;
230
+ background-color: #fff;
231
+ }
232
+ .frame h3 {
233
+ margin-top: 0;
234
+ }
235
+
236
+ /* ๊ทธ๋ฆฌ๋””์˜ค ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ๋ณธ ์—ฌ๋ฐฑ ๋“ฑ์„ ์ œ๊ฑฐ/์กฐ์ • */
237
+ .gradio-container {
238
+ padding: 0 !important;
239
+ }
240
+
241
+ /* ์ž…๋ ฅ๋ถ€์™€ ์ถœ๋ ฅ๋ถ€ ๊ฐ™์€ ๋ผ์ธ ๋ฐฐ์น˜ */
242
+ .flex-row {
243
+ display: flex;
244
+ justify-content: space-around;
245
+ gap: 10px;
246
+ flex-wrap: wrap; /* ํ™”๋ฉด ์ข์„ ๋•Œ ์ž๋™ ์ค„๋ฐ”๊ฟˆ */
247
+ }
248
+ .flex-column {
249
+ flex: 1;
250
+ min-width: 300px;
251
+ }
252
+
253
+ /* ํƒ€์›ํ˜• ๋ฒ„ํŠผ */
254
+ .custom-button {
255
+ border-radius: 25px !important;
256
+ background-color: #fa8072 !important;
257
+ color: #fff !important;
258
+ font-weight: bold !important;
259
+ }
260
+
261
+ /* ํ•˜๋‹จ ์ธ๋„ค์ผ ์˜์—ญ์€ ํ•œ ์ค„(์ „์ฒด ํญ)๋กœ ๋ฐฐ๏ฟฝ๏ฟฝ */
262
+ .full-width-row {
263
+ display: flex;
264
+ justify-content: center;
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, theme=None) as demo:
275
+ # ์ƒ๋‹จ ์ œ๋ชฉ
276
+ with gr.HTML():
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
+ with gr.HTML():
287
+ html_guide = """
288
+ <div class="guide-container">
289
+ <p>๐ŸŒŸ ์›ํ•˜๋Š” ์˜์ƒ์„ ์—…๋กœ๋“œํ•˜๊ณ <br>
290
+ ์›ํ•˜๋Š” ํ•ด์ƒ๋„/๋น„์œจ, ์†๋„, ํ”„๋ ˆ์ž„ ๋“ฑ์„ ์กฐ์ •ํ•˜์„ธ์š”.<br>
291
+ <strong>GIF ์ƒ์„ฑํ•˜๊ธฐ</strong> ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ณ€ํ™˜์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.</p>
292
  </div>
293
  """
294
+ print(html_guide)
295
+ gr.HTML(html_guide)
296
+
297
+ # 1) ์ž…๋ ฅ๋ถ€ - ์ถœ๋ ฅ๋ถ€ (๊ฐ™์€ ํ–‰์— ๋ฐฐ์น˜)
298
+ with gr.Row(elem_id="row-input-output"):
299
+ with gr.Column(elem_id="col-input", variant="panel"):
300
+ custom_input_html = """
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(
309
+ label="ํ•ด์ƒ๋„/๋น„์œจ ์„ ํƒ",
310
+ choices=["์›๋ณธ ์œ ์ง€", "์œ ํŠœ๋ธŒ (16:9)", "์‡ผ์ธ /๋ฆด์Šค (9:16)", "์ •์‚ฌ๊ฐํ˜• (1:1)", "์ธ์Šคํƒ€๊ทธ๋žจ (4:5)", "ํด๋ž˜์‹ (4:3)"],
311
+ value="์›๋ณธ ์œ ์ง€"
312
+ )
313
+ resolution_scale_slider = gr.Slider(
314
+ label="์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ ๋น„์œจ (0.1 ~ 1.0)",
315
+ minimum=0.1, maximum=1.0, step=0.1, value=1.0
316
+ )
317
+ frame_rate_slider = gr.Slider(
318
+ label="ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ ๋ฐฐ์œจ ์กฐ์ ˆ (0.1 ~ 1.0)",
319
+ minimum=0.1, maximum=1.0, step=0.1, value=1.0
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
+ with gr.Column(elem_id="col-output", variant="panel"):
331
+ custom_output_html = """
332
+ <div class="frame">
333
+ <h3>์ถœ๋ ฅ๋ถ€ ๐ŸŽ‰</h3>
334
+ </div>
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
+ # 2) ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (๋‘ ๋ฒˆ์งธ ์ค„ ์ „์ฒด)
342
+ with gr.Row(elem_id="row-thumbnail"):
343
+ custom_thumb_html = """
344
+ <div class="frame">
345
+ <h3>์ž‘์—… ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๐Ÿ”</h3>
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
+ start_time = gr.Textbox(label="์‹œ์ž‘ ์‹œ๊ฐ„ (์˜ˆ: 00:00:05)", value="00:00:00", visible=False)
357
+ end_time = gr.Textbox(label="์ข…๋ฃŒ ์‹œ๊ฐ„ (์˜ˆ: 00:00:10)", value="00:00:05", visible=False)
358
+
359
+ # ์ธ๋„ค์ผ ์—…๋ฐ์ดํŠธ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  start_time.change(
361
  fn=update_thumbnails,
362
  inputs=[video_input, start_time, end_time],
 
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=[
390
+ gif_preview_output,
391
+ download_output,
392
+ logs_output
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()