另一種「看」影片的方式
所以,有沒有辨法在短時間抓到長影片的重點,若符合我需要,再點擊進去看整部呢?

影片太長怎麼看

假設有一部30分鐘的影片,我要如何決定是否該點擊進去收看呢?萬一標題吸引人,其實內容不是我需要或感興趣的,那30分鐘的青春歲月不就再也回不來,讓我可以重新選一部了?所以,有沒有辨法在短時間抓到長影片的重點,若符合我需要,再點擊進去看整部呢?

幾分鐘內看完重點

有辨法喔,先把影片的聲音轉成文字後,請AI整理出重點,若是我要的,再把影片看完。

適合這些人使用

好吧,我也承認,我看文字比聽人講話還更容易專心,這方法頗適合聽覺專注力沒那麼夠的人,例如:一進音樂廳沒多久就聽覺疲勞到睡著、老婆在講什麼好像聽不到重點的人…。

抵抗被動餵食

用這樣的方式看影片有幾個好處,首先,它能幫助我節省時間與精力,避免浪費在與需求不符的內容上。其次,重點摘要比影片更容易做筆記、回顧,甚至搜尋,讓知識真正留得下來。最後,我覺得很重要,尤其在演算法舖天蓋地的襲來、影響學習的管道與來源、讓人似乎無從選擇之際,這也讓我不再被動接受演算法推薦,而是主動選擇是否要自主學習,完全掌握自己的學習與娛樂節奏。

簡單來說,先看重點再決定,就像是替時間加上一道防護牆來把關,別讓時間不經意流逝,讓我用五分鐘的下載、轉檔、讀重點的判斷力換取三十分鐘的時間,讓影片不再主宰我,而是成為我生活中可自由掌控的資訊!

我的做法如下:

🎙️使用Whisper (ASR)

(OpenAI的免費開源模型Whisper)
(OpenAI的免費開源模型Whisper)

Whisper是一套自動語音辨識(ASR) 系統,與ChatGPT是同一家公司OpenAI開發的強大工具。

轉檔的速度與電腦硬體規格有很大的關係,在LInux Mint 上如此安裝和操作:

🖥️ 需要什麼環境

  1. NVIDIA 顯卡(支援 CUDA)以筆電的RTX 4060,TGP 105W 示範
  2. Linux Mint 作業系統
  3. 已安裝好 NVIDIA Driver(可用 nvidia-smi 確認)

📦經過以下步驟

  • 下載影片(從YouTube下載或拿本地音檔去轉文字)
  • 用 GPU 加速的faster-whisper轉錄成文字
  • 自動轉成台灣正體 (使用openCC)
  • 全程有進度條,顯示百分比、剩餘時間

安裝系統套件

sudo apt update
sudo apt install -y ffmpeg opencc python3-venv

安裝 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 +x /usr/local/bin/yt-dlp

建立whisper虛擬環境 + 安裝 Python 套件

python3 -m venv ~/whisper_venv/asr_gpu
source ~/whisper_venv/asr_gpu/bin/activate

pip install --upgrade pip setuptools wheel
pip install faster-whisper nvidia-cudnn-cu12 nvidia-cublas-cu12

準備腳本

儲存以下shell檔並命名為 yt2txt.sh

#!/usr/bin/env zsh
# =========================================================
# yt2txt_fast.zsh  —  YouTube / 本地音檔 → mp3 → 文字稿(.txt)
# 2025-09 綜合強化版(zsh)
# - 下載端:多 client(ios/mweb/web_embedded/tv/web,android 可選)避開 SABR/PO Token
# - 轉錄端:faster-whisper(Python API,GPU),自動建立/啟用 venv、進度條、OpenCC 正體化
# - I/O:mp3 → ~/Documents/media,txt → ~/Documents/transcripts
# 環境變數(可選):
#   COOKIES_FROM_BROWSER=chrome|firefox|chromium  # 需要登入/年齡/地區限制時
#   YT_PO_TOKEN='android.gvs+XXXX...'             # 有 Token 再啟用 android client
#   MODEL='large-v3-turbo|medium|small|...'       # faster-whisper 模型(預設 large-v3-turbo)
#   COMPUTE_TYPE='float16|int8_float16|...'       # 推論精度(預設 float16)
#   DEVICE_INDEX=0                                 # GPU 編號
# =========================================================

# 若目前不是在 zsh,立刻用 zsh 重新執行(避免 /bin/sh 誤跑)
if [ -z "$ZSH_VERSION" ]; then exec /usr/bin/env zsh "$0" "$@"; fi

