Roberta2024 commited on
Commit
fc9c0b1
·
verified ·
1 Parent(s): 92bcb14

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +173 -383
src/streamlit_app.py CHANGED
@@ -1,442 +1,232 @@
1
  import streamlit as st
 
 
2
  import os
3
  import threading
4
- from pathlib import Path
5
- import tempfile
6
  import time
7
- import subprocess
8
- import sys
9
- import random
10
-
11
- # 检查 yt-dlp 模块
12
- def check_yt_dlp():
13
- try:
14
- import yt_dlp
15
- return True, yt_dlp
16
- except ImportError:
17
- return False, None
18
-
19
- yt_dlp_installed, yt_dlp = check_yt_dlp()
20
-
21
- if not yt_dlp_installed:
22
- st.error("🚨 缺少必要模組 yt-dlp")
23
- st.markdown("""
24
- ### 請先安裝套件:
25
- ```bash
26
- pip install yt-dlp
27
- ```
28
- """)
29
-
30
- if st.button("🔧 自動安裝 yt-dlp"):
31
- with st.spinner("正在安裝..."):
32
- try:
33
- result = subprocess.run([sys.executable, "-m", "pip", "install", "yt-dlp"],
34
- capture_output=True, text=True)
35
- if result.returncode == 0:
36
- st.success("✅ 安裝成功!請重新載入頁面")
37
- st.balloons()
38
- else:
39
- st.error(f"❌ 安裝失敗: {result.stderr}")
40
- except Exception as e:
41
- st.error(f"❌ 安裝過程出錯: {e}")
42
- st.stop()
43
 
44
- # 页面配置
45
  st.set_page_config(
46
- page_title="YouTube 影片下載器",
47
  page_icon="🎬",
48
- layout="wide"
49
  )
50
 
51
- # 初始化状态
52
- if 'video_info' not in st.session_state:
53
- st.session_state.video_info = None
54
- if 'download_logs' not in st.session_state:
55
- st.session_state.download_logs = []
56
  if 'is_downloading' not in st.session_state:
57
  st.session_state.is_downloading = False
58
  if 'download_progress' not in st.session_state:
59
  st.session_state.download_progress = 0
