Spaces:
Running
Running
| import subprocess | |
| import os | |
| import tempfile | |
| def merge_videos(video_files): | |
| """ | |
| 合併多個 MP4 影片檔案 | |
| Args: | |
| video_files: 影片檔案路徑列表 | |
| Returns: | |
| tuple: (輸出檔案路徑, 狀態訊息) | |
| """ | |
| if not video_files or len(video_files) == 0: | |
| return None, "請至少上傳一個影片檔案" | |
| if len(video_files) == 1: | |
| return None, "請上傳至少兩個影片進行合併" | |
| try: | |
| # 創建臨時檔案列表 | |
| list_file = tempfile.mktemp(suffix='.txt') | |
| output_path = tempfile.mktemp(suffix='.mp4') | |
| # 寫入 ffmpeg concat 檔案列表 | |
| with open(list_file, 'w', encoding='utf-8') as f: | |
| for video_file in video_files: | |
| # 使用絕對路徑並轉義特殊字元 | |
| abs_path = os.path.abspath(video_file) | |
| f.write(f"file '{abs_path}'\n") | |
| # 使用 ffmpeg concat demuxer 合併影片 | |
| merge_cmd = [ | |
| 'ffmpeg', | |
| '-f', 'concat', | |
| '-safe', '0', | |
| '-i', list_file, | |
| '-c', 'copy', | |
| output_path, | |
| '-y' | |
| ] | |
| result = subprocess.run(merge_cmd, capture_output=True, text=True) | |
| # 清理臨時列表檔案 | |
| if os.path.exists(list_file): | |
| os.remove(list_file) | |
| if result.returncode != 0: | |
| # 如果直接複製失敗,嘗試重新編碼 | |
| merge_cmd_reencode = [ | |
| 'ffmpeg', | |
| '-f', 'concat', | |
| '-safe', '0', | |
| '-i', list_file, | |
| '-c:v', 'libx264', | |
| '-c:a', 'aac', | |
| '-preset', 'medium', | |
| output_path, | |
| '-y' | |
| ] | |
| # 重新創建列表檔案 | |
| with open(list_file, 'w', encoding='utf-8') as f: | |
| for video_file in video_files: | |
| abs_path = os.path.abspath(video_file) | |
| f.write(f"file '{abs_path}'\n") | |
| result = subprocess.run(merge_cmd_reencode, capture_output=True, text=True) | |
| if os.path.exists(list_file): | |
| os.remove(list_file) | |
| if result.returncode != 0: | |
| return None, f"合併失敗: {result.stderr}" | |
| if os.path.exists(output_path) and os.path.getsize(output_path) > 0: | |
| return output_path, f"成功合併 {len(video_files)} 個影片!" | |
| else: | |
| return None, "合併失敗,請確認影片格式正確" | |
| except Exception as e: | |
| return None, f"發生錯誤: {str(e)}" | |