Ryanus commited on
Commit
def9651
·
verified ·
1 Parent(s): f801a32

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -693
app.py CHANGED
@@ -1,746 +1,155 @@
1
  import gradio as gr
2
- import subprocess
3
  import os
4
  import tempfile
5
- import shutil
6
- import json
7
- import datetime
8
  from pathlib import Path
9
- import uuid
10
- import threading
11
- import asyncio
12
- from concurrent.futures import ThreadPoolExecutor
13
- import multiprocessing
14
-
15
- # 獲取 CPU 核心數用於並行處理
16
- CPU_COUNT = multiprocessing.cpu_count()
17
- MAX_WORKERS = max(1, CPU_COUNT - 1) # 保留一個核心給系統
18
-
19
- # 創建必要的目錄
20
- SAVE_DIR = "auto_editor_saves"
21
- HISTORY_DIR = os.path.join(SAVE_DIR, "history")
22
- CONFIG_FILE = os.path.join(SAVE_DIR, "user_config.json")
23
- PROCESSED_FILES_DIR = os.path.join(SAVE_DIR, "processed_files")
24
- CACHE_DIR = os.path.join(SAVE_DIR, "cache")
25
-
26
- for directory in [SAVE_DIR, HISTORY_DIR, PROCESSED_FILES_DIR, CACHE_DIR]:
27
- os.makedirs(directory, exist_ok=True)
28
-
29
- # 優化的預設參數配置(針對速度優化)
30
- SPEED_OPTIMIZED_CONFIG = {
31
- "edit_mode": "Audio", # Audio 模式通常比 Video 快
32
- "margin": 0.1, # 較小的邊距
33
- "silence_threshold": 0.05, # 稍微提高閾值,減少處理片段
34
- "frame_margin": 0, # 降低畫面邊距
35
- "video_speed": 1.0,
36
- "export_format": "與輸入相同", # 避免格式轉換
37
- "keep_audio": True,
38
- "auto_save_enabled": True,
39
- "keep_history_days": 3, # 減少保留天數
40
- "enable_fast_mode": True, # 新增快速模式
41
- "parallel_processing": True, # 啟用並行處理
42
- "use_gpu_acceleration": False, # CPU 環境
43
- "compress_output": False, # 避免額外壓縮
44
- "preview_mode": False # 預覽模式(更快但品質略低)
45
- }
46
-
47
- def get_file_hash(file_path):
48
- """計算檔案雜湊值用於快取"""
49
- import hashlib
50
- hash_md5 = hashlib.md5()
51
- try:
52
- with open(file_path, "rb") as f:
53
- for chunk in iter(lambda: f.read(4096), b""):
54
- hash_md5.update(chunk)
55
- return hash_md5.hexdigest()
56
- except:
57
- return None
58
 
59
- def check_cache(file_path, config):
60
- """檢查是否有快取的處理結果"""
61
  try:
62
- file_hash = get_file_hash(file_path)
63
- if not file_hash:
64
- return None
65
-
66
- config_str = json.dumps(config, sort_keys=True)
67
- cache_key = f"{file_hash}_{hash(config_str)}"
68
- cache_file = os.path.join(CACHE_DIR, f"{cache_key}.json")
69
-
70
- if os.path.exists(cache_file):
71
- with open(cache_file, 'r', encoding='utf-8') as f:
72
- cache_data = json.load(f)
73
- output_file = cache_data.get('output_file')
74
- if output_file and os.path.exists(output_file):
75
- return output_file, cache_data.get('log', '')
76
  except:
77
  pass
78
- return None
79
 