60
- if 'download_status' not in st.session_state:
61
- st.session_state.download_status = "準備就緒"
62
- if 'cookies_content' not in st.session_state:
63
- st.session_state.cookies_content = ""
64
-
65
- def add_log(message):
66
- """添加日志"""
67
- timestamp = time.strftime("%H:%M:%S")
68
- st.session_state.download_logs.append(f"[{timestamp}] {message}")
69
- if len(st.session_state.download_logs) > 50:
70
- st.session_state.download_logs.pop(0)
71
-
72
- def get_default_download_path():
73
- """获取默认下载路径"""
74
- try:
75
- downloads_path = Path.home() / "Downloads"
76
- if downloads_path.exists():
77
- return str(downloads_path)
78
- return str(Path.home())
79
- except:
80
- return tempfile.gettempdir()
81
-
82
- def get_random_user_agent():
83
- """获取随机User-Agent"""
84
- user_agents = [
85
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
86
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
87
- 'Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
88
- ]
89
- return random.choice(user_agents)
90
-
91
- def create_cookies_file(cookies_content):
92
- """创建cookies文件"""
93
- if not cookies_content.strip():
94
- return None
95
- try:
96
- with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
97
- if not cookies_content.startswith('# Netscape HTTP Cookie File'):
98
- f.write('# Netscape HTTP Cookie File\n')
99
- f.write(cookies_content)
100
- return f.name
101
- except Exception as e:
102
- add_log(f"❌ 無法創建 cookies 檔案: {e}")
103
- return None
104
-
105
- def get_ydl_options(cookies_file=None, use_mobile=False):
106
- """获取yt-dlp选项"""
107
- user_agent = get_random_user_agent()
108
-
109
- options = {
110
- 'quiet': False,
111
- 'verbose': True,
112
- 'http_headers': {
113
- 'User-Agent': user_agent,
114
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
115
- 'Accept-Language': 'en-US,en;q=0.9,zh-TW;q=0.8',
116
- },
117
- 'geo_bypass': True,
118
- 'extractor_args': {
119
- 'youtube': {
120
- 'skip': ['hls'],
121
- 'player_client': ['android', 'web'] if use_mobile else ['web', 'android'],
122
- }
123
- },
124
- 'retries': 3,
125
- 'socket_timeout': 30,
126
- 'no_check_certificate': True,
127
- }
128
-
129
- if cookies_file and os.path.exists(cookies_file):
130
- options['cookiefile'] = cookies_file
131
- add_log("📋 使用 cookies 檔案")
132
-
133
- return options
134
-
135
- def get_video_info(url, use_cookies=False, use_mobile=False):
136
- """获取视频信息"""
137
- if not url.strip():
138
- st.error("請輸入影片連結!")
139
- return None
140
-
141
- add_log("🔍 正在獲取影片資訊...")
142
-
143
- methods = [
144
- {'name': '標準模式', 'mobile': False},
145
- {'name': '行動版API', 'mobile': True},
146
- ]
147
-
148
- for method in methods:
149
- try:
150
- add_log(f"🔄 嘗試 {method['name']}...")
151
-
152
- cookies_file = None
153
- if use_cookies and st.session_state.cookies_content:
154
- cookies_file = create_cookies_file(st.session_state.cookies_content)
155
-
156
- ydl_opts = get_ydl_options(cookies_file=cookies_file, use_mobile=method['mobile'])
157
-
158
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
159
- info = ydl.extract_info(url, download=False)
160
-
161
- if info:
162
- video_info = {
163
- 'title': info.get('title', '未知標題'),
164
- 'uploader': info.get('uploader', '未知上傳者'),
165
- 'duration': info.get('duration', 0),
166
- 'view_count': info.get('view_count', 0),
167
- 'thumbnail': info.get('thumbnail', ''),
168
- 'description': info.get('description', '')[:200] + '...' if info.get('description') else '',
169
- 'method_used': method['name']
170
- }
171
-
172
- add_log(f"✅ {method['name']} 成功獲取影片資訊")
173
- return video_info
174
-
175
- if cookies_file and os.path.exists(cookies_file):
176
- os.unlink(cookies_file)
177
-
178
- except Exception as e:
179
- add_log(f"❌ {method['name']} 失敗: {str(e)[:100]}...")
180
- if 'cookies_file' in locals() and cookies_file and os.path.exists(cookies_file):
181
- os.unlink(cookies_file)
182
- continue
183
-
184
- add_log("❌ 所有方法都失敗了")
185
- st.error("❌ 無法獲取影片資訊,請嘗試使用 Cookies 或稍後重試")
186
- return None
187
 
188
  def progress_hook(d):
189
- """下载进度回调"""
190
  if d['status'] == 'downloading':
191
- if 'total_bytes' in d:
192
- downloaded = d.get('downloaded_bytes', 0)
193
- total = d.get('total_bytes', 1)
194
- percent = (downloaded / total) * 100
195
- st.session_state.download_progress = percent
196
- st.session_state.download_status = f"下載中... {percent:.1f}%"
 
 
 
 
 
 
197
  else:
198
- st.session_state.download_status = "下載中..."
199
  elif d['status'] == 'finished':
200
  st.session_state.download_progress = 100
201
- st.session_state.download_status = "下載完成!"
202
  filename = os.path.basename(d['filename'])
