init biliup-next

This commit is contained in:
theshy
2026-04-01 00:44:58 +08:00
commit d0cf1fd0df
127 changed files with 15582 additions and 0 deletions

View File

@ -0,0 +1,64 @@
# ADR 0001: Use A Modular Monolith As The Target Architecture
## Status
Accepted
## Context
当前项目由多个 Python 脚本、目录扫描逻辑、flag 文件和外部命令拼接而成。
主要问题:
- 状态不统一
- 配置不统一
- 重复逻辑多
- 扩展新功能需要继续增加脚本
- 运维和业务边界不清晰
重构目标要求:
- 可扩展
- 可配置
- 可观测
- 易部署
- 易文档化
## Decision
新系统采用模块化单体架构,而不是:
- 继续维护脚本集合
- 直接拆成微服务
## Rationale
选择模块化单体的原因:
- 当前系统规模和团队协作模式不需要微服务
- 单机部署和本地运维是核心需求
- 统一数据库、配置和日志对当前问题最直接有效
- 模块化单体足以提供清晰边界和未来插件扩展能力
## Consequences
正面影响:
- 部署简单
- 重构成本可控
- 便于引入统一状态机和管理 API
- 后续可以逐步插件化
负面影响:
- 需要严格维持模块边界,避免重新长成“大脚本”
- 单进程内错误隔离不如微服务天然
## Follow-Up Decisions
后续还需要补充的 ADR
- 是否使用 SQLite 作为主状态存储
- 是否引入事件总线
- 插件机制如何注册
- 管理台采用什么技术栈

245
docs/api/openapi.yaml Normal file
View File

@ -0,0 +1,245 @@
openapi: 3.1.0
info:
title: biliup-next Control API
version: 0.1.0
summary: 本地 worker、任务和控制台 API
servers:
- url: http://127.0.0.1:8787
paths:
/:
get:
summary: 控制台首页
responses:
"200":
description: HTML dashboard
/health:
get:
summary: 健康检查
responses:
"200":
description: OK
/settings:
get:
summary: 获取当前设置
responses:
"200":
description: 当前配置,敏感字段已掩码
put:
summary: 更新设置
responses:
"200":
description: 保存成功
/settings/schema:
get:
summary: 获取 settings schema
responses:
"200":
description: schema-first UI 元数据
/doctor:
get:
summary: 运行时依赖检查
responses:
"200":
description: doctor result
/modules:
get:
summary: 查询已注册模块与 manifest
responses:
"200":
description: module list
/runtime/services:
get:
summary: 查询 systemd 服务状态
responses:
"200":
description: service list
/runtime/services/{serviceId}/{action}:
post:
summary: 执行 service start/stop/restart
parameters:
- in: path
name: serviceId
required: true
schema:
type: string
- in: path
name: action
required: true
schema:
type: string
enum: [start, stop, restart]
responses:
"202":
description: action accepted
/logs:
get:
summary: 查询日志列表或日志内容
parameters:
- in: query
name: name
schema:
type: string
- in: query
name: lines
schema:
type: integer
- in: query
name: contains
schema:
type: string
responses:
"200":
description: log list or log content
/history:
get:
summary: 查询全局动作流
parameters:
- in: query
name: task_id
schema:
type: string
- in: query
name: action_name
schema:
type: string
- in: query
name: status
schema:
type: string
- in: query
name: limit
schema:
type: integer
responses:
"200":
description: action records
/tasks:
get:
summary: 查询任务列表
parameters:
- in: query
name: limit
schema:
type: integer
responses:
"200":
description: task list
post:
summary: 从 source_path 手动创建任务
responses:
"201":
description: task created
/tasks/{taskId}:
get:
summary: 查询任务详情
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"200":
description: task detail
/tasks/{taskId}/steps:
get:
summary: 查询任务步骤
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"200":
description: task steps
/tasks/{taskId}/artifacts:
get:
summary: 查询任务产物
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"200":
description: task artifacts
/tasks/{taskId}/history:
get:
summary: 查询单任务动作历史
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"200":
description: task action history
/tasks/{taskId}/timeline:
get:
summary: 查询单任务时间线
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"200":
description: task timeline
/tasks/{taskId}/actions/run:
post:
summary: 推进单个任务
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"202":
description: accepted
/tasks/{taskId}/actions/retry-step:
post:
summary: 从指定 step 重试
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"202":
description: accepted
/tasks/{taskId}/actions/reset-to-step:
post:
summary: 重置到指定 step 并重跑
parameters:
- in: path
name: taskId
required: true
schema:
type: string
responses:
"202":
description: accepted
/worker/run-once:
post:
summary: 执行一轮 worker
responses:
"202":
description: accepted
/stage/import:
post:
summary: 从本机已有绝对路径复制到隔离 stage
responses:
"201":
description: imported
/stage/upload:
post:
summary: 上传文件到隔离 stage
responses:
"201":
description: uploaded

198
docs/architecture.md Normal file
View File

