Communitygithub.com

RaganaBloodedge/read-video-skill

按需读取远程视频内容,无需下载完整文件。支持1700+视频网站链接(Bilibili走手写适配器,其余使用yt-dlp通用后端)与视频直链,自动解析带签名的直链与所需请求头。

read-video-skill 是什麼?

本技能提供一种高效、节省资源的远程视频内容读取方案。核心思想是“按需取字节”,仅获取目标时刻附近的小段数据,而非下载整个视频文件。它支持从1700多个视频网站(如Bilibili、YouTube等)的网页链接或直接视频链接中提取信息,并能自动解析平台特定的签名直链和请求头。技能工作流程包括:首先探测HTTP Range支持和MP4索引位置(moov box);然后使用ffmpeg的输入侧seek(`-ss`放在`-i`之前)精确提取视频帧或音频片段;最后,音频片段通过本地的SenseVoice模型离线转写为文字,视频帧则交由视觉模型分析。该技能特别适用于用户提供视频链接并询问“看看这个视频”、“这个视频讲了什么”或需要提取特定时刻的视觉证据或语音文稿等场景。它遵循“不下载整片”的硬性原则,并通过计量(`--measure`)功能严格记录上游字节消耗,确保按需取用的透明性和高效性。主要支持平台包括Claude Code、Cursor、Codex等AI代理工具。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add RaganaBloodedge/read-video-skill

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

read-video —— 按需取字节地「讀」影片

這條技能解決什麼問題

用戶丟來一個影片連結,想理解內容。樸素做法是把影片下載下來再處理 —— 慢、佔儲存、有版權風險,而且往往只需要其中幾秒。本技能的做法是:只取目標時刻附近的那一小段位元組。音訊軌在本地離線轉成文字(回答「講了什麼」的主力),視訊軌解出關鍵幀交給視覺模型補畫面。

實測基準(249 MB / 9 分 56 秒的 MP4,非 faststart):

動作上游取位元組佔整片
取第 30 秒一幀2.0 MB0.839%
取第 8 分鐘一幀1.7 MB0.707%
取 480s 起 20 秒音訊6.6 MB2.784%

何時用 / 何時不用

用:用戶給了影片直鏈,或給了含影片的網頁連結(bilibili 等平台由 resolve.py 自動解析,不必手寫 API 呼叫)。 不用:本地檔案(直接讀)、需要逐秒完整理解的長影片(成本仍高,先問用戶要時間點)。

硬原則

  1. 不下載整片。 任何一步都不要 wget/curl -O 整個檔案。
  2. 先探測再動手。 不探測就 seek,會在不支援 Range 的伺服器上白等一場。
  3. -ss 必須放在 -i 之前。 放後面是輸出側 seek,等於先順序解到目標點再丟,退化為拉全片。放在前面才是輸入側 seek,解復用器直接跳。
  4. 計量開著。 --measure 預設開:它用本地流式代理數上游位元組,是「真的按需取了」的唯一硬證據,也是向用戶證明沒偷偷下整片的依據。
  5. 網頁連結交給腳本解析。 頁面 URL ≠ 直鏈,且直鏈帶簽名會過期。用 resolve.py,或直接把連結傳給 grab.py/transcribe.py(它們會自動解析並補上請求頭)。別在會話裡現寫站方 API 呼叫 —— 那種程式碼每次都長得不一樣,也沒人 review 過。

工作流

第 0 步:分清輸入 —— 網頁連結還是直鏈

  • 網頁連結(bilibili、YouTube、抖音、微博、Vimeo…共 1700+ 站點)→ 先解析成直鏈。也可以直接把連結交給 grab.py / transcribe.py,它們認得出並自動解析、自動帶上所需請求頭。
  • 直鏈(.mp4 / .m3u8 等媒體副檔名)→ 可跳過解析。

判定規則在 resolvers.is_page_url():帶媒體副檔名的一律當直鏈,其餘 http 連結都送去解析層。

第一步:拿到可取的流

