Unified entry principle (important)
- This skill is the only entry for social media video downloads.
- When the task comes from Feishu Bitable (Base)/table batch processing, the download action must also call this skill's
scripts/download.py, do not write a separate direct downloader in the business script. - When body text/likes/favorites are needed, they can be scraped separately in the business script; but "video file acquisition" must reuse this skill's download pipeline and validation capabilities.
Download videos from share links to local. WeChat Channels uses the user-configured self-hosted parser to obtain a temporary direct link; Bilibili prioritizes yt-dlp, and falls back to Playwright if it fails; Douyin/Xiaohongshu prioritizes Playwright, and falls back to yt-dlp if stream capture fails; TikTok prioritizes real browser CDP stream capture.
Download process
Important: Before downloading Bilibili videos, you must first check login status (if not logged in, only 480p can be obtained).
First enter the skill root directory before executing the following commands (commands all use relative paths):
cd <your-skill-root>/video-download
Step 1: Check login status (required for Bilibili)
python3 ./scripts/download.py check-login bilibili
- Exit code
0(outputLOGIN_OK) → skip directly to Step 3 download - Exit code
2(outputLOGIN_REQUIRED) → need to perform Step 2 login
Step 2: Interactive login (when needed)
When check-login returns exit code 2, operate according to the following process:
- Start the login browser in the background(set block_until_ms: 0):
python3 ./scripts/download.py login bilibili --signal-file /tmp/video_dl_login_done
-
Immediately show the confirmation button to the user, using the AskQuestion tool:
- Prompt: "The Bilibili login page has been opened. Please complete login in the browser, then click the confirmation button below."
- Option A: "Login completed"
- Option B: "Skip login (use lower quality)"
-
After the user clicks confirm:
- Choose A: Create signal file
touch /tmp/video_dl_login_done, wait for the login script to exit, then continue downloading - Choose B: Terminate the login script process, and download directly (lower quality)
- Choose A: Create signal file
-
Fallback: If the user directly closes the browser window, the login script will automatically detect it and save cookies, without needing a signal file.
Step 3: Download video
python3 ./scripts/download.py "<分享文本或链接>" [输出文件名.mp4]
Platform support
The script automatically identifies the platform, and uses different primary engines and fallback engines according to the platform:
| Platform | Supported link formats | Engine | Login required | Notes |
|---|---|---|---|---|
| WeChat Channels | weixin.qq.com/sph/xxx、channels.weixin.qq.com/finder-preview/pages/sph?id=xxx |
Self-hosted parser + direct link download | Required on the parser side | Prefer H.264, validate with ffprobe after download |
| Douyin | v.douyin.com/xxx short link,www.douyin.com/video/xxx、modal_id=xxx |
Playwright→yt-dlp | Depends on risk control | Prefer capturing network requests; if failed, fall back using saved Cookie |
| Xiaohongshu | xiaohongshu.com/discovery/item/xxx、explore/xxx、xhslink.com/xxx |
Playwright→yt-dlp | Depends on content | Prefer capturing network requests; if failed, fall back using saved Cookie |
| Bilibili | bilibili.com/video/BVxxx、b23.tv/xxx short link |
yt-dlp→Playwright | Recommended | yt-dlp handles definition and audio-video merging; requires ffmpeg |
| TikTok | tiktok.com/@user/video/xxx、vm.tiktok.com/xxx |
CDP→tikwm→yt-dlp | Recommended | Prefer real browser CDP; for app-only/shop scenarios, automatically attempt the tikwm fallback |
| YouTube | youtube.com/watch?v=xxx、youtu.be/xxx |
yt-dlp | No | |
| Twitter/X | x.com/xxx/status/xxx、twitter.com/... |
yt-dlp | No | |
instagram.com/reel/xxx、instagram.com/p/xxx |
yt-dlp | No | Private content requires login | |
| Other | Any video link | yt-dlp | Depends on the site | Supports 1700+ sites |
- Fallback conditions include "the primary engine crashes", not just "failed to capture the address": exceptions thrown by Playwright are wrapped as
RuntimeError, caught by the caller and passed to the fallback engine; at the top level it only logs a single error line and does not throw a traceback - The output filename is optional and defaults to being generated from the video title
- Files are saved to
~/Downloads/ - Dependencies:
playwright、yt-dlp、ffmpeg(for Bilibili and merging separate audio/video formats)
Bilibili, Douyin, and Xiaohongshu fallback strategy
Download in the following order:
- Bilibili:
yt-dlp → Playwright。 - Douyin, Xiaohongshu:
Playwright → yt-dlp。 - When yt-dlp needs cookies, only use the cookies for the corresponding platform in
~/.config/video-download/<平台>_cookies.json. - The script temporarily converts that platform's cookies to Netscape format, sets permissions to
0600, and deletes them immediately after yt-dlp finishes; do not read the entire browser cookie database by default. - If cookies are missing or expired, first run
python3 ./scripts/download.py login <平台>, then retry the download.
WeChat Channels special notes
WeChat Channels share pages usually only expose the cover and basic metadata, and do not directly expose a downloadable video address. This Skill draws on the share link parsing interface shape of ltaoo/wx_channels_download , but it does not install a root certificate, modify the system proxy, or directly save or send Yuanbao cookies.
Prefer configuring Video-Picture-OSS-Auth self-hosted parser; the script also supports the nested response structure of wx_channels_download :
export WECHAT_CHANNELS_RESOLVER_URL="http://api-ai.modianinc.com:8080/wechat/channels/resolve"
export WECHAT_CHANNELS_RESOLVER_API_KEY="<WECHAT_API_KEY>"
WECHAT_CHANNELS_RESOLVER_API_KEYOptional; once configured, send it via theX-API-Keyrequest header.- Yuanbao login cookies should only be stored in the parser server's
WECHAT_CHANNELS_YUANBAO_COOKIEenvironment variable or GitHub Secret, and must not be placed in the Skill, command arguments, Git, or metadata files. - Public HTTP transmits the API Key and parsing results in plaintext; when HTTPS is currently unavailable, it is recommended only for temporary use on trusted networks, and you should migrate to HTTPS or an intranet as soon as possible.
- The parser should return directly downloadable URLs; if it returns a decryption key, the Skill will refuse to download and require the parser to provide a decrypted proxy address.
Parse and view metadata and temporary direct links only:
python3 ./scripts/download.py resolve "https://weixin.qq.com/sph/ARebDCbPGy"
Parse and download:
python3 ./scripts/download.py "https://weixin.qq.com/sph/ARebDCbPGy" "video.mp4"
On success, generate:
- Video file (default
~/Downloads/, can be changed withVIDEO_DOWNLOAD_OUTPUT_DIR) <视频文件>.meta.json, containing the author, description, cover, time, and interaction data, and not containing temporary video URLs, API Keys, or cookies
TikTok special notes (CDP preferred)
The script will preferentially try to connect to the following CDP ports to capture the video/mp4 response body:
VIDEO_DOWNLOAD_TIKTOK_CDP_ENDPOINT(if set)http://127.0.0.1:9225
Port conventions (team rules):
- In TikTok batch tasks, if
VIDEO_DOWNLOAD_TIKTOK_CDP_ENDPOINTis explicitly set, it should be treated as the only target port (for example,9225), and the task layer should not switch to other ports for retries. - The current team default TikTok port is
http://127.0.0.1:9225。 - TikTok tasks do not fall back to
9222, to avoid crossing over to the browser of another platform. - The account homepage batch process can be configured with
VIDEO_DOWNLOAD_TIKTOK_LIST_RECORDandVIDEO_DOWNLOAD_TIKTOK_AUTHOR_HANDLE, and carries cookies via the 9225 browser context to request theplayAddrin the verified list records, but does not navigate to the work detail page. After settingVIDEO_DOWNLOAD_TIKTOK_LIST_ONLY=1, a direct-link failure will be left for retry and will not enter the work detail page.
If CDP scraping fails, it will automatically try tikwm parsing; if it still fails, then depending on the environment variable, decide whether to fall back to yt-dlp。
Failure retry strategy (built in):
- First round:
CDP -> tikwm - Second round (retry once with a different path):
tikwm -> CDP - If it still fails: depending on
VIDEO_DOWNLOAD_TIKTOK_ALLOW_YTDLP_FALLBACK=1, decide whether to fall back toyt-dlp
The port can be specified via an environment variable:
VIDEO_DOWNLOAD_TIKTOK_CDP_ENDPOINT=http://127.0.0.1:9225 \
python3 ./scripts/download.py "<tiktok链接>" "output.mp4"
Optional environment variables:
VIDEO_DOWNLOAD_TIKTOK_DISABLE_TIKWM=1: disable the tikwm fallbackVIDEO_DOWNLOAD_TIKTOK_ALLOW_YTDLP_FALLBACK=1: allow final fallback to yt-dlp
TikTok scraping metadata output
After each successful TikTok download, a metadata file will be generated next to the video:
<视频文件路径>.meta.json
Key fields:
source:cdp/tikwm/ytdlptarget_video_idresolved_video_idexpected_durationactual_durationvalidation.id_okvalidation.duration_okvalidation.video_track_ok
The terminal will also print a summary line, making it easy for batch processing to write to the table:
[TikTok/META] source=... id_ok=... duration_ok=... video_track_ok=...
Login management
# 登录(打开可见浏览器)
python3 ./scripts/download.py login bilibili
# 带信号文件的登录(Agent 交互模式用)
python3 ./scripts/download.py login bilibili --signal-file /tmp/video_dl_login_done
# 检查登录状态
python3 ./scripts/download.py check-login bilibili
Cookies are saved in ~/.config/video-download/<平台>_cookies.json, with automatic expiration detection.
Supported platforms: bilibili / douyin / xiaohongshu
Existing Douyin CDP environment
When a Douyin browser environment already exists, explicitly set VIDEO_DOWNLOAD_DOUYIN_CDP_ENDPOINT=http://127.0.0.1:9222, and still use the scripts/download.py download entry point. When the author is known, set VIDEO_DOWNLOAD_DOUYIN_AUTHOR_ID to the account's sec_uid. The endpoint should be based on the original MD-Browser configuration.
This mode only connects to the existing environment, opens and closes the current task page, does not import cookies, and does not start a headless browser; if the connection or work verification fails, it reports an error directly and does not fall back to other download paths. It obtains the media address from the detail corresponding to the target aweme_id, verifies the author, video track, and duration, and after success generates .mp4.meta.json, without saving the video's temporary CDN address or cookies; the detail cover address is for the caller to archive promptly.
Dependency installation
pip3 install playwright && python3 -m playwright install chromium
brew install ffmpeg # B站视频合并需要
brew install yt-dlp # B站、抖音/小红书兜底及通用站点需要
Troubleshooting
| Problem | Solution |
|---|---|
| SSL certificate error | The script already has built-in ssl._create_unverified_context |
| No video address captured | Increase the wait time, or the content requires login/is image-text rather than video |
| After Douyin/Xiaohongshu falls back to yt-dlp, it prompts fresh cookies | Run the corresponding platform's login command, save the new cookies, and retry |
| curl/download 403 | Check whether the Referer header matches the platform domain |
| Bilibili ffmpeg does not exist | brew install ffmpeg |
| Bilibili low quality | Run login bilibili and download again after logging in |
| CDN address expired | Run again; the URL is valid for a few hours |
| cookie expired | Run the login command again |
| yt-dlp is not installed | brew install yt-dlp or pip3 install yt-dlp |
| WeChat Channels reports no parser configured | Set WECHAT_CHANNELS_RESOLVER_URL |
| WeChat Channels parsing failed/login expired | Update the Yuanbao login cookie on the parser server side; do not pass the cookie to the Skill |
| WeChat Channels returns an encrypted stream | Have the parser return a decrypted proxy URL; the Skill does not copy the decryption code of restricted projects |
Douyin material factory list priority download
The caller can set VIDEO_DOWNLOAD_DOUYIN_LIST_RECORD to the verified homepage record JSON, together with the existing CDP endpoint returned by the manager and the expected author ID. Prefer downloading the 720p candidate in the list, and if that fails, access the details; the actual selection and a summary of all candidates are written to meta. New fields returned by the details are passed to the caller via detail_updates to overwrite the current values. When there are no parameters, retain the work link entry point. For resolution selection and archiving rules, see the complete material factory workflow of douyin-scraper.