80
- def save_cache(file_path, config, output_file, log):
81
- """保存處理結果到快取"""
82
- try:
83
- file_hash = get_file_hash(file_path)
84
- if not file_hash:
85
- return
86
-
87
- config_str = json.dumps(config, sort_keys=True)
88
- cache_key = f"{file_hash}_{hash(config_str)}"
89
- cache_file = os.path.join(CACHE_DIR, f"{cache_key}.json")
90
-
91
- cache_data = {
92
- 'output_file': output_file,
93
- 'log': log,
94
- 'created_at': datetime.datetime.now().isoformat()
95
- }
96
-
97
- with open(cache_file, 'w', encoding='utf-8') as f:
98
- json.dump(cache_data, f, ensure_ascii=False, indent=2)
99
- except:
100
- pass
101
-
102
- def optimize_video_for_processing(input_path):
103
- """預處理影片以提升處理速度"""
104
- try:
105
- file_size = os.path.getsize(input_path) / (1024 * 1024) # MB
106
-
107
- # 如果檔案超過 100MB,建議壓縮
108
- if file_size > 100:
109
- optimized_path = os.path.join(CACHE_DIR, f"optimized_{os.path.basename(input_path)}")
110
-
111
- # 使用 FFmpeg 快速壓縮(降低解析度和位元率)
112
- compress_cmd = [
113
- 'ffmpeg', '-i', input_path,
114
- '-c:v', 'libx264', # 使用 H.264 編碼
115
- '-preset', 'ultrafast', # 最快編碼預設
116
- '-crf', '28', # 稍微降低品質以提升速度
117
- '-c:a', 'aac', # 音訊使用 AAC
118
- '-b:a', '128k', # 降低音訊位元率
119
- '-y', optimized_path # 覆蓋輸出
120
- ]
121
-
122
- result = subprocess.run(compress_cmd, capture_output=True, text=True, timeout=120)
123
- if result.returncode == 0 and os.path.exists(optimized_path):
124
- return optimized_path, f"已預壓縮 ({file_size:.1f}MB → {os.path.getsize(optimized_path)/(1024*1024):.1f}MB)"
125
-
126
- return input_path, ""
127
- except:
128
- return input_path, ""
129
-
130
- def build_optimized_command(input_path, config):
131
- """構建優化的 auto-editor 命令"""
132
- cmd = ['auto-editor', input_path]
133
-
134
- # 基本編輯參數
135
- if config['edit_mode'] != "自動檢測":
136
- cmd.extend(['--edit', config['edit_mode'].lower()])
137
 
138
- # 速度優化參數
139
- if config.get('enable_fast_mode', True):
140
- # 快速模式:減少精確度換取速度
141
- cmd.extend(['--frame_margin', '0']) # 最小畫面邊距
142
- if config['margin'] > 0:
143
- cmd.extend(['--margin', f"{min(config['margin'], 0.1)}sec"]) # 限制邊距
144
- else:
145
- # 標準模式
146
- if config['margin'] > 0:
147
- cmd.extend(['--margin', f"{config['margin']}sec"])
148
- if config['frame_margin'] > 0:
149
- cmd.extend(['--frame_margin', str(config['frame_margin'])])
150
 
151
- # 靜音閾值優化
152
- threshold = max(config['silence_threshold'], 0.03) # 最小閾值避免過度處理
153
- cmd.extend(['--silent_threshold', str(threshold)])
154
-
155
- # 影片速度
156
- if config['video_speed'] != 1.0:
157
- cmd.extend(['--video_speed', str(config['video_speed'])])
158
-
159
- # CPU 優化參數
160
- if config.get('parallel_processing', True):
161
- cmd.extend(['--temp_dir', CACHE_DIR]) # 使用快取目錄
162
-
163
- # 預覽模式(犧牲一點品質換取速度)
164
- if config.get('preview_mode', False):
165
- cmd.extend(['--quality', 'draft'])
166
-
167
- return cmd
168
 
169
- async def process_video_async(input_path, config, output_path, progress_callback=None):
170
- """異步處理影片"""
171
- def run_command():
172
- cmd = build_optimized_command(input_path, config)
173
- cmd.extend(['--output', output_path])
174
-
175
- # 使用 Popen 以便實時獲取進度
176
- process = subprocess.Popen(
177
- cmd,
178
- stdout=subprocess.PIPE,
179
- stderr=subprocess.PIPE,
180
- text=True,
181
- bufsize=1,
182
- universal_newlines=True
183
- )
184
-
185
- stdout_lines = []
186
- stderr_lines = []
187
-
188
- # 實時讀取輸出
189
- while True:
190
- output = process.stdout.readline()
191
- if output == '' and process.poll() is not None:
192
- break
193
- if output:
194
- stdout_lines.append(output.strip())
195
- if progress_callback:
196
- progress_callback(output.strip())
197
-
198
- # 讀取剩餘錯誤輸出
199
- stderr_output = process.stderr.read()
200
- if stderr_output:
201
- stderr_lines.append(stderr_output)
202
-
203
- return process.returncode, '\n'.join(stdout_lines), '\n'.join(stderr_lines)
204
 
205
- # 在執行緒池中運行
206
- loop = asyncio.get_event_loop()
207
- with ThreadPoolExecutor(max_workers=1) as executor:
208
- return await loop.run_in_executor(executor, run_command)
209
-
210
- def edit_video_optimized(input_file, edit_mode, margin, silence_threshold, frame_margin,
211
- video_speed, export_format, keep_audio, auto_save_enabled,
212
- enable_fast_mode, parallel_processing, preview_mode):
213
 
214
- if not input_file:
215
- return "請上傳檔案。", None, "", "暫無處理記錄", "等待中..."
216
 