python scripts/resolve.py "<網頁連結>"

輸出 video_url(取幀用)、audio_url(轉寫用,與前者不是同一條)、headers(CDN 要求的請求頭),外加標題、時長,以及一組建議抽幀時間點。

平台 那行會標出用的是哪個後端:native(手寫適配器,目前是 bilibili)還是 yt-dlp(通用,1700+ 站點)。要強制指定用 --backend native|yt-dlp|auto。

一條命令走完全程(解析與取數自動銜接,推薦):

python scripts/transcribe.py --url "<網頁連結>" --seconds 90
python scripts/grab.py "<網頁連結>" --times 14 29 44 58 73 --prefix shot --outdir ./out

第二步:探測(直鏈建議先做,網頁連結可跳過)

python scripts/probe.py <直鏈> --json

若直鏈來自 CDN,要帶來源頭:--header 'Referer: https://www.bilibili.com'。

看三個欄位:

  • seekable —— false 就停,伺服器忽略 Range,定點取幀會退化為順序拉流。改走「邊下邊解」(-i <url> 不加 -ss,取流最前面的幀)或直接告訴用戶做不到。
  • container.moov_offset / faststart —— 索引在頭部(faststart)時固定開銷幾十 KB;在尾部時要先取檔案尾一段(實測約 441 KB)。這是「取一幀」的固定成本,省不掉。
  • content_length —— 後面算「佔整片百分比」的分母。

第三步:按用戶意圖取數

用戶給了具體時間點 → 定點取證:

python scripts/grab.py <url> --time 183.5 --out shot.jpg

用戶沒給時間點 → 先抽幾幀了解大概:

python scripts/grab.py <url> --times 5 60 180 420 --prefix shot --outdir /tmp/rv

選點技巧:按內容密度取 4~6 幀,別均勻鋪滿全片(那等於把整片解了一遍)。拿不準就先取 3 幀,看完了再決定要不要細看某一段。

要理解講了什麼 → 直接轉成文字(推薦先做這一步):

python scripts/transcribe.py --url <url> --segments 0:90 320:90 630:97

一條命令完成「截音訊 → 本地識別」,輸出帶時間戳的中文文字,不需要外部 API、不需要連線推理。首次執行自動下載 SenseVoice 模型(約 240 MB,從 hf-mirror 拉,僅一次)。

長影片取 3~4 段各 90 秒即可覆蓋主線,不必整片轉 —— 這比逐幀看畫面快得多,也更適合回答「講了什麼」這類問題。先轉文字,再用幀補充視覺資訊,是這個順序。

轉寫完先看產率。 正常口播約 46 字/秒;腳本輸出裡帶 chars_per_second,低於 1 字/秒就會主動提示。遇到這種(純音樂、遊戲實況、無聲演示)不要拿那點文字當內容,直接改走更密的抽幀(810 幀)做畫面分析 —— 聲音裡沒資訊,答案只在畫面裡。

只需要音訊檔案本身時(比如送去別處轉寫):

python scripts/grab.py <url> --start 0 --seconds 60 --audio clip.wav

有內嵌字幕軌時優先讀字幕(ffmpeg -i <url> 看有沒有 subtitle 流),比 ASR 更準更快。

第四步:把幀交給視覺模型

grab.py 只負責把像素拿到本地,不理解內容。把輸出的 jpg 路徑交給視覺模型提問(「這一幀裡有什麼」「這段在講什麼」)。多幀時按時間順序一起給,讓模型串成敘述。

輸出怎麼讀

grab.py 輸出 JSON,每個動作帶一份位元組帳:

{
  "ok": true,
  "actions": [{
    "kind": "frame", "time": 30,
    "out_bytes": 48211,
    "upstream_bytes": 2097152,
    "requests": 4,
    "wall_seconds": 2.317
  }],
  "total_upstream_bytes": 2097152
}
  • upstream_bytes / content_length 就是「佔整片多少」。超過 5% 就要停下來複查:多半是 -ss 位置寫錯了,或者伺服器不認 Range。
  • requests 多是正常的:ffmpeg 要分幾次取(moov、關鍵幀到目標幀之間、音訊頭)。
  • ok: false 時看 err_tail,常見是超時和 5XX —— 腳本已內置重試,仍失敗就換時間點再試。

