很高兴你对参与 yutto 的贡献感兴趣,在提交你的贡献之前,请花一点点时间阅读本指南
为了获得最佳的开发体验,希望你能够安装一些开发工具
这些工具都是可选的,都有一定的替代方案,不过可能会稍微麻烦些……
uv 是 yutto 用来进行项目管理的工具,你可以从安装指南找到合适的安装方式~
just 是一款用 rust 编写的简单易用的命令执行工具,通过它可以方便地执行一些开发时常用的命令。安装方法请参考它的文档
替代方案(不方便安装或者 Windows 上无法运行这些命令时建议使用):自行查看 justfile 中对应的详细命令。
VS Code 是一款功能强大的编辑器,由于 yutto 全面使用了 Type Hints,所以这里建议使用 VS Code + 扩展 pylance 来保证类型提示的准确性,同时配置 Format/Lint 工具 Ruff 以保证代码格式的一致性。
当然,如果你有更熟悉的编辑器或 IDE 的话,也是完全可以的。
本 repo 是一个 monorepo,同时包含 yutto 和 biliass 两个包,其中 biliass 采用 Rust 编写,如果你有 biliass 联调的需求,则需要安装 Rust 工具链,安装方法请参考 Rust 官方文档
如果你不需要联调 biliass,那么可以通过注释掉 pyproject.toml 中的 tool.uv.sources
和 tool.uv.workspace
,避免 uv 将其当作一个子项目来处理。此时 uv 会安装 pypi 上预编译的 biliass wheel 包,而不会编译源码。这在大多数情况下是没有问题的,除非 yutto 使用了 biliass 的最新特性。
如果你想要本地调试,最佳实践是从 GitHub 上下载最新的源码来运行
git clone git@github.com:yutto-dev/yutto.git
cd yutto/
uv sync
uv run yutto -v
注意本地调试请不要直接使用 yutto
命令,那只会运行从 pip 安装的 yutto,而不是本地调试的 yutto。
另外请注意如果你确定想为 yutto 做贡献请 fork 之后 clone 自己的 repo 再修改,以便发起 PR。
这部分内容带你了解下 yutto 的主要模块结构与工作流程。
本部分内容可能略有滞后,这里列出的是 2024-09-09 时 bb207c1a0cff4ff338a0464dd2d6f967441ca0e2 的模块结构,
.
├── CONTRIBUTING.md # 贡献指南
├── Dockerfile # 一个轻量的 yutto docker
├── LICENSE # GPL-3.0 License
├── README.md # 项目说明
├── _typos.toml # typos 配置
├── justfile # just 命令启动文件
├── pyproject.toml # Python 统一配置,含各种工具链配置、依赖项声明等
├── src
│ └── yutto
│ ├── __init__.py
│ ├── __main__.py # 命令行入口,含所有命令选项
│ ├── __version__.py
│ ├── _typing.py # yutto 的主要类型声明(非全部,部分类型是定义在自己模块之内的)
│ ├── api # bilibili API 的基本函数封装,输入输出转换为 yutto 的主要类型
│ │ ├── __init__.py
│ │ ├── bangumi.py # 番剧相关
│ │ ├── cheese.py # 课程相关
│ │ ├── collection.py # 合集相关
│ │ ├── danmaku.py # 弹幕相关(xml、protobuf)
│ │ ├── space.py # 个人空间相关(收藏夹、合集、列表)
│ │ ├── ugc_video.py # 投稿视频相关
│ │ └── user_info.py # 用户信息相关
│ ├── bilibili_typing # bilibili 自己的一些数据类型绑定
│ │ ├── __init__.py
│ │ ├── codec.py # bilibili 的 codec
│ │ └── quality.py # bilibili 的 qn
│ ├── exceptions.py # yutto 异常声明模块
│ ├── extractor # 页面提取器(每种入口 url 对应一个 extractor)
│ │ ├── __init__.py
│ │ ├── _abc.py # 基本抽象类
│ │ ├── bangumi.py # 番剧单话
│ │ ├── bangumi_batch.py # 番剧全集
│ │ ├── cheese.py # 课程单话
│ │ ├── cheese_batch.py # 课程全集
│ │ ├── collection.py # 合集
│ │ ├── common.py # 低阶提取器(投稿视频、番剧、课程),每种视频类型对应一个低阶提取器
│ │ ├── favourites.py # 收藏夹
│ │ ├── series.py # 视频列表
│ │ ├── ugc_video.py # 投稿视频单集
│ │ ├── ugc_video_batch.py # 投稿视频批量
│ │ ├── user_all_favourites.py # 全部收藏夹
│ │ ├── user_all_ugc_videos.py # 个人空间全部
│ │ └── user_watch_later.py # 稍后再看
│ ├── processor # 一些在提取/下载过程中用到的基本处理方法(该部分很可能进一步重构)
│ │ ├── __init__.py
│ │ ├── downloader.py # 下载器
│ │ ├── parser.py # 文件解析器(解析任务列表、alias 文件)
│ │ ├── path_resolver.py # 路径处理器(需处理路径变量)
│ │ ├── progressbar.py # 进度条(本部分可替换成为其他行为以支持更丰富的进度显示方式)
│ │ └── selector.py # 选集、内容过滤器(本部分可修改成支持交互的)
│ ├── py.typed
│ ├── utils # yutto 无关或弱相关模块,不应依赖 yutto 强相关模块(api、extractor、processor),含部分类型资源的基本封装(弹幕、字幕、描述文件)
│ │ ├── __init__.py
│ │ ├── asynclib.py # 封装部分异步相关方法
│ │ ├── console # 命令行打印相关
│ │ │ ├── __init__.py
│ │ │ ├── attributes.py
│ │ │ ├── colorful.py
│ │ │ ├── formatter.py
│ │ │ ├── logger.py # 其中的 Logger 是 yutto 主要的打印方式,yutto 中只应使用这一种打印方式
│ │ │ └── status_bar.py # 底部状态栏(主要用于显示进度条)
│ │ ├── danmaku.py # 「资源文件」弹幕基本封装
│ │ ├── fetcher.py # 基本抓取器
│ │ ├── ffmpeg.py # FFmpeg 驱动单例模块
│ │ ├── file_buffer.py # 文件缓冲器(yutto 下载原理的核心)
│ │ ├── filter.py # 数据过滤器(根据时间过滤选择的剧集)
│ │ ├── funcutils # yutto 需要用的一些实用基本函数(很多是直接参考 StackOverflow 的)
│ │ │ ├── __init__.py # 一些实用函数
│ │ │ ├── aobject.py # 一个简单的抽象类
│ │ │ ├── as_sync.py # 异步转同步
│ │ │ ├── data_access.py # 数据访问
│ │ │ ├── filter_none_value.py # 过滤 None 值
│ │ │ ├── singleton.py # 单例模式
│ │ │ └── xmerge.py # 合并多个迭代器
│ │ ├── metadata.py # 「资源文件」描述文件基本封装
│ │ ├── priority.py # 资源优先级判定(用于 codec、quality 判定)
│ │ ├── subtitle.py # 「资源文件」字幕基本封装
│ │ └── time.py # 时间基本模块
│ └── validator.py # 命令参数验证器(内含初期全局状态的设置)
├── tests # 测试目录
│ ├── __init__.py
│ ├── conftest.py # pytest 配置
│ ├── test_api # API 测试模块,对应 yutto/api
│ │ ├── __init__.py
│ │ ├── test_bangumi.py
│ │ ├── test_cheese.py
│ │ ├── test_collection.py
│ │ ├── test_danmaku.py
│ │ ├── test_space.py
│ │ ├── test_ugc_video.py
│ │ └── test_user_info.py
│ ├── test_e2e.py # 端到端测试
│ ├── test_processor # processor 测试模块,对应 yutto/processor
│ │ ├── __init__.py
│ │ ├── test_downloader.py
│ │ ├── test_path_resolver.py
│ │ └── test_selector.py
│ └── test_utils # utils 测试模块,对应 yutto/utils
│ ├── __init__.py
│ ├── test_data_access.py
│ └── test_ffmpeg.py
└── uv.lock # uv 依赖 lockfile
切入代码的最好方式自然是从入口开始啦~ yutto 的命令行入口是 src/yutto/__main__.py
,这里列出了 yutto 整个的工作流程:
- 解析参数并利用 yutto/validator.py 验证参数的正确性,虽然 argparse 已经做了基本的验证,但 validator 会进一步的验证。另外目前 validator 还会顺带做全局状态的设置的工作,这部分以后可能修改。
- 利用 yutto/processor/parser.py 解析 alias 和任务列表
- 遍历任务列表下载:
- 初始化提取器 yutto/extractor/
- 利用所有提取器处理 id 为可识别的 url
- 重定向一下入口 url 到可识别的 url
- 从入口 url 提取信息,构造解析任务
- 如果是单话下载(继承
yutto.extractor._abc.SingleExtractor
)- 解析有用信息以提供给路径变量
- 使用
yutto.extractor.common
里的低阶提取器构造链接解析任务
- 如果是批量下载(继承
yutto.extractor._abc.BatchExtractor
)- 循环解析列表
- 展平列表
- 选集(如果支持的话)
- 根据列表构造协程任务(任务包含了解析信息和利用低阶提取器提取)
- 构造解析任务
- 如果是单话下载(继承
- 依次执行解析任务,并将结果依次传入
src/yutto/utils/downloader.py
进行下载- 选择清晰度
- 显示详细信息
- 字幕、弹幕、描述文件等额外资源下载
- 下载音频、视频
- 合并音频、视频
嗯,你现在已经基本了解 yutto 的结构了,可以尝试去修改部分源码了。
yutto 已经编写好了一些测试,请确保在改动后仍能通过测试
just test
当然,如果你修改的内容需要对测试用例进行修改和增加,请尽管修改。
如果你的改动是需要用户感知的,请务必更新文档,文档位于 docs 目录下,你可以在本地启动文档服务来查看你的修改
在此之前,请确保自行安装 Node.js 18 或以上版本
# 启用 corepack,确保 pnpm 可用
corepack enable
# 安装依赖项
just docs-setup
# 启动文档开发服务器
just docs-dev
之后你可以在浏览器中访问 http://localhost:5173
来查看你的修改
yutto 使用 Ruff 对代码进行格式化,如果你的编辑器或 IDE 没有自动使用 Ruff 进行格式化,请使用下面的命令对代码进行格式化
just fmt
提交 PR 的最佳实践是 fork 一个新的 repo 到你的账户下,并创建一个新的分支,在该分支下进行改动后提交到 GitHub 上,并发起 PR(请注意在发起 PR 时不要取消掉默认已经勾选的 Allow edits from maintainers
选项)
# 首先在 GitHub 上 fork
git clone git@github.com:<YOUR_USER_NAME>/yutto.git # 将你的 repo clone 到本地
cd yutto/ # cd 到该目录
git remote add upstream git@github.com:yutto-dev/yutto.git # 将原分支绑定在 upstream
git checkout -b <NEW_BRANCH> # 新建一个分支,名称随意,最好含有你本次改动的语义
git push origin <NEW_BRANCH> # 将该分支推送到 origin (也就是你 fork 后的 repo)
# 对源码进行修改、并通过测试
# 此时可以在 GitHub 发起 PR
如果你的贡献需要继续修改,直接继续向该分支提交新的 commit 即可,并推送到 GitHub,PR 也会随之更新
如果你的 PR 已经被合并,就可以放心地删除这个分支了
git checkout main # 切换到 main
git fetch upstream # 将原作者分支下载到本地
git merge upstream/main # 将原作者 main 分支最新内容合并到本地 main
git branch -d <NEW_BRANCH> # 删除本地分支
git push origin --delete <NEW_BRANCH> # 同时删除远程分支
表明你所作的更改即可,没有太过苛刻的格式(合并时会重命名)
如果可能,可以按照 <gitmoji> <type>: <subject>
来进行命名
这里的 <type>
采取和 vite 一样的可选值
Vite Git Commit Message Convention 参考:https://github.com/vitejs/vite/blob/main/.github/commit-convention.md
Gitmoji 参考:https://gitmoji.dev/
尽可能按照模板书写
本章节内容仅针对有发布权限的维护者
现阶段书写版本号的代码包括以下几个文件,发布版本前需要全部更改:
我们优先使用 GitHub Actions 构建并发布到 PyPI,这可以通过如下命令触发
just release
简单来说就是创建一个 tag 并 push,此时便会触发 GitHub Actions 中的 Release 构建
如果你想要手动发布到 PyPI,可以使用下面的命令
just publish
需预先自行安装 Docker
just docker-publish
修改 https://github.com/SigureMo/homebrew-tap/blob/main/Formula/yutto.rb,按照提示构建新版本 Formula。
因为有你,yutto 才会更加完善,感谢你的贡献 (・ω< )★