@ -0,0 +1,198 @@
# Architecture
## Architecture Style
采用模块化单体架构。
原因:
- 当前规模不需要微服务
- 需要统一配置、状态和任务模型
- 需要较低部署复杂度
- 需要明确模块边界和未来插件扩展能力
## High-Level Layers
### 1. Core
核心领域层,不依赖具体外部服务。
- 领域模型
- 状态机
- 任务编排接口
- 事件定义
- 配置模型
### 2. Modules
业务模块层,每个模块只关心自己的一步能力。
- `ingest`
- `transcribe`
- `song_detect`
- `split`
- `publish`
- `comment`
- `collection`
### 3. Infra
基础设施层,对外部依赖做适配。
- 文件系统
- SQLite 存储
- Groq adapter
- Codex adapter
- FFmpeg adapter
- Bili API adapter
- biliup adapter
- 日志与审计
### 4. App
应用层,对外暴露统一运行入口。
- API Server
- Worker
- Scheduler
- CLI
- Admin Web
## Proposed Directory Layout
```text
biliup-next/
src/
app/
api/
worker/
scheduler/
cli/
core/
models/
services/
events/
state_machine/
config/
modules/
ingest/
transcribe/
song_detect/
split/
publish/
comment/
collection/
infra/
db/
fs/
adapters/
groq/
codex/
ffmpeg/
bili/
biliup/
logging/
plugins/
docs/
tests/
```
## Runtime Components
### API Server
负责:
- 配置管理
- 任务查询
- 手动操作
- 日志聚合查询
- 模块与插件可见性展示
### Worker
负责:
- 消费任务
- 推进状态机
- 执行模块步骤
### Scheduler
负责:
- 定时扫描待补偿任务
- 定时同步外部状态
- 触发重试
## Control Plane
新系统应明确区分控制面和数据面。
### Control Plane
负责:
- 配置管理
- 模块/插件注册
- 任务可视化
- 手动操作入口
- 日志与诊断
### Data Plane
负责:
- 实际执行转录
- 实际执行识歌
- 实际执行切歌
- 实际执行上传
- 实际执行评论和合集归档
## Registry
系统内部建立统一 registry用于注册和查找模块能力。
例如:
- 当前转录 provider
- 当前识歌 provider
- 当前上传 provider
- 当前合集策略
核心模块只依赖抽象接口和 registry不直接依赖具体实现。
## Task Lifecycle
```text
created
-> ingested
-> transcribed
-> songs_detected
-> split_done
-> published
-> commented
-> collection_synced
-> completed
```
失败状态不结束任务,而是转入:
- `failed_retryable`
- `failed_manual`
## Data Ownership
- SQLite任务、步骤、产物索引、配置、审计记录
- 文件系统视频、字幕、切片、AI 输出、日志
- 外部平台B 站稿件、评论、合集
## Key Design Rules
- 所有状态变更必须落库
- 模块间只通过领域对象和事件通信
- 外部依赖不可直接在业务模块中调用 shell 或 HTTP
- 配置统一由 `core.config` 读取
- 管理端展示的数据优先来自数据库,不直接从日志推断
- 配置系统必须 schema-first
- 插件系统必须 manifest-first

193
docs/config-system.md Normal file
View File

@ -0,0 +1,193 @@
# Config System
## Design Goal
配置系统不是辅助功能,而是新系统的控制面基础设施。
目标:
- 所有运行参数有统一来源
- 配置可被严格校验
- 配置可被 UI、CLI、文件三种方式修改
- 配置变更可审计
- 无效配置不得直接污染运行态
## Design Principles
借鉴 OpenClaw 的做法,配置系统采用 `schema-first` 设计。
核心原则:
- 所有配置项必须先声明在 schema 中
- 所有模块只能通过配置服务读取配置
- UI 表单由 schema 驱动渲染
- 保存前必须校验
- 校验失败不允许生效
- 配置有版本和变更记录
## Config Sources
### 1. Default Config
系统默认值,由代码内置或默认配置文件提供。
### 2. Active Config
当前生效的正式配置。
建议位置:
- `biliup-next/config/settings.json`
### 3. Staged Config
用户在 UI 或 CLI 中提交、但尚未生效的待校验配置。
建议位置:
- `biliup-next/config/settings.staged.json`
### 4. Schema
定义配置结构、类型、默认值、枚举范围、校验规则和 UI 元信息。
建议位置:
- `biliup-next/config/settings.schema.json`
## Config Flow
```text
User edits config
-> Write staged config
-> Validate against schema
-> Run dependency checks
-> If valid: promote to active
-> If invalid: keep current active config
```
## Validation Layers
### 1. Schema Validation
检查:
- 类型
- 必填字段
- 枚举值
- 数值范围
- 字段格式
### 2. Semantic Validation
检查:
- 路径是否存在
- 可执行文件是否可用
- season id 是否合法
- 依赖组合是否冲突
### 3. Runtime Validation
检查:
- provider 是否可初始化
- 凭证是否存在
- 外部连接是否可用
## Suggested Config Groups
### runtime
- `workspace_dir`
- `database_path`
- `log_level`
- `scan_interval_seconds`
### paths
- `stage_dir`
- `backup_dir`
- `session_dir`
- `cookies_file`
- `upload_config_file`
### ingest
- `min_duration_seconds`
- `allowed_extensions`
### transcribe
- `provider`
- `groq_api_key`
- `max_file_size_mb`
- `ffmpeg_bin`
### song_detect
- `provider`
- `codex_cmd`
- `poll_interval_seconds`
### split
- `ffmpeg_bin`
- `poll_interval_seconds`
### publish
- `provider`
- `biliup_path`
- `cookie_file`
- `retry_count`
- `retry_schedule_minutes`
- `retry_backoff_seconds`
### comment
- `enabled`
- `max_retries`
- `base_delay_seconds`
- `poll_interval_seconds`
### collection
- `enabled`
- `season_id_a`
- `season_id_b`
- `allow_fuzzy_full_video_match`
- `append_collection_a_new_to_end`
- `append_collection_b_new_to_end`
## UI Strategy
管理台不手写业务表单,而是由 schema 驱动生成配置界面。
每个配置项除了类型信息,还应有:
- `title`
- `description`
- `group`
- `ui:widget`
- `ui:secret`
- `ui:order`
这样可以让配置页面和底层配置保持同源。
## Audit Trail
每次配置变更都应记录:
- 修改人
- 修改时间
- 修改前值
- 修改后值
- 校验结果
- 是否已生效
## Non-Goals
- 不追求兼容任意格式的配置文件
- 不允许模块私自定义一份独立配置入口
- 不允许 UI 和代码维护两套不同的字段定义

