中文 | English
Research project — this is an academic/research project for agent web-adaptation (LLM agents operating web UIs). It is not intended for commercial redistribution of copyrighted content.
Fully-automatic pipeline to repost YouTube videos/playlists to Bilibili: download → transcode → localize metadata → upload (private). Supports scheduled monitoring and dedup by YouTube URL.
It bypasses YouTube's anti-scraping (player-client fallback, Chrome cookies, JS-challenge solver) and uploads via Bilibili's official API (biliup), with private upload + phone verification for safe publishing. See How It Works for the technical details.
🤖 For AI agents: read
AGENT.md(agent manual) or load theskill/bilimoveskill — no need to read code/docs.
- 2026-09-03: single
config.yamlat root — title/description/credit templates + numbers/tags/download/transcode, foolproof customization via placeholders - 2026-09-03: manual video add —
./run.sh --addinteractive URL input (bypasses dedup, user-specified video is always re-uploaded) - 2026-09-03: failed-video auto-retry — videos that failed (download/upload) are automatically retried on restart/next round
- 2026-08-29: heartbeat mode (sync every minute, auto download+upload) + local privacy channels (
channels_local.txt, gitignored) - 2026-08-29: AGENT.md + skill — agent manual and loadable skill so AI agents can use the project without reading code/docs
- 2026-08-29: channels.txt — configure monitor channels at project root (one URL per line), interactive prompt on first run
- 2026-08-29: pipeline established — download → transcode → localize → biliup upload (private + repost), dedup by YouTube URL, quality-first, scheduled monitoring
A resident loop that syncs your channel/playlist every minute and, when a new video appears, automatically runs the full pipeline: download → transcode → localize → upload (private).
./run.sh --heartbeat --upload --auto- Interval: default 60s, adjust with
--interval <seconds>. - Stop:
Ctrl-C. - Privacy: channels are read from
channels_local.txt(gitignored) first, falling back tochannels.txt. - Dedup: already-processed videos are skipped via
config/processed.json(keyed on YouTubevideo_id).
Equivalent: python -m src.monitor --heartbeat [--interval <s>] [--upload] [--auto] [--dry-run]
- ✅ Fully-automatic pipeline: download → transcode → localize → upload, one command
- ✅ Dedup by YouTube URL: keyed on
video_id(stable URL part), title changes don't matter - ✅ Quality-first: yt-dlp smart format strings, grab best quality+audio, then transcode to Bilibili-compatible H.264+AAC
- ✅ Private upload: biliup uploads as private, verify then publish
- ✅ Repost-compliant: auto
--copyright 2+ source (original YouTube link) - ✅ Scheduled monitoring: auto-discover and repost new videos (cron, unattended)
- ✅ Extensible: modular design, add visualizer / multi-platform etc.
YouTube actively blocks automated downloads via three mechanisms:
- Player-client restrictions — different clients (web, android, ios, tv) receive different player responses; some are restricted or return lower quality.
- JS challenges (n-sig / PO token) — YouTube requires solving a JavaScript challenge to obtain valid stream URLs.
- Signed-in requirements — some content needs a logged-in session.
Our approach:
- Player-client fallback chain — try
web_embedded → android_vr → android → web_safari → web_creatorin order. Each client has different restrictions; we pick the first that works. Some clients (e.g.android_vr) don't need cookies at all. - Chrome cookies extraction — pull signed-in cookies from your Chrome browser (
--cookies-from-browser chrome) for content that needs auth. - JS-challenge solver — yt-dlp uses an external JS runtime (Deno) to solve the n-sig / PO-token challenge. We keep yt-dlp updated to the latest version, which ships the fixes for these challenges.
- Smart format strings —
bestvideo+bestaudio/bestwith/fallback chains grab the best quality in a single command, avoiding wasted attempts on fixed format IDs.
- biliup CLI — uses Bilibili's official upload API directly (chunked upload, retries), instead of fragile browser automation that produced broken drafts.
- Cookies auth — authenticates via
config/cookies.json. - Private upload — uploads as 仅自己可见 (private), so you verify on your phone before publishing.
- Repost compliance —
--copyright 2(repost) +--source(original YouTube URL), required by Bilibili for reposted content.
- Fully automatic — one command: download → transcode → localize → upload.
- Quality-first — grabs the best quality, then transcodes to Bilibili-compatible H.264+AAC.
- Robust dedup — keyed on YouTube
video_id(stable URL part), so title changes don't cause duplicates. - Safe publishing — private upload + phone verification, avoids publishing broken content.
- Metadata localization — auto-Chinese title/desc/tags.
- Unattended monitoring — cron-based scheduled reposting.
- Modular & extensible — clean module separation, easy to add features.
# Python deps
pip install -r requirements.txt
# System deps
pip install yt-dlp biliup # download + upload
brew install ffmpeg # transcode (macOS)The tool grabs cookies automatically — no manual extraction needed:
- Bilibili: run
./run.sh --login, scan the QR code with your phone once. Cookies are auto-saved toconfig/cookies.json. - YouTube: just be logged into YouTube in your Chrome browser — the tool auto-extracts cookies from Chrome.
./run.sh --loginEdit channels_local.txt at the project root (local privacy, gitignored) — one channel/playlist URL per line, foolproof copy-paste:
# 每行一个 YouTube 频道/播放列表 URL
https://www.youtube.com/@ChannelName/videos
https://music.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx
- Privacy:
channels_local.txtis loaded first and is gitignored — never synced to git. Fallback tochannels.txtif absent. - First run: if both are empty, the tool prompts you to enter channels interactively.
- Multiple channels: just add more lines.
- Template:
channels.example.txt.
./run.sh --dry-runBefore running: make sure
channels_local.txthas your channels (see step 3). If it's empty, the tool prompts you on first run.
# Monitor + auto-upload (private)
./run.sh --upload --auto
# Heartbeat: sync every minute, auto download+upload new videos
./run.sh --heartbeat --upload --auto| Purpose | Command |
|---|---|
| Bilibili login (first) | ./run.sh --login |
| Dry-run (see new videos) | ./run.sh --dry-run |
| Monitor + confirm upload | ./run.sh --upload |
| Monitor + auto-upload | ./run.sh --upload --auto |
| Heartbeat (sync every minute, auto download+upload) | ./run.sh --heartbeat --upload --auto |
| Process only, no upload | ./run.sh |
| Single video | ./run.sh --once <URL> |
| Single video + upload | ./run.sh --once <URL> --upload --auto |
| Manual add (interactive URL input) | ./run.sh --add --upload --auto |
Monitor target (config/monitors.yaml)
│
▼
yt-dlp fetch video list
│
▼
compare config/processed.json ── processed → skip
│ new video
▼
┌─ download ─────────────────────────────────────────────┐
│ player-client fallback chain (web_embedded → android…) │
│ + Chrome cookies + JS-challenge solver (Deno) │
│ + smart format string (bestvideo+bestaudio/best) │
└─────────────────────────────────────────────────────────┘
│
▼
transcode → H.264 + AAC (Bilibili-compatible)
│
▼
localize metadata → Chinese title / desc / tags
│
▼
[confirm / --auto] → biliup upload (official API)
│ private (仅自己可见) + repost (--copyright 2 --source)
▼
record to processed.json (BV + source_url)
Each stage is a separate module (downloader.py, transcoder.py, metadata_localizer.py, biliup_uploader.py), so you can swap or extend any step.
video_moving/
├── run.sh # one-click script
├── AGENT.md # agent manual (read this first)
├── channels_local.txt # local private channels (gitignored) ← edit this
├── channels.txt # fallback channels (gitignored)
├── channels.example.txt # channels template
├── config.yaml # single config (format + runtime) ← edit this
├── skill/
│ ├── README.md # skill usage
│ └── bilimove/SKILL.md # loadable agent skill
├── pyproject.toml # package + deps
├── requirements.txt # Python deps
├── config/
│ ├── monitors.yaml # advanced monitor config (optional)
│ ├── monitors.yaml.example # config template
│ ├── cookies.json # Bilibili login (sensitive, gitignored)
│ └── processed.json # processed records (auto, gitignored)
├── src/
│ ├── config.py # global config
│ ├── models.py # shared data models (UploadTask/Result)
│ ├── downloader.py # yt-dlp download
│ ├── transcoder.py # ffmpeg transcode
│ ├── metadata_localizer.py # metadata localization
│ ├── biliup_uploader.py # Bilibili upload (biliup CLI)
│ ├── pipeline.py # main orchestrator (single video)
│ ├── monitor.py # channel monitor
│ └── cookie_extractor.py # YouTube cookies extraction
├── data/ # generated data (gitignored, auto-created)
│ ├── downloads/ # raw downloads (one folder per video)
│ ├── output/ # processed output
│ └── logs/ # logs
├── test/ # test/debug artifacts (gitignored)
└── legacy/ # unused code (local only, gitignored)
Convention:
data/,test/,legacy/are all gitignored. The project is self-bootstrapping: first run auto-createsdata/(downloads/output/logs/archive/failed). Move unused code tolegacy/, debug/test artifacts totest/— never delete.
pip install pytest
pytestCovers: data models, config, downloader (ID extraction / filename sanitize / quality presets), transcoder (compatibility / command build), metadata localization, biliup upload (BV parse / command build / task load), monitor dedup logic.
Optional, use muvid to render "audio + cover" into a dynamic spectrum video, avoiding duplication with the original:
pip install -e ".[visualizer]"from muvid.visualize import render_audio_video
render_audio_video("song.wav", image="cover.png", visual="spectrum")Edit config.yaml at the project root — the single config for title/description format, numbers, tags, and download/transcode. Foolproof, out of the box:
# 标题格式(占位符 {title} {uploader} {source_title})
title_format_music: "【搬运】{title} - {uploader}" # music videos
title_format: "【搬运】{title}" # other videos
# 简介模板(占位符 {title} {music_info} {summary} {uploader} {source_title} {copyright} {credit} {tags})
description_template: |
🎵 {title}
原作者:{uploader}
{music_info}
{summary}
{credit}
━━━━━━━━━━━━━━━━━━━━
{copyright}
{tags}
# 简介里的 credit 内容(通过 {credit} 占位符引用)
credit: |
本视频由 bilimove 自动搬运
开源项目:https://github.com/Aztech-Lab/bilimove
# 数字配置
max_desc_length: 2000
max_title_length: 80
max_tags: 10
default_tid: 3
add_copyright_notice: true
base_tags:
- 音乐搬运
# 下载/转码(可选覆盖)
download:
max_retries: 5
transcode:
video_crf: 20The description structure lives in config.yaml → description_template. Placeholders are filled per video:
| Placeholder | Meaning |
|---|---|
{title} |
video title |
{music_info} |
music info block (artist/album/date/label) |
{summary} |
description summary (non-music) |
{uploader} |
original uploader |
{source_title} |
original video title |
{copyright} |
copyright notice |
{credit} |
content of credit.txt |
{tags} |
hashtags |
Default template:
🎵 {title}
{music_info}
{summary}
━━━━━━━━━━━━━━━━━━━━
原作者:{uploader}
原视频:{source_title}
{copyright}
{credit}
{tags}
Edit credit.txt for the credit/attribution content (referenced via {credit}). Delete or empty a file to disable that part.
- Not logged in:
./run.sh --login - Download fails: YouTube sometimes rate-limits, retry; ensure Chrome is running (cookies extraction)
- Upload fails: check
config/cookies.jsonis valid; check title/desc for special chars - Monitor finds no new videos: check
monitors.yamlURL;processed.jsonrecords processed videos, delete an entry to reprocess
- This is a research/educational project for agent web-adaptation (how LLM agents interact with web UIs).
- All downloaded content (audio/video/cover) belongs to the original creators. This tool only automates the repost workflow.
- 侵删 (remove on request): if any content infringes your copyright, contact us and it will be removed immediately.
- Do not use this to commercially redistribute copyrighted content. Use only for your own study/research or content you have rights to.
- You are responsible for complying with YouTube/Bilibili terms of service and applicable copyright law.
Thanks to the open-source tools and platforms that make this possible:
- yt-dlp — YouTube download engine (player-client fallback, JS-challenge solver)
- biliup — Bilibili upload API client
- ffmpeg — transcoding to Bilibili-compatible H.264+AAC
- YouTube / Bilibili — the platforms this pipeline bridges
This project is specially planned by Aztech Labs, executed and compiled by DeepSeek V4 Flash, and engineered end-to-end by DeepSeek Harness.