add project
This commit is contained in:
43
ui_fragments/audio_manager.py
Normal file
43
ui_fragments/audio_manager.py
Normal file
@ -0,0 +1,43 @@
|
||||
import streamlit as st
|
||||
from utils.callbacks import toggle_all_audio_checkboxes, callback_delete_selected_audios
|
||||
|
||||
@st.fragment
|
||||
def audio_management_fragment(paths: dict):
|
||||
"""A self-contained fragment for managing and deleting project audio files."""
|
||||
with st.expander("🎧 管理專案音訊素材"):
|
||||
audio_dir = paths["audio"]
|
||||
output_dir = paths["output"]
|
||||
|
||||
audio_files = []
|
||||
if audio_dir.exists():
|
||||
audio_files.extend(sorted(audio_dir.glob('*.wav')))
|
||||
if output_dir.exists():
|
||||
audio_files.extend(sorted(output_dir.glob('*.wav')))
|
||||
|
||||
if not audio_files:
|
||||
st.info("專案的 `audio` 和 `output` 資料夾中目前沒有音訊檔。")
|
||||
else:
|
||||
all_selected = all(st.session_state.get(f"delete_audio_cb_{f.name}", False) for f in audio_files)
|
||||
if st.session_state.get('select_all_audios', False) != all_selected:
|
||||
st.session_state.select_all_audios = all_selected
|
||||
|
||||
st.markdown("勾選您想要刪除的音訊檔案,然後點擊下方的按鈕。")
|
||||
|
||||
st.checkbox(
|
||||
"全選/取消全選",
|
||||
key="select_all_audios",
|
||||
on_change=toggle_all_audio_checkboxes,
|
||||
args=(audio_files,)
|
||||
)
|
||||
|
||||
with st.form("delete_audios_form"):
|
||||
for audio_file in audio_files:
|
||||
file_size_kb = audio_file.stat().st_size / 1024
|
||||
label = f"**{audio_file.parent.name}/{audio_file.name}** ({file_size_kb:.2f} KB)"
|
||||
st.checkbox(label, key=f"delete_audio_cb_{audio_file.name}")
|
||||
|
||||
submitted = st.form_submit_button("🟥 確認刪除選取的音訊", use_container_width=True, type="primary")
|
||||
|
||||
if submitted:
|
||||
callback_delete_selected_audios(paths)
|
||||
st.rerun(scope="fragment")
|
||||
45
ui_fragments/video_manager.py
Normal file
45
ui_fragments/video_manager.py
Normal file
@ -0,0 +1,45 @@
|
||||
import streamlit as st
|
||||
from utils.callbacks import toggle_all_video_checkboxes, callback_delete_selected_videos
|
||||
|
||||
@st.fragment
|
||||
def video_management_fragment(paths):
|
||||
"""A self-contained fragment for managing and deleting project videos."""
|
||||
with st.expander("🎬 管理專案影片素材"):
|
||||
output_dir = paths["output"]
|
||||
video_files = []
|
||||
if output_dir.exists():
|
||||
video_files = sorted(
|
||||
[f for f in output_dir.iterdir() if f.suffix.lower() in ['.mp4', '.mov']],
|
||||
key=lambda f: f.stat().st_mtime,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
if not video_files:
|
||||
st.info("專案輸出資料夾 (`/output`) 中目前沒有影片檔。")
|
||||
else:
|
||||
all_selected = all(st.session_state.get(f"delete_cb_{f.name}", False) for f in video_files)
|
||||
if st.session_state.get('select_all_videos', False) != all_selected:
|
||||
st.session_state.select_all_videos = all_selected
|
||||
|
||||
st.markdown("勾選您想要刪除的影片,然後點擊下方的按鈕。")
|
||||
|
||||
st.checkbox(
|
||||
"全選/取消全選",
|
||||
key="select_all_videos",
|
||||
on_change=toggle_all_video_checkboxes,
|
||||
args=(video_files,)
|
||||
)
|
||||
|
||||
with st.form("delete_videos_form"):
|
||||
for video_file in video_files:
|
||||
file_size_mb = video_file.stat().st_size / (1024 * 1024)
|
||||
st.checkbox(
|
||||
f"**{video_file.name}** ({file_size_mb:.2f} MB)",
|
||||
key=f"delete_cb_{video_file.name}"
|
||||
)
|
||||
|
||||
submitted = st.form_submit_button("🟥 確認刪除選取的影片", use_container_width=True, type="primary")
|
||||
|
||||
if submitted:
|
||||
callback_delete_selected_videos(paths)
|
||||
st.rerun(scope="fragment")
|
||||
136
ui_fragments/video_search.py
Normal file
136
ui_fragments/video_search.py
Normal file
@ -0,0 +1,136 @@
|
||||
import streamlit as st
|
||||
from utils.helpers import analyze_ass_for_keywords, search_pixabay_videos, search_pexels_videos
|
||||
from utils.callbacks import callback_download_videos, callback_toggle_preview
|
||||
|
||||
|
||||
@st.fragment
|
||||
def video_search_fragment(paths: dict):
|
||||
"""一個用於從多個來源搜尋並下載影片的獨立UI片段。"""
|
||||
st.header("步驟 5: 從線上圖庫搜尋影片素材")
|
||||
pixabay_api_key = st.secrets.get("PIXABAY_API_KEY")
|
||||
pexels_api_key = st.secrets.get("PEXELS_API_KEY")
|
||||
|
||||
if not pixabay_api_key and not pexels_api_key:
|
||||
st.warning("請至少在 Streamlit secrets 中設定 `PIXABAY_API_KEY` 或 `PEXELS_API_KEY` 以使用此功能。")
|
||||
return
|
||||
|
||||
st.subheader("5.1 搜尋影片")
|
||||
|
||||
# 【新增】影片來源選擇
|
||||
available_sources = []
|
||||
if pixabay_api_key: available_sources.append("Pixabay")
|
||||
if pexels_api_key: available_sources.append("Pexels")
|
||||
|
||||
if not available_sources:
|
||||
st.error("沒有可用的影片來源 API 金鑰。")
|
||||
return
|
||||
|
||||
source_choice = st.radio(
|
||||
"選擇影片來源:",
|
||||
options=available_sources,
|
||||
horizontal=True,
|
||||
key="video_source_choice"
|
||||
)
|
||||
|
||||
keywords = analyze_ass_for_keywords(paths)
|
||||
default_query = keywords[0] if keywords else ""
|
||||
|
||||
with st.form(key="search_form"):
|
||||
col1, col2 = st.columns([4, 1])
|
||||
with col1:
|
||||
st.text_input(
|
||||
"Search Videos",
|
||||
value=default_query,
|
||||
key="search_query_input",
|
||||
placeholder="輸入關鍵字後按 Enter 或點擊右側按鈕搜尋",
|
||||
label_visibility="collapsed"
|
||||
)
|
||||
with col2:
|
||||
submitted = st.form_submit_button("🔍 搜尋影片", use_container_width=True)
|
||||
|
||||
if submitted:
|
||||
with st.spinner("正在搜尋並過濾影片..."):
|
||||
query = st.session_state.get("search_query_input", "")
|
||||
|
||||
# 【修改】根據選擇呼叫對應的 API
|
||||
if source_choice == "Pixabay":
|
||||
success, message, results = search_pixabay_videos(pixabay_api_key, query)
|
||||
elif source_choice == "Pexels":
|
||||
success, message, results = search_pexels_videos(pexels_api_key, query)
|
||||
else:
|
||||
success, message, results = False, "未知的影片來源", []
|
||||
|
||||
st.session_state.operation_status = {"success": success, "message": message, "source": "search_videos"}
|
||||
|
||||
# --- 【修改】資料標準化 ---
|
||||
standardized_results = []
|
||||
if success and results:
|
||||
if source_choice == "Pixabay":
|
||||
for v in results:
|
||||
try:
|
||||
standardized_results.append({
|
||||
'id': f"pixabay-{v['id']}",
|
||||
'thumbnail_url': v['videos']['tiny']['thumbnail'],
|
||||
'video_url': v['videos']['large']['url'],
|
||||
'preview_url': v['videos']['tiny']['url'],
|
||||
'width': v['videos']['large']['width'],
|
||||
'height': v['videos']['large']['height']
|
||||
})
|
||||
except KeyError: continue
|
||||
elif source_choice == "Pexels":
|
||||
for v in results:
|
||||
try:
|
||||
# 尋找合適的影片檔案連結
|
||||
video_link_hd = next((f['link'] for f in v['video_files'] if f.get('quality') == 'hd'), None)
|
||||
video_link_sd = next((f['link'] for f in v['video_files'] if f.get('quality') == 'sd'), None)
|
||||
|
||||
# 優先使用 HD 畫質,若無則用 SD,再沒有就用第一個
|
||||
final_video_url = video_link_hd or video_link_sd or v['video_files'][0]['link']
|
||||
|
||||
standardized_results.append({
|
||||
'id': f"pexels-{v['id']}",
|
||||
'thumbnail_url': v['image'],
|
||||
'video_url': final_video_url,
|
||||
'preview_url': video_link_sd or final_video_url, # 預覽用 SD 或更高畫質
|
||||
'width': v['width'],
|
||||
'height': v['height']
|
||||
})
|
||||
except (KeyError, IndexError): continue
|
||||
|
||||
st.session_state.search_results = standardized_results
|
||||
st.session_state.selected_videos = {str(v['id']): {"url": v['video_url'], "selected": False} for v in standardized_results}
|
||||
st.session_state.active_preview_id = None
|
||||
|
||||
if keywords and (keywords[1] or keywords[2]):
|
||||
st.caption(f"建議關鍵字: `{keywords[1]}` `{keywords[2]}`")
|
||||
|
||||
st.divider()
|
||||
|
||||
if st.session_state.search_results:
|
||||
st.subheader("5.2 選擇影片並下載")
|
||||
st.button("📥 下載選取的影片到專案素材庫", on_click=callback_download_videos, args=(paths,), help="將下方勾選的影片下載至目前專案的 `output/test` 資料夾,並自動循序命名。")
|
||||
|
||||
num_cols = 5
|
||||
cols = st.columns(num_cols)
|
||||
# 【修改】使用標準化後的結果進行渲染
|
||||
for i, video in enumerate(st.session_state.search_results):
|
||||
with cols[i % num_cols]:
|
||||
with st.container(border=True):
|
||||
media_placeholder = st.empty()
|
||||
video_id_str = str(video['id'])
|
||||
|
||||
if st.session_state.active_preview_id == video_id_str:
|
||||
media_placeholder.video(video['preview_url'])
|
||||
preview_button_text, preview_button_type = "⏹️ 停止預覽", "secondary"
|
||||
else:
|
||||
media_placeholder.image(video['thumbnail_url'], use_container_width=True)
|
||||
preview_button_text, preview_button_type = "▶️ 預覽", "primary"
|
||||
|
||||
st.caption(f"ID: {video_id_str}")
|
||||
st.caption(f"尺寸: {video['width']}x{video['height']}")
|
||||
|
||||
is_selected = st.checkbox("選取", key=f"select_{video_id_str}", value=st.session_state.selected_videos.get(video_id_str, {}).get('selected', False))
|
||||
if video_id_str in st.session_state.selected_videos:
|
||||
st.session_state.selected_videos[video_id_str]['selected'] = is_selected
|
||||
|
||||
st.button(preview_button_text, key=f"preview_{video_id_str}", on_click=callback_toggle_preview, args=(video_id_str,), use_container_width=True, type=preview_button_type)
|
||||
Reference in New Issue
Block a user