67 Commits
Author SHA1 Message Date
Noah 3f4583b40d feat(stream-gears): ship stream_gears package with accurate stubs (#1732)
* feat(stream-gears): ship stream_gears package with accurate stubs

The stream_gears/ package (__init__.py, stub, py.typed) never made it into
the wheel: pyproject only listed python-packages = ["biliup"], so maturin
generated its own __init__.py and the stub drifted from the implementation
(7 of 16 upload lines, login_by_qrcode typed as bool, missing upload
parameters, pyobject.Segment/Credit not accepted by the Rust side).

- Move the package to the repo root next to biliup/ (maturin resolves
  python-packages relative to python-source) and set
  module-name = "stream_gears.stream_gears" for a mixed layout.
- __init__.py re-exports the native module, adds Segment = PySegment and a
  Credit TypedDict; pyobject.py is removed.
- Rewrite stream_gears.pyi from the implementation, including
  StreamGearsError and the proxy/file parameters.
- Give upload an explicit text_signature so its string and list defaults
  show up in inspect.signature/help() instead of Ellipsis.

Top-level names only grow: Credit and Segment are added.

* ci(desktop): keep PyInstaller off the source stream_gears package

PyInstaller puts the repo root first on its search path, so the source
stream_gears/ (which has no native module) shadowed the installed package
and the frozen biliup failed to import stream_gears.stream_gears. Remove
the source copy after installing the wheel.

* test(stream-gears): check package layout and run stubtest in CI

test_package.py covers the new top-level names, the native submodule,
__all__ staying in sync with the native module, py.typed and the stub in
the wheel, upload's text_signature against PyO3's argument parsing, and
mypy --strict on a valid and an invalid example.

CI runs python -m mypy.stubtest stream_gears against the installed wheel
so the stub cannot drift from the implementation again.

* build(stream-gears): drop unused danmaku-client and async-trait deps

Neither is referenced anywhere in crates/stream-gears. danmaku is still
built through biliup-cli, so this only trims the manifest and lockfile
entries.

* refactor(stream-gears): keep the package in crates/stream-gears via python-source

Point maturin's python-source at crates/stream-gears instead of moving
stream_gears/ to the repository root. python-packages is resolved under
python-source, so the root biliup/ package is shipped through include.
The wheel contents are unchanged.

* ci: drop workarounds for a root-level stream_gears package

With stream_gears/ back in crates/stream-gears, nothing at the repository
root shadows the installed package, so PyInstaller and stubtest no longer
need to avoid it.
2026-09-24 06:48:40 +00:00
Noah 753aade9f4 refactor(cli): single entry for native binary and main_loop (#1728)
* refactor(cli): single entry for native binary and main_loop

Move argument parsing, logging setup and command dispatch from
biliup-cli's main.rs into `biliup_cli::entry`, and make
stream_gears.main_loop call the same entry instead of its own copy.

- Both entries default to `server` when no subcommand is given (or the
  legacy `start`), expand `~` in paths, and log to `ds_update.log`
  (server only, no rotation) instead of `download.log` on the wheel.
- The log filter honours `RUST_LOG`; an explicit `--rust-log` overrides it.
- main_loop no longer calls process::exit on clap errors: `--help` and
  `--version` print and return, other parse errors raise SystemExit with
  clap's exit code. Logging uses try_init, so a second call in the same
  process does not panic.
- Export `entry::serve(ServeOptions)` so a host can run the Web server on
  its own tokio runtime.
- Document config_bindings as deprecated (behaviour unchanged).

Ref #1721

* test(stream-gears): cover the shared entry from the installed wheel

Exit codes of --help/--version/--bad-flag, main_loop called twice in one
process, and biliup server writing ds_update.log with RUST_LOG and
--rust-log precedence.

Ref #1721

* test(stream-gears): update smoke test comment now that --help no longer exits

* build(stream-gears): drop clap, unused after the shared entry

* ci(docker): start the built image and check GET / returns 200

The test job only built the image. Also run it with the default CMD and
require the Web UI to answer and ds_update.log to be written.

Ref #1721
2026-09-24 03:58:52 +00:00
Noah 8d482db4a6 feat(uploader): season (合集) API in biliup crate (#1727)
Add BiliBili::seasons, season_section, archive_view, add_to_season,
remove_from_season and sort_season_episodes, matching the endpoints and
payloads of bili_webup.py. Response types keep unknown fields in
`extra` so new fields from Bilibili do not break parsing.

Ref #1721
2026-09-24 03:55:02 +00:00
Noah 44df87f211 build(stream-gears): make crate cargo-testable, drop unused deps (#1724)
* build(stream-gears): enable pyo3 extension-module via default crate feature

Plain cargo build/test of stream-gears no longer links libpython, so it
works without Python dev files. build.rs adds the macOS cdylib link args
that maturin would otherwise pass. Wheel builds are unaffected: maturin
already enabled pyo3/extension-module through pyproject.toml.

Ref #1721

* build(stream-gears): drop 13 unused dependencies and biliup-cli python-bridge feature

bytes, ormlite, rust-embed, mime_guess, tower-http, sqlx, serde,
thiserror, futures, async-channel, url, chrono and fancy-regex have no
references under crates/stream-gears/src. The python-bridge feature of
biliup-cli is declared but never used by any cfg or dependent crate.

Ref #1721

* ci(test): run cargo test for the whole workspace including stream-gears

Ref #1721
2026-09-24 03:16:55 +00:00
Noah 303f6a40f0 Bumping version (#1720) 2026-09-23 15:41:52 +00:00
ForgQi 03ed3f5ff2 Bumping version 2026-09-23 00:33:34 +08:00
Noah 072604eed5 feat: 直播预览阶段二——内置播放器复用录制流(卡片弹层 + 历史页监视器) (#1712)
* feat(downloader): 直播预览 hub 与写盘旁路(httpflv / hls / mesio)

每路录制任务一个 PreviewHub(寿命 = 整场 execute(),跨分段与断流重试),下载器开始拉流时
attach 一个写入端,在写盘点旁边把同一份媒体字节 push 给它:httpflv 在关键帧写出循环、
stream-gears/hls 在分片 chunk 循环、mesio 在条目进入修复管线前。fMP4 分片流与 HEVC 序列头
标为不可预览;ffmpeg / streamlink / yt-dlp / 边录边传明确不可预览并给出原因。

写入端热路径只做三件事:GOP 缓冲追加、try_recv 订阅请求、broadcast::send。没有 .await、
没有锁等待、没有 ?;订阅请求由写入端在下一次推送时回应,快照(文件头 + 序列头 + 当前 GOP)
与随后的实时分块严格衔接。GOP 快照上限 8 MB,广播缓冲 256 槽 × 64 KB 且无订阅者时不占用,
每路订阅者数由信号量限制;慢订阅者收到 Lagged 后由端点断开让其重连。

Part of #1708

* feat(api): GET /v1/streamers/{id}/live 与列表的 preview 字段

新增 chunked 视频流端点:先发关键帧对齐的快照再接实时分块,Content-Type 按容器为
video/x-flv 或 video/mp2t,Cache-Control: no-store。房间不存在 / 未录制 404,下载器不能
旁路 415(带原因),连接数超限 429(每路 4、进程 16),尚未开始拉流或正在重连 503 +
Retry-After。慢客户端 Lagged 或写入端换代时结束响应,由播放器重连。注册在 router() 内,
--auth 时与 /v1/streamers 同一道登录校验。

GET /v1/streamers 每项新增 preview: { available, format: "flv" | "mpegts" | null, reason },
未录制为 null。

Part of #1708

* feat(preview): fMP4 分片流也能预览——init segment 快照、按分片广播、解出 codecs

mesio 的 HLS 会话拿到 fMP4 分片时不再标为不可预览:init segment(ftyp + moov)作为文件头
进快照,每个 moof + mdat 分片作为一个关键帧分块广播,新订阅者拿到 init + 最近一个完整分片,
端点 Content-Type 为 video/mp4,列表字段 preview.format 为 "fmp4"。

从 init segment 的 moov/trak/…/stsd 解出 RFC 6381 编码串(avc1.PPCCLL、hvc1.…、mp4a.40.N、
av01 / vp09 / ac-3 / ec-3 / opus / flac)放进 preview.codecs,供前端 MediaSource.isTypeSupported()
校验后 addSourceBuffer;解析只在 attach 后收到 init 时做一次,不在逐分块路径上。
stream-gears 的 HLS 路径没有下载 #EXT-X-MAP 初始化分片,遇到非 TS 分片时标为不可预览并说明原因。

按 ForgQi 在 #1708 的要求把 fMP4 纳入阶段二。

Part of #1708

* feat(ui): 直播间卡片的「预览」弹层——复用录制流的内置播放器

Player.tsx 加 isLive / muted / autoplay / type / codecs 与 onEnded / onError:flv 与 mpegts 走
mpegts.js(直播模式:不攒缓冲、追帧、自动清理 SourceBuffer),fmp4 走一个轻量 MediaSource
播放器(fetch ReadableStream → appendBuffer 串行队列,首个缓冲对齐播放位置,落后 > 3 s 追到
末尾,定期 remove 30 s 之前的缓冲),都以 Artplayer customType 挂载保持 UI 一致;
type 未知时用一次 GET 读 Content-Type 再起播。MediaSource.isTypeSupported 不通过时报错并显示
封面 + 原因,不转圈。

StreamerCard 录制中加「预览」图标按钮(不可预览时禁用,tooltip 显示后端给的原因),封面可点
即打开预览;弹层(LivePreview.tsx)关闭即卸载播放器、断开连接。服务端断开(掉队 / 换直链)
自动重连 3 次,之后交给用户手动重试;不可恢复错误 Toast 提示一次。零新依赖。

Part of #1708

* feat(ui): 历史记录页「实时监视」Tab——多路同屏监视器

<LiveMonitor /> 是独立组件:所有正在录制的直播间以小窗网格展示,可预览且在同屏路数内的
直接播放(默认静音),超出的显示封面、点击替换最早开始的一路(先进先出),下载器不能旁路
的显示封面与原因。同屏路数默认 DEFAULT_MONITOR_TILES = 4(可选 1/2/4/6/9,localStorage 记住),
每路小窗占对应直播间的一个预览连接。正在播放的列表由「用户选择 + 当前可预览房间 + 路数」
纯推导,不在 effect 里 setState。

历史记录页改为两个 Tab:「录制文件」(原表格)与「实时监视」,keepDOM=false 切走即卸载全部
播放器。要把监视器挪到别的页面只需换挂载点。

Part of #1708

* test(preview): 让旁路测试不依赖调度时机

rolling 测试先把订阅请求 poll 进写入端队列再开始写盘,源数据按 2 ms 一块限速送入而不是瞬间灌完
(瞬间灌完时订阅者必然掉队,那是慢订阅者测试覆盖的场景);慢订阅者测试只断言
「收到 + 被告知跳过 = 全部」与写入端不等待,不再对读取任务在负载下的吞吐做假设。

* fix(ui): mpegts.js 的 MSE 解码失败给出可读提示

典型来源是 TS 的 PES 没按访问单元对齐(如 Twitch 的 HLS),mpegts.js 拆出的帧不完整、浏览器拒绝
解码;原文 "Failed to execute 'appendBuffer'…" 对用户没有信息量,改为说明原因并强调录制不受影响。

* fix(preview): fMP4 与 TS 的快照从关键帧起,而不是从分段边界起

B 站 hls_fmp4 的分段(约 1 s)各含多个 moof/mdat 分片、且分段边界不对齐关键帧(实测 10 次冷启动
只有 1 次首帧是 I 帧):写入端现在把每个分段按 moof/mdat 切开,读 init 里的视频轨 ID 与
trex 默认 flags、trun 的 first_sample_flags / 首样本 flags / tfhd 默认 flags,视频首帧为同步样本的
分片才作为 GOP 起点。TS 分片起点则嗅探第一个视频 PES 的 NAL 类型(IDR/SPS → 起点,只有普通片
→ 追加到上一 GOP,说不准 → 按起点处理)。FLV 路径本来就按关键帧 tag 切,未变。

单测覆盖:分片切分(前导 styp、非同步 / 同步 / trex 默认 / 无视频轨)、跨分段边界的快照、
TS 嗅探(跨包的 PES、音频 PES 在前、非 TS 输入);另有 #[ignore] 的 B 站真实抓样核对。

* feat(danmaku): 预览播放器的实时弹幕——录制中的弹幕客户端旁路一份到 SSE

danmaku crate 的 RecorderConfig 新增可选的 live_tx(tokio broadcast):每解出一条事件,在写 XML
之前 send 一份(不等待,无订阅者即丢弃),XML 录制路径不变。DownloadTask 按 stream.danmaku
是否存在建一条 256 槽的广播,传给 danmaku_client;新增 GET /v1/streamers/{id}/danmaku(SSE,
每条 event 为 danmaku / gift / super_chat / guard_buy,data 为 {kind, text, name, color, ts},
进场消息不进;掉队跳过不断开;进程上限 32 路,超出 429;无弹幕客户端的平台 415)。
/v1/streamers 的 preview 字段新增 danmaku: bool,前端据此启用 / 禁用弹幕开关。

Part of #1708

* feat(ui): 预览弹层装载实时弹幕(开关控制),监视器默认关

Player.tsx 新增 danmaku: { url, enabled }:装 artplayer-plugin-danmuku(新依赖,Artplayer 官方弹幕
插件,5.3.0),开关打开时用 EventSource 连 /v1/streamers/{id}/danmaku,每条事件 emit 进弹幕层
(普通弹幕滚动、礼物 / 醒目留言 / 上舰置顶描边),关闭时断开 SSE 并隐藏弹幕层,不重建播放器。
弹层标题栏加「弹幕」Switch,默认开、记在 localStorage(biliup.preview.danmaku);平台没有弹幕
客户端时禁用并 tooltip 说明。监视器工具栏加同名开关,默认关(biliup.monitor.danmaku),
对全部小窗生效。localStorage 偏好统一走 useSyncExternalStore(app/lib/use-local-pref.ts),
同屏路数也改用它。

Part of #1708

* fix(preview): FLV 广播缓冲扩到 1024 槽,装得下 CDN 的整 GOP 突发

斗鱼 CDN 把整个 GOP(~4 s、~320 个 tag、~2 MB)一次突发送达,httpflv 解析器 1 ms 内推完;
刚建立的订阅者还没读到第一个实时分块,256 槽的缓冲就已被覆盖,收到 Lagged 被断开,重连后
又赶上下一个突发——冷启动 10 次只有 1 次播起来。FLV 分块是单个 tag,小而多,缓冲按格式分开:
FLV 1024 槽(实测均值 ~10 KB → ~10 MB),TS / fMP4 沿用 256 槽(分块大而少,16 MB 上限)。
回归测试:请求在关键帧回应后紧跟 600 个 tag 同步推完,订阅者仍逐个读到。

Part of #1708

* fix(ui): 弹幕开关禁用时说明是平台不支持还是未开对应 *_danmaku 配置

* feat(danmaku): 多个直播间的实时弹幕可复用一条 SSE(GET /v1/danmaku?ids=…)

浏览器对同一主机的 HTTP/1.1 并发连接只有 6 个:监视器同屏 N 路视频已占 N 个,弹幕若每路再开
一条 SSE,N=4 时就把列表轮询也饿死。新增 /v1/danmaku?ids=1,3,9 把若干路合成一条,事件 JSON
带 id 供前端分发;没有弹幕 / 未录制的 id 静默跳过,一个都没有时 404;一条最多 16 路,占 1 个
连接许可。原 /v1/streamers/{id}/danmaku 保留,走同一实现(事件同样带 id)。

Part of #1708

* feat(ui): 弹幕连接由页面持有并复用,监视器路数按 HTTP 协议限制

浏览器对同一主机 HTTP/1.1 只有 6 个并发连接,监视器 4 路视频 + 每路一条弹幕 SSE 就把列表轮询
饿死;9 路时也只有 6 路能播(实测)。改为 DanmakuFeed:页面(弹层 / 监视器)持有一条
/v1/danmaku?ids=… 连接,各播放器按 id 订阅;构造无副作用、open() 后有订阅者才连,StrictMode
下重复构造无害。同屏路数选项在 HTTP/1.1 下为 1 / 2 / 4,页面经 h2 / h3 加载时放开 6 / 9。
小窗弹幕字号 15px,弹层 22px。

Part of #1708

* fix(ui): 解码失败后关闭弹层必须断开拉流连接

<video>.error 非空时 mpegts.js destroy() 里清 SourceBuffer 的 remove() 抛 InvalidStateError,
后面的 transmuxer.close() 跑不到,连接留在服务端占着该路的预览许可直到刷新页面——真实 Twitch
TS 房间(mpegts.js 放不了)连开 4 次弹层第 4 次就 429。销毁前先把 MediaSource 从元素上摘下
(load() 使其 closed,mpegts.js 对 closed 的 MediaSource 跳过清理),destroy 仍抛则直接关
内部 transmuxer 兜底。

Part of #1708

* fix(ui): mpegts.js 换用可中止的自定义 loader,弹层关闭一定断开拉流

上一提交把 MediaSource 从元素上摘下只避免了 destroy() 抛异常,连接仍未断开:mpegts.js 自带的
FetchStreamLoader 在 Chrome 上处于 buffering 状态时 abort() 不调 AbortController.abort(),
而是等下一个 read() 回来再 reader.cancel()——解码出错后读取链已断,那一次永远等不到,连接留在
服务端占着该路许可(真实 Twitch TS 房间:关闭弹层后用 4 条 curl 探,1 条 429;再开一次 2 条 429,
直到浏览器退出才释放)。改为 customLoader:每次 open() 一个 AbortController,abort() 直接调它,
其余与原 loader 一致(直播不需要 Range)。

Part of #1708

* fix(ui): 忽略 Artplayer 在实例销毁后的自动重连,杜绝孤儿拉流连接

真正的泄漏来源:Artplayer 收到 video:error 后每隔 1 s 重设 art.url 最多 5 次(RECONNECT_TIME_MAX),
不检查自己是否已 destroy。弹层在解码失败时立刻卸载播放器,1 s 后 Artplayer 仍在已销毁的实例上
重设 url → customType 再建一个 mpegts.js 实例、再拉一路流,没有任何人持有它——每次打开泄漏
最多 5 条连接,直到刷新页面。mpegts / fMP4 两个 customType 入口对 art.isDestroy 直接返回;
fMP4 入口同时复用上一次的 dispose(同一实例重设 url 时先清掉旧的 MediaSource)。
mock 上用循环发送的 Twitch 坏样本复现:修前每开一次多 1 条连接(1→2→3),修后开关 4 次始终 0。

Part of #1708

* fix(ui): 自动播放重试前确认播放器还活着

* fix(preview): 最后一个订阅者走后释放广播缓冲

tokio broadcast 的缓冲只在被新数据覆盖时丢旧分块,而没有订阅者时写入端不再 send——最后一个
订阅者走后,缓冲里最后 N 个分块就一直挂着(FLV 1024 槽约 10 MB / 路;7 路真实短测中 RSS 在
一轮预览后从 95 MB 停在 117 MB)。写入端发现订阅者归零且缓冲里有数据时换一条新通道,旧缓冲
整个释放;之后的 subscribe() 拿新通道,对订阅者透明。

Part of #1708

* feat(preview): 直播预览取流方式配置(relay / direct)与浏览器直连的能力判定、直链端点

后端与浏览器不在一台机器时,浏览器直接向 CDN 拉一路能省服务器出口带宽(ForgQi,#1712 review 2)。
全局配置新增 preview_transport:relay(默认,复用录制流经 biliup 中转)/ direct(浏览器直连 CDN),
JSON 配置里可选字段,不需要迁移。/v1/streamers 的 preview 新增 direct: { capable, reason },按
review 1 实测的平台表判定:B 站 / 抖音 / 虎牙 FLV 能直连;斗鱼一 token 一连接、直连会挤掉录制;
Twitch CDN 无 ACAO;HLS(TS / fMP4)本版本回落——mpegts.js 放不了 m3u8,直连需 hls.js;其它平台
未验证按不能。新增 GET /v1/streamers/{id}/live-url:当前录制中那条流的直链、容器、平台与过期时间
估计(B 站 expires / 抖音 expire 十进制 Unix 秒,虎牙 wsTime 十六进制;斗鱼的相对秒数不算),
Cache-Control: no-store,与 /live 同在登录校验之内。DownloadTask 随每次 check_stream 刷新直链。

Part of #1708

* feat(ui): 直播预览取流方式——全局设置下拉,direct 模式下浏览器直连 CDN、失败自动回落中转

空间配置 → 全局设置新增「直播预览取流方式(preview_transport)」:经 biliup 中转(默认)/ 浏览器直连
CDN,文案说明两者区别与适用场景。LivePreviewPlayer 按(房间, 配置, 能力)派生取流方式:direct 且
平台能直连时先 GET /v1/streamers/{id}/live-url 拿直链(https 页面下 http 直链换 https),mpegts.js
以 cors / 不带凭据拉 CDN;出错或断开时重取一次直链再播,仍失败回落中转;平台不能直连的房间在
direct 模式下直接走中转。播放器左上角角标:「直连 CDN」或「已回落中转:<原因>」。监视器小窗共用
同一组件。异步结果与回落都带 sourceKey,配置切换时不在 effect 里同步 setState。

Part of #1708

* fix(ui): 直连模式下弹层脚注说明取流方式

* feat(preview): 直连模式向平台另取一条直链,不复用录制那条;斗鱼由此也能直连

ForgQi(#1712):走直连就不要复用链接,直接获取新链接。/v1/streamers/{id}/live-url 改为对该房间的
平台插件再调一次 check_stream,把新拿到的 raw_stream_url(新 token)给浏览器;平台返回未开播或
出错时 503,前端回落中转。实测新 token 的斗鱼 / 虎牙直链各读 25 s 稳定,同时录制那条连接无重试
——review 1 观察到的「斗鱼一 token 一连接」「虎牙第二条连接只给 GOP 缓存即断」都是同一直链两条
连接的问题,另取一条就绕开了。能力表:B 站 / 抖音 / 斗鱼 / 虎牙 FLV 能直连;Twitch 无 ACAO、HLS 需
hls.js 仍回落。Monitor 新增 PluginFor 消息按房间 URL 取插件。

Part of #1708

* fix(ui): 直连播够 60 s 后再断按直链到期处理,重取直链继续直连

斗鱼 CDN 的 token 每 5 分钟切一次连接(录制那条同样如此,日志里每 5:01 一次 StreamEnded → 重连),
B 站直链 1 h 过期。之前直连只允许重取一次,第二次到期就回落中转;现在区分「刚起播就失败」
(重取一次仍失败 → 回落)与「播了一阵才断」(视为到期,照常再取)。

Part of #1708

* feat(preview): HLS 直连——直链是 m3u8 时走 hls.js(按需加载),B 站 hls_fmp4 等也能直连

ForgQi(#1712):HLS 直连也支持一下。新依赖 hls.js ^1.7(动态 import,只在直连模式打开 HLS 房间时
才下载那一个 chunk,首屏包不变)。live-url 的 format 按新直链后缀给 flv / hls;direct_capability
只看平台的跨域放行,不再因容器排除——B 站 .m4s 分片的 CDN 同样 ACAO: *。Player 新增 hls 分支:
跟到最后 3 个分片、后向缓冲 30 s、分片请求不带凭据;清单 / 分片 404、CORS、解码等致命错误报给
LivePreviewPlayer 走重取直链 / 回落中转,清单变为非直播视为断开。Twitch 仍因无 ACAO 回落。

Part of #1708

* feat(preview): live-url 同一房间 5 秒去抖,播放失败后的重取带 fresh=1 绕过

每次打开预览都向平台 API 取一条新直链(带 cookie 时消耗登录态配额,B 站接口有频控)。同一房间
5 s 内的重复请求(StrictMode 双调用、连点、同一房间多个小窗)复用上一次结果并标 cached: true,
不再打平台;前端播放失败后的重取传 ?fresh=1 一定拿新 token(抖音某节点 404、直链到期都靠它)。
缓存插入时顺手清掉一分钟前的条目,表不会长。

Part of #1708
2026-09-22 16:25:00 +00:00
ForgQi ea3298340f Bump workspace version to 1.2.6 and update docs
Update workspace package version from 1.2.5 to 1.2.6 in Cargo.toml and synchronize package versions in Cargo.lock (biliup, biliup-cli, danmaku, stream-gears). Also add a README bullet noting streaming-upload without local save (边录边传 0 落盘).
2026-09-21 23:07:55 +08:00
ForgQi 9d02856a50 Integrate mesio (rust-srec) downloader
Add an in-process mesio downloader using the rust-srec mesio-engine. Introduces crates/biliup-cli/src/server/core/downloader/mesio.rs implementing FLV/HLS pull, timestamp fixes, keyframe indexing, and size/duration-based segmentation with pipeline_common, flv_fix and hls_fix. Wire Mesio into downloader enum/runtime and add UI options and public config docs. Update crates/biliup-cli/Cargo.toml and workspace Cargo.toml/Cargo.lock to include new dependencies and bump version to 1.2.5. Include tests for protocol selection, pipeline behavior and an e2e FLV download. Small helper changes: config enum docs and segment handling.
2026-09-21 00:24:52 +08:00
ForgQi 03b7a84f55 Allow remote Web UI bootstrap with auth
This change updates the server default bind to 127.0.0.1 for safer local-only access while documenting how to expose the Web UI via --bind 0.0.0.0 --auth for remote or Docker use. It also removes the previous restriction that blocked first-time admin bootstrap on non-loopback addresses, allowing authenticated remote initialization and bumping the workspace version to 1.2.4.
2026-08-24 01:13:29 +08:00
ForgQi eb8699b203 Add recording policy and time-range checks
This change adds a recording policy layer that enforces time-window eligibility and excluded title keywords before probing and before recording. It updates the streamer API to surface reasons like OutOfSchedule and TitleExcluded, clamps ffmpeg/segment durations to the configured window, and exposes the last rejection reason from the worker state for the frontend.
2026-08-22 19:15:48 +08:00
Noahandransxd e1c4b6887e fix(downloader): 纯视频流分段时避免序列头 expect 导致的 panic (#1664)
分段重建逻辑此前对 on_meta_data / aac_sequence_header / h264_sequence_header 使用 expect。纯视频流(无音频,aac_sequence_header 恒为 None)在首次分段时必然 panic,导致 7x24 录制任务中断。改为缺失时跳过并告警,不再中断录制。

新增回归测试 pure_video_stream_segments_without_panic:构造仅含 onMetaData 与 H264 序列头、无音频标签的 FLV 流并触发分段;修复前该测试会 panic(aac_sequence_header does not exist),修复后通过。

Co-authored-by: ransxd <ransxd@users.noreply.github.com>
2026-08-22 08:35:19 +00:00
Mostly_Harmless b438d72415 feat(web): 完善账号管理、稿件查询与安全边界 (#1660)
启动 Web 服务时自动登记 CLI 指定的 B 站凭据,并为账号登记、刷新和删除统一补上并发去重、原子写入及引用检查。

同时收紧默认监听、管理员初始化、认证路由、静态文件和日志 WebSocket,恢复 server 的 ds_update.log,并避免凭据及上游响应进入日志。

新增按账号查询远程稿件的 API 与 Web 页面,补齐严格分页和响应校验。

关联 biliup/biliup#1624

关联 biliup/biliup#1658

关联 biliup/biliup#724
2026-08-16 14:05:20 +00:00
ForgQi ddc29f63b4 Update deps & fix server upload/download
Bump dependencies (Cargo.toml + Cargo.lock) to newer crate versions and adapt code to API changes. Refactor server-side upload/download logic, the upload_streamer model, and related API endpoints to improve streaming handling, buffer/error propagation and compatibility with updated crates (e.g. tokio-tungstenite/futures/etc.). These changes align the codebase with the upgraded dependency set and fix streaming/endpoint issues introduced by those upgrades.
2026-07-11 01:57:48 +08:00
ForgQi b5ddff0564 Refactor Python package and simplify entry
Remove legacy engine and many plugin modules, refactor upload plugins to standalone classes, and simplify package entry points. __init__.py now provides a safe __version__ fallback; __main__.py directly invokes stream_gears.main_loop. BiliWeb/BiliWebAsync plugins were adapted to use a FileInfo NamedTuple and no longer depend on engine.UploadBase/Plugin decorator. Documentation updated (README expanded with new commands and CLI/SKILL integration) and a new SKILL.md added. Workspace version bumped to 1.2.1 in Cargo.toml (lock updated accordingly).
2026-06-06 03:22:54 +08:00
ForgQi 431d1f61ab Support optional segment limits and danmaku files
Make file_size and segment_time optional and normalize/validate their values so segmentation can be disabled. Frontend: send nulls for empty fields and allow empty segment_time via validation; add showClear to inputs. Downloader: extend SegmentInfo with optional danmaku_file_path and propagate it from various downloaders; DanmakuClient::rolling now returns a bool to signal whether a danmaku file was produced. Uploader: add segment_paths helper to include danmaku files in upload/postprocessing, and preserve processed paths; add unit tests. HookStep: stream child process stdout/stderr as raw bytes to terminal and append to a log file (tee), and simplify run/move logic to use explicit path lists. Cargo.toml: bump reqwest to 0.13.4. Tests and minor adjustments added throughout to support these changes.
2026-06-04 01:19:27 +08:00
ForgQi 3655679a58 Refactor live downloader architecture
Migrate the server-side live downloading to the new biliup::downloader::live API and simplify the actor model. Key changes:

- Introduce a new server/core/live module that builds LiveRequest/LiveOptions/LiveCredentials, maps runtimes, and creates danmaku clients.
- Replace legacy plugin/DownloadBase actor flow with LivePlugin-based workflows and a start_download_workflow entrypoint; monitor now acquires a Semaphore permit before starting a download to enforce concurrency.
- Remove the old plugin module and per-platform plugin plumbing; many platform implementations were moved/renamed under crates/biliup/src/downloader/live.
- Adjust DownloadManager, Monitor, and common download/upload code to use LivePlugin, LiveStream and related types; pass uploader channel and download slot semaphore into Monitor.
- Add download_stream helper to support direct FLV/HLS downloads via StatelessClient, and extend Streamlink downloader to support Niconico credentials/options.
- Update usage of context/streamer info and danmaku initialization to the new LiveStream-based model.
- Bump workspace version to 1.2.0 (Cargo.toml) and related package versions.

This refactor centralizes live logic on the shared biliup downloader live API, simplifies concurrency control, and removes the previous actor-based plugin indirection.
2026-06-01 01:50:00 +08:00
ForgQi 219334f936 fix ci 2026-05-30 02:24:52 +08:00
ForgQi c395b31148 Replace Python Danmaku with Rust implementation
Remove the legacy Python biliup/Danmaku package and related douyin utilities, and adjust PyInstaller spec to stop bundling those resources. Introduce and expand Rust-based danmaku and plugin implementations (crates/danmaku, crates/biliup-cli server/core/plugin/*, and stream-gears changes) and update various server/downloader modules to integrate them. Also add a TOML dependency to Cargo.toml to support the new Rust changes.
2026-05-30 01:17:18 +08:00
XuanRan_Nyan a24d46aae7 refactor: Implementing the Danmaku client in Rust (#1570)
* refactor: Implementing the Danmaku client in Rust

* test
2026-05-18 04:16:23 +00:00
ForgQi c2a52973a7 Refactor hook command execution; bump version
Unify and simplify how hook Run steps execute external commands: execute_command now accepts raw stdin bytes (src: &[u8]) and runs the full command string via the platform shell (cmd /C or sh -c), writing src to the child's stdin, streaming stdout/stderr lines to logs, and checking exit status. execute_with for the Run case now builds a newline-separated paths string (when called with file paths) and delegates to execute_command. Removed manual arg parsing and duplicated process-handling logic. Also bumped workspace/package version to 1.1.29 (Cargo.toml) and updated lockfile accordingly.
2026-03-01 21:14:50 +08:00
ForgQi b57dc29e96 Add cover downloader and update dependencies
Introduced a new cover_downloader module in the downloader core. Updated several dependencies in Cargo.toml and Cargo.lock, including major upgrades to image processing, HTTP, and async libraries. Adjusted related code in util, download, plugin, and uploader modules to support new features and dependency changes.
2025-12-15 00:41:34 +08:00
Dzming Li 175be7625e feat: 断点续传、标题自动截断及限流重试优化 (#1558)
* feat: 添加断点续传功能

- 实现自动保存上传进度到临时文件
- 支持上传中断后自动恢复并跳过已上传文件
- 为 Video 结构体添加 Clone trait
- 添加 .envrc 用于 direnv 自动加载 Nix 环境
- 更新 flake.lock 依赖

* fix: Auto-truncate video titles exceeding 80 characters

B站API限制分P视频标题不能超过80个字符。当文件名过长时,上传会在提交阶段失败并报错"第(N)个视频的标题过长,已经超过80个字符"。

此次修改在所有上传线路(Upos/Kodo/Cos)中添加自动截断逻辑:
- 添加 Video::truncate_title() 辅助函数,按字符(非字节)截断
- 文件名超过80字符时,自动设置title字段为截断版本(77字符+"...")
- 文件名不超过80字符时,title保持None,使用完整文件名

影响范围:
- crates/biliup/src/uploader/bilibili.rs - 新增truncate_title方法
- crates/biliup/src/uploader/line/upos.rs - Upos线路应用截断
- crates/biliup/src/uploader/line/kodo.rs - Kodo线路应用截断
- crates/biliup/src/uploader/line/cos.rs - Cos线路应用截断

* feat: Auto-retry on upload rate limit (code 601)

当遇到B站限流错误(code: 601, "您上传视频过快")时,自动等待后重试,最多重试5次,等待时间递增。

- 在 pre_upload 中添加重试循环
- 解析错误响应JSON,检测 code: 601
- 递增等待时间:第1次等待1分钟,第2次2分钟,第3次3分钟,第4次4分钟
- 最多重试5次(从原来的3次增加)
- 记录警告日志,显示重试进度和等待时间
- 配合断点续传功能,提高批量上传成功率

* refactor: 将重试逻辑从库层移至 CLI 层

主要变更:
- 新增 RateLimit 错误类型以区分限流错误(code: 601)
- 移除 line.rs 中 pre_upload() 的重试循环,遇到 601 错误直接返回
- 在 CLI 层 (uploader.rs) 实现重试逻辑

* fix: 使用跨平台路径获取断点续传文件位置

- 添加 dirs 依赖
- 使用 dirs::data_local_dir() 获取平台相关的数据目录
- Windows 下使用 %LOCALAPPDATA%,避免 /tmp 路径不存在导致的 panic
- 如果无法获取数据目录,回退到 std::env::temp_dir()

* refactor: 使用通用 retry 工具函数重构重试逻辑

主要变更:
- 改造 lib.rs 中的 retry 函数,新增 retry_with_config 支持:
  - 自定义最大重试次数
  - 条件重试(可指定只在特定错误时重试)
  - 保留原有的指数退避和抖动策略
- 在 CLI 层使用 retry_with_config 替换手写的重试循环
- 代码从 30+ 行简化为 15 行
- 更新 dirs 依赖从 5.0 到 6.0

* feat: 添加账号级上传互斥锁机制

为防止多个进程同时使用同一账号上传触发限流,实现账号级互斥锁:

- 新增 upload_lock.rs 模块:
  - 基于文件锁实现跨进程互斥
  - 使用账号 mid 作为锁标识符
  - 自动清理超过30分钟的僵尸锁
  - Drop 时自动释放锁

- 集成到上传流程(uploader.rs):
  - 在上传前检查是否有其他进程正在等待限流恢复
  - 检测到限流错误时尝试获取锁
  - 如果无法获取锁(其他进程已持有),本进程立即退出
  - 使用 Arc<Mutex<>> 在闭包间共享锁状态

工作流程:
1. 进程1 上传时遇到限流 (Rate Limit)
2. 进程1 获取锁并进入重试等待
3. 进程2 启动并检测到锁已被占用
4. 进程2 立即退出,避免继续触发限流
5. 进程1 重试成功后释放锁
2025-12-06 16:12:44 +00:00
ForgQi 08c8370c49 Refactor context and downloader logic for modularity
This commit refactors the context handling in the server, introducing PluginContext and improving separation between plugin and runtime contexts. It updates downloader and uploader logic to use the new context structure, streamlines file suffix handling, and removes redundant fields and code. The changes enhance code maintainability, modularity, and clarity, especially around download/upload workflows and plugin integration.
2025-12-05 09:09:31 +08:00
ForgQi c172efb6cd Add dynamic log level configuration support
Introduces a new `loggers_level` field in the server config to allow dynamic adjustment of log levels via the API and UI. Updates the developer UI to reflect the new log level controls and ensures the backend applies changes to the logging filter at runtime. Also bumps the version to 1.1.27.
2025-12-01 02:18:20 +08:00
ForgQi 9e9c9617d4 Refactor plugin config handling and add release workflow
Removed biliup/config.py and refactored all plugin and downloader classes to accept a config parameter, passing configuration explicitly instead of relying on a global import. Updated Rust backend to pass config objects to Python plugins and added a OnceConfig Python class for config access. Introduced a GitHub Actions release workflow. Updated version to 1.1.26 and added a disclaimer to the README. Minor bug fixes and improvements in Rust uploader and config handling.
2025-11-29 23:24:53 +08:00
Dzming Li 37abf9222e fix: 修复 shellexpand::tilde 类型错误并优化 Nix 构建 (#1552)
1. 修复 shellexpand::tilde 类型处理
   - shellexpand::tilde() 返回 Cow<'_, str> 而不是 Result
   - 移除了错误的 Result 模式匹配,直接使用返回值

2. 优化 Nix flake 配置
   - 将耗时的 buildNpmPackage 替换为轻量级占位符,跳过前端构建
   - 禁用测试以避免 server 模块测试依赖前端的问题
   - 减少构建时间和网络依赖
   - 命令行用户可通过 biliup login 的多种方式登录
2025-11-28 06:18:49 +00:00
ForgQi 55370dd87a Add dynamic log level reload support to server
Introduces tracing_subscriber::reload to enable dynamic log level changes at runtime. Updates the main server initialization and ServiceRegister to accept and store a log handle. Refactors related code to support this feature and removes disabled state from developer UI log level selectors.
2025-11-27 03:41:09 +08:00
ForgQi 3698bcf28a Add struct-patch support and improve config handling
Introduces the struct-patch crate for partial config updates and applies it to configuration models. Refactors config access patterns, adds PartialEq to several types, and updates LiveStreamer to use ConfigPatch for override_cfg. Also updates the CI workflow to use macos-15-intel, improves download task cancellation logic, and upgrades @douyinfe/semi-ui and related dependencies in the frontend.
2025-11-20 03:26:40 +08:00
ForgQi 4fdd0f81c1 Add Bilibili anonymous origin option and improve hook step execution
Introduces a new 'bili_anonymous_origin' option to the config for Bilibili original quality without login. Refactors hook step command execution to use platform-specific shells, capture and log stdout/stderr in real time, and improves process handling. Also updates workspace version to 1.1.23 and makes minor code cleanups.
2025-11-19 02:28:49 +08:00
ForgQi 7763a4cf3a Refactor download workflow and add is_only_self support
Refactored the download workflow to improve danmaku client handling, status transitions, and resource cleanup. Enhanced logging for better traceability. Added support for the 'is_only_self' field in upload streamer models and the Studio struct, ensuring correct type usage and propagation through the upload pipeline. Updated pythonize dependency to version 0.27.0 and improved queue management logic in the monitor.
2025-11-18 03:28:04 +08:00
ForgQi 58e7d3c80f Add Streamlink downloader and refactor Twitch plugin
Introduces a new Streamlink downloader implementation supporting multiple platforms and output modes. Refactors the Twitch plugin to use async traits and separates TwitchDownloader logic. Cleans up endpoint logging and updates workspace version to 1.1.22.
2025-11-05 01:44:31 +08:00
ForgQi 673a1e7eb8 Refactor plugin system and update dependencies
Refactored the plugin system, including changes to plugin loading and Twitch plugin implementation. Updated dependencies in Cargo.lock to their latest versions, improving compatibility and security. Adjusted downloader and download logic to align with the new plugin structure.
2025-11-04 00:56:51 +08:00
ForgQi 99b00d3bc3 Add Twitch plugin and refactor ffmpeg downloader
Introduces a new Twitch plugin module for stream handling, including GraphQL queries and stream URL extraction. Refactors ffmpeg_downloader to extract process logging and waiting logic into a reusable async function. Updates dependencies to use workspace versions for rand and serde_urlencoded, and bumps version numbers to 1.1.21.
2025-11-03 00:58:43 +08:00
ForgQi f5f2998d16 Add streamer file info API and improve file listing
Introduces a new API endpoint `/v1/streamer-info/files/{id}` to fetch file lists for a specific streamer, and updates the frontend to fetch file lists dynamically per row. Also updates serialization for streamer date fields, improves comments and error handling, and changes the default downloader type for plugins. Minor documentation and code cleanup included.
2025-10-27 01:28:08 +08:00
ForgQi 2ede5cda33 Refactor YY plugin to Rust and update downloader logic
Migrates the YY plugin from Python to Rust, implementing it under crates/biliup-cli/src/server/core/plugin/yy.rs and registering it in the service register. Removes the old Python plugin. Refactors downloader instantiation logic into DownloadPlugin, and updates header construction utility. Updates CLI and README to clarify the --auth option. Cleans up duplicate code in stream-gears and synchronizes workspace version numbers.
2025-10-26 17:01:45 +08:00
ForgQi b829731ad9 Refactor CLI, update dependencies, and improve server logic
Refactored CLI argument parsing and main entrypoint in both Python and Rust, updated pyo3 and pythonize dependencies, and improved modularization in biliup-cli. Added an 'auth' flag to the server command, fixed several import and ownership issues, and improved plugin and server initialization logic. Also cleaned up unused imports and streamlined code for better maintainability.
2025-10-24 03:11:27 +08:00
ForgQi a0b6800c0a Refactor async worker status and improve file move logic
Refactored worker status management to use async RwLock and updated all related methods to be async. Improved file move logic in hook steps to support cross-filesystem moves by falling back to copy+delete when necessary. Updated API endpoints, download/upload logic, and monitor state transitions to be fully async and more robust. Also added a Debug implementation for Credentials to avoid logging sensitive data, and made minor UI tweaks to disable certain developer options.
2025-10-22 02:13:37 +08:00
ForgQi 2fcdb0fe7b Move associated XML file with video in HookStep
When moving a video file, also check for an associated XML file (e.g., subtitles or metadata) and move it to the target directory if it exists. This ensures related files are kept together during the move operation.
2025-10-01 01:52:04 +08:00
ForgQi af82185618 Refactor recorder usage and context handling
Refactored the handling of the Recorder struct to include recording start time and improved filename templating. The Context struct now holds a Recorder instance, and its initialization was updated throughout the codebase. Adjusted upload and download logic to use the new Recorder, and improved logging in hook steps. Bumped version to 1.1.15.
2025-10-01 01:45:55 +08:00
ForgQi ea17305b0a Refactor actor system and add upload status tracking
Refactored download and upload actor implementations into separate modules for better maintainability. Added upload_status field to LiveStreamerEntity and API responses, and updated the frontend to display upload status. Also added Huya protocol config option and improved Huya stream header handling in stream-gears. Minor bug fixes and code cleanups included.
2025-10-01 00:26:07 +08:00
ForgQi e2da70b9ea Update config.rs
Bump workspace version to 1.1.13

Updated the workspace and related package versions from 1.1.12 to 1.1.13 in Cargo.toml and Cargo.lock for release and dependency consistency.
2025-09-30 01:52:54 +08:00
ForgQi 247f231640 Refactor log tabs and improve download/upload handling
Merged download and upload logs into a single tab in the log viewer and commented out the separate upload log tab. Removed unused and legacy methods from the Python upload and download engine classes. Improved logging and error handling in Rust server modules, including more informative log messages and better context propagation. Updated plugin interface to pass context instead of just URL, and enhanced dictionary filtering in config state. Bumped workspace version to 1.1.12.
2025-09-30 01:44:46 +08:00
ForgQi bd1ccdd0ff Refactor upload pipeline and improve code consistency
Refactored upload pipeline to use stream inspection for file item insertion, improving modularity and resource handling. Cleaned up imports, improved formatting, and removed redundant logging and code. Minor fixes in module ordering and code style for better maintainability.
2025-09-29 04:19:22 +08:00
ForgQi 92b5a30df5 Add streamer pause/resume feature and UI updates
Introduces a pause/resume button for streamers in the UI and backend, including API endpoint and status handling. Refactors streamer status display, updates related TypeScript interfaces, and improves modal forms. Also updates @douyinfe/semi-ui and related dependencies to v2.86.0, and bumps workspace version to 1.1.10. Minor logging and trait changes in Rust backend for improved clarity and error handling.
2025-09-29 03:18:21 +08:00
ForgQi d4b6a5fb22 Refactor models and context for streamer separation
Split LiveStreamer and UploadStreamer models into separate modules and update references throughout the codebase. Refactor StreamInfo to StreamInfoExt, embedding StreamerInfo for improved database integration. Update context, plugin, monitor, and related logic to support new model structure and database operations. Bump version to 1.1.9.
2025-09-27 16:42:53 +08:00
ForgQi e41acef883 Refactor upload logic and add WebSocket log endpoint
Refactored upload logic to decouple upload and submission, moving UploadLine and related logic to biliup-cli crate root. Added a new WebSocket log endpoint in a dedicated ws.rs module and routed it accordingly. Updated database migration to set certain fields to null instead of empty strings. Adjusted context and download/upload manager logic to pass StreamInfo and Context as needed. Added static file serving route and improved filename generation for uploads. Bumped version to 1.1.8 and enabled 'fs' feature for tower-http.
2025-09-27 01:13:18 +08:00
ForgQi 0320890823 Add WebSocket log streaming and status endpoint
Introduces a WebSocket endpoint for real-time log streaming and a new /v1/status API for server status. Refactors log file handling, updates dependencies (adds ws feature to axum), and improves file validation logic. Also updates workspace and crate versions to 1.1.7, and makes minor improvements to log rotation and struct derivations.
2025-09-26 04:11:13 +08:00
ForgQi e7e795dd72 Replace typed-builder with bon and update builder usage
Replaced the 'typed-builder' crate with 'bon' across the project, updating all builder pattern usages to match the new derive macros and method names. Adjusted struct derives and builder method calls in uploader and upload logic to use 'bon::Builder' and the new 'maybe_' method conventions. Updated dependencies in Cargo.toml files accordingly and removed 'typed-builder' from the workspace.
2025-09-26 02:13:31 +08:00
ForgQi 69caca628c Refactor segment event handling and add download task module
Introduces a new `download` module to encapsulate download task logic, including segment event processing and file validation. Refactors segment event handling to use a new `SegmentInfo` struct and updates related interfaces and modules for improved clarity and maintainability. Adjusts uploader and downloader logic to work with the new event structure, and enhances FFmpeg and stream gears downloaders to emit and process segment events accordingly.
2025-09-26 01:42:03 +08:00