YT2025 / src /streamlit_app.py
Roberta2024's picture
Update src/streamlit_app.py
fc9c0b1 verified
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)