476
docs/control-plane-guide.md Normal file
View File

@ -0,0 +1,476 @@
# Control Plane Guide
本文档面向 `biliup-next` 控制台的日常使用。
默认地址:
```text
http://127.0.0.1:8787/
```
如果当前机器已经开放公网访问,也可以使用服务器 IP + `8787` 端口访问。
## 页面分区
控制台主要分成两列:
- 左侧全局状态、服务、动作流、导入入口、任务列表、Settings
- 右侧:当前任务详情、步骤、产物、历史、时间线、模块、日志
建议的使用顺序是:
1. 先看 `Runtime`
2. 再看 `Tasks`
3. 选中一个任务后,看右侧详情
4. 如果任务异常,再看 `Logs``History`
## Runtime
这里可以看 3 个汇总指标:
- `Health`
- `Doctor`
- `Tasks`
含义:
- `Health = OK`
- API 服务本身还活着
- `Doctor = OK`
- 关键路径、二进制和依赖文件都存在
- `Tasks`
- 当前数据库里的任务数
如果 `Doctor` 不是 `OK`,优先不要继续点任务操作,先修运行环境。
## Services
这里可以查看并控制:
- `biliup-next-worker.service`
- `biliup-next-api.service`
- 如果还保留旧服务,也可能看到 `biliup-python.service`
可执行操作:
- `start`
- `restart`
- `stop`
建议:
- 页面打不开时,先看 `biliup-next-api.service`
- 任务不推进时,先看 `biliup-next-worker.service`
- 不要随便再启动旧 `biliup-python.service`,除非你明确知道自己要同时跑旧链路
## Recent Actions
这里显示最近的控制面动作流,例如:
- `worker_run_once`
- `task_run`
- `retry_step`
- `reset_to_step`
- `stage_import`
- `stage_upload`
- `service_action`
可过滤:
- 仅当前任务
- `status`
- `action_name`
用途:
- 判断最近有没有人工操作过任务
- 判断任务是不是被重试过
- 判断服务是不是被重启过
## Import To Stage
这里有两种入口。
### 1. 复制本机已有文件到隔离 stage
输入服务器上的绝对路径,例如:
```text
/home/theshy/video/test.mp4
```
点击:
```text
复制到隔离 Stage
```
适合:
- 服务器本地已有文件
- 想快速把已有文件丢进新系统测试
### 2. 浏览器直接上传文件
选择本地文件后点击:
```text
上传到隔离 Stage
```
适合:
- 本地电脑上的测试视频
- 不想先手动传到服务器
上传成功后,`worker` 会自动扫描 `stage` 并开始建任务。
## Tasks
这里列出任务列表。
每个任务会显示:
- 标题
- 当前状态
- 任务 ID
常见状态:
- `created`
- `transcribed`
- `songs_detected`
- `split_done`
- `published`
- `commented`
- `collection_synced`
- `failed_retryable`
- `failed_manual`
建议:
- 先选中你关心的任务
- 再看右侧 `Task Detail / Steps / Logs`
## Task Detail
显示当前选中任务的核心信息:
- `Task ID`
- `Status`
- `Title`
- `Source`
- `Created`
- `Updated`
上方有 3 个操作按钮:
- `执行当前任务`
- `重试选中 Step`
- `重置到选中 Step`
### 执行当前任务
作用:
- 手动推进当前任务一次
适合:
- 你刚改完配置
- 你不想等下一轮 worker 轮询
### 重试选中 Step
作用:
- 从当前选中的 step 重新尝试
适合:
- 某一步是临时失败
- 不需要删除后续产物
### 重置到选中 Step
作用:
- 清理该 step 之后的产物和标记
- 把任务回拨到该 step
- 然后重新执行
适合:
- 后续结果已经不可信
- 需要从某一步重新跑整段链路
注意:
- 这是破坏性动作
- 页面会要求确认
## Steps
这里显示任务步骤列表,例如:
- `ingest`
- `transcribe`
- `song_detect`
- `split`
- `publish`
- `comment`
- `collection_a`
- `collection_b`
点击某个 step 之后:
- 这个 step 会成为“当前选中 step”
- 然后你就可以点:
- `重试选中 Step`
- `重置到选中 Step`
排查原则:
- `transcribe` 失败:先看 `Groq API Key``ffmpeg`
- `song_detect` 失败:先看 `codex_cmd`
- `publish` 失败:先看 `cookies.json``biliup`
- `collection_*` 失败:再看任务历史和日志
评论规则补充:
- `comment`
- 纯享版视频下默认发“编号歌单”,不带时间轴
- 完整版主视频下默认才发“带时间轴评论”
- 如果当前任务找不到 `full_video_bvid.txt`,也没能从最近发布列表解析出完整版 BV主视频评论会跳过
## Artifacts
这里显示任务当前已经产出的文件,例如:
- `source_video`
- `subtitle_srt`
- `songs_json`
- `songs_txt`
- `clip_video`
- `publish_bvid`
用途:
- 判断任务跑到了哪一步
- 判断关键输出是否已经落盘
如果开启了 cleanup 配置,任务在 `collection_synced` 后:
- `source_video` 对应的原始视频可能会被删除
- `clip_video` 对应的 `split_video/` 目录也可能被清理
这是正常收尾行为,不代表任务失败。
## History
这里是单任务动作历史。
`Recent Actions` 的区别:
- `Recent Actions` 看全局
- `History` 只看当前任务
可以看到:
- 动作名
- 状态
- 摘要
- 时间
- 结构化 `details_json`
用途:
- 判断某次 `retry``reset` 的结果
- 判断 worker 最近对这个任务做了什么
## Timeline
这里把任务事件串成一条时间线:
- `Task Created`
- step started / finished
- artifact created
- action records
适合:
- 回放任务完整过程
- 查清楚任务到底卡在什么时候
## Modules
这里显示当前注册的模块 / provider。
用途:
- 确认当前用的是哪套 provider
- 确认模块有没有注册成功
如果将来切 provider这里会很有用。
## Doctor Checks
这里比顶部的 `Doctor = OK/FAIL` 更详细。
会列出:
- workspace 目录
- `cookies_file`
- `upload_config_file`
- `ffprobe`
- `ffmpeg`
- `codex_cmd`
- `biliup_path`
如果某个依赖显示 `(external)`,表示它还在用系统或父项目路径,不是 `biliup-next` 自己目录内的副本。
## Logs
这里可以看日志文件。
支持:
- 切换日志文件
- 刷新日志
- 按当前任务标题过滤
使用建议:
- 任务异常时,先选中任务
- 再勾选“按当前任务标题过滤”
- 然后查看相关日志
这样比直接翻整份日志快很多。
## Settings
Settings 分成两层:
- 上半部分schema 驱动表单
- 下半部分:`Advanced JSON Editor`
### 表单区
这里适合日常参数调整,例如:
- `min_duration_seconds`
- `groq_api_key`
- `codex_cmd`
- `retry_count`
- `season_id_a`
- `season_id_b`
- `post_split_comment`
- `post_full_video_timeline_comment`
- `delete_source_video_after_collection_synced`
- `delete_split_videos_after_collection_synced`
支持:
- 搜索配置项
- 高频参数优先展示
- 低频参数收纳到 `Advanced Settings`
### Advanced JSON Editor
适合:
- 批量调整
- 一次改多个字段
- 需要直接编辑原始 JSON
页面上有两个同步按钮:
- `表单同步到 JSON`
- `JSON 重绘表单`
### 敏感字段规则
敏感字段会显示为:
```text
__BILIUP_NEXT_SECRET__
```
规则:
- 保留占位符:不改原值
- 改成空字符串:清空原值
- 改成新的字符串:更新为新值
## 推荐操作流
### 新上传一个测试视频
1. 打开控制台
2.`Import To Stage` 上传视频
3.`Tasks` 是否出现新任务
4. 选中该任务
5. 观察 `Steps / Artifacts / Timeline`
6. 如需加速,点击 `执行当前任务`
### 某个任务失败
1. 选中任务
2.`Task Detail` 当前状态
3.`Steps` 哪一步失败
4.`Logs`
5. 若只是临时失败:
- 选中 step
-`重试选中 Step`
6. 若后续产物也需要重建:
- 选中 step
-`重置到选中 Step`
### 修改合集或上传参数
1. 打开 `Settings`
2. 搜索目标参数,例如 `season` / `retry`
3. 修改表单
4. 点击 `保存 Settings`
5. 对目标任务执行:
- `执行当前任务`
-`重置到相关 step`
### 控制评论与清理行为
1. 打开 `Settings`
2. 搜索:
- `post_split_comment`
- `post_full_video_timeline_comment`
- `delete_source_video_after_collection_synced`
- `delete_split_videos_after_collection_synced`
3. 修改后保存
4. 新任务会按新规则执行
建议:
- 如果你希望纯享版评论更适合分P浏览保持 `post_split_comment = true`
- 如果你不希望尝试给完整版主视频发时间轴评论,可以关闭 `post_full_video_timeline_comment`
- 如果磁盘紧张,再开启 cleanup默认建议先关闭等确认流程稳定后再开
### 服务异常
1.`Services`
2. 优先 `restart biliup-next-worker.service`
3. 如果页面自身异常,再 `restart biliup-next-api.service`
4. 重启后看:
- `Health`
- `Doctor`
- `Recent Actions`
## 安全建议
当前控制台已经对公网开放时,建议立刻设置:
- `runtime.control_token`
设置后:
-`/``/health` 外,其余 API 都要求 `X-Biliup-Token`
如果你通过公网访问控制台,不建议长期保持空 token。