203
- add_log(f"✅ 下載完成: {filename}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- def download_video(url, quality, audio_only, download_path, use_cookies=False, use_mobile=False):
206
- """下载视频"""
207
  try:
208
- os.makedirs(download_path, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
- add_log("🚀 開始下載...")
211
- add_log(f"🎯 類型: {'音訊 (MP3)' if audio_only else f'影片 ({quality})'}")
 
 
 
 
 
 
212
 
213
- methods = [
214
- {'name': '標準下載', 'mobile': False},
215
- {'name': '行動版下載', 'mobile': True},
216
- ]
217
 
218
- for method in methods:
219
- try:
220
- add_log(f"🔄 嘗試 {method['name']}...")
221
-
222
- cookies_file = None
223
- if use_cookies and st.session_state.cookies_content:
224
- cookies_file = create_cookies_file(st.session_state.cookies_content)
225
-
226
- # 设置格式
227
- if audio_only:
228
- format_selector = 'bestaudio/best'
229
- filename = '%(title)s.%(ext)s'
230
- else:
231
- height = quality.replace('p', '')
232
- format_selector = f'best[height<={height}]/best'
233
- filename = '%(title)s.%(ext)s'
234
-
235
- ydl_opts = get_ydl_options(cookies_file=cookies_file, use_mobile=method['mobile'])
236
- ydl_opts.update({
237
- 'format': format_selector,
238
- 'outtmpl': os.path.join(download_path, filename),
239
- 'progress_hooks': [progress_hook],
240
- 'noplaylist': True,
241
- })
242
-
243
- # 音频转换
244
- if audio_only:
245
- ydl_opts['postprocessors'] = [{
246
- 'key': 'FFmpegExtractAudio',
247
- 'preferredcodec': 'mp3',
248
- 'preferredquality': '192',
249
- }]
250
-
251
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
252
- ydl.download([url])
253
-
254
- add_log(f"🎉 {method['name']} 下載成功完成!")
255
- st.session_state.is_downloading = False
256
- st.success("下載完成!")
257
- return
258
-
259
- except Exception as e:
260
- add_log(f"❌ {method['name']} 失敗: {str(e)[:100]}...")
261
- if cookies_file and os.path.exists(cookies_file):
262
- os.unlink(cookies_file)
263
- continue
264
 
265
- # 所有方法失败
266
- st.session_state.download_status = "下載失敗"
267
- add_log("❌ 所有下載方法都失敗")
268
  st.session_state.is_downloading = False
269
- st.error("下載失敗,請檢查網路連線或嘗試使用 Cookies")
270
 
271
  except Exception as e:
272
- st.session_state.download_status = "下載失敗"
273
- error_msg = f"下載過程出錯: {str(e)}"
274
- add_log(f"❌ {error_msg}")
275
  st.session_state.is_downloading = False
276
- st.error(error_msg)
277
 
278
- # 主界面
279
- st.title("🎬 YouTube 影片下載器")
280
- st.markdown("---")
281
 
282
- # 侧边栏设置
283
- with st.sidebar:
284
- st.header("⚙️ 下載設定")
285
-
286
- # 下载类型
287
- download_type = st.radio("下載類型", ["影片", "音訊 (MP3)"])
288
-
289
- # 质量选择
 
 
290
  if download_type == "影片":
291
- quality = st.selectbox("影片品質", ["1080p", "720p", "480p", "360p"], index=1)
292
  else:
293
  quality = None
294
- st.info("音訊將以 192kbps MP3 格式下載")
295
-
296
- # 下载路径
297
- st.subheader("📁 下載路徑")
298
- default_path = get_default_download_path()
299
- download_path = st.text_input("路徑", value=default_path)
300
-
301
- if not os.path.exists(download_path):
302
- st.warning("⚠️ 路徑不存在")
303
- else:
304
- st.success("✅ 路徑有效")
305
-
306
- st.markdown("---")
307
-
308
- # 高级设置
309
- st.subheader("🔧 進階設定")
310
- use_cookies = st.checkbox("使用 Cookies(推薦)", value=True)
311
-
312
- if use_cookies:
313
- st.info("輸入 YouTube cookies 可解決認證問題")
314
- cookies_input = st.text_area(
315
- "貼上 cookies(Netscape 格式)",
316
- placeholder="# Netscape HTTP Cookie File\n.youtube.com\tTRUE\t/\tFALSE\t...",
317
- height=100
318
- )
319
- if cookies_input != st.session_state.cookies_content:
320
- st.session_state.cookies_content = cookies_input
321
-
322
- use_mobile = st.checkbox("使用行動版 API")
323
 
324
- # 主内容
325
- col1, col2 = st.columns([2, 1])
326
 
327
- with col1:
328
- # URL输入
329
- st.subheader("🔗 影片連結")
330
- url = st.text_input("請輸入 YouTube 影片連結", placeholder="https://www.youtube.com/watch?v=...")
331
-
332
- col_btn1, col_btn2, col_btn3 = st.columns(3)
333
-
334
- with col_btn1:
335
- if st.button("🔍 獲取影片資訊", type="primary"):
336
- if url:
337
- with st.spinner("正在獲取影片資訊..."):
338
- result = get_video_info(url, use_cookies, use_mobile)
339
- if result:
340
- st.session_state.video_info = result
341
- else:
342
- st.error("請先輸入影片連結!")
343
-
344
- with col_btn2:
345
- if st.button("🚀 開始下載", disabled=st.session_state.is_downloading):
346
- if not url:
347
- st.error("請輸入影片連結!")
348
- else:
349
- st.session_state.is_downloading = True
350
- st.session_state.download_progress = 0
351
- st.session_state.download_status = "準備下載..."
352
-
353
- audio_only = download_type == "音訊 (MP3)"
354
- thread = threading.Thread(
355
- target=download_video,
356
- args=(url, quality, audio_only, download_path, use_cookies, use_mobile),
357
- daemon=True
358
- )
359
- thread.start()
360
-
361
- with col_btn3:
362
- if st.button("🔄 更新 yt-dlp"):
363
- with st.spinner("正在更新..."):
364
- try:
365
- result = subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", "yt-dlp"],
366
- capture_output=True, text=True)
367
- if result.returncode == 0:
368
- st.success("✅ 更新成功!")
369
- add_log("✅ yt-dlp 已更新到最新版本")
370
- else:
371
- st.error(f"❌ 更新失敗")
372
- except Exception as e:
373
- st.error(f"❌ 更新失敗: {e}")
374
 
375
- # 视频信息显示
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  if st.session_state.video_info:
 
377
  st.subheader("📹 影片資訊")
 
378
  info = st.session_state.video_info
379
 
380
- info_col1, info_col2 = st.columns([3, 1])
 
381
 
382
  with info_col1:
383
- st.write(f"**標題:** {info['title']}")
384
- st.write(f"**上傳者:** {info['uploader']}")
 
385
  if info['duration']:
386
- st.write(f"**長度:** {info['duration']//60}:{info['duration']%60:02d}")
387
- if info['view_count']:
388
- st.write(f"**觀看次數:** {info['view_count']:,}")
389
- st.success(f"**使用方法:** {info['method_used']}")
390
 
391
- if info['description']:
392
- with st.expander("📝 影片描述"):
393
- st.write(info['description'])
394
 
395
  with info_col2:
396
  if info['thumbnail']:
397
- st.image(info['thumbnail'], caption="影片縮圖")
398
 
399
- # 下��进度
400
  if st.session_state.is_downloading or st.session_state.download_progress > 0:
 
401
  st.subheader("📊 下載進度")
402
- progress_bar = st.progress(st.session_state.download_progress / 100)
403
- st.write(f"狀態: {st.session_state.download_status}")
404
-
405
- with col2:
406
- # 下载日志
407
- st.subheader("📋 下載日誌")
408
 
409
- if st.button("🗑️ 清除日誌"):
410
- st.session_state.download_logs = []
411
- st.rerun()
412
 
413
- if st.session_state.download_logs:
414
- log_text = "\n".join(st.session_state.download_logs[-15:])
415
- st.text_area("", value=log_text, height=250, disabled=True)
416
- else:
417
- st.info("暫無日誌")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
 
419
- # 自动刷新
420
  if st.session_state.is_downloading:
421
- time.sleep(1)
422
  st.rerun()
423
 
424
- # 故障排除指南
425
  st.markdown("---")
426
- with st.expander("🔧 故障排除指南"):
427
- st.markdown("""
428
- ### 針對認證錯誤的解決方案:
429
-
430
- **推薦方法:使用 Cookies**
431
- 1. 在瀏覽器中登入 YouTube
432
- 2. 安裝 "Get cookies.txt" 擴展
433
- 3. 在 YouTube 頁面點擊擴展
434
- 4. 複製 cookies 內容到左側設定區
435
- 5. 勾選"使用 Cookies"並重試下載
436
-
437
- **其他方法:**
438
- - 嘗試行動版 API
439
- - 等待幾分鐘後重試
440
- - 檢查網路連線
441
- - 更新 yt-dlp 到最新版本
442
- """)
 
1
  import streamlit as st
2
+ import yt_dlp
3
+ import tempfile
4
  import os
5
  import threading
 
 
6
  import time
7
+ from pathlib import Path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ # 頁面配置
10
  st.set_page_config(
11
+ page_title="🎬 YouTube 下載器",
12
  page_icon="🎬",
13
+ layout="centered"
14
  )
15
 
16
+ # 初始化狀態
17
+ if 'download_status' not in st.session_state:
18
+ st.session_state.download_status = ""
 
 
19
  if 'is_downloading' not in st.session_state:
20
  st.session_state.is_downloading = False
21
  if 'download_progress' not in st.session_state:
22
  st.session_state.download_progress = 0
23
+ if 'video_info' not in st.session_state:
24
+ st.session_state.video_info = None
25
+ if 'download_path' not in st.session_state:
26
+ st.session_state.download_path = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  def progress_hook(d):
29
+ """下載進度回調"""
30
  if d['status'] == 'downloading':
31
+ if 'total_bytes' in d or '_total_bytes_str' in d:
32
+ try:
33
+ downloaded = d.get('downloaded_bytes', 0)
34
+ total = d.get('total_bytes') or d.get('_total_bytes_estimate', 1)
35
+ if total and total > 0:
36
+ percent = min((downloaded / total) * 100, 100)
37
+ st.session_state.download_progress = percent
38
+ st.session_state.download_status = f"📥 下載中... {percent:.1f}%"
39
+ else:
40
+ st.session_state.download_status = "📥 下載中..."
41
+ except:
42
+ st.session_state.download_status = "📥 下載中..."
43
  else:
44
+ st.session_state.download_status = "📥 下載中..."
45
  elif d['status'] == 'finished':
46
  st.session_state.download_progress = 100
47
+ st.session_state.download_status = "下載完成!"
48
  filename = os.path.basename(d['filename'])
49
+ st.session_state.download_path = d['filename']
50
+
51
+ def get_video_info(url):
52
+ """獲取影片資訊"""
53
+ try:
54
+ ydl_opts = {
55
+ 'quiet': True,
56
+ 'no_warnings': True,
57
+ }
58
+
59
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
60
+ info = ydl.extract_info(url, download=False)
61
+ return {
62
+ 'title': info.get('title', '未知標題'),
63
+ 'uploader': info.get('uploader', '未知上傳者'),
64
+ 'duration': info.get('duration', 0),
65
+ 'thumbnail': info.get('thumbnail', ''),
66
+ 'view_count': info.get('view_count', 0)
67
+ }
68
+ except Exception as e:
69
+ st.error(f"❌ 無法獲取影片資訊: {str(e)}")
70
+ return None
71
 
72
+ def download_video(url, quality, audio_only):
73
+ """下載影片"""
74
  try:
75
+ # 創建臨時目錄
76
+ temp_dir = tempfile.mkdtemp()
77
+
78
+ # 設定下載選項
79
+ if audio_only:
80
+ format_selector = 'bestaudio/best'
81
+ filename = '%(title)s.%(ext)s'
82
+ postprocessors = [{
83
+ 'key': 'FFmpegExtractAudio',
84
+ 'preferredcodec': 'mp3',
85
+ 'preferredquality': '192',
86
+ }]
87
+ else:
88
+ height = quality.replace('p', '')
89
+ format_selector = f'best[height<={height}]/best'
90
+ filename = '%(title)s.%(ext)s'
91
+ postprocessors = []
92
 
93
+ ydl_opts = {
94
+ 'format': format_selector,
95
+ 'outtmpl': os.path.join(temp_dir, filename),
96
+ 'progress_hooks': [progress_hook],
97
+ 'quiet': True,
98
+ 'no_warnings': True,
99
+ 'postprocessors': postprocessors,
100
+ }
101
 
102
+ st.session_state.download_status = "🚀 開始下載..."
 
 
 
103
 
104
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
105
+ ydl.download([url])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
 
 
 
107
  st.session_state.is_downloading = False
 
108
 
109
  except Exception as e:
110
+ st.session_state.download_status = f"❌ 下載失敗: {str(e)}"
 
 
111
  st.session_state.is_downloading = False
 
112
 
113
+ # 主介面
114
+ st.title("🎬 YouTube 下載器")
115
+ st.markdown("簡單快速的YouTube影片下載工具")
116
 
117
+ # URL 輸入
118
+ url = st.text_input("🔗 請輸入 YouTube 影片連結", placeholder="https://www.youtube.com/watch?v=...")
119
+
120
+ # 下載設定
121
+ col1, col2 = st.columns(2)
122
+
123
+ with col1:
124
+ download_type = st.selectbox("📱 下載類型", ["影片", "音訊 (MP3)"])
125
+
126
+ with col2:
127
  if download_type == "影片":
128
+ quality = st.selectbox("🎯 影片品質", ["1080p", "720p", "480p", "360p"], index=1)
129
  else:
130
  quality = None
131
+ st.info("音訊品質: 192kbps MP3")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ # 按鈕區域
134
+ btn_col1, btn_col2 = st.columns(2)
135
 
136
+ with btn_col1:
137
+ if st.button("🔍 預覽影片資訊", use_container_width=True):
138
+ if url:
139
+ with st.spinner("正在獲取影片資訊..."):
140
+ info = get_video_info(url)
141
+ if info:
142
+ st.session_state.video_info = info
143
+ else:
144
+ st.error("請先輸入影片連結!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
+ with btn_col2:
147
+ if st.button("⬇️ 開始下載", use_container_width=True, disabled=st.session_state.is_downloading):
148
+ if not url:
149
+ st.error("請輸入影片連結!")
150
+ else:
151
+ st.session_state.is_downloading = True
152
+ st.session_state.download_progress = 0
153
+ st.session_state.download_status = "準備下載..."
154
+ st.session_state.download_path = None
155
+
156
+ audio_only = download_type == "音訊 (MP3)"
157
+ thread = threading.Thread(
158
+ target=download_video,
159
+ args=(url, quality, audio_only),
160
+ daemon=True
161
+ )
162
+ thread.start()
163
+
164
+ # 顯示影片資訊
165
  if st.session_state.video_info:
166
+ st.markdown("---")
167
  st.subheader("📹 影片資訊")
168
+
169
  info = st.session_state.video_info
170
 
171
+ # 創建兩欄布局
172
+ info_col1, info_col2 = st.columns([2, 1])
173
 
174
  with info_col1:
175
+ st.write(f"**📝 標題:** {info['title']}")
176
+ st.write(f"**👤 上傳者:** {info['uploader']}")
177
+
178
  if info['duration']:
179
+ minutes = info['duration'] // 60
180
+ seconds = info['duration'] % 60
181
+ st.write(f"**⏱️ 長度:** {minutes}:{seconds:02d}")
 
182
 
183
+ if info['view_count']:
184
+ st.write(f"**👁️ 觀看次數:** {info['view_count']:,}")
 
185
 
186
  with info_col2:
187
  if info['thumbnail']:
188
+ st.image(info['thumbnail'], caption="影片縮圖", width=200)
189
 
190
+ # 顯示下載進度
191
  if st.session_state.is_downloading or st.session_state.download_progress > 0:
192
+ st.markdown("---")
193
  st.subheader("📊 下載進度")
 
 
 
 
 
 
194
 
195
+ if st.session_state.download_progress > 0:
196
+ progress_bar = st.progress(st.session_state.download_progress / 100)
 
197
 
198
+ if st.session_state.download_status:
199
+ st.write(st.session_state.download_status)
200
+
201
+ # 下載完成後提供下載連結
202
+ if st.session_state.download_path and os.path.exists(st.session_state.download_path):
203
+ st.markdown("---")
204
+ st.success("🎉 下載完成!")
205
+
206
+ try:
207
+ with open(st.session_state.download_path, 'rb') as file:
208
+ file_data = file.read()
209
+ filename = os.path.basename(st.session_state.download_path)
210
+
211
+ st.download_button(
212
+ label="📁 下載檔案",
213
+ data=file_data,
214
+ file_name=filename,
215
+ mime="application/octet-stream",
216
+ use_container_width=True
217
+ )
218
+ except Exception as e:
219
+ st.error(f"檔案讀取失敗: {e}")
220
 
221
+ # 自動刷新進度
222
  if st.session_state.is_downloading:
223
+ time.sleep(0.5)
224
  st.rerun()
225
 
226
+ # 頁腳資訊
227
  st.markdown("---")
228
+ st.markdown("""
229
+ <div style="text-align: center; color: #666;">
230
+ <small>⚠️ 請確保您有權下載所選內容,並遵守相關版權法規</small>
231
+ </div>
232
+ """, unsafe_allow_html=True)