set -eo pipefail
setopt pipefail extendedglob

# ========= 可調參數 =========
VENV="${VENV:-$HOME/whisper_venv/asr_gpu}"
MODEL="${MODEL:-large-v3-turbo}"
COMPUTE_TYPE="${COMPUTE_TYPE:-float16}"
DEVICE_INDEX="${DEVICE_INDEX:-0}"
MEDIA_DIR="${MEDIA_DIR:-$HOME/Documents/media}"
OUT_DIR="${OUT_DIR:-$HOME/Documents/transcripts}"
LOG="$OUT_DIR/last_run.log"
COOKIES_FROM_BROWSER="${COOKIES_FROM_BROWSER:-}"   # 例:chrome / firefox / chromium
YT_PO_TOKEN="${YT_PO_TOKEN:-}"                     # 有才會啟用 android client

mkdir -p "$MEDIA_DIR" "$OUT_DIR"

# ========= 小工具 =========
sec2hms() {
  local s=${1:-0}
  printf "%02d:%02d:%02d" $((s/3600)) $(((s%3600)/60)) $((s%60))
}
sanitize_path() {
  local p="$1"
  p="${p/#\~/$HOME}"
  p="${p/#file:\/\//}"
  p="${p%\"}"; p="${p#\"}"
  p="${p%\'}"; p="${p#\'}"
  echo "$p"
}
get_duration_s() {
  local f="$1"
  if command -v ffprobe >/dev/null 2>&1; then
    local d
    d=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f" 2>/dev/null | awk '{printf("%.0f",$1)}')
    [[ -n "$d" ]] && echo "$d" && return 0
  fi
  echo 0
}
profile_path() {
  local name="$1"
  [[ -f "/usr/share/opencc/$name" ]] && { echo "/usr/share/opencc/$name"; return; }
  [[ -f "/usr/lib/x86_64-linux-gnu/opencc/$name" ]] && { echo "/usr/lib/x86_64-linux-gnu/opencc/$name"; return; }
  echo "$name"
}

# ========= 前置檢查 =========
command -v yt-dlp >/dev/null 2>&1 || { echo "❌ 缺少 yt-dlp"; exit 1; }
command -v ffmpeg >/dev/null 2>&1 || { echo "❌ 缺少 ffmpeg(含 ffprobe)"; exit 1; }

# ========= venv:建立 / 啟用 + 依賴 =========
if [[ ! -x "$VENV/bin/python" ]]; then
  echo "⚙️ 建立虛擬環境:$VENV"
  mkdir -p "${VENV:h}"
  python3 -m venv "$VENV"
  source "$VENV/bin/activate"
  pip install --upgrade pip setuptools wheel
  pip install faster-whisper nvidia-cudnn-cu12 nvidia-cublas-cu12
else
  source "$VENV/bin/activate"
fi

# ========= 設定 NVIDIA cuDNN/cuBLAS 的路徑(自動偵測)=========
# 動態抓 venv 中 site-packages 的 nvidia/{cudnn,cublas}/lib 目錄
NV_LIBS=()
for d in "$VENV"/lib/python*/site-packages/nvidia/(cudnn|cublas)/lib(N/); do
  NV_LIBS+=$d