238
docs/design-principles.md Normal file
View File

@ -0,0 +1,238 @@
# Design Principles
## Positioning
`biliup-next` 以 OpenClaw 的设计哲学为指引,但不复制它的产品形态。
本项目的核心目标不是做聊天代理系统,而是构建一个面向本地视频流水线的控制面驱动系统。
因此我们借鉴的是方法论:
- 单体优先
- 控制面优先
- 配置与扩展元数据优先
- 严格校验
- 本地优先
- 可读、可审计、可替换
## Principle 1: Modular Monolith First
系统优先采用模块化单体架构,而不是微服务。
原因:
- 当前问题主要来自边界混乱,而不是部署扩展性不足
- 单机部署和 systemd 管理仍然是核心场景
- 统一配置、任务状态和日志比进程拆分更重要
约束:
- 所有模块运行在同一系统边界内
- 模块之间通过抽象接口和统一模型交互
- 不得通过随意脚本调用形成隐式耦合
## Principle 2: Control Plane First
功能模块不是系统中心,控制面才是系统中心。
控制面负责:
- 配置管理
- 任务状态管理
- 模块与插件注册
- 手动操作入口
- 日志与诊断
数据面负责:
- 执行转录
- 执行识歌
- 执行切歌
- 执行上传
- 执行评论和合集归档
任何新增功能,都必须先回答:
- 它如何进入控制面
- 它的状态如何呈现
- 它的配置如何管理
- 它失败后如何恢复
## Principle 3: Schema-First Configuration
配置必须先有 schema再有实现和 UI。
要求:
- 所有配置项先定义在 schema
- 所有配置项有默认值、校验规则和说明
- UI 基于 schema 生成
- CLI 和 API 使用同一套字段定义
禁止:
- 在模块里私自增加隐藏配置常量
- UI 和代码维护不同字段名
- 配置错误仍然带病启动
## Principle 4: Manifest-First Extensibility
扩展能力先注册元数据,再执行运行时代码。
manifest 负责描述:
- 插件是谁
- 提供什么能力
- 需要什么配置
- 入口在哪
- 是否可启用
这样控制面可以在不执行插件代码时完成:
- 能力发现
- 配置渲染
- 可用性检查
- 兼容性检查
## Principle 5: Registry Over Direct Coupling
模块和插件必须通过统一 registry 接入。
例如:
- transcriber registry
- song detector registry
- publisher registry
- collection strategy registry
核心模块只依赖接口,不依赖具体 provider。
这意味着:
- 更换 Groq 为其他转录器不影响任务引擎
- 更换 Codex 为其他识歌器不影响控制面
- 更换 biliup 为其他上传方式不影响领域模型
## Principle 6: Local-First And Human-Readable
系统优先本地运行,本地保存,本地可读。
要求:
- 主状态存储可本地访问
- 日志保存在本地
- 配置保存在本地
- 关键元数据可被开发者直接理解
这不意味着只靠文件系统。
建议做法:
- SQLite 保存结构化状态
- 文件系统保存产物
- JSON / YAML 保存配置
- 文本日志保存审计和错误
## Principle 7: Strict Validation
系统不能接受“差不多能跑”的配置和模块状态。
启动或配置变更前,应验证:
- schema 是否合法
- 可执行依赖是否存在
- 必要凭证是否存在
- provider 是否可初始化
- 插件 manifest 是否正确
失败时:
- 保留旧运行态
- 返回明确错误
- 不进入半失效状态
## Principle 8: Single Source Of Truth
任务状态必须有统一来源。
不得同时依赖:
- flag 文件推断状态
- 日志推断状态
- 目录结构推断状态
正确做法:
- 数据库记录任务状态
- 文件系统存放任务产物
- 日志记录过程和诊断
三者职责分离,不互相替代。
## Principle 9: Replaceability With Stable Core
可替换的是 provider不可随意漂移的是核心模型。
稳定核心包括:
- Task
- TaskStep
- Artifact
- PublishRecord
- CollectionBinding
- Settings
这些模型一旦定义,应保持长期稳定,避免每新增一个模块就改核心语义。
## Principle 10: Observability Is A First-Class Feature
可观测性不是补丁,而是正式能力。
管理台必须能够回答:
- 当前有哪些任务
- 每个任务在哪一步
- 最近一次失败是什么
- 当前启用的 provider 是谁
- 当前配置是否有效
- 哪个模块健康异常
如果系统不能快速回答这些问题,说明设计不完整。
## Principle 11: Backward-Compatible Migration
重构必须与旧系统并行推进。
要求:
- 原项目继续运行
- 新项目只在 `./biliup-next` 演进
- 先搭文档和骨架
- 再逐步迁移模块
- 最后切换生产入口
禁止:
- 直接在旧系统里边修边重构
- 在未定义状态模型前大规模搬代码
## Principle 12: Documentation Before Expansion
任何新的模块、插件、控制面能力,在动手实现前都要先回答:
- 它属于哪个层
- 它的输入输出是什么
- 它如何配置
- 它如何注册
- 它如何被 UI 展示
- 它如何失败和恢复
如果这些问题没有写清楚,就不应进入实现阶段。
## Summary
`biliup-next` 的核心方向不是“把旧脚本改漂亮”,而是:
- 建立一个本地优先、控制面驱动、模块边界清晰的系统
- 让配置、模块、状态、操作和诊断都回到统一模型之下
- 让未来扩展建立在 schema、manifest、registry 和稳定领域模型之上

