下載 YouTube Shorts 轉換成 MP3,進行語音辨識轉文本,v1
介紹
```bash showLineNumbers 作業環境:Windows + WSL 服務管理: 主要應用:ytdlp + ffmpeg + openai-whisper 功能重點: a. 下載 YouTube Shorts 並轉換成 MP3 b. 語音辨識 (Speech-to-Text)
1. openai-whisper
Whisper 會在第一次載入時下載語音模型:
tiny → 約 74 MB base → 約 142 MB small → 約 466 MB medium → 約 1.5 GB large → 約 1.9 GB
openai-whisper 本身 Python 套件不大,只有幾 MB,但它會依賴 PyTorch(torch、torchaudio、torchvision),這些套件通常就有 1GB ~ 2GB 以上
本地版這個會安裝超級久,如果只是為了短影片語音辨識可以考慮雲端版
pip install -U openai-whisper
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
目前 WSL 的 Python 環境被 Ubuntu “受管理”(externally managed)”。
這種情況常發生在 Ubuntu 22.04/23.10 以上自帶 Python 3.12, 系統為了安全,不允許你直接用 pip install 安裝到系統 Python, 會建議你用 apt install python3-package
2. 用 Python 虛擬環境 (推薦) 安裝必要的插件
這是最乾淨、最安全的方法,不會破壞系統 Python:
先測試:建立虛擬環境
在 WSL (Ubuntu) 上,建立虛擬環境需要 venv 模組 如果出現錯誤像 No module named venv,就需要安裝:
# 進入你要放專案的資料夾
cd ~/myproject
# 建立虛擬環境
python3 -m venv venv
# 啟動虛擬環境
source venv/bin/activate
# 升級 pip
pip install --upgrade pip
# 安裝 openai-whisper
pip install openai-whisper
# 安裝 ffmpeg (系統套件)
sudo apt install ffmpeg -y
腳本
1. Python 腳本說明
- 從 YouTube Shorts 下載影片(yt-dlp)
- 自動 user-agent 避免 403
- 自動 fallback mp4 → mp3
- 自動抓取影片標題
- 使用 Whisper 本地模型轉成文字
- 支援 --model 調整 Whisper 模型
- 如果沒傳 output_folder,直接用影片標題命名 .mp3 和 .txt,存在當前路徑
2. transcribe.py 腳本內容
#!/usr/bin/env python3
import subprocess
import whisper
import sys
import os
import shlex
import argparse
import glob
# ---------------------------
# 參數設定
# ---------------------------
parser = argparse.ArgumentParser(description="下載 YouTube Shorts,轉 mp3 並用 Whisper 轉文字")
parser.add_argument("url", help="YouTube Shorts 影片 URL")
parser.add_argument("output_folder", nargs="?", default=None, help="輸出資料夾(可選)")
parser.add_argument("--model", default="base", help="Whisper 模型 (tiny/base/small/medium/large)")
parser.add_argument("--cookies", default=None, help="YouTube cookies.txt 檔案(可選)")
args = parser.parse_args()
yt_url = args.url
output_folder = args.output_folder
model_name = args.model
cookies_file = args.cookies
# ---------------------------
# 輸出路徑設定
# ---------------------------
if output_folder:
os.makedirs(output_folder, exist_ok=True)
out_template = os.path.join(output_folder, "%(title)s.%(ext)s")
else:
out_template = "%(title)s.%(ext)s" # 當前目錄
# ---------------------------
# yt-dlp 下載函式
# ---------------------------
def download_audio(url):
# 嘗試直接下載 mp3
cmd = f'yt-dlp -x --audio-format mp3 -o "{out_template}" --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"'
if cookies_file:
cmd += f' --cookies {shlex.quote(cookies_file)}'
cmd += f' {shlex.quote(url)}'
try:
subprocess.run(cmd, shell=True, check=True)
except subprocess.CalledProcessError:
print("直接下載 mp3 失敗,嘗試先下載 mp4 再轉 mp3...")
# 下載 mp4
mp4_template = os.path.splitext(out_template)[0] + ".mp4"
cmd_mp4 = f'yt-dlp -f bestvideo+bestaudio -o "{mp4_template}" --merge-output-format mp4 --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"'
if cookies_file:
cmd_mp4 += f' --cookies {shlex.quote(cookies_file)}'
subprocess.run(cmd_mp4, shell=True, check=True)
# 轉 mp3
mp3_file = os.path.splitext(mp4_template)[0] + ".mp3"
subprocess.run(f'ffmpeg -y -i "{mp4_template}" "{mp3_file}"', shell=True, check=True)
return mp3_file
# 找最新 mp3
search_dir = output_folder if output_folder else "."
mp3_files = glob.glob(os.path.join(search_dir, "*.mp3"))
if not mp3_files:
raise FileNotFoundError("未找到 mp3 檔案")
latest_mp3 = max(mp3_files, key=os.path.getmtime)
return latest_mp3
# ---------------------------
# 執行下載
# ---------------------------
try:
mp3_file = download_audio(yt_url)
except Exception as e:
print("下載失敗:", e)
sys.exit(1)
# ---------------------------
# Whisper 語音辨識
# ---------------------------
model = whisper.load_model(model_name)
result = model.transcribe(mp3_file)
# ---------------------------
# 輸出文字檔(同名 mp3 + .txt)
# ---------------------------
txt_file = os.path.splitext(mp3_file)[0] + ".txt"
with open(txt_file, "w", encoding="utf-8") as f:
f.write(result["text"])
print(f"轉寫完成: {txt_file}")
3. 使用範例
指定模型和資料夾:
指定模型,但不給資料夾 → 會存到當前路徑,檔名用影片標題:
不指定模型 → 預設 base:
python transcribe.py "https://www.youtube.com/shorts/XXXXXXXXXXX"
python3 transcribe.py "https://www.youtube.com/shorts/sKntYRHWMN0" --model tiny
其他狀況
1. yt-dlp 在 window 本地執行 ok 但切到 WSL 環境就不行?
yt-dlp -x --audio-format mp3 --cookies cookies.txt -o "%(title)s.%(ext)s" https://www.youtube.com/shorts/sKntYRHWMN0
本地可以正常下載 shorts
但WSL 加 user-agent 和 使用 cookies.txt(登入 YouTube)
還是 ERROR: unable to download video data: HTTP Error 403: Forbidden
WSL 的網路環境與 Windows 不完全一樣,導致 yt-dlp 在 WSL 下載 YouTube Shorts 可能會被拒絕或 403。
2. ytdlp 403/400
即使能連線,yt-dlp 下載時仍可能被拒絕,原因主要是 YouTube 動態檢查下載請求:
User-Agent 或 headers 不對
yt-dlp 在 WSL 執行,可能沒有模擬瀏覽器 headers,YouTube 會判斷為「非正常瀏覽器」,直接拒絕。
Shorts API 改版
YouTube 不定期更新 Shorts 播放器/簽名方式
yt-dlp 需要最新版才能抓到有效下載 URL
Cookie / 登入限制
若影片受地區或年齡限制,沒登入 cookie 就會 403
Windows 可能自動有 cookie 或瀏覽器 session,WSL 沒有
3. 比較本地與 WSL 的 yt-dlp 版本
本地 yt-dlp 版本
WSL yt-dlp 版本
可以看出WSL 版本是比較舊的,可能無法抓到有效下載 URL
4. 更新 yt-dlp 方式
用 apt 更新(可能還是不最新)
直接用官方腳本安裝最新 yt-dlp(推薦)那得先移除 apt 版:
建議 先退出 venv 再操作系統全域的安裝
# 退出 venv
deactivate
# 下載官方最新 yt-dlp
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
sudo chmod a+rx /usr/local/bin/yt-dlp
# 確認版本
yt-dlp --version # 應該顯示最新版本
# 回到你的 venv
source /path/to/venv/bin/activate
vnev 會優先使用虛擬環境內的 PATH
如果在 venv 裡用 sudo curl ... -o /usr/local/bin/yt-dlp,雖然檔案放到系統路徑 /usr/local/bin,但是 venv 的 PATH 可能把 /usr/local/bin 放在後面或被 venv 覆蓋,導致 Python 找不到 yt-dlp 或仍然用舊版。
系統安裝是全域的
更新 /usr/local/bin/yt-dlp 是針對整個系統,而 venv 只是 Python 套件隔離。
退出 venv 安裝完後,確認全域可執行檔可用,再回 venv 呼叫即可。
# 在 WSL 重新執行 yt-dlp
yt-dlp -x --audio-format mp3 --cookies cookies.txt -o "%(title)s.%(ext)s" https://www.youtube.com/shorts/sKntYRHWMN0
結果
雖然順利進行了語音辨識轉文本,但文本的精準度一般
python3 transcribe.py "https://www.youtube.com/shorts/sKntYRHWMN0" --model tiny
[youtube] Extracting URL: https://www.youtube.com/shorts/sKntYRHWMN0
[youtube] sKntYRHWMN0: Downloading webpage
[youtube] sKntYRHWMN0: Downloading tv simply player API JSON
[youtube] sKntYRHWMN0: Downloading tv client config
[youtube] sKntYRHWMN0: Downloading tv player API JSON
[info] sKntYRHWMN0: Downloading 1 format(s): 251
[download] 那些只有前半句的名句! #cheems小剧场 mp4.mp3 has already been downloaded
[ExtractAudio] Not converting audio 那些只有前半句的名句! #cheems小剧场 mp4.mp3; file is already in target format mp3
100%|█████████████████████████████████████| 72.1M/72.1M [00:28<00:00, 2.69MiB/s]
/mnt/d/new-start-2025-09/ytdlp/download/whisper_test/venv/lib/python3.12/site-packages/whisper/transcribe.py:132: UserWarning: FP16 is not supported on CPU; using FP32 instead
warnings.warn("FP16 is not supported on CPU; using FP32 instead")
轉寫完成: ./1.txt