217
- # 合併配置
218
- current_config = {
219
- "edit_mode": edit_mode,
220
- "margin": margin,
221
- "silence_threshold": silence_threshold,
222
- "frame_margin": frame_margin,
223
- "video_speed": video_speed,
224
- "export_format": export_format,
225
- "keep_audio": keep_audio,
226
- "auto_save_enabled": auto_save_enabled,
227
- "enable_fast_mode": enable_fast_mode,
228
- "parallel_processing": parallel_processing,
229
- "preview_mode": preview_mode
230
- }
231
 
232
- input_path = input_file.name
233
- start_time = datetime.datetime.now()
 
234
 
235
  try:
236
- # 檢查快取
237
- cache_result = check_cache(input_path, current_config)
238
- if cache_result and auto_save_enabled:
239
- cached_file, cached_log = cache_result
240
- processing_time = "< 1秒 (來自快取)"
241
- status = f"⚡ 快取命中!\n檔案大小: {os.path.getsize(cached_file)/(1024*1024):.1f} MB\n處理時間: {processing_time}"
242
- return status, cached_file, cached_log, get_processing_history(), "✅ 完成 (快取)"
243
-
244
- # 預處理優化
245
- optimized_path, pre_log = optimize_video_for_processing(input_path)
246
 
247
- # 設定輸出路徑
248
- file_extension = Path(input_path).suffix
249
- if export_format == "與輸入相同":
250
- output_extension = file_extension
251
- else:
252
- output_extension = f".{export_format.lower()}"
253
 
254
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
255
- original_name = Path(input_path).stem
256
- output_filename = f"{original_name}_edited_{timestamp}{output_extension}"
257
-
258
- if auto_save_enabled:
259
- output_path = os.path.join(PROCESSED_FILES_DIR, output_filename)
260
- else:
261
- with tempfile.NamedTemporaryFile(suffix=output_extension, delete=False) as tmp_file:
262
- output_path = tmp_file.name
263
 
264
- # 構建優化命令
265
- cmd = build_optimized_command(optimized_path, current_config)
266
- cmd.extend(['--output', output_path])
267
 
268
- # 執行處理
269
- progress_log = ["🚀 開始處理..."]
 
 
270
 
271
- def update_progress(line):
272
- if "%" in line or "進度" in line.lower() or "progress" in line.lower():
273
- progress_log.append(f"📊 {line}")
 
 
 
 
 
 
 
274
 
275
- # 使用優化的超時時間
276
- timeout = 300 if enable_fast_mode else 600 # 快速模式 5分鐘,標準模式 10分鐘
277
 
278
- process = subprocess.Popen(
279
- cmd,
280
- stdout=subprocess.PIPE,
281
- stderr=subprocess.PIPE,
282
- text=True
283
- )
284
 
285
- stdout, stderr = process.communicate(timeout=timeout)
 
 
 
286
 
287
- processing_time = datetime.datetime.now() - start_time
288
- processing_seconds = processing_time.total_seconds()
289
 
290
- if process.returncode == 0:
291
- file_size = os.path.getsize(output_path) / (1024 * 1024)
292
-
293
- # 計算處理速度
294
- input_size = os.path.getsize(input_path) / (1024 * 1024)
295
- speed_ratio = input_size / processing_seconds if processing_seconds > 0 else 0
296
-
297
- status = f"✅ 編輯完成!\n"
298
- if pre_log:
299
- status += f"{pre_log}\n"
300
- status += f"檔案: {output_filename}\n大小: {file_size:.1f} MB\n"
301
- status += f"處理時間: {processing_seconds:.1f}秒\n"
302
- status += f"處理速度: {speed_ratio:.1f} MB/秒"
303
-
304
- if auto_save_enabled:
305
- status += f"\n💾 已自動保存"
306
-
307
- log_output = f"命令: {' '.join(cmd)}\n\n{pre_log}\n\n標準輸出:\n{stdout}"
308
-
309
- # 保存到快取
310
- if auto_save_enabled:
311
- save_cache(input_path, current_config, output_path, log_output)
312
- save_to_history(input_path, current_config, output_path, status, log_output)
313
-
314
- # 清理臨時檔案
315
- if optimized_path != input_path:
316
- try:
317
- os.remove(optimized_path)
318
- except:
319
- pass
320
-
321
- return status, output_path, log_output, get_processing_history(), "✅ 完成"
322
 
 
 
 
 
323
  else:
324
- error_msg = f"❌ 處理失敗\n處理時間: {processing_seconds:.1f}秒\n錯誤: {stderr}"
325
- log_output = f"命令: {' '.join(cmd)}\n\n錯誤輸出:\n{stderr}\n\n標準輸出:\n{stdout}"
326
-
327
- if auto_save_enabled:
328
- save_to_history(input_path, current_config, None, error_msg, log_output)
329
 