135
docs/domain-model.md Normal file
View File

@ -0,0 +1,135 @@
# Domain Model
## Task
一个任务代表一条完整的视频处理链路。
```json
{
"id": "task_01",
"source_type": "local_file",
"source_path": "stage/example.mp4",
"title": "王海颖唱歌录播 03月29日 22时02分",
"status": "published",
"created_at": "2026-03-30T07:50:42+08:00",
"updated_at": "2026-03-30T07:56:13+08:00"
}
```
### Fields
- `id`: 内部唯一 ID
- `source_type`: 输入来源,例如 `local_file`
- `source_path`: 原始文件路径
- `title`: 任务显示名称
- `status`: 当前状态
- `created_at`
- `updated_at`
## TaskStep
一个任务中的单个处理步骤。
### Step Names
- `ingest`
- `transcribe`
- `song_detect`
- `split`
- `publish`
- `comment`
- `collection_a`
- `collection_b`
### Step Status
- `pending`
- `running`
- `succeeded`
- `failed_retryable`
- `failed_manual`
- `skipped`
## Artifact
任务产物。
### Artifact Types
- `source_video`
- `subtitle_srt`
- `songs_json`
- `songs_txt`
- `clip_video`
- `publish_bvid`
- `comment_record`
- `collection_record`
## PublishRecord
记录上传结果。
```json
{
"task_id": "task_01",
"platform": "bilibili",
"aid": 123456,
"bvid": "BV1xxxx",
"title": "【王海颖 (歌曲纯享版)】_03月29日 22时02分 共18首歌",
"published_at": "2026-03-30T07:56:13+08:00"
}
```
## CollectionBinding
记录视频与合集之间的绑定关系。
### Fields
- `task_id`
- `target`
- `season_id`
- `section_id`
- `bvid`
- `status`
- `last_error`
### Target Values
- `full_video_collection`
- `song_collection`
## Settings
统一配置项,按逻辑分组。
### Example Groups
- `runtime`
- `paths`
- `transcribe`
- `song_detect`
- `publish`
- `comment`
- `collection`
## Domain Events
### Core Events
- `TaskCreated`
- `TaskStepStarted`
- `TaskStepSucceeded`
- `TaskStepFailed`
- `ArtifactCreated`
- `PublishCompleted`
- `CommentCompleted`
- `CollectionSynced`
## State Machine Rules
- 同一时刻,一个步骤只能有一个 `running`
- 失败必须记录 `error_code``error_message`
- `published` 之前不能进入评论和合集步骤
- `songs_detected` 之前不能进入切歌步骤
- `transcribed` 之前不能进入识歌步骤

