CLI tool
biliup/biliup avatar
biliup/biliup

biliup: a Rust-backed recorder and uploader for Bilibili, Twitch and 19 live platforms

自动直播录制、投稿、twitch、ytb频道搬运工具。命令行投稿(B站)和视频下载工具,提供多种登录方式,支持多p。边录边传0落盘

5,457 stars666 forksRustMIT

At a glance

What is it?
biliup watches live streams, records them with danmaku, and uploads to Bilibili in one pass. It is a tool for people who archive streams on their own hardware, and its defaults assume you are one of them.
Who is it for?
biliup fits someone who wants unattended recording of Chinese live platforms with automatic Bilibili upload, and who can run a server process and keep cookies.json current. It does not fit anyone who needs a stability guarantee, commercial use, or a hosted service: the README disclaims all three.
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 received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap biliup fills between a stream and a Bilibili submission

Recording a live stream and publishing it as a Bilibili submission are two separate jobs, and most tools do one of them. biliup does both in a single process: it watches a streamer URL, pulls the stream, captures the danmaku alongside it, and hands the result to an uploader that creates or appends to a Bilibili submission. The README describes the intended shape of that workflow as multi-streamer recording and upload, running unattended around the clock, with customisable metadata.

The audience is narrow and specific. Someone archiving Chinese live platforms (Bilibili, Douyin, Douyu, Huya, Kuaishou, AcFun, NetEase CC, YY and others) who also wants the archive to end up on Bilibili without a manual upload step. The README also lists Twitch, YouTube, TwitCasting, niconico, AfreecaTV, Bigo Live, Picarto and KilaKila, so the recording side is broader than the upload side. Upload targets beyond Bilibili are not documented in the README; the uploader names that appear are bili_web and Noop, and the sync-downloader section only discusses Bilibili submissions.

Two claims in the README are worth separating. The first is 0-disk recording and upload, which is a real mode with real preconditions. The second is the platform count: 19 built-in parsers, with anything unmatched falling through to a generic adapter that depends on a local yt-dlp or Streamlink. The second claim is only as good as the tools installed on the machine.

Two runtimes in one repository: Rust crates behind a Python CLI

The repository is a Cargo workspace with four members: crates/biliup, crates/biliup-cli, crates/stream-gears and crates/danmaku. The workspace version is 1.2.7, matching the latest release. The Python side is packaged with maturin, and pyproject.toml points the build at crates/stream-gears/Cargo.toml, so the pip package ships a compiled extension module rather than pure Python. That is where the default downloader comes from: the README states stream-gears is implemented in Rust and needs no external dependency.

The dependency list in pyproject.toml tells you what still lives in Python: yt-dlp, aiohttp with speedups, Pillow, requests, streamlink and rsa. So installing the PyPI package pulls in yt-dlp and streamlink regardless of whether your target platform needs them. The WebUI is a separate concern: the top level holds a Next.js application (app/, next.config.js, package.json) with React 19, semi-ui, artplayer and mpegts.js, plus a tauri-app/ directory for the desktop build. The package.json in the repository root is named my-app and carries Next 16, which is a leftover name rather than a description of anything.

The important architectural point is that the CLI, the server and the downloader are not the same code path. `biliup download` runs a one-off grab. `biliup server` runs the long-lived recorder and exposes a Web API. `biliup upload` posts a local file. Choosing the wrong one for your task is the most common way to be confused by this project.

Installing biliup on Linux or macOS and starting the WebUI

The README's Linux and macOS path goes through uv. Install uv first, then install biliup as a tool, then start the server with authentication enabled.

bash
uv tool install biliup
biliup server --auth

After that the WebUI is at http://127.0.0.1:19159. Note the bind address: since PR #1660 the default for --bind changed from 0.0.0.0 to 127.0.0.1, so the server listens only on the local machine. If you are on a remote box and open that URL from your laptop, nothing will answer, and that is the intended default rather than a bug.

For access from another device, the README requires both a non-loopback bind and authentication. The server refuses to start with an unauthenticated Web API on 0.0.0.0, printing a message about exposing the unauthenticated Web API.

bash
biliup server --bind 0.0.0.0 --auth

The first visit to http://your-ip:19159 walks you through setting an admin password, with the username fixed as biliup. The README warns that whoever reaches the page first can claim that password, so initialise immediately after starting. If you put the WebUI behind an HTTPS reverse proxy, add --secure-session-cookie; over plain HTTP remote access, do not add it, because the browser will drop the session cookie.

Windows users get a different artefact: the README points at the bbup-app release rather than the pip package. Termux users are sent to the project wiki. Docker is covered below because its defaults differ from the bare install.