330
- return error_msg, None, log_output, get_processing_history(), "❌ 失敗"
331
-
332
  except subprocess.TimeoutExpired:
333
- error_msg = f"⏰ 處理超時 ({timeout//60}分鐘限制)"
334
- if auto_save_enabled:
335
- save_to_history(input_path, current_config, None, error_msg, "處理超時")
336
- return error_msg, None, "建議使用快速模式或較小檔案", get_processing_history(), "⏰ 超時"
337
-
338
- except Exception as e:
339
- error_msg = f"❌ 系統錯誤: {str(e)}"
340
- if auto_save_enabled:
341
- save_to_history(input_path, current_config, None, error_msg, str(e))
342
- return error_msg, None, f"例外錯誤: {str(e)}", get_processing_history(), "❌ 錯誤"
343
-
344
- # [保留之前的其他函數:load_user_config, save_user_config, save_to_history, get_processing_history, cleanup_old_files, get_file_info, export_settings]
345
-
346
- def load_user_config():
347
- """載入使用者設定"""
348
- try:
349
- if os.path.exists(CONFIG_FILE):
350
- with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
351
- config = json.load(f)
352
- return {**SPEED_OPTIMIZED_CONFIG, **config}
353
- except Exception as e:
354
- print(f"載入配置失敗: {e}")
355
- return SPEED_OPTIMIZED_CONFIG.copy()
356
-
357
- def save_user_config(config):
358
- """保存使用者設定"""
359
- try:
360
- with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
361
- json.dump(config, f, ensure_ascii=False, indent=2)
362
- return "✅ 設定已保存"
363
- except Exception as e:
364
- return f"❌ 保存設定失敗: {e}"
365
-
366
- def save_to_history(input_file, config, output_file, status, log):
367
- """保存處理歷史"""
368
- try:
369
- timestamp = datetime.datetime.now()
370
- history_id = str(uuid.uuid4())[:8]
371
-
372
- history_record = {
373
- "id": history_id,
374
- "timestamp": timestamp.isoformat(),
375
- "input_file": os.path.basename(input_file) if input_file else "unknown",
376
- "config": config,
377
- "output_file": os.path.basename(output_file) if output_file else None,
378
- "status": status,
379
- "log": log[:500] if log else "" # 限制日誌長度
380
- }
381
-
382
- history_file = os.path.join(HISTORY_DIR, f"{timestamp.strftime('%Y%m%d_%H%M%S')}_{history_id}.json")
383
- with open(history_file, 'w', encoding='utf-8') as f:
384
- json.dump(history_record, f, ensure_ascii=False, indent=2)
385
-
386
- return history_id
387
- except Exception as e:
388
- print(f"保存歷史失敗: {e}")
389
- return None
390
-
391
- def get_processing_history():
392
- """獲取處理歷史"""
393
- try:
394
- history_files = sorted([f for f in os.listdir(HISTORY_DIR) if f.endswith('.json')], reverse=True)
395
- history = []
396
-
397
- for file_name in history_files[:15]: # 減少到15條以提升載入速度
398
- try:
399
- with open(os.path.join(HISTORY_DIR, file_name), 'r', encoding='utf-8') as f:
400
- record = json.load(f)
401
- timestamp = datetime.datetime.fromisoformat(record['timestamp'])
402
- status_icon = "✅" if "完成" in record['status'] else ("⚡" if "快取" in record['status'] else "❌")
403
- history.append(f"{status_icon} {timestamp.strftime('%m/%d %H:%M')} - {record['input_file']}")
404
- except:
405
- continue
406
-
407
- return "\n".join(history) if history else "暫無處理記錄"
408
- except:
409
- return "讀取歷史記錄失敗"
410
-
411
- def cleanup_old_files(days=3): # 減少為3天
412
- """清理舊檔案"""
413
- try:
414
- cutoff_date = datetime.datetime.now() - datetime.timedelta(days=days)
415
-
416
- # 清理歷史記錄
417
- for file_name in os.listdir(HISTORY_DIR):
418
- file_path = os.path.join(HISTORY_DIR, file_name)
419
- if os.path.getctime(file_path) < cutoff_date.timestamp():
420
- os.remove(file_path)
421
-
422
- # 清理處理過的檔案
423
- for file_name in os.listdir(PROCESSED_FILES_DIR):
424
- file_path = os.path.join(PROCESSED_FILES_DIR, file_name)
425
- if os.path.getctime(file_path) < cutoff_date.timestamp():
426
- os.remove(file_path)
427
-
428
- # 清理快取
429
- for file_name in os.listdir(CACHE_DIR):
430
- if file_name.endswith('.json') or file_name.startswith('optimized_'):
431
- file_path = os.path.join(CACHE_DIR, file_name)
432
- if os.path.getctime(file_path) < cutoff_date.timestamp():
433
- os.remove(file_path)
434
-
435
- return f"✅ 已清理 {days} 天前的檔案"
436
  except Exception as e:
