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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -153
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
- total_seconds = int(h) * 3600 + int(m) * 60 + float(s)
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
- thumbnail_img = Image.fromarray(frame)
37
- return thumbnail_img
38
  except Exception as e:
39
  add_log(f"[ERROR] ์ธ๋„ค์ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
40
  frame = video_clip.get_frame(0)
41
- 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)":
54
- target_ratio = 9 / 16
55
  elif option == "์ •์‚ฌ๊ฐํ˜• (1:1)":
56
  target_ratio = 1.0
57
  elif option == "์ธ์Šคํƒ€๊ทธ๋žจ (4:5)":
58
- target_ratio = 4 / 5
59
  elif option == "ํด๋ž˜์‹ (4:3)":
60
- target_ratio = 4 / 3
61
  else:
62
  return clip
63
 
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
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, # ํ”Œ๋žซํผ๋ณ„ ๋น„์œจ ์„ ํƒ (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] ๋น„๋””์˜ค ๋กœ๋“œ ์ค‘...")
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] ์—…๋กœ๋“œ๋œ ์˜์ƒ์˜ ์žฌ์ƒ์‹œ๊ฐ„: {duration:.2f}์ดˆ")
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
- end_sec = duration
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] ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์ถ•์†Œ: {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
-
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, 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:
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
- # 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()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()