192
docs/migration-plan.md Normal file
View File

@ -0,0 +1,192 @@
# Migration Plan
## Goal
在不破坏原项目运行的前提下,逐步将能力迁移到 `biliup-next`
## Migration Principles
- 原项目继续作为生产系统运行
- 新项目只在 `./biliup-next` 中演进
- 先文档、后骨架、再迁移功能
- 先控制面,后数据面
- 先兼容旧目录结构,再逐步替换旧入口
## Phase 0: Documentation Baseline
目标:
- 明确设计原则
- 明确架构分层
- 明确领域模型
- 明确配置系统和插件系统
产物:
- `vision.md`
- `architecture.md`
- `domain-model.md`
- `design-principles.md`
- `config-system.md`
- `plugin-system.md`
- `state-machine.md`
- `module-contracts.md`
- `migration-plan.md`
## Phase 1: Project Skeleton
目标:
- 建立 `biliup-next/src` 目录结构
- 建立基础 Python 包
- 建立最小配置系统
- 建立 SQLite 存储层
- 建立任务模型和状态模型
不做:
- 不迁移业务逻辑
- 不接管生产入口
## Phase 2: Control Plane MVP
目标:
- 提供最小 API
- 提供任务列表和配置读取能力
- 提供最小 CLI
- 提供 health / logs / settings / tasks 查询能力
产物:
- API server
- config service
- task repository
- runtime doctor
## Phase 3: Data Plane Adapters
目标:
- 把旧系统依赖的外部能力封装成 adapter
优先顺序:
1. `ffmpeg` adapter
2. `Groq` adapter
3. `Codex` adapter
4. `biliup` adapter
5. `Bili API` adapter
理由:
- 先封装外部依赖,后迁移业务模块,能减少后续反复返工
## Phase 4: Module Migration
按顺序迁移业务模块。
### 4.1 Ingest
- 替代 `monitor.py` 的任务创建部分
### 4.2 Transcribe
- 替代 `video2srt.py`
### 4.3 Song Detect
- 替代 `monitorSrt.py`
### 4.4 Split
- 替代 `monitorSongs.py`
### 4.5 Publish
- 替代 `upload.py`
### 4.6 Comment
- 替代 `session_top_comment.py`
### 4.7 Collection
- 替代 `add_to_collection.py`
## Phase 5: Parallel Verification
目标:
- 新旧系统并行验证
- 新系统只处理测试任务
- 对比产物、日志、状态和 B 站结果
重点检查:
- 字幕结果
- 歌曲识别结果
- 切片结果
- 上传结果
- 评论和合集结果
## Phase 6: Admin UI
目标:
- 构建本地控制台
第一版包含:
- 配置页
- 任务页
- 模块页
- 日志页
- 手动操作页
## Phase 7: Cutover
目标:
- 新系统逐步接管生产入口
顺序建议:
1. 只接管任务可视化
2. 接管配置管理
3. 接管测试任务处理
4. 接管单模块生产流量
5. 最终接管全部生产流量
## Risks
### Risk 1: 旧系统与新系统语义不一致
缓解:
- 先定义领域模型和状态机
- 迁移前写适配层,不直接照抄旧脚本行为
### Risk 2: 边迁移边污染旧项目
缓解:
- 所有新内容只放在 `./biliup-next`
- 不改原项目运行入口
### Risk 3: UI 先行导致底层不稳
缓解:
- 先做控制面模型和 API
- 最后做 UI
## Definition Of Done
迁移完成的标准不是“代码搬完”,而是:
- 新系统可以独立运行
- 配置统一管理
- 状态统一落库
- UI 可以完整观察任务
- 旧脚本不再是主入口

229
docs/module-contracts.md Normal file
View File

