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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -44
app.py CHANGED
@@ -5,7 +5,7 @@ from PIL import Image
5
  import gradio as gr
6
 
7
  # -------------------------------
8
- # ๋‚ด๋ถ€ ํ•จ์ˆ˜๋“ค (๋™์˜์ƒ ์ฒ˜๋ฆฌ ๋“ฑ)
9
  # -------------------------------
10
  global_logs = []
11
 
@@ -16,7 +16,7 @@ def add_log(message: str):
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
 
@@ -44,7 +44,6 @@ def adjust_aspect_ratio(clip, option):
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:
@@ -65,7 +64,7 @@ def process_video(video,
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)
@@ -73,7 +72,7 @@ def process_video(video,
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:
@@ -82,28 +81,26 @@ def process_video(video,
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)
@@ -130,26 +127,41 @@ def update_thumbnails(video, start_time_str, end_time_str):
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;
@@ -158,24 +170,44 @@ body {
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
 
@@ -183,11 +215,12 @@ input, button, select {
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"):
@@ -206,7 +239,7 @@ with gr.Blocks(css=custom_css, title="์˜์ƒ -> GIF ๋ณ€ํ™˜ ์„œ๋น„์Šค") as demo:
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"):
@@ -214,7 +247,7 @@ with gr.Blocks(css=custom_css, title="์˜์ƒ -> GIF ๋ณ€ํ™˜ ์„œ๋น„์Šค") as demo:
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():
@@ -223,12 +256,9 @@ with gr.Blocks(css=custom_css, title="์˜์ƒ -> GIF ๋ณ€ํ™˜ ์„œ๋น„์Šค") as demo:
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])
@@ -238,11 +268,10 @@ with gr.Blocks(css=custom_css, title="์˜์ƒ -> GIF ๋ณ€ํ™˜ ์„œ๋น„์Šค") as demo:
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()
 
5
  import gradio as gr
6
 
7
  # -------------------------------
8
+ # ๋‚ด๋ถ€ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜๋“ค
9
  # -------------------------------
10
  global_logs = []
11
 
 
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
 
 
44
  target_ratio = 4/3
45
  else:
46
  return clip
 
47
  width, height = clip.size
48
  current_ratio = width/height
49
  if current_ratio > target_ratio:
 
64
  resolution_scale):
65
  global global_logs
66
  global_logs = []
67
+ add_log("๐ŸŽฅ [LOG] ์˜์ƒ ์ฒ˜๋ฆฌ ์‹œ์ž‘")
68
  video_path = video if isinstance(video, str) else video.name
69
  try:
70
  input_video = mp.VideoFileClip(video_path)
 
72
  add_log(f"[ERROR] ๋น„๋””์˜ค ๋กœ๋“œ ์‹คํŒจ: {e}")
73
  return None, None, "\n".join(global_logs)
74
  duration = input_video.duration
75
+ add_log(f"[LOG] ์˜์ƒ ์žฌ์ƒ์‹œ๊ฐ„: {duration:.2f}์ดˆ")
76
  start_sec = parse_time_to_seconds(start_time_str)
77
  end_sec = parse_time_to_seconds(end_time_str)
78
  if start_sec < 0:
 
81
  end_sec = duration
82
  if start_sec >= end_sec:
83
  start_sec, end_sec = 0, duration
84
+ add_log(f"[LOG] ์‹œ๊ฐ„ ์„ค์ •: {start_sec}์ดˆ ~ {end_sec}์ดˆ")
85
  clip = input_video.subclip(start_sec, end_sec)
86
+ add_log(f"[LOG] ํ”Œ๋žซํผ ๋น„์œจ ์ ์šฉ: {platform_option}")
87
  clip = adjust_aspect_ratio(clip, platform_option)
88
  if abs(resolution_scale - 1.0) > 1e-3:
89
+ add_log(f"[LOG] ํ•ด์ƒ๋„ ์ถ•์†Œ: {resolution_scale*100:.0f}%")
90
  clip = clip.resize(resolution_scale)
91
  if abs(speed_factor - 1.0) > 1e-3:
92
+ add_log(f"[LOG] ์žฌ์ƒ์†๋„ {speed_factor}๋ฐฐ ์ ์šฉ")
93
  clip = clip.fx(mp.vfx.speedx, speed_factor)
94
  original_fps = clip.fps
95
  target_fps = original_fps * frame_rate_factor