done
if [[ ${#NV_LIBS[@]} -gt 0 ]]; then
  export LD_LIBRARY_PATH="$(IFS=:; echo "${NV_LIBS[*]}"):${LD_LIBRARY_PATH:-}"
fi

# ========= YouTube 下載(多 client 輪詢,強制 mp3)=========
ytdlp_fetch() {
  local url="$1"
  local tmpl="$2"   # 輸出模板(含路徑)
  local base_args=(-x --audio-format mp3 --audio-quality 0 --no-playlist -N 4 -R 5 --retry-sleep 2 -o "$tmpl")
  local cookies_args=()
  [[ -n "$COOKIES_FROM_BROWSER" ]] && cookies_args=(--cookies-from-browser "$COOKIES_FROM_BROWSER")

  local clients=(mweb web_embedded tv web)
  [[ -n "$YT_PO_TOKEN" ]] && clients+=("android")

  for c in "${clients[@]}"; do
    echo "→ 嘗試 client: $c"
    local exargs=()
    if [[ "$c" == "android" ]]; then
      exargs=(--extractor-args "youtube:player_client=android,po_token=${YT_PO_TOKEN}")
    else
      exargs=(--extractor-args "youtube:player_client=${c}")
    fi
    if yt-dlp "${base_args[@]}" "${cookies_args[@]}" "${exargs[@]}" "$url"; then
      return 0
    else
      echo "⚠️ $c 失敗,換下一個…"
    fi
  done
  return 1
}

# ========= 選來源 =========
echo "來源:1) YouTube  2) 本地音檔"
read -r "?輸入 1 或 2: " SRC

FILE=""
if [[ "$SRC" == "1" ]]; then
  read -r "?貼上 YouTube 連結: " URL
  echo "[1/2] 下載音訊…"
  OUTPUT_TMPL="$MEDIA_DIR/%(title)s.%(id)s.%(ext)s"

  if ! ytdlp_fetch "$URL" "$OUTPUT_TMPL"; then
    echo "❌ 全部 client 失敗。建議:sudo yt-dlp -U / 設定 COOKIES_FROM_BROWSER / 設定 YT_PO_TOKEN"
    exit 1
  fi

  # 用相同模板推導實際檔名(保險)
  FILE=$(yt-dlp --get-filename -o "$OUTPUT_TMPL" "$URL" | tail -n 1)
  if [[ ! -f "$FILE" ]]; then
    # 後備:抓 media 目錄最新 mp3/m4a/webm/wav
    FILE=$(ls -t "$MEDIA_DIR"/*.(mp3|m4a|webm|wav)(N) | head -n1)
  fi
elif [[ "$SRC" == "2" ]]; then
  read -r "?輸入音檔路徑(可拖曳): " RAW
  FILE="$(sanitize_path "$RAW")"
  [[ -f "$FILE" ]] || { echo "❌ 找不到檔案:$FILE"; exit 1; }
  # 複製到 media 目錄再處理(統一路徑)
  cp -f "$FILE" "$MEDIA_DIR/"
  FILE="$MEDIA_DIR/$(basename "$FILE")"
else
  echo "❌ 無效輸入"; exit 1
fi

[[ ! -f "$FILE" ]] && { echo "❌ 找不到音檔:$FILE"; exit 1; }
echo "🎵 音檔:$FILE"

# ========= 估時 =========
BASENAME="${${FILE##*/}%.*}"
LANG_FILE="$OUT_DIR/$BASENAME.lang"    #(保留欄位,未來若要寫入語言偵測)
DUR_S="$(get_duration_s "$FILE" || echo 0)"
if [[ -n "$DUR_S" && "$DUR_S" -gt 0 ]]; then
  EST=$(( (DUR_S*8)/10 + 8 ))   # 約略估計:檔長 *0.8 + 8s
else
  EST=60
fi

# ========= 啟動轉錄(faster-whisper Python API;自動偵測語言)=========
tput civis || true
printf "[2/2] 轉錄(模型:%s/%s) | 檔長 %s | 初始化…" \
  "$MODEL" "$COMPUTE_TYPE" "$(sec2hms "$DUR_S")"

{
  "$VENV/bin/python3" - "$FILE" "$OUT_DIR" "$MODEL" "$COMPUTE_TYPE" "$DEVICE_INDEX" "$LANG_FILE" <<'PYCODE'
import sys
from pathlib import Path
from faster_whisper import WhisperModel

file_path  = sys.argv[1]
out_dir    = sys.argv[2]
model_name = sys.argv[3]
compute    = sys.argv[4]
device_idx = int(sys.argv[5])

out_dir_p = Path(out_dir)
out_dir_p.mkdir(parents=True, exist_ok=True)

print(f"啟動轉錄 → 模型: {model_name}, device: cuda:{device_idx}, compute_type: {compute}", flush=True)

# 載入模型(建議首次用 large-v3-turbo;想更快可改 medium/small/int8_float16)
model = WhisperModel(model_name, device="cuda", device_index=device_idx, compute_type=compute)

# 轉錄:開啟 beam=3 做準確度與速度的折衷;想更快可改 beam_size=1
segments, info = model.transcribe(file_path, beam_size=3)

print(f"偵測語言:{info.language}(p={getattr(info, 'language_probability', 1.0):.4f})", flush=True)

# 輸出文字檔(與音檔同名)
bn = Path(file_path).name.rsplit(".", 1)[0]
out_path = out_dir_p / f"{bn}.txt"
with open(out_path, "w", encoding="utf-8") as f:
    for seg in segments:
        t = (seg.text or "").strip()
        if t:
            f.write(t + "\n")

print(f"完成轉錄,輸出檔:{out_path}", flush=True)
PYCODE
} &> "$LOG" &
PID=$!

# ========= 進度條 =========
ELAPSED=0
BARLEN=40
while kill -0 "$PID" >/dev/null 2>&1; do
  ELAPSED=$((ELAPSED+1))
  PCT=0; FILL=0
  if [ "${EST:-0}" -gt 0 ]; then
    PCT=$(( ELAPSED*100/EST ))
    [ "$PCT" -gt 99 ] && PCT=99
    FILL=$(( ELAPSED*BARLEN/EST ))
    [ "$FILL" -gt $((BARLEN-1)) ] && FILL=$((BARLEN-1))
  fi
  filled="$(printf "%${FILL}s" | tr ' ' '#')"
  empty="$(printf "%$((BARLEN-FILL))s" | tr ' ' '-')"
  used_hms="$(sec2hms "$ELAPSED")"
  total_hms="$(sec2hms "${EST:-0}")"
  rem=$(( ${EST:-0} - ELAPSED ))
  [ "$rem" -lt 0 ] && rem=0
  eta_hms="$(sec2hms "$rem")"
  printf "\r[%s%s] %3d%% | %s/%s | ETA %s | 轉錄(模型:%s/%s)" \
    "$filled" "$empty" "$PCT" "$used_hms" "$total_hms" "$eta_hms" "$MODEL" "$COMPUTE_TYPE"
  sleep 1
done

set +e
wait "$PID"
EC=$?
set -e

printf "\r[%s] 100%% | %s/%s | ETA 0s | 轉錄(模型:%s/%s)\n" \
  "$(printf "%${BARLEN}s" | tr ' ' '#')" "$(sec2hms "$ELAPSED")" "$(sec2hms "${EST:-0}")" "$MODEL" "$COMPUTE_TYPE"
tput cnorm >/dev/null 2>&1 || true

if [ "$EC" -ne 0 ]; then
  echo "❌ 轉錄失敗,尾端日誌:"
  tail -n 120 "$LOG" || true
  exit "$EC"
fi

# ========= 找輸出 txt =========
CAND1="$OUT_DIR/$BASENAME.txt"
TXT=""
if [[ -f "$CAND1" ]]; then
  TXT="$CAND1"
else
  TXT=$(ls -t "$OUT_DIR"/*.txt(N) 2>/dev/null | head -n1)
fi
[[ -z "$TXT" || ! -f "$TXT" ]] && { echo "❌ 找不到 txt,請看日誌:$LOG"; exit 1; }

# ========= OpenCC 轉台灣正體(無條件執行)=========
FIRST_CFG="$(profile_path s2twp.json)"   # 先「簡→繁(台灣)」
SECOND_CFG="$(profile_path t2tw.json)"   # 再「繁→繁(台灣化用語)」
TMP="${TXT}.tmp"

if command -v opencc >/dev/null 2>&1; then
  if opencc -c "$FIRST_CFG" -i "$TXT" -o "$TMP"; then mv -f "$TMP" "$TXT"; fi
  if opencc -c "$SECOND_CFG" -i "$TXT" -o "$TMP"; then mv -f "$TMP" "$TXT"; fi
  echo "✅ 已轉台灣正體:$TXT"
else
  echo "⚠️ 未安裝 opencc:sudo apt install -y opencc"
  echo "✅ 完成:$TXT"
fi

yt2txt.sh 放到 ~/Documents/,加執行權限:

chmod +x ~/Documents/yt2txt.sh

▶️ 使用方式

在Documents目錄執行終端機,輸入:

./yt2txt.sh

腳本會問要處理什麼來源:

  1. YouTube:貼影片連結,它會自動下載音訊
  2. 本地檔案: 把 mp3/m4a/wav 檔拖進終端機

接著:

  • 顯示進度條
  • 轉錄完成後,會在 ~/Documents/transcripts/ 找到 .txt
  • 文字已自動轉成台灣正體

🔑快速輸入長串句子 autokey

把轉成文字的.txt檔直接給大型語言模型(LLM)請他抓重點,我會如此提問:「請仔細想過再寫。這篇文章的重點是什麼?先條列,再下小標題並寫成一篇流暢的文章,最後加相關反思,看可以延伸哪些議題?另外,看文章有沒有值得商榷的觀點?若有,列出來告訴我爭議的地方,沒有也不要硬寫。」

以上引號內這麼一大串字,有個快速輸入的方法,用autokey把這一長串句子存起來,打字時,輸入設定好的縮寫把這長串呼叫出來即可。


上次修改於 2025-09-19