@ -0,0 +1,229 @@
# Module Contracts
## Goal
定义各模块的职责边界、输入输出和契约,避免旧系统中“脚本互相读目录、互相猜状态”的耦合方式。
## Contract Principles
- 每个模块只处理一类能力
- 模块只接收明确输入,不扫描全世界
- 模块输出必须结构化
- 模块不直接操控其他模块的内部实现
- 模块不直接依赖具体 provider
## Shared Concepts
所有模块统一围绕这些对象协作:
- `Task`
- `TaskStep`
- `Artifact`
- `Settings`
- `ProviderRef`
## Ingest Module
### Responsibility
- 接收文件输入
- 校验最小处理条件
- 创建任务
### Input
- 本地文件路径
- 当前配置
### Output
- `Task`
- `source_video` artifact
### Must Not Do
- 不直接转录
- 不写上传状态
## Transcribe Module
### Responsibility
- 调用转录 provider
- 生成字幕产物
### Input
- `Task`
- `source_video` artifact
- `transcribe` settings
### Output
- `subtitle_srt` artifact
### Must Not Do
- 不识别歌曲
- 不决定切歌策略
## Song Detect Module
### Responsibility
- 根据字幕识别歌曲
- 生成歌曲结构化结果
### Input
- `Task`
- `subtitle_srt` artifact
- `song_detect` settings
### Output
- `songs_json` artifact
- `songs_txt` artifact
### Must Not Do
- 不切歌
- 不上传
## Split Module
### Responsibility
- 根据歌曲列表切割纯享版片段
### Input
- `Task`
- `songs_json` artifact
- `source_video` artifact
- `split` settings
### Output
- 多个 `clip_video` artifact
## Publish Module
### Responsibility
- 上传纯享版视频
- 记录发布结果
### Input
- `Task`
- `clip_video[]`
- `publish` settings
### Output
- `PublishRecord`
- `publish_bvid` artifact
### Must Not Do
- 不负责评论文案生成
- 不负责合集匹配策略
## Comment Module
### Responsibility
- 发布并置顶评论
### Input
- `Task`
- `PublishRecord`
- `songs_txt` artifact
- `comment` settings
### Output
- `comment_record` artifact
## Collection Module
### Responsibility
- 根据策略同步合集 A / B
### Input
- `Task`
- `PublishRecord` 或外部 `full_video_bvid`
- `collection` settings
- `collection strategy`
### Output
- `CollectionBinding`
### Internal Sub-Strategies
- `full_video_collection_strategy`
- `song_collection_strategy`
## Provider Contracts
### TranscribeProvider
```text
transcribe(task, source_video, settings) -> subtitle_srt
```
### SongDetector
```text
detect(task, subtitle_srt, settings) -> songs_json, songs_txt
```
### PublishProvider
```text
publish(task, clip_videos, settings) -> PublishRecord
```
### CommentStrategy
```text
sync_comment(task, publish_record, songs_txt, settings) -> comment_record
```
### CollectionStrategy
```text
sync_collection(task, context, settings) -> CollectionBinding[]
```
## Orchestration Rules
模块本身不负责全局编排。
全局编排由任务引擎或 worker 负责:
- 判断下一步该跑什么
- 决定是否重试
- 写入状态
- 调度具体模块
## Error Contract
所有模块失败时应返回统一错误结构:
- `code`
- `message`
- `retryable`
- `details`
不得只返回原始字符串日志作为唯一错误结果。
## Non-Goals
- 模块之间不共享私有目录扫描逻辑
- 模块契约不直接暴露 shell 命令细节

156
docs/plugin-system.md Normal file
View File

@ -0,0 +1,156 @@
# Plugin System
## Goal
插件系统的目标不是“让任何东西都能热插拔”,而是为未来的能力替换和扩展提供稳定边界。
优先支持:
- 转录提供者替换
- 歌曲识别提供者替换
- 上传器替换
- 评论策略替换
- 合集策略替换
- 输入源扩展
## Design Principles
借鉴 OpenClaw 的思路,采用 `manifest-first` + `registry` 设计。
原则:
- 插件先注册元信息,再执行运行时代码
- 控制面优先读取 manifest 和 schema
- 核心系统只依赖抽象接口和 registry
- 插件配置必须可校验
## Plugin Composition
每个插件由两部分组成:
### 1. Manifest
描述插件的元信息和配置能力。
例如:
```json
{
"id": "codex-song-detector",
"name": "Codex Song Detector",
"version": "0.1.0",
"type": "song_detector",
"entrypoint": "plugins.codex_song_detector.runtime:register",
"configSchema": "plugins/codex_song_detector/config.schema.json",
"capabilities": ["song_detect"],
"enabledByDefault": true
}
```
### 2. Runtime
真正实现业务逻辑的代码。
## Registry
系统启动时统一构建 registry。
registry 负责:
- 注册插件能力
- 按类型查找实现
- 根据配置激活当前 provider
### Registry Types
- `ingest_provider`
- `transcribe_provider`
- `song_detector`
- `split_provider`
- `publish_provider`
- `comment_strategy`
- `collection_strategy`
## Plugin Loading Flow
```text
Discover manifests
-> Validate manifests
-> Register capabilities in registry
-> Load plugin config schema
-> Validate plugin config
-> Activate runtime implementation
```
## Why Manifest-First
这样设计有 4 个直接好处:
- 管理台可以在不执行插件代码时展示插件信息
- UI 可以根据 schema 渲染配置表单
- 系统可以提前发现缺失字段或不兼容版本
- 插件运行失败不会影响元数据层的可见性
## Suggested Plugin Boundaries
### Transcribe Provider
示例:
- `groq`
- `openai`
- `local_whisper`
### Song Detector
示例:
- `codex`
- `rule_engine`
- `custom_llm`
### Publish Provider
示例:
- `biliup_cli`
- `bilibili_api`
### Collection Strategy
示例:
- `default_song_collection`
- `title_match_full_video`
- `manual_binding`
## Control Plane Integration
插件系统必须服务于控制面。
因此管理台至少需要知道:
- 当前有哪些插件
- 每个插件类型是什么
- 当前启用的是哪一个
- 配置是否有效
- 最近一次健康检查结果
## Restrictions
为了避免再次走向“任意脚本散落”,插件系统需要约束:
- 插件不得直接修改核心数据库结构
- 插件不得绕过统一配置系统
- 插件不得私自写独立日志目录作为唯一状态来源
- 插件不得直接互相调用具体实现
## Initial Strategy
第一阶段不追求真正的第三方插件生态。
先实现“内置插件化”:
- 核心仓库内提供多个 provider
- 统一用 manifest + registry 管理
- 等边界稳定后,再考虑开放外部插件目录

