import streamlit as st import time from pathlib import Path import json from ui_fragments import tab0_data_processing,tab1_asset_generation,tab2_online_video_search,tab3_video_composition # --- 本地模組匯入 --- from utils.paths import get_project_paths, SHARED_ASSETS_DIR, PROJECTS_DIR,get_project_list import callbacks from utils.helpers import get_notion_page_titles, display_operation_status # from ui_fragments.audio_manager import audio_management_fragment # from ui_fragments.video_manager import video_management_fragment # from ui_fragments.video_search import video_search_fragment # --- 匯入外部處理腳本 --- # 雖然這些腳本主要在 callbacks 中被呼叫,但在此處匯入有助於理解全貌 # from scripts.step1_notion_sync import update_project_from_notion # from scripts.step2_translate_ipa import run_step2_translate_ipa # from scripts.step3_generate_audio import run_step3_generate_audio # from scripts.step4_concatenate_audio import run_step4_concatenate_audio # from scripts.step5_generate_ass import run_step5_generate_ass # from scripts.step6_assemble_video import run_step6_assemble_video # --- Streamlit UI 設定 --- st.set_page_config(layout="wide", page_title="英語影片自動化工作流程") # --- 狀態初始化 --- if 'show_video' not in st.session_state: st.session_state.show_video = False if 'operation_status' not in st.session_state: st.session_state.operation_status = {} if 'search_query' not in st.session_state: st.session_state.search_query = "" if 'search_results' not in st.session_state: st.session_state.search_results = [] if 'selected_videos' not in st.session_state: st.session_state.selected_videos = {} if 'active_preview_id' not in st.session_state: st.session_state.active_preview_id = None if 'project' not in st.session_state: st.session_state.project = None # 'project' 現在儲存的是 Project 物件或 None if 'project_selector' not in st.session_state: st.session_state.project_selector = None # 'project_selector' 用來追蹤 selectbox 的狀態 # --- UI 介面 --- st.title("🎬 英語影片自動化工作流程") display_operation_status() # --- 側邊欄 --- with st.sidebar: st.header("API & Project Control") notion_api_key = st.secrets.get("NOTION_API_KEY") notion_database_id = st.secrets.get("NOTION_DATABASE_ID") google_creds_for_translate_path = st.secrets.get("GOOGLE_CREDS_TRANSLATE_PATH") google_creds_for_TTS_path = st.secrets.get("GOOGLE_CREDS_TTS_PATH") st.divider() st.header("1. 建立新專案") try: if notion_api_key and notion_database_id: page_titles_map = get_notion_page_titles(notion_api_key, notion_database_id) st.session_state.page_titles_map = page_titles_map st.selectbox("選擇 Notion 頁面以建立新專案:", options=[""] + list(page_titles_map.keys()), key="selected_title") if st.session_state.selected_title: st.button(f"從 '{st.session_state.selected_title}' 建立專案", on_click=callbacks.callback_create_project) # 綁定到標準回呼 else: st.warning("請在 Streamlit secrets 中設定 Notion API 金鑰和資料庫 ID。") except Exception as e: st.error(f"無法載入 Notion 頁面: {e}") st.divider() st.header("2. 選擇現有專案") # 1. 介面邏輯變得極簡:直接呼叫工具函式 existing_projects = get_project_list() # 2. UI 只負責綁定 key 和 on_change 事件 st.selectbox( "或選擇一個現有專案:", options=[""] + existing_projects, key="project_selector", # selectbox 的狀態由 st.session_state.project_selector 控制 on_change=callbacks.callback_set_project, # 綁定到標準回呼 help="選擇您已經建立的專案。" ) # --- 主畫面 ---# 1. 從 session_state 中獲取 Project 物件。這是與專案互動的唯一入口。 project = st.session_state.get('project') # 2. 判斷的依據變成了 project 物件是否存在,而不是一個字串。 if not project: # 如果沒有專案,提示使用者。這部分的邏輯是完美的。 st.info("👈 請在側邊欄建立新專案或選擇一個現有專案以開始。") else: st.header(f"目前專案:`{project.name}`") tab_names = ["1. 資料處理 & AI 加註", "2. 素材生成", " 線上素材搜尋", "3. 影片合成"] active_tab = st.radio("選擇工作流程步驟:", options=tab_names, key="main_tabs_radio", horizontal=True, label_visibility="collapsed") st.markdown("---") # # --- 分頁內容 --- if active_tab == tab_names[0]: with st.container(border=True): tab0_data_processing.render_tab(project) if active_tab == tab_names[1]: tab1_asset_generation.render_tab(project, callbacks) if active_tab == tab_names[2]: tab2_online_video_search.render_tab(project,callbacks) if active_tab == tab_names[3]: tab3_video_composition.render_tab(project, callbacks) st.divider() # st.subheader("步驟 6.1: 管理與選擇共享影片素材") # shared_videos = [""] + [f.name for f in sorted(SHARED_ASSETS_DIR.glob('*.mp4'))] + [f.name for f in sorted(SHARED_ASSETS_DIR.glob('*.mov'))] # with st.container(border=True): # st.markdown("##### 從共享素材庫中選擇影片") # c1, c2 = st.columns(2) # with c1: # logo_selection = st.selectbox("選擇 Logo 影片:", shared_videos, key="logo_select") # open_selection = st.selectbox("選擇開場影片:", shared_videos, key="open_select") # with c2: # end_selection = st.selectbox("選擇結尾影片:", shared_videos, key="end_select") # with st.expander("**上傳新的共享素材**"): # st.markdown("上傳的影片將存入 `shared_assets` 公共資料夾,可供所有專案重複使用。") # uploaded_files = st.file_uploader("上傳新的影片到共享素材庫", type=["mp4", "mov"], accept_multiple_files=True, label_visibility="collapsed") # if uploaded_files: # for uploaded_file in uploaded_files: # with open(SHARED_ASSETS_DIR / uploaded_file.name, "wb") as f: # f.write(uploaded_file.getbuffer()) # st.success(f"成功上傳 {len(uploaded_files)} 個檔案到共享素材庫!") # time.sleep(1) # st.rerun() # st.divider() # st.subheader("步驟 6.2: 執行最終影片合成") # all_videos_selected = all([logo_selection, open_selection, end_selection]) # if not all_videos_selected: # st.info("請從上方的下拉選單中選擇所有四個影片以啟用合成按鈕。") # st.button( # "🎬 合成最終影片", # on_click=callback_assemble_final_video, # kwargs={ # "paths": paths, # "source": "assemble_video", # "spinner_text": "影片合成中,請稍候...", # "project_path": paths['root'], # 傳遞給 run_step6_assemble_video # "logo_video": SHARED_ASSETS_DIR / logo_selection if logo_selection else None, # "open_video": SHARED_ASSETS_DIR / open_selection if open_selection else None, # "end_video": SHARED_ASSETS_DIR / end_selection if end_selection else None # }, # disabled=not all_videos_selected # ) # if st.session_state.final_video_path and Path(st.session_state.final_video_path).exists(): # st.divider() # st.header("🎉 影片製作完成!") # st.checkbox("顯示/隱藏影片", key="show_video", value=True) # if st.session_state.show_video: # st.video(st.session_state.final_video_path) # with open(st.session_state.final_video_path, "rb") as file: # st.download_button( # "📥 下載專案影片", # data=file, # file_name=Path(st.session_state.final_video_path).name, # mime="video/mp4", # use_container_width=True # )