96
+ add_log(f"[LOG] FPS: {target_fps:.2f} (์›๋ณธ {original_fps})")
97
  clip = clip.set_fps(target_fps)
98
+ add_log(f"[LOG] GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜: {repeat_count} (0: ๋ฌดํ•œ)")
 
99
  output_filename = f"temp_{uuid.uuid4().hex}.gif"
100
  try:
 
101
  loop_param = 0 if int(repeat_count) == 0 else int(repeat_count)
102
+ clip.write_gif(output_filename, fps=target_fps, loop=loop_param)
103
+ add_log(f"[LOG] GIF ์ƒ์„ฑ ์™„๋ฃŒ: {output_filename}, loop={loop_param}")
104
  except Exception as e:
105
  add_log(f"[ERROR] GIF ์ƒ์„ฑ ์‹คํŒจ: {e}")
106
  return None, None, "\n".join(global_logs)
 
127
  return start_thumb, end_thumb
128
 
129
  # -------------------------------
130
+ # Custom CSS
131
  # -------------------------------
132
  custom_css = """
133
+ /* ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ ํญ์„ 70%๋กœ ์ค„์ž„ */
134
+ .gradio-container {
135
+ width: 70% !important;
136
+ margin: 0 auto;
137
  }
138
+
139
+ /* ์ œ๋ชฉ ๋ฐ ์‚ฌ์šฉ๊ฐ€์ด๋“œ ์™ผ์ชฝ ์ •๋ ฌ ๋ฐ ํฌ๊ธฐ ์ฆ๊ฐ€ */
140
  .custom-title {
141
  font-size: 2.8em;
 
142
  font-weight: bold;
143
  margin: 20px 0;
144
  color: #2c3e50;
145
+ text-align: left;
146
  }
147
  .custom-user-guide {
148
  font-size: 1.2em;
149
+ margin-bottom: 20px;
 
150
  color: #34495e;
151
+ text-align: left;
152
+ }
153
+
154
+ /* ์‚ฌ์šฉ๊ฐ€์ด๋“œ ๋ฐ•์Šค ์Šคํƒ€์ผ */
155
+ .guide-box {
156
+ border: 2px solid #3498db;
157
+ border-radius: 10px;
158
+ padding: 15px;
159
+ background-color: #d6eaf8;
160
+ margin: 20px 0;
161
+ text-align: left;
162
  }
163
+
164
+ /* ํ”„๋ ˆ์ž„ ์Šคํƒ€์ผ (์ž…๋ ฅ๋ถ€, ์ถœ๋ ฅ๋ถ€, ์ธ๋„ค์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ) */
165
  .frame {
166
  border: 2px solid #888;
167
  border-radius: 20px;
 
170
  margin: 10px;
171
  box-shadow: 3px 3px 10px rgba(0,0,0,0.1);
172
  }
173
+
174
+ /* ๊ฐ ํ”„๋ ˆ์ž„ ์ œ๋ชฉ ํฌ๊ฒŒ */
175
+ .frame h3 {
176
+ font-size: 1.8em;
177
+ margin-bottom: 15px;
178
+ text-align: left;
179
+ }
180
+
181
+ /* ํ–‰ ๋ ˆ์ด์•„์›ƒ */
182
  .row-container {
183
  display: flex;
184
  justify-content: space-between;
185
  }
186
+
187
+ /* ์ปฌ๋Ÿผ ๋ ˆ์ด์•„์›ƒ */
188
  .column {
189
  flex: 1;
190
  margin: 10px;
191
  }
192
+
193
+ /* ๋ฒ„ํŠผ ์—ฌ๋ฐฑ */
194
+ .gif-button {
195
+ margin-top: 30px;
196
+ padding: 10px 20px;
197
+ font-size: 1.2em;
198
  }
199
+
200
+ /* ์Šฌ๋ผ์ด๋” ์Šคํƒ€์ผ (๊ธ€์ž์™€ ๋ฐ” ํฌ๊ฒŒ) */
201
+ input[type=range] {
202
+ height: 20px;
203
  }
204
+ input[type=range]::-webkit-slider-thumb {
205
+ height: 20px;
206
+ width: 20px;
207
+ }
208
+ .slider-label {
209
  font-size: 1.2em;
210
+ margin-bottom: 5px;
211
  }
212
  """
213
 
 
215
  # Gradio UI ๊ตฌ์„ฑ (HTML/CSS ์ปค์Šคํ„ฐ๋งˆ์ด์ง•)