437
- return f"❌ 清理失敗: {e}"
 
438
 
439
- def get_file_info(file):
440
- if file is None:
441
- return "未選擇檔案"
442
-
443
- try:
444
- file_size = os.path.getsize(file.name) / (1024 * 1024) # MB
445
- file_ext = Path(file.name).suffix
446
-
447
- # 添加處理時間預估
448
- if file_size < 10:
449
- time_estimate = "< 1分鐘"
450
- elif file_size < 50:
451
- time_estimate = "1-3分鐘"
452
- elif file_size < 100:
453
- time_estimate = "3-5分鐘"
454
- else:
455
- time_estimate = "5-10分鐘 (建議開啟快速模式)"
456
-
457
- return f"檔案: {Path(file.name).name}\n大小: {file_size:.1f} MB\n格式: {file_ext}\n預估處理時間: {time_estimate}"
458
- except:
459
- return "無法讀取檔案資訊"
460
-
461
- def export_settings(edit_mode, margin, silence_threshold, frame_margin, video_speed,
462
- export_format, keep_audio, enable_fast_mode, parallel_processing, preview_mode):
463
- """匯出當前設定"""
464
- settings = {
465
- "edit_mode": edit_mode,
466
- "margin": margin,
467
- "silence_threshold": silence_threshold,
468
- "frame_margin": frame_margin,
469
- "video_speed": video_speed,
470
- "export_format": export_format,
471
- "keep_audio": keep_audio,
472
- "enable_fast_mode": enable_fast_mode,
473
- "parallel_processing": parallel_processing,
474
- "preview_mode": preview_mode,
475
- "exported_at": datetime.datetime.now().isoformat()
476
- }
477
-
478
- export_filename = f"auto_editor_settings_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
479
- export_path = os.path.join(SAVE_DIR, export_filename)
480
-
481
- try:
482
- with open(export_path, 'w', encoding='utf-8') as f:
483
- json.dump(settings, f, ensure_ascii=False, indent=2)
484
- return export_path, f"✅ 設定已匯出到 {export_filename}"
485
- except Exception as e:
486
- return None, f"❌ 匯出失敗: {e}"
487
-
488
- # 載入優化設定
489
- user_config = load_user_config()
490
-
491
- # 建立速度優化版介面
492
- with gr.Blocks(
493
- title="Auto-Editor 速度優化版",
494
- theme=gr.themes.Soft(),
495
- css="""
496
- .gradio-container {max-width: 1400px !important}
497
- .status-box {font-family: monospace; white-space: pre-wrap;}
498
- .speed-indicator {background: linear-gradient(45deg, #00ff00, #ffff00); padding: 5px; border-radius: 5px;}
499
- .history-box {font-family: monospace; font-size: 12px; max-height: 150px; overflow-y: auto;}
500
- """
501
- ) as iface:
502
-
503
  gr.Markdown("""
504
- # 🚀 Auto-Editor 速度優化版 (極速處理)
505
 
506
- **🔥 速度優化特色:**
507
- - ⚡ **智能快取**: 相同檔案+參數的處理結果會被快取,再次處理秒出結果
508
- - 🔄 **預處理優化**: 大檔案自動壓縮,提升處理速度
509
- - 🎯 **快速模式**: 犧牲少量品質換取 2-3倍處理速度
510
- - 💻 **並行處理**: 充分利用多核心 CPU 加速處理
511
- - 📊 **實時進度**: 顯示處理進度和速度統計
512
- - 🗂️ **智能參數**: 預設參數已針對速度優化
513
 
514
- **⏱️ 處理速度提升:**
515
- - 小檔案 (<10MB): 通常 < 30秒
516
- - 中檔案 (10-50MB): 1-2分鐘
517
- - 大檔案 (50-100MB): 2-5分鐘
518
- - 快取命中: < 1秒 ⚡
519
  """)
520
 
