mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2026-10-10 21:13:10 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b28208bd76 | |||
| ffbbc1ccdf | |||
| 1d071c0fd8 | |||
| 948e87159d | |||
| c3df8da577 | |||
| 7cfb25558f | |||
| 31b41f99f8 | |||
| 9eae62581a | |||
| daad21ef91 | |||
| 219564b56c | |||
| ea19bed247 | |||
| b6db83ed5e | |||
| 0726d16632 | |||
| 7feec2b7d9 |
@@ -82,3 +82,9 @@ body:
|
||||
label: 复现链接(可选)
|
||||
description: |
|
||||
请提供能复现此问题的链接。
|
||||
- type: textarea
|
||||
id: aigenerated
|
||||
attributes:
|
||||
label: AI生成内容(可选)
|
||||
description: |
|
||||
如果此问题是由AI辅助您发现的,请提供全部聊天记录,包括使用的模型信息。
|
||||
|
||||
@@ -82,3 +82,9 @@ body:
|
||||
label: Reproduction Link (optional)
|
||||
description: |
|
||||
Please provide a link to a repo or page that can reproduce this issue.
|
||||
- type: textarea
|
||||
id: aigenerated
|
||||
attributes:
|
||||
label: AI Generated Content (optional)
|
||||
description: |
|
||||
If this issue was identified with the assistance of AI, please provide the complete chat log, including information about the model used.
|
||||
|
||||
@@ -48,3 +48,9 @@ body:
|
||||
label: 附加信息
|
||||
description: |
|
||||
相关的任何其他上下文或截图,或者你觉得有帮助的信息
|
||||
- type: textarea
|
||||
id: aigenerated
|
||||
attributes:
|
||||
label: AI生成内容(可选)
|
||||
description: |
|
||||
如果此请求是由AI辅助您提交的,请提供全部聊天记录,包括使用的模型信息。
|
||||
|
||||
@@ -48,3 +48,9 @@ body:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Any other context or screenshots related to this feature request, or information you find helpful.
|
||||
- type: textarea
|
||||
id: aigenerated
|
||||
attributes:
|
||||
label: AI Generated Content (optional)
|
||||
description: |
|
||||
If this request was submitted with the assistance of an AI, please provide the complete chat log, including information about the model used.
|
||||
|
||||
@@ -1,58 +1,114 @@
|
||||
<!--
|
||||
Provide a general summary of your changes in the Title above.
|
||||
The PR title must start with `feat(): `, `docs(): `, `fix(): `, `style(): `, or `refactor(): `, `chore(): `. For example: `feat(component): add new feature`.
|
||||
If it spans multiple components, use the main component as the prefix and enumerate in the title, describe in the body.
|
||||
For breaking changes, add `!` after the type, e.g., `feat(component)!: breaking change`.
|
||||
-->
|
||||
<!--
|
||||
在上方标题中提供您更改的总体摘要。
|
||||
PR 标题需以 `feat(): `, `docs(): `, `fix(): `, `style(): `, `refactor(): `, `chore(): ` 其中之一开头,例如:`feat(component): 新增功能`。
|
||||
如果跨多个组件,请使用主要组件作为前缀,并在标题中枚举、描述中说明。
|
||||
如果是破坏性变更,请在类型后添加 `!`,例如 `feat(component)!: 破坏性变更`。
|
||||
PR title / PR 标题:
|
||||
- Use Conventional Commits: `type(scope): summary`
|
||||
- Allowed types: `feat`, `docs`, `fix`, `style`, `refactor`, `chore`
|
||||
- Scope is required by the current PR title check.
|
||||
- For breaking changes, add `!`: `feat(driver)!: change auth flow`
|
||||
-->
|
||||
|
||||
## Description / 描述
|
||||
## Summary / 摘要
|
||||
|
||||
<!-- Describe your changes in detail -->
|
||||
<!-- 详细描述您的更改 -->
|
||||
<!--
|
||||
Briefly describe what changed and why.
|
||||
简要说明改了什么,以及为什么需要改。
|
||||
-->
|
||||
|
||||
## Motivation and Context / 背景
|
||||
<!--
|
||||
- List user-visible behavior changes.
|
||||
- List important implementation changes.
|
||||
- Mention config, storage, API, or compatibility changes if any.
|
||||
|
||||
<!-- Why is this change required? What problem does it solve? -->
|
||||
<!-- 为什么需要此更改?它解决了什么问题? -->
|
||||
- 列出用户可感知的行为变化。
|
||||
- 列出重要实现变化。
|
||||
- 如涉及配置、存储、API 或兼容性变化,请明确说明。
|
||||
-->
|
||||
|
||||
<!-- If it fixes an open issue, please link to the issue here. -->
|
||||
<!-- 如果修复了一个打开的issue,请在此处链接到该issue -->
|
||||
- [ ] This PR has breaking changes.
|
||||
/ 此 PR 包含破坏性变更。
|
||||
- [ ] This PR changes public API, config, storage format, or migration behavior.
|
||||
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
|
||||
- [ ] This PR requires corresponding changes in related repositories.
|
||||
/ 此 PR 需要关联仓库同步修改。
|
||||
|
||||
Closes #XXXX
|
||||
Related repository PRs / 关联仓库 PR:
|
||||
|
||||
<!-- or -->
|
||||
<!-- 或者 -->
|
||||
- OpenList-Frontend:
|
||||
- OpenList-Docs:
|
||||
|
||||
Relates to #XXXX
|
||||
## Related Issues / 关联 Issue
|
||||
|
||||
## How Has This Been Tested? / 测试
|
||||
<!--
|
||||
Use `Closes #123`, `Fixes #123`, or `Relates to #123`.
|
||||
Remove this section if not applicable.
|
||||
使用 `Closes #123`、`Fixes #123` 或 `Relates to #123`。
|
||||
不适用时请删除本节。
|
||||
-->
|
||||
|
||||
<!-- Please describe in detail how you tested your changes. -->
|
||||
<!-- 请详细描述您如何测试更改 -->
|
||||
## Testing / 测试
|
||||
|
||||
<!--
|
||||
Describe commands, platforms, and manual checks.
|
||||
If not tested, explain why.
|
||||
|
||||
说明执行过的命令、测试平台和手动验证。
|
||||
如果未测试,请说明原因。
|
||||
-->
|
||||
|
||||
- [ ] `go test ./...`
|
||||
- [ ] Manual test / 手动测试:
|
||||
|
||||
## Checklist / 检查清单
|
||||
|
||||
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!-- 检查以下所有要点,并在所有适用的框中打`x` -->
|
||||
- [ ] I have read [CONTRIBUTING](https://github.com/OpenListTeam/OpenList/blob/main/CONTRIBUTING.md).
|
||||
/ 我已阅读 [CONTRIBUTING](https://github.com/OpenListTeam/OpenList/blob/main/CONTRIBUTING.md)。
|
||||
- [ ] I confirm this contribution follows the repository license, contribution policy, and code of conduct.
|
||||
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
|
||||
- [ ] I have formatted the changed code with `gofmt`, `go fmt`, or `prettier` where applicable.
|
||||
/ 我已按适用情况使用 `gofmt`、`go fmt` 或 `prettier` 格式化变更代码。
|
||||
- [ ] I have requested review from relevant maintainers or code owners where applicable.
|
||||
/ 我已在适用情况下请求相关维护者或代码所有者审查。
|
||||
|
||||
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
<!-- 如果您对其中任何一项不确定,请不要犹豫提问。我们会帮助您! -->
|
||||
## AI Disclosure / AI 使用声明
|
||||
|
||||
- [ ] I have read the [CONTRIBUTING](https://github.com/OpenListTeam/OpenList/blob/main/CONTRIBUTING.md) document.
|
||||
我已阅读 [CONTRIBUTING](https://github.com/OpenListTeam/OpenList/blob/main/CONTRIBUTING.md) 文档。
|
||||
- [ ] I have formatted my code with `go fmt` or [prettier](https://prettier.io/).
|
||||
我已使用 `go fmt` 或 [prettier](https://prettier.io/) 格式化提交的代码。
|
||||
- [ ] I have added appropriate labels to this PR (or mentioned needed labels in the description if lacking permissions).
|
||||
我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
|
||||
- [ ] I have requested review from relevant code authors using the "Request review" feature when applicable.
|
||||
我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
|
||||
- [ ] I have updated the repository accordingly (If it’s needed).
|
||||
我已相应更新了相关仓库(若适用)。
|
||||
- [ ] [OpenList-Frontend](https://github.com/OpenListTeam/OpenList-Frontend) #XXXX
|
||||
- [ ] [OpenList-Docs](https://github.com/OpenListTeam/OpenList-Docs) #XXXX
|
||||
<!--
|
||||
Please disclose any substantial AI assistance used in this PR.
|
||||
Minor AI assistance, such as typo fixes, autocomplete, formatting suggestions,
|
||||
or wording polish, does not need to be disclosed.
|
||||
Remove this section if not applicable.
|
||||
|
||||
请披露此 PR 中使用的重要 AI 辅助内容。
|
||||
轻微 AI 辅助,例如拼写修正、自动补全、格式建议或文字润色,无需披露。
|
||||
如不适用,请删除本节。
|
||||
|
||||
Deliberate non-disclosure may be treated as a trust and compliance issue.
|
||||
|
||||
故意隐瞒 AI 使用情况可能被视为信任与合规问题。
|
||||
-->
|
||||
|
||||
- [ ] This PR includes AI-assisted content.
|
||||
/ 此 PR 包含 AI 辅助内容。
|
||||
|
||||
Tools used / 使用工具:
|
||||
|
||||
- [ ] ChatGPT
|
||||
- [ ] Codex
|
||||
- [ ] GitHub Copilot
|
||||
- [ ] Claude
|
||||
- [ ] Gemini
|
||||
- [ ] Other (please specify) / 其他(请注明):
|
||||
|
||||
Usage scope / 使用范围:
|
||||
|
||||
- [ ] Code generation / 代码生成
|
||||
- [ ] Refactoring / 重构
|
||||
- [ ] Documentation / 文档
|
||||
- [ ] Tests / 测试
|
||||
- [ ] Translation / 翻译
|
||||
- [ ] Review assistance / 审查辅助
|
||||
|
||||
- [ ] I have reviewed and validated all AI-assisted content included in this PR.
|
||||
/ 我已审核并验证此 PR 中的所有 AI 辅助内容。
|
||||
- [ ] I have ensured that all AI-assisted commits include `Co-Authored-By` attribution.
|
||||
/ 我已确保所有 AI 辅助提交都包含 `Co-Authored-By` 归属信息。
|
||||
- [ ] I can reproduce all AI-assisted content included in this PR without any AI tools.
|
||||
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。
|
||||
|
||||
+4
-4
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
@@ -116,7 +116,7 @@ the community.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
@@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity).
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations>.
|
||||
|
||||
+30
-7
@@ -7,7 +7,7 @@
|
||||
Prerequisites:
|
||||
|
||||
- [git](https://git-scm.com)
|
||||
- [Go 1.24+](https://golang.org/doc/install)
|
||||
- [Go](https://golang.org/doc/install) version declared in [`go.mod`](./go.mod)
|
||||
- [gcc](https://gcc.gnu.org/)
|
||||
- [nodejs](https://nodejs.org/)
|
||||
|
||||
@@ -16,8 +16,8 @@ Prerequisites:
|
||||
Fork and clone `OpenList` and `OpenList-Frontend` anywhere:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/<your-username>/OpenList.git
|
||||
$ git clone --recurse-submodules https://github.com/<your-username>/OpenList-Frontend.git
|
||||
git clone https://github.com/<your-username>/OpenList.git
|
||||
git clone --recurse-submodules https://github.com/<your-username>/OpenList-Frontend.git
|
||||
```
|
||||
|
||||
## Creating a branch
|
||||
@@ -25,7 +25,7 @@ $ git clone --recurse-submodules https://github.com/<your-username>/OpenList-Fro
|
||||
Create a new branch from the `main` branch, with an appropriate name.
|
||||
|
||||
```shell
|
||||
$ git checkout -b <branch-name>
|
||||
git checkout -b <branch-name>
|
||||
```
|
||||
|
||||
## Preview your change
|
||||
@@ -33,26 +33,36 @@ $ git checkout -b <branch-name>
|
||||
### backend
|
||||
|
||||
```shell
|
||||
$ go run main.go
|
||||
go run main.go
|
||||
```
|
||||
|
||||
### frontend
|
||||
|
||||
```shell
|
||||
$ pnpm dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Add a new driver
|
||||
|
||||
Copy `drivers/template` folder and rename it, and follow the comments in it.
|
||||
|
||||
## Community and policies
|
||||
|
||||
By contributing, you agree to follow the repository's code of conduct and license terms.
|
||||
|
||||
- Code of conduct: [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
|
||||
- License: [LICENSE](./LICENSE)
|
||||
- Security issues: please report privately according to [SECURITY.md](./SECURITY.md)
|
||||
|
||||
If your contribution includes substantial AI-assisted content, disclose the tools used and the scope of assistance in the pull request.
|
||||
|
||||
## Create a commit
|
||||
|
||||
Commit messages should be well formatted, and to make that "standardized".
|
||||
|
||||
Submit your pull request. For PR titles, follow [Conventional Commits](https://www.conventionalcommits.org).
|
||||
|
||||
https://github.com/OpenListTeam/OpenList/issues/376
|
||||
<https://github.com/OpenListTeam/OpenList/issues/376>
|
||||
|
||||
It's suggested to sign your commits. See: [How to sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
|
||||
|
||||
@@ -72,6 +82,19 @@ At least 1 approving review is required by reviewers with write access. You can
|
||||
|
||||
(Optional) After your pull request is merged, you can delete your branch.
|
||||
|
||||
## AI Disclosure
|
||||
|
||||
If your pull request includes substantial AI-assisted content, disclose it in the PR description.
|
||||
|
||||
Please include:
|
||||
|
||||
- Tools used, such as ChatGPT, GitHub Copilot, Claude, Cursor, or other AI tools.
|
||||
- Usage scope, such as code generation, refactoring, documentation, tests, translation, or review assistance.
|
||||
- Confirmation that you have reviewed and validated all AI-assisted content before submission.
|
||||
- Confirmation that the submitted content complies with this repository's license and contribution policies.
|
||||
|
||||
Minor AI assistance, such as typo fixes, autocomplete, formatting suggestions, or wording polish, does not need to be disclosed.
|
||||
|
||||
---
|
||||
|
||||
Thank you for your contribution! Let's make OpenList better together!
|
||||
|
||||
@@ -116,9 +116,10 @@ Thank you for your support and understanding of the OpenList project.
|
||||
|
||||
## Document
|
||||
|
||||
- 📘 [Global Site](https://doc.oplist.org)
|
||||
- 📚 [Backup Site](https://doc.openlist.team)
|
||||
- 🌏 [CN Site](https://doc.oplist.org.cn)
|
||||
- 📘 [Docs](https://doc.oplist.org)
|
||||
- 🌏 [CN Mirror](https://doc.oplist.org.cn)
|
||||
- ⚖️ [Terms of Use](https://doc.oplist.org/terms)
|
||||
- 🔒 [Privacy Policy](https://doc.oplist.org/privacy)
|
||||
|
||||
## Demo
|
||||
|
||||
|
||||
+7
-6
@@ -116,14 +116,15 @@ OpenList 是一个由 OpenList 团队独立维护的开源项目,遵循 AGPL-3
|
||||
|
||||
## 文档
|
||||
|
||||
- 🌏 [国内站点](https://doc.oplist.org.cn)
|
||||
- 📘 [海外站点](https://doc.oplist.org)
|
||||
- 📚 [备用站点](https://doc.openlist.team)
|
||||
- 📘 [文档](https://doc.oplist.org)
|
||||
- 🌏 [中国镜像](https://doc.oplist.org.cn)
|
||||
- ⚖️ [使用条款](https://doc.oplist.org/terms)
|
||||
- 🔒 [隐私政策](https://doc.oplist.org/privacy)
|
||||
|
||||
## 演示
|
||||
## Demo
|
||||
|
||||
- 🇨🇳 [国内演示站](https://demo.oplist.org.cn)
|
||||
- 🌎 [海外演示站](https://demo.oplist.org)
|
||||
- 🌎 [全球 Demo](https://demo.oplist.org)
|
||||
- 🇨🇳 [中国 Demo](https://demo.oplist.org.cn)
|
||||
|
||||
## 讨论
|
||||
|
||||
|
||||
+7
-6
@@ -116,14 +116,15 @@ OpenListプロジェクトへのご支援とご理解をありがとうござい
|
||||
|
||||
## ドキュメント
|
||||
|
||||
- 📘 [グローバルサイト](https://doc.oplist.org)
|
||||
- 📚 [バックアップサイト](https://doc.openlist.team)
|
||||
- 🌏 [CNサイト](https://doc.oplist.org.cn)
|
||||
- 📘 [ドキュメント](https://doc.oplist.org)
|
||||
- 🌏 [中国ミラー](https://doc.oplist.org.cn)
|
||||
- ⚖️ [利用規約](https://doc.oplist.org/terms)
|
||||
- 🔒 [プライバシーポリシー](https://doc.oplist.org/privacy)
|
||||
|
||||
## デモ
|
||||
## Demo
|
||||
|
||||
- 🌎 [グローバルデモ](https://demo.oplist.org)
|
||||
- 🇨🇳 [CNデモ](https://demo.oplist.org.cn)
|
||||
- 🌎 [グローバル Demo](https://demo.oplist.org)
|
||||
- 🇨🇳 [中国 Demo](https://demo.oplist.org.cn)
|
||||
|
||||
## ディスカッション
|
||||
|
||||
|
||||
+4
-3
@@ -116,9 +116,10 @@ Dank u voor uw ondersteuning en begrip
|
||||
|
||||
## Documentatie
|
||||
|
||||
- 📘 [Global Site](https://doc.oplist.org)
|
||||
- 📚 [Backup Site](https://doc.openlist.team)
|
||||
- 🌏 [CN Site](https://doc.oplist.org.cn)
|
||||
- 📘 [Documentatie](https://doc.oplist.org)
|
||||
- 🌏 [CN Mirror](https://doc.oplist.org.cn)
|
||||
- ⚖️ [Gebruiksvoorwaarden](https://doc.oplist.org/terms)
|
||||
- 🔒 [Privacybeleid](https://doc.oplist.org/privacy)
|
||||
|
||||
## Demo
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,167 +0,0 @@
|
||||
# OpenList 转码协议 v1(自研)
|
||||
|
||||
> 用于 OpenList Master 与远程 FFmpeg 转码 Worker 之间的通信。
|
||||
> 协议本身、所有依赖、推荐编码 (H.264/AV1) 均为开源免费,可商用。
|
||||
|
||||
## 总览
|
||||
|
||||
```
|
||||
Player ──► Master(/api/transcode/play) ──► Scheduler.Submit
|
||||
Worker ──► Master(/api/transcode/worker/register) # 启动一次
|
||||
Worker ──► Master(/api/transcode/worker/heartbeat) # 周期 10s
|
||||
Worker ──► Master(/api/transcode/worker/claim) # 长轮询拉任务
|
||||
Worker ──► Master(/api/transcode/worker/segment) # 流式回推切片
|
||||
Worker ──► Master(/api/transcode/worker/job/finish) # 上报完成
|
||||
Player ──► Master(/tc/{job}/{token}/master.m3u8) # 拉取播放列表
|
||||
Player ──► Master(/tc/{job}/{token}/{profile}/seg-N.ts) # 拉取切片
|
||||
```
|
||||
|
||||
## 通用约定
|
||||
- 所有请求体均为 JSON。
|
||||
- Worker 与 Master 间通过两类凭据鉴权:
|
||||
- **shared_secret**(管理端 `transcode_worker_secret` 设置项):用于 register / heartbeat / claim
|
||||
- **callback_token**(Master 在 job 中下发,一次性):用于 segment / job/finish
|
||||
- Header 形式:`Authorization: Bearer <token>`
|
||||
- 返回包统一使用 OpenList 通用响应:`{"code":200,"message":"success","data":{...}}`
|
||||
|
||||
## 端点详细
|
||||
|
||||
### POST /api/transcode/worker/register
|
||||
> 鉴权:Bearer = shared_secret
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"name": "gpu-node-01",
|
||||
"version": "1.0.0",
|
||||
"capacity": 4,
|
||||
"hwaccel": ["nvenc"],
|
||||
"codecs_decode": ["h264","hevc","av1","vp9"],
|
||||
"codecs_encode": ["h264","hevc"],
|
||||
"max_resolution": "3840x2160",
|
||||
"tags": ["linux","amd64"]
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code":200,"message":"success",
|
||||
"data": {
|
||||
"worker_id":"wk_xxx",
|
||||
"heartbeat_interval":10,
|
||||
"claim_strategy":"pull",
|
||||
"protocol_version":"v1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/transcode/worker/heartbeat
|
||||
> 鉴权:Bearer = shared_secret
|
||||
> 间隔:register 返回的 `heartbeat_interval`;服务端 30s 未收到则剔除 worker
|
||||
|
||||
请求:
|
||||
```json
|
||||
{ "worker_id":"wk_xxx", "load":0.4, "running":["job_a"], "free_slots":3 }
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{ "code":200,"message":"success","data":{"ok":true,"kick":false,"cancel":["job_x"]}}
|
||||
```
|
||||
- `kick=true`:服务端要求 Worker 主动退出
|
||||
- `cancel`:需要 Worker 立刻终止的 job_ids(异步取消通道)
|
||||
|
||||
### POST /api/transcode/worker/claim
|
||||
> 鉴权:Bearer = shared_secret
|
||||
> 长轮询:当无任务时,服务端最多挂起 `wait` 秒(最大 30)
|
||||
|
||||
请求:
|
||||
```json
|
||||
{ "worker_id":"wk_xxx", "slots":1, "wait":20 }
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{ "code":200,"message":"success","data":{"jobs":[{
|
||||
"id":"job_xxx",
|
||||
"path":"/movies/x.mkv",
|
||||
"source_url":"https://example.com/d/movies/x.mkv?sign=...",
|
||||
"profiles":[{
|
||||
"name":"1080p","video_codec":"h264","video_bitrate":"4000k",
|
||||
"scale":"1920:-2","audio_codec":"aac","audio_bitrate":"160k",
|
||||
"hwaccel":"nvenc"
|
||||
}],
|
||||
"output":{"format":"hls","segment_duration":6},
|
||||
"callback_token":"tk_xxx",
|
||||
"deadline":1731601234
|
||||
}]}}
|
||||
```
|
||||
|
||||
### PUT /api/transcode/worker/segment
|
||||
> 鉴权:Bearer = callback_token
|
||||
> Query 参数:
|
||||
> - `job` 任务 ID
|
||||
> - `profile` profile 名(如 `1080p`)
|
||||
> - `seq` 切片序号(从 0 开始)
|
||||
> - `duration` 该切片秒数
|
||||
> - `final` 是否为最后一片(true/false)
|
||||
>
|
||||
> Body:原始 MPEG-TS 二进制(`Content-Type: video/mp2t`)
|
||||
|
||||
响应:`204 No Content`
|
||||
|
||||
### POST /api/transcode/worker/job/finish
|
||||
> 鉴权:Bearer = callback_token
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"job_id":"job_xxx",
|
||||
"status":"finished",
|
||||
"error":"",
|
||||
"stats":{"elapsed":312.4,"fps":120,"speed":2.1,"bytes_out":890123456}
|
||||
}
|
||||
```
|
||||
status 取值:`finished` / `failed` / `cancelled`
|
||||
|
||||
## 播放端
|
||||
|
||||
### POST /api/fs/transcode/play
|
||||
> 由前端播放器调用,用户登录态可见
|
||||
> 请求:`{"path":"/movies/x.mkv"}`
|
||||
>
|
||||
> 响应(无需转码):
|
||||
> `{"transcode":false,"reason":"size below threshold"}`
|
||||
>
|
||||
> 响应(已下发转码任务):
|
||||
> `{"transcode":true,"job_id":"job_xx","master_url":"https://.../tc/.../master.m3u8","profile":"1080p"}`
|
||||
|
||||
### GET /tc/:job/:token/master.m3u8
|
||||
返回多档位主 m3u8。
|
||||
|
||||
### GET /tc/:job/:token/:profile/playlist.m3u8
|
||||
阻塞等待首切片就绪(最多 30s)后返回 HLS 播放列表,未结束时不带 `#EXT-X-ENDLIST`。
|
||||
|
||||
### GET /tc/:job/:token/:profile/seg-N.ts
|
||||
返回 N 号切片;如果尚未生成,最多等待 60s。
|
||||
|
||||
## 启动远程 Worker
|
||||
|
||||
```bash
|
||||
# 用 Master 配置中的 transcode_worker_secret 作为密钥
|
||||
go build -o openlist-worker ./cmd/worker
|
||||
|
||||
./openlist-worker \
|
||||
-master http://openlist.example.com:5244 \
|
||||
-secret <transcode_worker_secret> \
|
||||
-capacity 2 \
|
||||
-hwaccel nvenc \
|
||||
-ffmpeg /usr/bin/ffmpeg \
|
||||
-workdir /tmp/openlist-worker
|
||||
```
|
||||
|
||||
## License 说明
|
||||
- 协议:自研,可自由使用
|
||||
- FFmpeg:使用系统包提供的 LGPL 版本即可(含 libx264/libx265,libx264 是 GPL,建议在 Worker 镜像中遵循 GPL 公开/动态链接合规)
|
||||
- 推荐编码:H.264 互联网分发免授权 / AV1 永久免版税
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
streamPkg "github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
@@ -68,6 +69,9 @@ func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
||||
return nil, err
|
||||
}
|
||||
userAgent := args.Header.Get("User-Agent")
|
||||
if userAgent == "" {
|
||||
userAgent = base.UserAgent
|
||||
}
|
||||
downloadInfo, err := d.client.DownloadWithUA(file.(*FileObj).PickCode, userAgent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,6 +2,7 @@ package _115_share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
@@ -96,8 +97,12 @@ func (d *Pan115Share) Link(ctx context.Context, file model.Obj, args model.LinkA
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{URL: downloadInfo.URL.URL}, nil
|
||||
header := http.Header{}
|
||||
header.Set("User-Agent", ua)
|
||||
return &model.Link{
|
||||
URL: downloadInfo.URL.URL,
|
||||
Header: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
||||
@@ -1180,7 +1180,6 @@ func (d *Yun139) step3_third_party_login(dycpwd string) (string, error) {
|
||||
"x-DeviceInfo": "4|127.0.0.1|5|1.2.6|Xiaomi|23116PN5BC||02-00-00-00-00-00|android 15|1440x3200|android|||",
|
||||
"Content-Type": "text/plain;charset=UTF-8",
|
||||
"Host": "user-njs.yun.139.com",
|
||||
"Connection": "Keep-Alive",
|
||||
"Accept-Encoding": "gzip",
|
||||
"User-Agent": "okhttp/3.12.2",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package _189
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/torrent"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
)
|
||||
|
||||
// GenerateTorrent 根据上传过程中收集的哈希信息生成包含 CAS 扩展的 torrent 文件
|
||||
func GenerateTorrent(fileName string, fileSize int64, fileMD5 string, sliceMD5s []string, sliceSize int64, pieceHashes []byte) ([]byte, error) {
|
||||
// 计算 sliceMD5
|
||||
sliceMD5 := fileMD5
|
||||
if len(sliceMD5s) > 1 {
|
||||
joined := strings.Join(sliceMD5s, "\n")
|
||||
sliceMD5 = strings.ToUpper(torrent.GetMD5Str(joined))
|
||||
}
|
||||
|
||||
t := torrent.NewTorrent(fileName, fileSize, fileMD5)
|
||||
t.Info.PieceLength = sliceSize
|
||||
t.SetPieces(pieceHashes)
|
||||
t.SetCASInfo(&torrent.CASInfo{
|
||||
FileMD5: fileMD5,
|
||||
SliceMD5: sliceMD5,
|
||||
SliceMD5s: sliceMD5s,
|
||||
SliceSize: sliceSize,
|
||||
Cloud: "189",
|
||||
})
|
||||
|
||||
return t.Encode()
|
||||
}
|
||||
|
||||
// RapidUploadFromTorrent 从 torrent 文件中提取 CAS 信息进行秒传
|
||||
func (d *Cloud189) RapidUploadFromTorrent(ctx context.Context, dstDir model.Obj, torrentData []byte) error {
|
||||
// 解析 torrent
|
||||
t, err := torrent.Decode(torrentData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析 torrent 失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否包含 CAS 扩展信息
|
||||
if !t.HasCASInfo() {
|
||||
return fmt.Errorf("torrent 不包含 CAS 扩展信息,无法秒传")
|
||||
}
|
||||
|
||||
cas := t.CAS
|
||||
fileName := t.Info.Name
|
||||
fileSize := t.GetTotalSize()
|
||||
|
||||
// 获取 sessionKey
|
||||
sessionKey, err := d.getSessionKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.sessionKey = sessionKey
|
||||
|
||||
// 初始化上传
|
||||
res, err := d.uploadRequest("/person/initMultiUpload", map[string]string{
|
||||
"parentFolderId": dstDir.GetID(),
|
||||
"fileName": encode(fileName),
|
||||
"fileSize": fmt.Sprint(fileSize),
|
||||
"sliceSize": fmt.Sprint(cas.SliceSize),
|
||||
"lazyCheck": "1",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化上传失败: %w", err)
|
||||
}
|
||||
|
||||
uploadFileId := utils.Json.Get(res, "data", "uploadFileId").ToString()
|
||||
|
||||
// 提交上传(使用 CAS 信息秒传)
|
||||
_, err = d.uploadRequest("/person/commitMultiUploadFile", map[string]string{
|
||||
"uploadFileId": uploadFileId,
|
||||
"fileMd5": cas.FileMD5,
|
||||
"sliceMd5": cas.SliceMD5,
|
||||
"lazyCheck": "1",
|
||||
"opertype": "3",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("秒传提交失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComputeTorrentFromReader 从 io.Reader 计算并生成 torrent 文件
|
||||
func ComputeTorrentFromReader(reader io.Reader, fileName string, fileSize int64, sliceSize int64) ([]byte, error) {
|
||||
if sliceSize <= 0 {
|
||||
sliceSize = torrent.DefaultPieceSize
|
||||
}
|
||||
|
||||
hw := torrent.NewHashWriter(sliceSize, sliceSize)
|
||||
|
||||
buf := make([]byte, 32*1024)
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n > 0 {
|
||||
hw.Write(buf[:n])
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
hw.Finish()
|
||||
|
||||
fileMD5 := hw.GetFileMD5()
|
||||
sliceMD5s := hw.GetSliceMD5s()
|
||||
pieceHashes := hw.GetPieceHashes()
|
||||
|
||||
return GenerateTorrent(fileName, fileSize, fileMD5, sliceMD5s, sliceSize, pieceHashes)
|
||||
}
|
||||
|
||||
// ComputePieceSHA1 计算单个分片的 SHA-1 哈希
|
||||
func ComputePieceSHA1(data []byte) []byte {
|
||||
h := sha1.Sum(data)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// ExtractCASFromTorrent 从 torrent 数据中提取 CAS 信息
|
||||
func ExtractCASFromTorrent(torrentData []byte) (*torrent.CASInfo, string, int64, error) {
|
||||
t, err := torrent.Decode(torrentData)
|
||||
if err != nil {
|
||||
return nil, "", 0, fmt.Errorf("解析 torrent 失败: %w", err)
|
||||
}
|
||||
|
||||
if !t.HasCASInfo() {
|
||||
return nil, "", 0, fmt.Errorf("torrent 不包含 CAS 扩展信息")
|
||||
}
|
||||
|
||||
return t.CAS, t.Info.Name, t.GetTotalSize(), nil
|
||||
}
|
||||
|
||||
// GetInfoHashHex 获取 torrent 的 info_hash(十六进制字符串)
|
||||
func GetInfoHashHex(torrentData []byte) (string, error) {
|
||||
t, err := torrent.Decode(torrentData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(t.InfoHash), nil
|
||||
}
|
||||
+173
-25
File diff suppressed because it is too large
Load Diff
+13
-6
@@ -87,7 +87,12 @@ func (y *Cloud189PC) Init(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
// 先尝试用Token刷新,之后尝试登陆
|
||||
if y.Addition.RefreshToken != "" {
|
||||
if y.Addition.AccessToken != "" {
|
||||
y.tokenInfo = &AppSessionResp{AccessToken: y.Addition.AccessToken, RefreshToken: y.Addition.RefreshToken}
|
||||
if err = y.refreshSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if y.Addition.RefreshToken != "" {
|
||||
y.tokenInfo = &AppSessionResp{RefreshToken: y.Addition.RefreshToken}
|
||||
if err = y.refreshToken(); err != nil {
|
||||
return err
|
||||
@@ -288,7 +293,7 @@ func (y *Cloud189PC) Rename(ctx context.Context, srcObj model.Obj, newName strin
|
||||
req.SetContext(ctx).SetQueryParams(queryParam)
|
||||
}, nil, resp, isFamily)
|
||||
if err != nil {
|
||||
if resp.ResCode == "FileAlreadyExists" {
|
||||
if code, ok := resp.ResCode.(string); ok && code == "FileAlreadyExists" {
|
||||
return nil, errs.ObjectAlreadyExists
|
||||
}
|
||||
return nil, err
|
||||
@@ -338,6 +343,7 @@ func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
|
||||
// 响应时间长,按需启用
|
||||
if y.Addition.RapidUpload && !stream.IsForceStreamUpload() {
|
||||
// 尝试妙传
|
||||
if newObj, err := y.RapidUpload(ctx, dstDir, stream, isFamily, overwrite); err == nil {
|
||||
return newObj, nil
|
||||
}
|
||||
@@ -346,10 +352,11 @@ func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
uploadMethod := y.UploadMethod
|
||||
if stream.IsForceStreamUpload() {
|
||||
uploadMethod = "stream"
|
||||
}
|
||||
|
||||
// 旧版上传家庭云也有限制
|
||||
if uploadMethod == "old" {
|
||||
} else if y.Addition.RapidUpload && stream.GetFile() != nil {
|
||||
// 文件流支持随机读取,走FastUpload计算MD5并尝试秒传
|
||||
uploadMethod = "rapid"
|
||||
} else if uploadMethod == "old" {
|
||||
// 旧版上传家庭云也有限制
|
||||
return y.OldUpload(ctx, dstDir, stream, up, isFamily, overwrite)
|
||||
}
|
||||
|
||||
|
||||
+11
-9
@@ -10,17 +10,19 @@ type Addition struct {
|
||||
Username string `json:"username" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
VCode string `json:"validate_code"`
|
||||
AccessToken string `json:"access_token" required:"false"`
|
||||
RefreshToken string `json:"refresh_token" help:"To switch accounts, please clear this field"`
|
||||
driver.RootID
|
||||
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||
Type string `json:"type" type:"select" options:"personal,family" default:"personal"`
|
||||
FamilyID string `json:"family_id"`
|
||||
UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"`
|
||||
UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"`
|
||||
FamilyTransfer bool `json:"family_transfer"`
|
||||
RapidUpload bool `json:"rapid_upload"`
|
||||
NoUseOcr bool `json:"no_use_ocr"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||
Type string `json:"type" type:"select" options:"personal,family" default:"personal"`
|
||||
FamilyID string `json:"family_id"`
|
||||
UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"`
|
||||
UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"`
|
||||
FamilyTransfer bool `json:"family_transfer"`
|
||||
RapidUpload bool `json:"rapid_upload"`
|
||||
NoUseOcr bool `json:"no_use_ocr"`
|
||||
GenerateTorrent bool `json:"generate_torrent" help:"Generate torrent file with CAS extension after upload"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user