import gradio as gr import subprocess import os import tempfile import shutil from video_merger import merge_videos def extract_last_frame(video_file): if video_file is None: return None, "請上傳影片檔案" try: # 創建固定名稱的輸出檔案 temp_output = tempfile.mktemp(suffix='.jpg') # 獲取影片總幀數 frame_count_cmd = [ 'ffprobe', '-v', 'error', '-select_streams', 'v:0', '-count_frames', '-show_entries', 'stream=nb_read_frames', '-of', 'default=nokey=1:noprint_wrappers=1', video_file ] frame_result = subprocess.run(frame_count_cmd, capture_output=True, text=True) # 如果無法獲取幀數,使用反向讀取方法 if not frame_result.stdout.strip() or frame_result.returncode != 0: # 方法2: 使用 select 過濾器選擇最後一幀 extract_cmd = [ 'ffmpeg', '-i', video_file, '-vf', 'select=eq(n\\,0)', '-vsync', '0', '-q:v', '2', '-frames:v', '1', temp_output, '-y' ] # 先嘗試從尾部讀取 extract_cmd_reverse = [ 'ffmpeg', '-sseof', '-3', '-i', video_file, '-vframes', '1', '-vf', 'select=eq(pict_type\\,I)', '-vsync', '0', '-q:v', '2', temp_output, '-y' ] result = subprocess.run(extract_cmd_reverse, capture_output=True, text=True) # 如果失敗,使用標準方法 if result.returncode != 0 or not os.path.exists(temp_output): subprocess.run(extract_cmd, capture_output=True, text=True) else: # 方法1: 使用幀數選擇最後一幀 total_frames = int(frame_result.stdout.strip()) last_frame = total_frames - 1 extract_cmd = [ 'ffmpeg', '-i', video_file, '-vf', f'select=eq(n\\,{last_frame})', '-vsync', '0', '-q:v', '2', '-frames:v', '1', temp_output, '-y' ] subprocess.run(extract_cmd, check=True, capture_output=True) if os.path.exists(temp_output) and os.path.getsize(temp_output) > 0: # 重新命名為 lastframe.jpg output_dir = tempfile.gettempdir() final_output = os.path.join(output_dir, 'lastframe.jpg') shutil.copy2(temp_output, final_output) os.remove(temp_output) return final_output, "成功提取最後一幀!" else: return None, "提取失敗,請確認影片格式正確" except subprocess.CalledProcessError as e: return None, f"處理錯誤: {str(e)}" except Exception as e: return None, f"發生錯誤: {str(e)}" def merge_videos_wrapper(video_files): """包裝合併函數以處理檔名""" if not video_files: return None, "請上傳影片檔案" temp_output, message = merge_videos(video_files) if temp_output and os.path.exists(temp_output): # 重新命名為 final.mp4 output_dir = tempfile.gettempdir() final_output = os.path.join(output_dir, 'final.mp4') shutil.copy2(temp_output, final_output) os.remove(temp_output) return final_output, message else: return None, message # 創建 Gradio 界面 with gr.Blocks(title="Grok影片工具箱") as demo: gr.Markdown("# Grok影片工具箱") gr.Markdown("提供影片最後一幀提取和多段影片合併功能") with gr.Tabs(): # Tab 1: 提取最後一幀 with gr.Tab("提取最後一幀"): gr.Markdown("上傳影片(最大 100MB),自動提取最後一幀為 JPEG 圖片") with gr.Row(): with gr.Column(): video_input = gr.Video( label="上傳影片", max_length=None, height=400 ) extract_btn = gr.Button("提取最後一幀", variant="primary") with gr.Column(): image_output = gr.Image( label="最後一幀 (lastframe.jpg)", type="filepath", height=400 ) status_output = gr.Textbox(label="狀態", interactive=False) extract_btn.click( fn=extract_last_frame, inputs=[video_input], outputs=[image_output, status_output] ) # Tab 2: 合併影片 with gr.Tab("合併影片"): gr.Markdown("上傳多個 MP4 影片,按照上傳順序合併為一個影片") with gr.Row(): with gr.Column(): videos_input = gr.File( label="上傳影片檔案(可多選)", file_count="multiple", file_types=["video"], height=300 ) merge_btn = gr.Button("合併影片", variant="primary") with gr.Column(): merged_video_output = gr.Video( label="合併後的影片 (final.mp4)", height=400 ) merge_status_output = gr.Textbox(label="狀態", interactive=False) merge_btn.click( fn=merge_videos_wrapper, inputs=[videos_input], outputs=[merged_video_output, merge_status_output] ) if __name__ == "__main__": demo.launch()