Recording a single stream without running the server

Not every job needs a daemon. The README gives a one-shot downloader that takes a URL and an output template, and it is the quickest way to confirm that a platform is reachable and that your machine has whatever that platform needs.

bash
biliup download <URL> -o "./video/%Y-%m-%dT%H_%M_%S{title}" --split-time 1h

The -o template accepts a {title} placeholder and strftime time formats, so the example produces filenames like a timestamp followed by the stream title. --split-time 1h cuts the output hourly; --split-size does the same by volume. Both are useful when a stream runs for hours and you want bounded files.

What the README does not say is what happens when the download is interrupted, or whether a partial file is kept. It also does not document a resume flag here. If your use case involves flaky networks, treat the one-shot downloader as a best-effort grab and check the output before assuming it is complete.

The same binary exposes other subcommands worth knowing about: login, renew, upload, append, show, comments, reply, dump-flv, server and list. login supports SMS, username and password, QR code, browser and web cookie methods, and writes the resulting cookie and token to cookies.json, which the README notes can be reused by other projects. The -u/--user-cookie option selects a different login file, which is how you keep multiple accounts apart.

Docker and the bind-address trap

The Docker image is the one place where the loopback default is deliberately overridden. The README states the image already contains --bind 0.0.0.0 --auth, so the compose file works without editing.

bash
docker compose up -d

Recordings and configuration persist in the container's /opt volume. The README does not describe how to change that path, so if you need the archive on a specific host directory, you are working it out from the compose file yourself.

The trap is customisation. If you override command in your compose file, you must include --bind 0.0.0.0 yourself. A container listening on 127.0.0.1 cannot be reached through the host's port mapping, and the README says the Web UI becomes completely inaccessible. This is a common failure when someone copies the server command from the Linux instructions into a container.

The image also bundles ffmpeg, which matters because the ffmpeg downloader and any post-processing require it on PATH. On a bare install you supply ffmpeg yourself.

sync-downloader: uploading while the stream is still running

The 0-disk claim in the README belongs to one downloader mode. Set downloader to sync-downloader and ffmpeg remuxes the live stream into Matroska on stdout; the output is split by UPOS and uploaded as it is produced. Every time file_size bytes accumulate (default 2.5 GB, rounded up to a multiple of 10 MiB), that chunk becomes another P appended to the same submission.

toml
downloader = "sync-downloader"
uploader = "bili_web"
file_size = 2621440000          # per-P size, lower it for slower upload links
# sync_save_dir = "/data/sync"  # optional: keep a local copy of each P

[streamers."some-streamer"]
url = ["https://live.bilibili.com/1234"]
title = "{streamer}%Y-%m-%d live recording"
tid = 171
user_cookie = "cookies.json"

The preconditions are strict, and this is where the mode is most likely to disappoint. ffmpeg must be on PATH. The streamer must have an upload template bound, and the cookies file that template points at must be usable. With uploader = "Noop" or no template at all, nothing is recorded; the log emits a message saying that sync upload requires an upload template to be set for the streamer first. Upload concurrency is fixed at three threads and is not governed by threads or segment_time.

There is also a fallback mechanism the README explains in detail. During recording, each P is written to the system temporary directory under $TMPDIR/biliup-sync/worker-<id>/. If the pre-upload completes and verifies, the chunk is marked complete; if the stream ends early or the upload falls behind, the chunk is re-sent from the temporary file at its actual length. After the submission is confirmed the temporary file is deleted, and any copy under sync_save_dir is kept. So the 0-disk framing is accurate for the happy path, not absolute: the temporary file exists while the P is in flight, and HLS streams on Bilibili (bili_protocol = "hls_fmp4") will use streamlink to pull the stream if it is installed, falling back to ffmpeg otherwise.

Limits, failure modes, and when biliup is the wrong tool

The README carries a disclaimer that is unusually direct for a project of this kind: personal learning and research purposes only, no stability guarantee, no technical support, users responsible for consequences, commercial use strictly prohibited. Whatever the licence file says, the project's own front page tells you not to depend on it commercially or operationally. Take that at face value.

Platform coverage has a hard edge. Nineteen parsers are built in, and the danmaku column in the platform table shows ticks for Bilibili, Douyin, Douyu, Huya, Twitch, YouTube and TwitCasting only. Everything else records without danmaku, and anything outside the table goes to the generic adapter, which requires yt-dlp or Streamlink on the host. If your platform is not in the table and you have neither tool installed, the generic adapter has nothing to work with.

