ssboost commited on
Commit
cf97427
Β·
verified Β·
1 Parent(s): 7ce965a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -562
app.py CHANGED
@@ -1,563 +1,2 @@
1
- import gradio as gr
2
  import os
3
- import tempfile
4
- import base64
5
- import math
6
- import traceback
7
- import numpy as np
8
- from PIL import Image
9
-
10
- from moviepy.editor import VideoFileClip, vfx
11
- from shutil import copyfile
12
- from datetime import datetime, timedelta
13
-
14
- ########################################
15
- # 1) PIL ANTIALIAS μ—λŸ¬ λŒ€μ‘ (Monkey-patch)
16
- ########################################
17
- try:
18
- from PIL import Resampling
19
- if not hasattr(Image, "ANTIALIAS"):
20
- Image.ANTIALIAS = Resampling.LANCZOS
21
- except ImportError:
22
- pass
23
-
24
- ########################################
25
- # 2) λ‚΄λΆ€ 디버그 λ‘œκΉ… (UIμ—μ„œ 미좜λ ₯)
26
- ########################################
27
- DEBUG_LOG_LIST = []
28
-
29
- def log_debug(msg: str):
30
- print("[DEBUG]", msg)
31
- DEBUG_LOG_LIST.append(msg)
32
-
33
- ########################################
34
- # 3) μ‹œκ°„ ν˜•μ‹ λ³€ν™˜ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜
35
- ########################################
36
- END_EPSILON = 0.01
37
-
38
- def round_down_to_one_decimal(value: float) -> float:
39
- return math.floor(value * 10) / 10
40
-
41
- def safe_end_time(duration: float) -> float:
42
- tmp = duration - END_EPSILON
43
- if tmp < 0:
44
- tmp = 0
45
- return round_down_to_one_decimal(tmp)
46
-
47
- def coalesce_to_zero(val):
48
- """
49
- Noneμ΄λ‚˜ NaN, λ¬Έμžμ—΄ 였λ₯˜ 등이 λ“€μ–΄μ˜€λ©΄ 0.0으둜 λ³€ν™˜
50
- """
51
- if val is None:
52
- return 0.0
53
- try:
54
- return float(val)
55
- except:
56
- return 0.0
57
-
58
- def seconds_to_hms(seconds: float) -> str:
59
- """초λ₯Ό HH:MM:SS ν˜•μ‹μœΌλ‘œ λ³€ν™˜"""
60
- try:
61
- seconds = max(0, seconds)
62
- td = timedelta(seconds=round(seconds))
63
- return str(td)
64
- except Exception as e:
65
- log_debug(f"[seconds_to_hms] λ³€ν™˜ 였λ₯˜: {e}")
66
- return "00:00:00"
67
-
68
- def hms_to_seconds(time_str: str) -> float:
69
- """HH:MM:SS ν˜•μ‹μ„ 초둜 λ³€ν™˜"""
70
- try:
71
- parts = time_str.strip().split(':')
72
- parts = [int(p) for p in parts]
73
- while len(parts) < 3:
74
- parts.insert(0, 0) # λΆ€μ‘±ν•œ 뢀뢄은 0으둜 채움
75
- hours, minutes, seconds = parts
76
- return hours * 3600 + minutes * 60 + seconds
77
- except Exception as e:
78
- log_debug(f"[hms_to_seconds] λ³€ν™˜ 였λ₯˜: {e}")
79
- return -1 # 였λ₯˜ μ‹œ -1 λ°˜ν™˜
80
-
81
- ########################################
82
- # 4) μ—…λ‘œλ“œλœ μ˜μƒ 파일 μ €μž₯
83
- ########################################
84
- def save_uploaded_video(video_input):
85
- if not video_input:
86
- log_debug("[save_uploaded_video] video_input is None.")
87
- return None
88
-
89
- if isinstance(video_input, str):
90
- log_debug(f"[save_uploaded_video] video_input is str: {video_input}")
91
- if os.path.exists(video_input):
92
- return video_input
93
- else:
94
- log_debug("[save_uploaded_video] Path does not exist.")
95
- return None
96
-
97
- if isinstance(video_input, dict):
98
- log_debug(f"[save_uploaded_video] video_input is dict: {list(video_input.keys())}")
99
- if 'data' in video_input:
100
- file_data = video_input['data']
101
- if isinstance(file_data, str) and file_data.startswith("data:"):
102
- base64_str = file_data.split(';base64,')[-1]
103
- try:
104
- video_binary = base64.b64decode(base64_str)
105
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
106
- tmp.write(video_binary)
107
- tmp.flush()
108
- tmp.close()
109
- log_debug(f"[save_uploaded_video] Created temp file: {tmp.name}")
110
- return tmp.name
111
- except Exception as e:
112
- log_debug(f"[save_uploaded_video] base64 λ””μ½”λ”© 였λ₯˜: {e}")
113
- return None
114
- else:
115
- if isinstance(file_data, str) and os.path.exists(file_data):
116
- log_debug("[save_uploaded_video] data ν•„λ“œκ°€ μ‹€μ œ 경둜")
117
- return file_data
118
- else:
119
- log_debug("[save_uploaded_video] data ν•„λ“œκ°€ μ˜ˆμƒμΉ˜ λͺ»ν•œ ν˜•νƒœ.")
120
- return None
121
- else:
122
- log_debug("[save_uploaded_video] dictμ΄μ§€λ§Œ 'data' ν‚€κ°€ μ—†μŒ.")
123
- return None
124
-
125
- log_debug("[save_uploaded_video] Unrecognized type.")
126
- return None
127
-
128
- ########################################
129
- # 5) μ˜μƒ 길이, 해상도, μŠ€ν¬λ¦°μƒ·
130
- ########################################
131
- def get_video_duration(video_dict):
132
- path = save_uploaded_video(video_dict)
133
- if not path:
134
- return "00:00:00"
135
- try:
136
- clip = VideoFileClip(path)
137
- dur = clip.duration
138
- clip.close()
139
- log_debug(f"[get_video_duration] duration={dur}")
140
- return seconds_to_hms(dur)
141
- except Exception as e:
142
- log_debug(f"[get_video_duration] 였λ₯˜: {e}\n{traceback.format_exc()}")
143
- return "00:00:00"
144
-
145
- def get_resolution(video_dict):
146
- path = save_uploaded_video(video_dict)
147
- if not path:
148
- return "0x0"
149
- try:
150
- clip = VideoFileClip(path)
151
- w, h = clip.size
152
- clip.close()
153
- log_debug(f"[get_resolution] w={w}, h={h}")
154
- return f"{w}x{h}"
155
- except Exception as e:
156
- log_debug(f"[get_resolution] 였λ₯˜: {e}\n{traceback.format_exc()}")
157
- return "0x0"
158
-
159
- def get_screenshot_at_time(video_dict, time_in_seconds):
160
- path = save_uploaded_video(video_dict)
161
- if not path:
162
- return None
163
- try:
164
- clip = VideoFileClip(path)
165
- actual_duration = clip.duration
166
-
167
- # λ§ˆμ§€λ§‰ ν”„λ ˆμž„ μ ‘κ·Ό 방지
168
- if time_in_seconds >= actual_duration - END_EPSILON:
169
- time_in_seconds = safe_end_time(actual_duration)
170
-
171
- t = max(0, min(time_in_seconds, clip.duration))
172
- log_debug(f"[get_screenshot_at_time] t={t:.3f} / duration={clip.duration:.3f}")
173
- frame = clip.get_frame(t)
174
- clip.close()
175
- return frame # numpy λ°°μ—΄λ‘œ λ°˜ν™˜
176
- except Exception as e:
177
- log_debug(f"[get_screenshot_at_time] 였λ₯˜: {e}\n{traceback.format_exc()}")
178
- return None
179
-
180
- ########################################
181
- # 6) μ—…λ‘œλ“œ 이벀트
182
- ########################################
183
- def on_video_upload(video_dict):
184
- log_debug("[on_video_upload] Called.")
185
- dur_hms = get_video_duration(video_dict)
186
- w, h = map(int, get_resolution(video_dict).split('x'))
187
- resolution_str = f"{w}x{h}"
188
-
189
- start_t = 0.0
190
- end_t = safe_end_time(hms_to_seconds(dur_hms))
191
-
192
- start_img = get_screenshot_at_time(video_dict, start_t)
193
- end_img = None
194
- if end_t > 0:
195
- end_img = get_screenshot_at_time(video_dict, end_t)
196
-
197
- # μˆœμ„œλŒ€λ‘œ: μ˜μƒ 길이, 해상도(λ¬Έμžμ—΄), μ‹œμž‘ μ‹œκ°„, 끝 μ‹œκ°„, μ‹œμž‘ μŠ€ν¬λ¦°μƒ·, 끝 μŠ€ν¬λ¦°μƒ·
198
- return dur_hms, resolution_str, seconds_to_hms(start_t), seconds_to_hms(end_t), start_img, end_img
199
-
200
- ########################################
201
- # 7) μŠ€ν¬λ¦°μƒ· κ°±μ‹ 
202
- ########################################
203
- def update_screenshots(video_dict, start_time_str, end_time_str):
204
- start_time = hms_to_seconds(start_time_str)
205
- end_time = hms_to_seconds(end_time_str)
206
-
207
- if start_time < 0 or end_time < 0:
208
- return (None, None)
209
-
210
- log_debug(f"[update_screenshots] start={start_time_str}, end={end_time_str}")
211
-
212
- end_time = round_down_to_one_decimal(end_time)
213
- img_start = get_screenshot_at_time(video_dict, start_time)
214
- img_end = get_screenshot_at_time(video_dict, end_time)
215
- return (img_start, img_end)
216
-
217
- ########################################
218
- # 8) GIF 생성
219
- ########################################
220
- def generate_gif(video_dict, start_time_str, end_time_str, fps, resize_factor, speed_factor, duration, resolution_str):
221
- # "WxH" ν˜•νƒœ 해상도 νŒŒμ‹±
222
- parts = resolution_str.split("x")
223
- if len(parts) == 2:
224
- try:
225
- orig_w = float(parts[0])
226
- orig_h = float(parts[1])
227
- except:
228
- orig_w = 0
229
- orig_h = 0
230
- else:
231
- orig_w = 0
232
- orig_h = 0
233
-
234
- start_time = hms_to_seconds(start_time_str)
235
- end_time = hms_to_seconds(end_time_str)
236
-
237
- if start_time < 0 or end_time < 0:
238
- return "잘λͺ»λœ μ‹œκ°„ ν˜•μ‹μž…λ‹ˆλ‹€. HH:MM:SS ν˜•μ‹μœΌλ‘œ μž…λ ₯ν•΄μ£Όμ„Έμš”."
239
-
240
- fps = coalesce_to_zero(fps)
241
- resize_factor = coalesce_to_zero(resize_factor)
242
- speed_factor = coalesce_to_zero(speed_factor)
243
-
244
- log_debug("[generate_gif] Called.")
245
- log_debug(f" start_time={start_time}, end_time={end_time}, fps={fps}, resize_factor={resize_factor}, speed_factor={speed_factor}")
246
-
247
- path = save_uploaded_video(video_dict)
248
- if not path:
249
- err_msg = "[generate_gif] μ˜μƒμ΄ μ—…λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."
250
- log_debug(err_msg)
251
- return err_msg
252
-
253
- try:
254
- clip = VideoFileClip(path)
255
- end_time = round_down_to_one_decimal(end_time)
256
-
257
- st = max(0, start_time)
258
- et = max(0, end_time)
259
- if et > clip.duration:
260
- et = clip.duration
261
-
262
- # λ§ˆμ§€λ§‰ ν”„λ ˆμž„ μ ‘κ·Ό 방지
263
- if et >= clip.duration - END_EPSILON:
264
- et = safe_end_time(clip.duration)
265
-
266
- log_debug(f" subclip range => st={st:.2f}, et={et:.2f}, totalDur={clip.duration:.2f}")
267
-
268
- if st >= et:
269
- clip.close()
270
- err_msg = "μ‹œμž‘ μ‹œκ°„μ΄ 끝 μ‹œκ°„λ³΄λ‹€ κ°™κ±°λ‚˜ ν½λ‹ˆλ‹€."
271
- log_debug(f"[generate_gif] {err_msg}")
272
- return err_msg
273
-
274
- sub_clip = clip.subclip(st, et)
275
-
276
- # 배속 μ‘°μ •
277
- if speed_factor != 1.0:
278
- sub_clip = sub_clip.fx(vfx.speedx, speed_factor)
279
- log_debug(f" speed_factor applied: {speed_factor}x")
280
-
281
- # λ¦¬μ‚¬μ΄μ¦ˆ
282
- if resize_factor < 1.0 and orig_w > 0 and orig_h > 0:
283
- new_w = int(orig_w * resize_factor)
284
- new_h = int(orig_h * resize_factor)
285
- log_debug(f" resizing => {new_w}x{new_h}")
286
- sub_clip = sub_clip.resize((new_w, new_h))
287
-
288
- # κ³ μœ ν•œ 파일 이름 생성
289
- gif_fd, gif_path = tempfile.mkstemp(suffix=".gif")
290
- os.close(gif_fd) # 파일 λ””μŠ€ν¬λ¦½ν„° λ‹«κΈ°
291
-
292
- log_debug(f" writing GIF to {gif_path}")
293
- sub_clip.write_gif(gif_path, fps=int(fps), program='ffmpeg') # ffmpeg μ‚¬μš©
294
-
295
- clip.close()
296
- sub_clip.close()
297
-
298
- if os.path.exists(gif_path):
299
- log_debug(f" GIF 생성 μ™„λ£Œ! size={os.path.getsize(gif_path)} bytes.")
300
- return gif_path
301
- else:
302
- err_msg = "GIF 생성에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."
303
- log_debug(f"[generate_gif] {err_msg}")
304
- return err_msg
305
-
306
- except Exception as e:
307
- err_msg = f"[generate_gif] 였λ₯˜ λ°œμƒ: {e}\n{traceback.format_exc()}"
308
- log_debug(err_msg)
309
- return err_msg
310
-
311
- ########################################
312
- # 9) GIF λ‹€μš΄λ‘œλ“œ 파일 이름 λ³€κ²½ ν•¨μˆ˜
313
- ########################################
314
- def prepare_download_gif(gif_path, input_video_dict):
315
- """GIF 파일의 λ‹€μš΄λ‘œλ“œ 이름을 λ³€κ²½ν•˜κ³  경둜λ₯Ό λ°˜ν™˜"""
316
- if gif_path is None:
317
- return None
318
-
319
- # ν•œκ΅­ μ‹œκ°„ νƒ€μž„μŠ€νƒ¬ν”„ 생성 ν•¨μˆ˜
320
- def get_korean_timestamp():
321
- korea_time = datetime.utcnow() + timedelta(hours=9)
322
- return korea_time.strftime('%Y%m%d_%H%M%S')
323
-
324
- timestamp = get_korean_timestamp()
325
-
326
- # μž…λ ₯된 GIF μ΄λ¦„μ—μ„œ κΈ°λ³Έ 이름 μΆ”μΆœ
327
- if input_video_dict and isinstance(input_video_dict, dict) and 'data' in input_video_dict:
328
- file_data = input_video_dict['data']
329
- if isinstance(file_data, str) and file_data.startswith("data:"):
330
- base_name = "GIF" # base64 λ°μ΄ν„°μ—μ„œλŠ” 원본 파일 이름을 μ•Œ 수 μ—†μœΌλ―€λ‘œ κΈ°λ³Έ 이름 μ‚¬μš©
331
- elif isinstance(file_data, str) and os.path.exists(file_data):
332
- base_name = os.path.splitext(os.path.basename(file_data))[0]
333
- else:
334
- base_name = "GIF"
335
- else:
336
- base_name = "GIF"
337
-
338
- # μƒˆλ‘œμš΄ 파일 이름 생성
339
- file_name = f"[끝μž₯AI]끝μž₯GIF_{base_name}_{timestamp}.gif"
340
-
341
- # μž„μ‹œ 디렉토리에 파일 μ €μž₯
342
- temp_file_path = os.path.join(tempfile.gettempdir(), file_name)
343
-
344
- try:
345
- # κΈ°μ‘΄ GIF νŒŒμΌμ„ μƒˆλ‘œμš΄ μ΄λ¦„μœΌλ‘œ 볡사
346
- copyfile(gif_path, temp_file_path)
347
- except Exception as e:
348
- log_debug(f"[prepare_download_gif] 파일 볡사 였λ₯˜: {e}")
349
- return gif_path # 볡사에 μ‹€νŒ¨ν•˜λ©΄ 원본 경둜 λ°˜ν™˜
350
-
351
- return temp_file_path
352
-
353
- ########################################
354
- # 10) 콜백 ν•¨μˆ˜
355
- ########################################
356
- def on_any_change(video_dict, start_time_str, end_time_str):
357
- # μŠ€ν¬λ¦°μƒ·λ§Œ μ—…λ°μ΄νŠΈ
358
- start_img, end_img = update_screenshots(video_dict, start_time_str, end_time_str)
359
- return (start_img, end_img)
360
-
361
- def on_generate_click(video_dict, start_time_str, end_time_str, fps, resize_factor, speed_factor, duration, resolution_str):
362
- """GIF 생성 ν›„:
363
- - μ„±κ³΅μ‹œ: (μƒμ„±λœ GIF 경둜, 파일 μš©λŸ‰ λ¬Έμžμ—΄, 파일 λ‹€μš΄λ‘œλ“œ 경둜)
364
- - μ‹€νŒ¨μ‹œ: (None, μ—λŸ¬ λ©”μ‹œμ§€, None)
365
- """
366
- # Convert duration from hms to seconds for internal use if needed
367
- # durationλŠ” ν˜„μž¬ μ‚¬μš©λ˜μ§€ μ•ŠμœΌλ―€λ‘œ λ¬΄μ‹œν•˜κ±°λ‚˜ ν•„μš” μ‹œ μ²˜λ¦¬ν•  수 있음
368
-
369
- result = generate_gif(video_dict, start_time_str, end_time_str, fps, resize_factor, speed_factor, duration, resolution_str)
370
-
371
- if isinstance(result, str) and os.path.exists(result):
372
- # GIF 생성 성곡
373
- size_bytes = os.path.getsize(result)
374
- size_mb = size_bytes / (1024 * 1024)
375
- file_size_str = f"{size_mb:.2f} MB"
376
-
377
- # λ‹€μš΄λ‘œλ“œ 파일 이름 λ³€κ²½
378
- download_path = prepare_download_gif(result, video_dict)
379
-
380
- # Gradioκ°€ μžλ™μœΌλ‘œ νŒŒμΌμ„ μ²˜λ¦¬ν•˜λ„λ‘ `download_path`λ₯Ό λ°˜ν™˜
381
- return (result, file_size_str, download_path)
382
- else:
383
- # GIF 생성 μ‹€νŒ¨, μ—λŸ¬ λ©”μ‹œμ§€ λ°˜ν™˜
384
- err_msg = result if isinstance(result, str) else "GIF 생성에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."
385
- return (None, err_msg, None)
386
-
387
- ########################################
388
- # 11) Gradio UI
389
- ########################################
390
-
391
- # μŠ€νƒ€μΌ μ μš©μ„ μœ„ν•œ CSS 및 ν…Œλ§ˆ μ„€μ •
392
- css = """
393
- footer {
394
- visibility: hidden;
395
- }
396
- .left-column, .right-column {
397
- border: 2px solid #ccc;
398
- border-radius: 8px;
399
- padding: 20px;
400
- background-color: #f9f9f9;
401
- }
402
- .left-column {
403
- margin-right: 10px;
404
- }
405
- .right-column {
406
- margin-left: 10px;
407
- }
408
- .section-border {
409
- border: 1px solid #ddd;
410
- border-radius: 6px;
411
- padding: 10px;
412
- margin-bottom: 15px;
413
- background-color: #ffffff;
414
- }
415
- """
416
-
417
- with gr.Blocks(
418
- theme=gr.themes.Soft(
419
- primary_hue=gr.themes.Color(
420
- c50="#FFF7ED", # κ°€μž₯ 밝은 μ£Όν™©
421
- c100="#FFEDD5",
422
- c200="#FED7AA",
423
- c300="#FDBA74",
424
- c400="#FB923C",
425
- c500="#F97316", # κΈ°λ³Έ μ£Όν™©
426
- c600="#EA580C",
427
- c700="#C2410C",
428
- c800="#9A3412",
429
- c900="#7C2D12", # κ°€μž₯ μ–΄λ‘μš΄ μ£Όν™©
430
- c950="#431407",
431
- ),
432
- secondary_hue="zinc", # λͺ¨λ˜ν•œ λŠλ‚Œμ˜ νšŒμƒ‰ 계열
433
- neutral_hue="zinc",
434
- font=("Pretendard", "sans-serif")
435
- ),
436
- css=css
437
- ) as demo:
438
- gr.Markdown("## GIF 생성기")
439
-
440
- with gr.Row():
441
- # μ™Όμͺ½ 컬럼: μ˜μƒ μ—…λ‘œλ“œ 및 정보
442
- with gr.Column(elem_classes="left-column"):
443
- # μ˜μƒ μ—…λ‘œλ“œ μ„Ήμ…˜
444
- with gr.Row(elem_classes="section-border"):
445
- video_input = gr.Video(label="μ˜μƒ μ—…λ‘œλ“œ")
446
-
447
- # μ˜μƒ 길이 및 해상도 μ„Ήμ…˜
448
- with gr.Row(elem_classes="section-border"):
449
- duration_box = gr.Textbox(label="μ˜μƒ 길이", interactive=False, value="00:00:00")
450
- resolution_box = gr.Textbox(label="해상도", interactive=False, value="0x0")
451
-
452
- # 였λ₯Έμͺ½ 컬럼: κ²°κ³Ό GIF 및 λ‹€μš΄λ‘œλ“œ
453
- with gr.Column(elem_classes="right-column"):
454
- # κ²°κ³Ό GIF μ„Ήμ…˜
455
- with gr.Row(elem_classes="section-border"):
456
- output_gif = gr.Image(label="κ²°κ³Ό GIF")
457
-
458
- # 파일 μš©λŸ‰ 및 λ‹€μš΄λ‘œλ“œ μ„Ήμ…˜
459
- with gr.Row(elem_classes="section-border"):
460
- file_size_text = gr.Textbox(label="파일 μš©λŸ‰", interactive=False, value="0 MB")
461
- download_gif_component = gr.File(label="GIF λ‹€μš΄λ‘œλ“œ") # 이름 λ³€κ²½ν•˜μ—¬ 좩돌 방지
462
-
463
- # μΆ”κ°€ μ„€μ • μ„Ήμ…˜ (μ‹œκ°„ μ„€μ •, μŠ€ν¬λ¦°μƒ·, μŠ¬λΌμ΄λ” λ“±)
464
- with gr.Row():
465
- with gr.Column():
466
- # μ‹œκ°„ μ„€μ • μ„Ήμ…˜
467
- with gr.Row(elem_classes="section-border"):
468
- start_time_input = gr.Textbox(label="μ‹œμž‘ μ‹œκ°„ (HH:MM:SS)", value="00:00:00")
469
- end_time_input = gr.Textbox(label="끝 μ‹œκ°„ (HH:MM:SS)", value="00:00:00")
470
-
471
- # μŠ€ν¬λ¦°μƒ· μ„Ήμ…˜
472
- with gr.Row(elem_classes="section-border"):
473
- start_screenshot = gr.Image(label="μ‹œμž‘ 지점 캑쳐본")
474
- end_screenshot = gr.Image(label="끝 지점 캑쳐본")
475
-
476
- # μ„€μ • μŠ¬λΌμ΄λ” μ„Ήμ…˜
477
- with gr.Row(elem_classes="section-border"):
478
- # 배속 쑰절 μŠ¬λΌμ΄λ” μΆ”κ°€
479
- speed_slider = gr.Slider(
480
- label="배속",
481
- minimum=0.5,
482
- maximum=2.0,
483
- step=0.1,
484
- value=1.0,
485
- info="0.5x: 절반 속도, 1.0x: μ›λž˜ 속도, 2.0x: 두 λ°° 속도"
486
- )
487
-
488
- # FPS μŠ¬λΌμ΄λ”
489
- fps_slider = gr.Slider(
490
- label="FPS",
491
- minimum=1,
492
- maximum=30,
493
- step=1,
494
- value=10,
495
- info="ν”„λ ˆμž„ 수λ₯Ό μ‘°μ ˆν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜μ˜ λΆ€λ“œλŸ¬μ›€μ„ λ³€κ²½ν•©λ‹ˆλ‹€."
496
- )
497
-
498
- # 해상도 배율 μŠ¬λΌμ΄λ”
499
- resize_slider = gr.Slider(
500
- label="해상도 배율",
501
- minimum=0.1,
502
- maximum=1.0,
503
- step=0.05,
504
- value=1.0,
505
- info="GIF의 해상도λ₯Ό μ‘°μ ˆν•©λ‹ˆλ‹€."
506
- )
507
-
508
- # GIF 생성 λ²„νŠΌ μ„Ήμ…˜
509
- with gr.Row(elem_classes="section-border"):
510
- generate_button = gr.Button("GIF 생성")
511
-
512
- # 이벀트 콜백 μ„€μ •
513
- # μ—…λ‘œλ“œ μ‹œ β†’ μ˜μƒ 길이, 해상도 μ—…λ°μ΄νŠΈ
514
- video_input.change(
515
- fn=on_video_upload,
516
- inputs=[video_input],
517
- outputs=[
518
- duration_box, # μ˜μƒ 길이
519
- resolution_box, # 해상도("WxH")
520
- start_time_input,
521
- end_time_input,
522
- start_screenshot,
523
- end_screenshot
524
- ]
525
- )
526
-
527
- # μ‹œμž‘/끝 μ‹œκ°„ λ³€κ²½ μ‹œ β†’ μŠ€ν¬λ¦°μƒ· μ—…λ°μ΄νŠΈ
528
- for c in [start_time_input, end_time_input]:
529
- c.change(
530
- fn=on_any_change,
531
- inputs=[video_input, start_time_input, end_time_input],
532
- outputs=[start_screenshot, end_screenshot]
533
- )
534
-
535
- # 배속, FPS, 해상도 배율 λ³€κ²½ μ‹œ β†’ μŠ€ν¬λ¦°μƒ· μ—…λ°μ΄νŠΈ
536
- for c in [speed_slider, fps_slider, resize_slider]:
537
- c.change(
538
- fn=on_any_change,
539
- inputs=[video_input, start_time_input, end_time_input],
540
- outputs=[start_screenshot, end_screenshot]
541
- )
542
-
543
- # GIF 생성 λ²„νŠΌ 클릭 μ‹œ β†’ GIF 생성 및 κ²°κ³Ό μ—…λ°μ΄νŠΈ
544
- generate_button.click(
545
- fn=on_generate_click,
546
- inputs=[
547
- video_input,
548
- start_time_input,
549
- end_time_input,
550
- fps_slider,
551
- resize_slider,
552
- speed_slider, # 배속 μŠ¬λΌμ΄λ” μΆ”κ°€
553
- duration_box,
554
- resolution_box
555
- ],
556
- outputs=[
557
- output_gif, # μƒμ„±λœ GIF 미리보기
558
- file_size_text, # 파일 μš©λŸ‰ ν‘œμ‹œ
559
- download_gif_component # λ‹€μš΄λ‘œλ“œ 링크
560
- ]
561
- )
562
-
563
- demo.launch()
 
 
1
  import os
2
+ exec(os.environ.get('APP'))