216
  # -------------------------------
217
  with gr.Blocks(css=custom_css, title="์˜์ƒ -> GIF ๋ณ€ํ™˜ ์„œ๋น„์Šค") as demo:
218
+ # ์ œ๋ชฉ๊ณผ ์‚ฌ์šฉ๊ฐ€์ด๋“œ(์™ผ์ชฝ ์ •๋ ฌ, ๋ฐ•์Šคํ˜•ํƒœ)
219
+ gr.HTML("<div class='custom-title'>๐ŸŽฌ ์˜์ƒ์„ GIF๋ณ€ํ™˜ํ•˜๊ธฐ</div>")
220
+ gr.HTML("<div class='guide-box'><strong>์‚ฌ์šฉ๊ฐ€์ด๋“œ:</strong><br>"
221
+ "1. ์ขŒ์ธก ์ž…๋ ฅ๋ถ€์—์„œ ์˜์ƒ์„ ์—…๋กœ๋“œํ•˜๊ณ  ์˜ต์…˜์„ ์„ ํƒํ•˜์„ธ์š”.<br>"
222
+ "2. ํ•˜๋‹จ ์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ์‹œ์ž‘/์ข…๋ฃŒ ์‹œ๊ฐ„์„ ์ž…๋ ฅํ•ด ์ธ๋„ค์ผ์„ ํ™•์ธํ•˜์„ธ์š”.<br>"
223
+ "3. โœจ GIF ์ƒ์„ฑํ•˜๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ GIF๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์šฐ์ธก ์ถœ๋ ฅ๋ถ€์—์„œ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธ ๋ฐ ๋‹ค์šด๋กœ๋“œํ•˜์„ธ์š”. ๐Ÿ˜Š</div>")
224
 
225
  # ์ฒซ๋ฒˆ์งธ ํ–‰: ์ž…๋ ฅ๋ถ€ (์ขŒ์ธก) / ์ถœ๋ ฅ๋ถ€ (์šฐ์ธก)
226
  with gr.Row(elem_classes="row-container"):
 
239
  minimum=0.1, maximum=1.0, step=0.1, value=1.0)
240
  speed_slider = gr.Slider(label="์žฌ์ƒ ์†๋„ ์กฐ์ ˆ (0.5 ~ 5.0)",
241
  minimum=0.5, maximum=5.0, step=0.1, value=1.0)
242
+ repeat_slider = gr.Slider(label="GIF ๋ฐ˜๋ณต ํšŸ์ˆ˜ (0: ๋ฌดํ•œ, 1~10)",
243
  minimum=0, maximum=10, step=1, value=0)
244
  with gr.Column(elem_classes="column"):
245
  with gr.Group(elem_classes="frame"):
 
247
  gif_preview_output = gr.Image(label="GIF ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
248
  download_output = gr.File(label="GIF ๋‹ค์šด๋กœ๋“œ")
249
 
250
+ # ๋‘๋ฒˆ์งธ ํ–‰: ์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ „์ฒด ๋„ˆ๋น„)
251
  with gr.Group(elem_classes="frame full-width"):
252
  gr.Markdown("### ๐Ÿ–ผ๏ธ ์ž‘์—… ์ธ๋„ค์ผ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
253
  with gr.Row():
 
256
  with gr.Row():
257
  start_thumb_output = gr.Image(label="์‹œ์ž‘ ์ธ๋„ค์ผ")
258
  end_thumb_output = gr.Image(label="์ข…๋ฃŒ ์ธ๋„ค์ผ")
259
+ generate_button = gr.Button("โœจ GIF ์ƒ์„ฑํ•˜๊ธฐ", elem_classes="gif-button")
260
+
 
 
261
  # ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ
 
262
  start_time_tb.change(fn=update_thumbnails,
263
  inputs=[video_input, start_time_tb, end_time_tb],
264
  outputs=[start_thumb_output, end_thumb_output])
 
268
  video_input.change(fn=update_thumbnails,
269
  inputs=[video_input, start_time_tb, end_time_tb],
270
  outputs=[start_thumb_output, end_thumb_output])
 
271
  generate_button.click(fn=process_video,
272
  inputs=[video_input, start_time_tb, end_time_tb,
273
  platform_option, frame_rate_slider, speed_slider, repeat_slider,
274
  resolution_scale_slider],
275
+ outputs=[gif_preview_output, download_output])
276
+
277
  demo.launch()