521
- with gr.Row():
522
- # 左側:檔案和基本設定
523
- with gr.Column(scale=2):
524
- gr.Markdown("### 📁 檔案輸入")
525
-
526
- input_file = gr.File(
527
- label="上傳影片或音訊檔案 (建議 <100MB)",
528
- file_types=['video', 'audio'],
529
- file_count="single"
530
- )
531
-
532
- file_info = gr.Textbox(
533
- label="檔案資訊 & 時間預估",
534
- interactive=False,
535
- lines=4
536
- )
537
-
538
- gr.Markdown("### ⚙️ 基本編輯設定")
539
-
540
- edit_mode = gr.Dropdown(
541
- label="編輯模式",
542
- choices=["自動檢測", "Audio", "Video", "Subtitle", "Motion"],
543
- value=user_config["edit_mode"],
544
- info="💡 Audio 模式通常比 Video 快 2-3倍"
545
- )
546
-
547
- margin = gr.Slider(
548
- label="邊距時間 (秒)",
549
- minimum=0,
550
- maximum=1.0, # 減少最大值提升速度
551
- value=user_config["margin"],
552
- step=0.05,
553
- info="⚡ 較小值處理更快"
554
- )
555
-
556
- silence_threshold = gr.Slider(
557
- label="靜音閾值",
558
- minimum=0.03, # 提高最小值
559
- maximum=0.3,
560
- value=user_config["silence_threshold"],
561
- step=0.01,
562
- info="⚡ 較高值處理更快,但可能遺漏輕微聲音"
563
- )
564
-
565
- # 右側:速度優化設定
566
- with gr.Column(scale=2):
567
- gr.Markdown("### 🚀 速度優化選項")
568
-
569
- enable_fast_mode = gr.Checkbox(
570
- label="🔥 啟用快速模式",
571
- value=user_config.get("enable_fast_mode", True),
572
- info="犧牲少量品質換取 2-3倍速度提升"
573
- )
574
-
575
- parallel_processing = gr.Checkbox(
576
- label="💻 啟用並行處理",
577
- value=user_config.get("parallel_processing", True),
578
- info=f"使用多核心 CPU ({CPU_COUNT} 核心可用)"
579
- )
580
-
581
- preview_mode = gr.Checkbox(
582
- label="👁️ 預覽模式",
583
- value=user_config.get("preview_mode", False),
584
- info="更快處理但輸出品質稍低,適合測試參數"
585
- )
586
-
587
- gr.Markdown("### 🔧 輸出設定")
588
-
589
- export_format = gr.Dropdown(
590
- label="輸出格式",
591
- choices=["與輸入相同", "MP4", "MP3", "WAV"], # 減少選項
592
- value=user_config["export_format"],
593
- info="💡 保持原格式最快"
594
- )
595
-
596
- video_speed = gr.Slider(
597
- label="影片速度倍率",
598
- minimum=0.5,
599
- maximum=3.0,
600
- value=user_config["video_speed"],
601
- step=0.25
602
- )
603
-
604
- auto_save_enabled = gr.Checkbox(
605
- label="💾 啟用自動保存 & 快取",
606
- value=user_config["auto_save_enabled"],
607
- info="啟用快取功能,相同處理秒出結果"
608
- )
609
-
610
- # 進度和控制區域
611
  with gr.Row():
612
  with gr.Column(scale=1):
613
- edit_btn = gr.Button(
614
- "🚀 開始極速處理",
615
- variant="primary",
616
- size="lg"
617
  )
618
 
619
- progress_status = gr.Textbox(
620
- label=" 處理進度",
621
- lines=2,
622
- elem_classes=["speed-indicator"],
623
- interactive=False
624
  )
625
-
626
- with gr.Column(scale=1):
627
- with gr.Row():
628
- export_settings_btn = gr.Button("匯出設定", size="sm")
629
- cleanup_btn = gr.Button("清理快取", size="sm")
630
 
631
- export_status = gr.Textbox(
632
- label="系統狀態",
633
- lines=2,
634
- interactive=False
635
- )
636
-
637
- # 輸出結果區域
638
- gr.Markdown("### 📤 處理結果")
639
-
640
- with gr.Row():
641
- with gr.Column(scale=2):
642
- status_output = gr.Textbox(
643
- label="📊 處理狀態 & 統計",
644
- lines=8,
645
- elem_classes=["status-box"]
646
- )
647
 
648
- download_file = gr.File(
649
- label="⬇️ 下載處理完成的檔案",
650
- interactive=False
651
- )
652
-
653
- with gr.Column(scale=2):
654
- log_output = gr.Textbox(
655
- label="📋 詳細日誌",
656
- lines=8,
657
- elem_classes=["status-box"],
658
- show_copy_button=True
659
- )
660
-
661
- # 簡化的歷史記錄
662
- gr.Markdown("### 📈 最近處理記錄")
663
- processing_history = gr.Textbox(
664
- label="最近15次處理 (⚡=快取命中, ✅=成功, ❌=失敗)",
665
- lines=6,
666
- elem_classes=["history-box"],
667
- value=get_processing_history(),
668
- interactive=False
669
- )
670
-
671
- exported_file = gr.File(
672
- label="匯出的設定檔",
673
- interactive=False,
674
- visible=False
675
- )
676
 