212
docs/state-machine.md Normal file
View File

@ -0,0 +1,212 @@
# State Machine
## Goal
定义 `biliup-next` 的任务状态机,取代旧系统依赖 flag 文件、日志和目录结构推断状态的方式。
状态机目标:
- 让每个任务始终有明确状态
- 支持失败重试和人工介入
- 让 UI 和 API 可以直接消费状态
- 保证步骤顺序和依赖关系清晰
## State Model
任务状态分为两层:
- `task status`:任务整体状态
- `step status`:任务中每一步的执行状态
## Task Status
### Core Statuses
- `created`
- `ingested`
- `transcribed`
- `songs_detected`
- `split_done`
- `published`
- `commented`
- `collection_synced`
- `completed`
### Failure Statuses
- `failed_retryable`
- `failed_manual`
### Terminal Statuses
- `completed`
- `cancelled`
- `failed_manual`
## Step Status
每个步骤都独立维护自己的状态。
- `pending`
- `running`
- `succeeded`
- `failed_retryable`
- `failed_manual`
- `skipped`
## Step Definitions
### ingest
负责:
- 接收输入视频
- 基础校验
- 创建任务记录
### transcribe
负责:
- 生成字幕
- 记录字幕产物
### song_detect
负责:
- 识别歌曲列表
- 生成 `songs.json``songs.txt`
### split
负责:
- 根据歌单切割视频
- 生成切片产物
### publish
负责:
- 上传纯享版视频
- 记录 `aid/bvid`
### comment
负责:
- 发布评论
- 置顶评论
### collection_a
负责:
- 将完整版视频加入合集 A
### collection_b
负责:
- 将纯享版视频加入合集 B
## State Transition Rules
### Task-Level
```text
created
-> ingested
-> transcribed
-> songs_detected
-> split_done
-> published
-> commented
-> collection_synced
-> completed
```
### Failure Transition
任何步骤失败后:
- 若允许自动重试:任务进入 `failed_retryable`
- 若必须人工介入:任务进入 `failed_manual`
重试成功后:
- 任务回到该步骤成功后的下一个合法状态
## Dependency Rules
- `transcribe` 必须依赖 `ingest`
- `song_detect` 必须依赖 `transcribe`
- `split` 必须依赖 `song_detect`
- `publish` 必须依赖 `split`
- `comment` 必须依赖 `publish`
- `collection_b` 必须依赖 `publish`
- `collection_a` 通常依赖外部完整版 BV可独立于 `publish`
## Special Case: Collection A
合集 A 的数据来源与主上传链路不同。
因此:
- `collection_a` 不应阻塞主任务完成
- `collection_a` 可作为独立步骤存在
- 任务整体完成不必强依赖 `collection_a` 成功
建议:
- `completed` 表示主链路完成
- `collection_synced` 表示所有合集同步完成
## Retry Strategy
### Retryable Errors
适合自动重试:
- 网络错误
- 外部 API 临时失败
- 上传频控
- 外部命令短时异常
### Manual Errors
需要人工介入:
- 配置缺失
- 凭证失效
- 文件损坏
- provider 不可用
- 标题无法匹配完整版 BV
## Persistence Requirements
每次状态变更都必须落库:
- 任务状态
- 步骤状态
- 开始时间
- 结束时间
- 错误码
- 错误信息
- 重试次数
## UI Expectations
UI 至少需要直接展示:
- 当前任务状态
- 当前正在运行的步骤
- 最近失败步骤
- 重试次数
- 是否需要人工介入
## Non-Goals
- 不追求一个任务多个步骤完全并发执行
- 不允许继续依赖 flag 文件作为权威状态来源

54
docs/vision.md Normal file
View File

@ -0,0 +1,54 @@
# Vision
## Goal
将当前基于目录监听和脚本拼接的流水线,重构为一个模块化、可扩展、可观测、可运维的单体系统。
系统负责:
- 接收本地视频任务
- 执行转录、识歌、切歌、上传、评论、合集归档
- 记录任务状态、产物、错误和外部结果
- 提供统一配置和管理入口
系统不负责:
- 直播录制
- 完整版视频的外部发布流程
- 多账号复杂运营后台
- 分布式调度
## Users
- 运维者:部署、启动、排查、重试任务
- 内容生产者:投放视频、观察任务状态
- 开发者:新增模块、替换外部依赖、扩展功能
## Problems In Current Project
- 状态分散在目录名、flag 文件、日志中,缺少单一事实来源
- 业务逻辑和运维逻辑耦合严重
- 配置项散落在多个脚本和常量中
- 同类逻辑重复实现,例如 B 站列表解析、合集处理、任务扫描
- 可观测性不足,失败后需要人工翻日志定位
- 扩展新能力时只能继续加脚本,结构会越来越乱
## Target Characteristics
- 模块化单体,而不是脚本集合
- 显式任务状态机
- 统一配置系统
- 外部依赖适配器化
- 结构化任务存储
- 插件式扩展点
- Web 管理台
- 文档优先
## Milestones
1. 定义架构、领域模型、模块接口和 API。
2. 建立新系统骨架,不影响旧系统运行。
3. 落地统一配置、任务状态存储和最小管理 API。
4. 按模块迁移旧能力:转录、识歌、切歌、上传、评论、合集。
5. 接入 Web 管理台。
6. 逐步切换生产流量,最终替换旧脚本体系。