Spaces:
Sleeping
Sleeping
| 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(""" | |
| <div style="text-align: center; color: #666;"> | |
| <small>⚠️ 請確保您有權下載所選內容,並遵守相關版權法規</small> | |
| </div> | |
| """, unsafe_allow_html=True) |