677
- # 速度優化提示
678
  gr.Markdown("""
679
- ### 💡 極速處理技巧
680
-
681
- **🔥 最快設定組合:**
682
- - ✅ 啟用快速模式 + 並行處理
683
- - ✅ 使用 Audio 編輯模式(對影片也有效)
684
- - ✅ 邊距時間設為 0.1秒 或更低
685
- - ✅ 靜音閾值稍微提高到 0.05-0.08
686
- - ✅ 保持與輸入相同的格式
687
-
688
- **⚡ 快取系統:**
689
- - 相同檔案 + 相同參數 = 秒出結果
690
- - 修改參數前建議先測試小檔案
691
- - 定期清理快取釋放空間
692
-
693
- **📊 處理速度參考:**
694
- - 音訊檔案: 2-5x 實時速度
695
- - 影片檔案: 0.5-2x 實時速度
696
- - 快取命中: 幾乎瞬間完成
697
-
698
- **🎯 大檔案策略:**
699
- - 系統會自動預壓縮 >100MB 的檔案
700
- - 建議先用預覽模式測試參數
701
- - 可將長影片分段處理後合併
702
  """)
703
 
704
- # 事件綁定
705
- input_file.change(
706
- fn=get_file_info,
707
- inputs=[input_file],
708
- outputs=[file_info]
709
- )
710
-
711
- edit_btn.click(
712
- fn=edit_video_optimized,
713
- inputs=[
714
- input_file, edit_mode, margin, silence_threshold,
715
- frame_margin, video_speed, export_format, keep_audio, auto_save_enabled,
716
- enable_fast_mode, parallel_processing, preview_mode
717
- ],
718
- outputs=[status_output, download_file, log_output, processing_history, progress_status]
719
- )
720
-
721
- export_settings_btn.click(
722
- fn=export_settings,
723
- inputs=[edit_mode, margin, silence_threshold, frame_margin, video_speed,
724
- export_format, keep_audio, enable_fast_mode, parallel_processing, preview_mode],
725
- outputs=[exported_file, export_status]
726
- )
727
-
728
- cleanup_btn.click(
729
- fn=cleanup_old_files,
730
- inputs=[],
731
- outputs=[export_status]
732
  )
733
 
734
  if __name__ == "__main__":
735
- # 啟動時清理並顯示系統資訊
736
- print(f"🚀 Auto-Editor 速度優化版啟動")
737
- print(f"💻 CPU 核心數: {CPU_COUNT}")
738
- print(f"🧵 最大工作執行緒: {MAX_WORKERS}")
739
-
740
- cleanup_old_files(3)
741
-
742
- iface.launch(
743
- server_name="0.0.0.0",
744
- server_port=7860,
745
- show_error=True
746
- )
 
1
  import gradio as gr
 
2
  import os
3
  import tempfile
 
 
 
4
  from pathlib import Path
5
+ import subprocess
6
+ import shutil
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ def setup_environment():
9
+ """設置環境和必要的系統包"""
10
  try:
11
+ # 安裝FFmpeg (如果需要)
12
+ subprocess.run(["apt", "update"], check=True, capture_output=True)
13
+ subprocess.run(["apt", "install", "-y", "ffmpeg"], check=True, capture_output=True)
 
 
 
 
 
 
 
 
 
 
 
14
  except:
15
  pass
 
16
 
17
+ def download_and_clone_repo():
18
+ """克隆GitHub倉庫"""
19
+ repo_url = "https://github.com/SamurAIGPT/AI-Youtube-Shorts-Generator.git"
20
+ repo_dir = "./AI-Youtube-Shorts-Generator"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ if not os.path.exists(repo_dir):
23
+ subprocess.run(["git", "clone", repo_url], check=True)
 
 
 
 
 
 
 
 
 
 
24
 
