import streamlit as st import yt_dlp import tempfile import os import threading import time from pathlib import Path # 頁面配置 st.set_page_config( page_title="🎬 YouTube 下載器", page_icon="🎬", layout="centered" ) # 初始化狀態 if 'download_status' not in st.session_state: st.session_state.download_status = "" if 'is_downloading' not in st.session_state: st.session_state.is_downloading = False if 'download_progress' not in st.session_state: st.session_state.download_progress = 0 if 'video_info' not in st.session_state: st.session_state.video_info = None if 'download_path' not in st.session_state: st.session_state.download_path = None def progress_hook(d): """下載進度回調""" if d['status'] == 'downloading': if 'total_bytes' in d or '_total_bytes_str' in d: try: downloaded = d.get('downloaded_bytes', 0) total = d.get('total_bytes') or d.get('_total_bytes_estimate', 1) if total and total > 0: percent = min((downloaded / total) * 100, 100) st.session_state.download_progress = percent st.session_state.download_status = f"📥 下載中... {percent:.1f}%" else: st.session_state.download_status = "📥 下載中..." except: st.session_state.download_status = "📥 下載中..." else: st.session_state.download_status = "📥 下載中..." elif d['status'] == 'finished': st.session_state.download_progress = 100 st.session_state.download_status = "✅ 下載完成!" filename = os.path.basename(d['filename']) st.session_state.download_path = d['filename'] def get_video_info(url): """獲取影片資訊""" try: ydl_opts = { 'quiet': True, 'no_warnings': True, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=False) return { 'title': info.get('title', '未知標題'), 'uploader': info.get('uploader', '未知上傳者'), 'duration': info.get('duration', 0), 'thumbnail': info.get('thumbnail', ''), 'view_count': info.get('view_count', 0) } except Exception as e: st.error(f"❌ 無法獲取影片資訊: {str(e)}") return None def download_video(url, quality, audio_only): """下載影片""" try: # 創建臨時目錄 temp_dir = tempfile.mkdtemp() # 設定下載選項 if audio_only: format_selector = 'bestaudio/best' filename = '%(title)s.%(ext)s' postprocessors = [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }] else: height = quality.replace('p', '') format_selector = f'best[height<={height}]/best' filename = '%(title)s.%(ext)s' postprocessors = [] ydl_opts = { 'format': format_selector, 'outtmpl': os.path.join(temp_dir, filename), 'progress_hooks': [progress_hook], 'quiet': True, 'no_warnings': True, 'postprocessors': postprocessors, } st.session_state.download_status = "🚀 開始下載..." with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) st.session_state.is_downloading = False except Exception as e: st.session_state.download_status = f"❌ 下載失敗: {str(e)}" st.session_state.is_downloading = False # 主介面 st.title("🎬 YouTube 下載器") st.markdown("簡單快速的YouTube影片下載工具") # URL 輸入 url = st.text_input("🔗 請輸入 YouTube 影片連結", placeholder="https://www.youtube.com/watch?v=...") # 下載設定 col1, col2 = st.columns(2) with col1: download_type = st.selectbox("📱 下載類型", ["影片", "音訊 (MP3)"]) with col2: if download_type == "影片": quality = st.selectbox("🎯 影片品質", ["1080p", "720p", "480p", "360p"], index=1) else: quality = None st.info("音訊品質: 192kbps MP3") # 按鈕區域 btn_col1, btn_col2 = st.columns(2) with btn_col1: if st.button("🔍 預覽影片資訊", use_container_width=True): if url: with st.spinner("正在獲取影片資訊..."): info = get_video_info(url) if info: st.session_state.video_info = info else: st.error("請先輸入影片連結!") with btn_col2: if st.button("⬇️ 開始下載", use_container_width=True, disabled=st.session_state.is_downloading): if not url: st.error("請輸入影片連結!") else: st.session_state.is_downloading = True st.session_state.download_progress = 0 st.session_state.download_status = "準備下載..." st.session_state.download_path = None audio_only = download_type == "音訊 (MP3)" thread = threading.Thread( target=download_video, args=(url, quality, audio_only), daemon=True ) thread.start() # 顯示影片資訊 if st.session_state.video_info: st.markdown("---") st.subheader("📹 影片資訊") info = st.session_state.video_info # 創建兩欄布局 info_col1, info_col2 = st.columns([2, 1]) with info_col1: st.write(f"**📝 標題:** {info['title']}") st.write(f"**👤 上傳者:** {info['uploader']}") if info['duration']: minutes = info['duration'] // 60 seconds = info['duration'] % 60 st.write(f"**⏱️ 長度:** {minutes}:{seconds:02d}") if info['view_count']: st.write(f"**👁️ 觀看次數:** {info['view_count']:,}") with info_col2: if info['thumbnail']: st.image(info['thumbnail'], caption="影片縮圖", width=200) # 顯示下載進度 if st.session_state.is_downloading or st.session_state.download_progress > 0: st.markdown("---") st.subheader("📊 下載進度") if st.session_state.download_progress > 0: progress_bar = st.progress(st.session_state.download_progress / 100) if st.session_state.download_status: st.write(st.session_state.download_status) # 下載完成後提供下載連結 if st.session_state.download_path and os.path.exists(st.session_state.download_path): st.markdown("---") st.success("🎉 下載完成!") try: with open(st.session_state.download_path, 'rb') as file: file_data = file.read() filename = os.path.basename(st.session_state.download_path) st.download_button( label="📁 下載檔案", data=file_data, file_name=filename, mime="application/octet-stream", use_container_width=True ) except Exception as e: st.error(f"檔案讀取失敗: {e}") # 自動刷新進度 if st.session_state.is_downloading: time.sleep(0.5) st.rerun() # 頁腳資訊 st.markdown("---") st.markdown("""
⚠️ 請確保您有權下載所選內容,並遵守相關版權法規
""", unsafe_allow_html=True)