bili-sync: a Rust and Tokio synchroniser that turns Bilibili subscriptions into a Jellyfin or Emby library
由 Rust & Tokio 驱动的哔哩哔哩同步工具
At a glance
- What is it?
- bili-sync is a self-hosted tool for NAS owners that authenticates with Bilibili credentials, downloads favourites, collections and uploader feeds, merges the best video and audio streams with FFmpeg, and writes files under names media servers accept. The trade-off is that its credentials, its retry policy and its file layout are all things you configure once and then live with.
- Who is it for?
- Adopt bili-sync if you already run Jellyfin or Emby on a NAS, you have a Bilibili account whose favourites or uploader subscriptions you want on disk, and you are willing to keep a long-lived credential inside a container. Do not adopt it if you want a general-purpose downloader for arbitrary URLs, or if you cannot run FFmpeg alongside it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 21 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap between a Bilibili account and a media server library
Bilibili has no export. Favourites, collections and an uploader's back catalogue live inside the account, and the site is built for streaming, not for keeping a local copy that a media server can index. Anyone who wants those videos on a NAS ends up doing the same work by hand: pull the stream URLs, pick a quality, merge separate video and audio tracks, rename the result so Jellyfin or Emby recognises it, and remember to repeat the whole thing when new videos appear.
bili-sync targets exactly that person. The README describes it as a Bilibili synchronisation tool written for NAS users, driven by Rust and Tokio. The scope is narrow on purpose: favourites, video lists and video collections, the watch-later list, and an uploader's submitted videos. It is not a general downloader and it does not try to be a Bilibili client. The output is a directory tree that a media server can add as a library, which is the point at which the tool stops being a downloader and starts being part of a home setup.
How the sync loop actually works: credentials, streams, FFmpeg and a database
The pipeline has four visible stages. First, authentication: the tool uses credentials the user fills in and refreshes them when necessary, according to the feature list in the README. Second, discovery: it scans favourites, video lists and collections, the watch-later list, and uploader submissions. Third, selection and download: it picks the best video and audio streams within a range the user configures, then downloads them asynchronously with Tokio and Reqwest, with concurrency applied to videos, to video pages, and even to chunks within a single file. Fourth, assembly: FFmpeg merges the separate streams after the download finishes, and the result is written under a name the media server understands.
Two mechanisms deserve attention because they shape how the tool behaves over time. The first is the database. Media information is stored so that the tool does not repeat requests for the same video. That is what makes a recurring scan cheap, and it is also why the database, not the filesystem, is the record of what has been seen. The second is the retry policy: a failed download in the current round is retried in the next round, and after too many failures the item is dropped. That is a deliberate trade-off. A permanently broken video will not block the queue forever, but it will also stop being retried, and nothing in the README describes a manual reset for that state.
Risk control is handled the same way. The README says the tool prints logs and terminates automatically when a request hits risk control, then waits for the next round. This is a defensive design: rather than hammering the API, the round ends. The cost is that a single throttled request can cut a round short, so the parallel and request-rate limits are not cosmetic settings. They are the main lever you have over whether a sync completes.
Installing bili-sync with Docker and running a first sync
The README states that multi-platform binaries are provided and that a ready-to-use Docker image is available for Linux. The Dockerfile confirms the image layout: it is built from an Alpine base that installs ca-certificates, tzdata and ffmpeg, unpacks the musl tarball matching the target platform, and ends in a scratch image whose entry point is /app/bili-sync-rs. The declared volume is /app/.config/bili-sync, which is where configuration and state should persist across container replacements.
Because the image is built from a scratch stage, the practical consequence is that FFmpeg is present inside the container but almost nothing else is. Mount the configuration volume and a media directory. The Dockerfile declares the volume and the entry point as follows:
ENV LANG=zh_CN.UTF-8 \
TZ=Asia/Shanghai \
HOME=/app \
RUST_BACKTRACE=1 \
RUST_LOG=None,bili_sync=info
ENTRYPOINT [ "/app/bili-sync-rs" ]
VOLUME [ "/app/.config/bili-sync" ]The port the Web UI listens on is not stated in the README or the Dockerfile, so check the project documentation before publishing a port mapping. Once the container is running, the Web UI is where configuration happens. The README lists Web UI configuration, and viewing and managing videos and video sources, as supported features. The first real task is to enter Bilibili credentials there, because every later step depends on authentication succeeding and on the refresh path working.
After credentials are accepted, add a source. A favourite, a collection or an uploader is the unit the tool scans, and the settings that matter at this point are the quality range and the concurrency limits. The README describes limiting both task parallelism and API request frequency, which maps directly onto the risk-control behaviour described above. Start conservative, watch the logs, and raise the limits only if rounds complete without the tool terminating early. The environment variable RUST_LOG is set in the Dockerfile to None,bili_sync=info, so the container logs the sync activity by default; that is the first place to look when a source produces nothing.
Finally, point Jellyfin or Emby at the media directory. The README's claim is that files are named in a way media servers support, so the library should populate without manual renaming. If it does not, the naming is the thing to inspect, not the download.
Where bili-sync is the wrong tool
The most important limitation is the one the README states plainly rather than hides: when a request hits risk control, the tool stops and waits for the next round. On a large first sync, or on an account with many sources, that means progress can be uneven and a round can end earlier than expected. There is no documented mechanism for completing a round that was cut short beyond letting the next one run.
The second limitation is the retry ceiling. Failures are retried in the next round, and after too many failures the item is discarded. For a video that is temporarily unavailable, that is fine. For a video that fails for a reason that later gets fixed, the tool has already given up, and the README does not document a way to bring a discarded item back into the queue.
The third is scope. bili-sync is not a general-purpose downloader. It does not take an arbitrary URL and save it. If your actual need is archiving a single video, or pulling from several sites, this is the wrong shape of tool, and the effort of running a container, an FFmpeg dependency and a credential store is not repaid.
The fourth is operational. The container is built from scratch and its entry point is a single binary. There is no shell inside it to debug from. Anything you want to inspect has to come out through the configuration volume or the logs, and the tool's own database is the authority on what has been synced, not the directory listing.
bili-sync compared with a general archiver such as Pinchflat
The natural comparison is with a general-purpose archiver. Pinchflat appears in the related searches for this project, and the comparison is useful because the two tools solve the problem from opposite ends. Pinchflat is built around a source definition and a download pipeline that writes files to disk, and it is not tied to Bilibili. bili-sync is tied to Bilibili completely: its authentication, its stream selection, its source types (favourites, collections, watch-later, uploader submissions) and its database of media information all exist because the source is Bilibili.
That difference decides the choice. If a source is Bilibili and the destination is a Jellyfin or Emby library, bili-sync's integration is the reason to use it: it knows what a collection is, it knows how to refresh a Bilibili credential, and it stores media metadata so repeated scans do not re-request the same video. A generic archiver has to be told all of that, and its file naming and metadata handling are aimed at a broader set of sources. Conversely, if you need one tool for several sites, bili-sync will not be that tool, and running both is a reasonable outcome.
Licence, upgrade cost and what a version bump means here
The project is MIT licensed, stated in both the repository metadata and the Cargo.toml workspace package section. In practical terms that is a permissive licence, and the repository is not published to a registry: publish = false is set in the workspace package. There is no legal advice to give here beyond noting that MIT terms apply to the code, while the videos you download remain subject to Bilibili's own terms and to whatever rights the uploaders hold.
The upgrade path has one detail that matters. Releases are tagged, with v2.11.1 the most recent listed, and the workspace version matches it. The container declares a volume at /app/.config/bili-sync, and the repository contains a crates/bili_sync_migration member, which indicates that the database schema is versioned and migrated rather than recreated. That is good news for upgrades, but it also means the configuration volume is not disposable. Back it up before pulling a new image. The last push to the repository was on 2026-09-10, and the most recent release listed is v2.11.1 from 2026-05-07, so the release cadence and the commit cadence are not the same thing; check the release notes for a given version rather than assuming the image tag tracks every commit.
Editorial conclusion
Adopt bili-sync if you already run Jellyfin or Emby on a NAS, you have a Bilibili account whose favourites or uploader subscriptions you want on disk, and you are willing to keep a long-lived credential inside a container. Do not adopt it if you want a general-purpose downloader for arbitrary URLs, or if you cannot run FFmpeg alongside it. Before committing, verify three things: that the Web UI accepts your credentials and refreshes them, that your chosen parallel and request-rate limits do not trigger risk control, and that the generated file names are what your media server actually scrapes.
Frequently asked questions
How do I install bili-sync with Docker?
The README states that a ready-to-use Docker image is provided for Linux. The Dockerfile declares a volume at /app/.config/bili-sync, so mount that path to keep configuration and the media database across container replacements, and add a mount for your media directory. The image already includes FFmpeg, which the tool uses to merge video and audio streams.
What credentials does bili-sync need, and does it refresh them?
It authenticates using credentials the user fills in, and the feature list states that it refreshes them automatically when necessary. Configuration happens through the Web UI. The README does not document what happens if a credential expires while a sync round is running.
Which Bilibili sources can bili-sync download?
The README lists favourites, video lists and video collections, the watch-later list, and an uploader's submitted videos. It does not describe downloading arbitrary URLs, so it is not a general-purpose downloader.
What happens when bili-sync hits Bilibili risk control?
According to the README, the tool logs the event and terminates automatically, then waits for the next round to run. The parallel and request-rate limits are the settings that influence how often this happens.
Are failed downloads in bili-sync retried?
The README states that a failure in the current round is retried in the next round, and that after too many failures the item is discarded automatically. It does not document a way to requeue an item that has already been discarded.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/amtoaer-bili-sync)