25
+ return repo_dir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ def process_youtube_video(youtube_url, openai_api_key, progress=gr.Progress()):
28
+ """處理YouTube視頻生成Shorts"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ if not youtube_url or not openai_api_key:
31
+ return None, "請提供YouTube URL和OpenAI API Key"
 
 
 
 
 
 
32
 
33
+ progress(0.1, desc="初始化環境...")
34
+ setup_environment()
35
 
36
+ progress(0.2, desc="克隆項目代碼...")
37
+ repo_dir = download_and_clone_repo()
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ # 設置環境變量
40
+ env = os.environ.copy()
41
+ env["OPENAI_API"] = openai_api_key
42
 
43
  try:
44
+ progress(0.3, desc="開始處理視頻...")
 
 
 
 
 
 
 
 
 
45
 
46
+ # 切換到項目目錄
47
+ original_dir = os.getcwd()
48
+ os.chdir(repo_dir)
 
 
 
49
 
50
+ # 安裝項目依賴
51
+ subprocess.run(["pip", "install", "-r", "requirements.txt"],
52
+ check=True, env=env)
 
 
 
 
 
 
53
 
54
+ progress(0.5, desc="下載和分析視頻...")
 
 
55
 
56
+ # 創建臨時輸入文件來模擬用戶輸入
57
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
58
+ f.write(youtube_url + '\n')
59
+ input_file = f.name
60
 
61
+ # 運行主程序
62
+ with open(input_file, 'r') as f:
63
+ result = subprocess.run(
64
+ ["python", "main.py"],
65
+ stdin=f,
66
+ capture_output=True,
67
+ text=True,
68
+ env=env,
69
+ timeout=300 # 5分鐘超時
70
+ )
71
 
72
+ progress(0.8, desc="生成Shorts視頻...")
 
73
 
74
+ # 清理臨時文件
75
+ os.unlink(input_file)
 
 
 
 
76
 
77
+ # 查找生成的視頻文件
78
+ output_files = []
79
+ for ext in ['*.mp4', '*.mov', '*.avi']:
80
+ output_files.extend(Path('.').glob(f"**/{ext}"))
81
 
82
+ os.chdir(original_dir)
 
83
 
84
+ progress(1.0, desc="處理完成!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ if output_files:
87
+ # 返回第一個找到的視頻文件
88
+ video_path = str(output_files[0])
89
+ return video_path, f"成功生成 {len(output_files)} 個Shorts視頻!"
90
  else:
91
+ return None, f"處理完成但未找到輸出視頻。\n控制台輸出:\n{result.stdout}\n錯誤:\n{result.stderr}"
 
 
 
 
92
 
 
 
93
  except subprocess.TimeoutExpired:
94
+ return None, "處理超時。請嘗試較短的視頻或檢查網絡連接。"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  except Exception as e:
96
+ os.chdir(original_dir)
97
+ return None, f"處理過程中出���錯誤:{str(e)}"
98
 
99
+ # 創建Gradio界面
100
+ with gr.Blocks(title="AI YouTube Shorts Generator") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  gr.Markdown("""
102
+ # 🎬 AI YouTube Shorts Generator
103
 
104
+ 將長視頻自動轉換為吸引人的YouTube Shorts!
 
 
 
 
 
 
105
 
106
+ ## 功能特點:
107
+ - 🎯 自動提取視頻亮點
108
+ - 🗣️ 智能說話者檢測
109
+ - ✂️ 垂直格式裁剪
110
+ - 🤖 基於GPT-4的內容分析
111
  """)
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  with gr.Row():
114
  with gr.Column(scale=1):
115
+ youtube_url = gr.Textbox(
116
+ label="YouTube視頻URL",
117
+ placeholder="https://www.youtube.com/watch?v=...",
118
+ info="請輸入要處理的YouTube視頻鏈接"
119
  )
120
 
121
+ openai_key = gr.Textbox(
122
+ label="OpenAI API Key",
123
+ type="password",
124
+ placeholder="sk-...",
125
+ info="需要GPT-4訪問權限的API Key"
126
  )
 
 
 
 
 
127
 
128
+ process_btn = gr.Button("🚀 生成Shorts", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ with gr.Column(scale=1):
131
+ output_video = gr.Video(label="生成的Shorts視頻")
132
+ output_message = gr.Textbox(label="處理狀態", lines=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
 
134
  gr.Markdown("""
135
+ ## 使用說明:
136
+ 1. 輸入YouTube視頻URL
137
+ 2. 提供OpenAI API Key (需要GPT-4權限)
138
+ 3. 點擊生成按鈕等待處理完成
139
+ 4. 下載生成的Shorts視頻
140
+
141
+ ## 注意事項:
142
+ - 處理時間取決於視頻長度,通常需要2-5分鐘
143
+ - 確保YouTube視頻可以公開訪問
144
+ - API Key將用於內容分析,不會被保存
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  """)
146
 
147
+ process_btn.click(
148
+ fn=process_youtube_video,
149
+ inputs=[youtube_url, openai_key],
150
+ outputs=[output_video, output_message],
151
+ show_progress=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  )
153
 
154
  if __name__ == "__main__":
155
+ demo.launch()