transcribe.py 的輸出:

{
  "ok": true,
  "text": "這是一片被大河與群山環抱的土地……",
  "segments": [{"start": 0, "end": 25, "text": "…"}],
  "audio_seconds": 276.7,
  "model": "SenseVoiceSmall-int8"
}

text 是所有分段拼成的整段;segments 帶時間戳,需要「第 X 秒說了什麼」時查它。

環境

  • 解譯器:本機託管 venv 的 python。Windows 上路徑形如 ~/.workbuddy/binaries/python/envs/default/Scripts/python.exe。注意是 Scripts/,不是 Unix 風格的 bin/(照文件寫 bin/python 會報檔案不存在)。
  • 依賴:httpx、imageio-ffmpeg(取數)、sherpa-onnx、numpy(轉寫)、yt-dlp(通用平台解析),都已裝在同一個 venv 裡;缺哪個腳本會明確報錯。沒裝 yt-dlp 時 bilibili 仍可用(走手寫適配器),但其它站點會解析失敗。
  • ffmpeg:由 imageio-ffmpeg 自帶靜態構建,無需系統安裝。要指定別的用環境變數 RV_FFMPEG 或 --ffmpeg。
  • ASR 模型快取:~/.workbuddy/cache/read-video/models(約 240 MB,首次自動下載)。檔案存在且位元組數匹配就跳過下載。

已知邊界

  • CDN 常校驗來源:bilibili 等站點的影片 CDN 要求 Referer,不帶就 403。用 resolve.py 或 --page 會自動帶上;手動傳直鏈時記得 --header 'Referer: https://www.bilibili.com'(三個腳本都支援,可重複)。
  • 直鏈會過期:站方直鏈帶簽名與 deadline,同一批操作內用完;一旦報 403 就重新跑 resolve.py。
  • 平台覆蓋面 = yt-dlp 的覆蓋面(1700+ 站點),不是自己維護的。解析不了的站點先用 --backend yt-dlp 看原始報錯;需要登入 / Cookie 的站點會失敗,這類只能放棄或手工提供 Cookie。yt-dlp 本身要定期升級(站點改版後舊版會失效)——pip install -U yt-dlp。
  • 「解析通」≠「取得到」:解析層只負責給 URL。取數失敗時先分清是解析失敗還是網路不可達 —— 實測本機到 YouTube / Vimeo 不通(代理 502),而抖音 / 微博 / 西瓜影片可達。
  • HLS / m3u8:不是單檔案,probe.py 的 box 解剖會失敗(有 error 欄位)。此時 grab.py 仍可用,ffmpeg 自己會拉需要的分片;content_length 為空,算不出佔比。
  • DASH 音視訊分離:不少站點(bilibili 等)視訊流與音訊流是兩條獨立 URL。取幀用視訊流,轉寫用音訊流 —— 用錯了會靜默拿到空結果。
  • -ss 精度受 GOP 限制:要取某一秒的幀,必須從最近的關鍵幀解起,所以最小取數單位是「關鍵幀到目標幀」,不是「一個位元組區間」。
  • ASR 同音字:SenseVoiceSmall 對中文效果不錯,但人名、地名、古詞常見同音錯字(實測「阪泉之野」→「版圈之也」、「涿鹿」→「涿陸」)。引用專有名詞前要按上下文校正,別把轉寫文字原樣當成事實。
  • 依賴:httpx + imageio-ffmpeg(取數)+ sherpa-onnx + numpy(轉寫)。缺少時腳本會明確報錯並提示裝哪個。

更完整的原理、踩坑記錄與實測資料見 references/methodology.md。 本技能與 dsh 插件 dsh-readvideo 的關係見 references/dsh-dual-form.md。

相關技能