Cookie maintenance is an ongoing cost rather than a one-time setup. Login writes cookies.json, and the sync-downloader path requires the template's cookie file to be valid at record time. The README documents a renew subcommand for manual verification and refresh, which implies cookies expire and someone has to notice. There is no documented automatic renewal.

The WebUI's security posture is deliberately conservative after the bind default changed, and that is a trade-off in the other direction: a fresh install is unreachable from another machine until you opt in with --bind 0.0.0.0 --auth. People who expect a self-hosted tool to be network-visible by default will spend time debugging a firewall that is not the problem.

Finally, biliup is the wrong tool if you want a managed service, a guarantee of uptime, or an upload destination other than Bilibili. The upload side of the README is about Bilibili submissions; the recording side is broad, the publishing side is not.

How biliup differs from BililiveRecorder and yt-dlp

The most direct alternative for the recording half is BililiveRecorder, which appears in the related searches as bililive go and bililive tools. Its approach is narrower and deeper: it is a dedicated Bilibili live recorder, whereas biliup spreads across 19 platforms and adds an uploader. If all you need is reliable Bilibili capture with danmaku, a single-purpose recorder has fewer moving parts and no upload template to configure. biliup's advantage is the append-to-submission step and the platform breadth; its cost is that you now operate a server, a WebUI and a cookie file.

For the download half, the obvious comparison is yt-dlp itself, which biliup already depends on for YouTube, niconico and the generic adapter. Running yt-dlp directly gives you no scheduling, no per-streamer configuration and no Bilibili upload, but it also gives you no daemon and no state. If your task is occasionally pulling a YouTube archive, biliup is an unnecessary layer.

The sync-downloader mode has no real equivalent in either. Remuxing to stdout and appending P by P to a live submission is specific to the Bilibili upload API and to this project's implementation of it. That is the feature to evaluate biliup for; the rest is available elsewhere in simpler forms.

Maintenance, upgrades and what the MIT licence does not settle

The last push to the default branch was on 2026-09-22, the same day as the v1.2.7 release. The two preceding releases, v1.2.6 and v1.2.5, landed on 2026-09-21 and 2026-09-20, so the project is currently cutting releases on consecutive days. That cadence is a fact about the repository, not a promise about the future, and the README's no-stability-guarantee line applies to it.

Upgrade cost depends on how you installed it. The uv path is a single command (uv tool install biliup, or the equivalent upgrade), and the pip package is versioned through maturin, so a new release means a new compiled extension. The Docker image is the lowest-friction upgrade because ffmpeg is already inside it; on a bare install you manage ffmpeg, and potentially yt-dlp and streamlink, separately from biliup. Those external tools change on their own schedule and are the likeliest source of a break that has nothing to do with a biliup release.

The licence is MIT, which is permissive. The README adds a commercial-use prohibition on top of it. Those two statements are in tension, and resolving that tension is a question for a lawyer, not for this article. What can be said plainly is that the project's own documentation tells commercial users to stay away, and that platform terms of service for the sites being recorded are a separate constraint the README also names.

Editorial conclusion

biliup fits someone who wants unattended recording of Chinese live platforms with automatic Bilibili upload, and who can run a server process and keep cookies.json current. It does not fit anyone who needs a stability guarantee, commercial use, or a hosted service: the README disclaims all three. Before adopting it, verify that your target platform is in the 19-platform table or works through the generic adapter, and confirm whether your stream needs ffmpeg, yt-dlp or streamlink on PATH. The MIT licence covers the code, but the disclaimer against commercial use sits alongside it and is worth reading before you build anything on top.

Frequently asked questions

Does biliup need ffmpeg installed?

It depends on the downloader. The default downloader, stream-gears, is written in Rust and needs no external dependency, but configuring the ffmpeg downloader or using post-processing requires ffmpeg on the host. The Docker image already includes ffmpeg.

Why can I not reach the biliup WebUI from another machine?

Since PR #1660 the default --bind value is 127.0.0.1, so the server listens only on the local machine. To reach it from elsewhere, start it with --bind 0.0.0.0 --auth; the server refuses to start with an unauthenticated Web API on a non-loopback address.

What does 0-disk recording and upload mean in biliup?

With downloader set to sync-downloader, ffmpeg remuxes the live stream to stdout and chunks are uploaded as they are produced, each chunk becoming another P in the same submission. Each P is still written to a temporary directory under $TMPDIR/biliup-sync/ during recording as a fallback, and deleted once the submission is confirmed.

Official sources

  1. biliup/biliup on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/biliup-biliup.svg)](https://hysenlabs.com/projects/biliup-biliup)