mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2026-10-10 21:13:10 +08:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80b0980eac | |||
| 75632de2e6 | |||
| b85f82c76d | |||
| 3ce3dc4109 | |||
| 6a46f0e809 | |||
| 609d0a7b84 | |||
| 9a28f7c09c | |||
| 6646ffe891 | |||
| 20d9d39f83 | |||
| ccb74025ea | |||
| 94002a2118 | |||
| e750ff502a | |||
| 97e885fee6 | |||
| f891d3df1f | |||
| 6e9878fe61 | |||
| 08d9e2eb70 | |||
| 0386f87110 | |||
| 4c9e63611f | |||
| b00e694f68 | |||
| ae6f05af11 | |||
| 8dbcd0fd88 | |||
| 2182ccff96 | |||
| b5f72ecdcc | |||
| 5a27d5fa6d | |||
| ce6bff2ad8 | |||
| cc1fec59ac | |||
| 29f0636833 | |||
| 8a72c78075 | |||
| 3527cab26b | |||
| dc92fccf52 | |||
| e33ff8d9f7 | |||
| 7571afcd41 | |||
| 85c7f85b18 | |||
| 9775971eb7 | |||
| 9096eb60c2 | |||
| 51c703c0a8 | |||
| 0e33727675 | |||
| 879870ef63 | |||
| bd84607537 | |||
| 0f340111da | |||
| e776d2ca43 |
@@ -0,0 +1,153 @@
|
||||
name: Beta Media Release builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev-media"]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
name: Beta Media Release Changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create or update ref
|
||||
id: create-or-update-ref
|
||||
uses: ovsds/create-or-update-ref-action@v1
|
||||
with:
|
||||
ref: tags/beta-media
|
||||
sha: ${{ github.sha }}
|
||||
|
||||
- name: Delete beta-media tag
|
||||
run: git tag -d beta-media
|
||||
continue-on-error: true
|
||||
|
||||
- name: changelog
|
||||
id: changelog
|
||||
run: |
|
||||
git tag -l
|
||||
npx changelogithub --output CHANGELOG.md
|
||||
|
||||
- name: Upload assets to beta-media release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: CHANGELOG.md
|
||||
files: CHANGELOG.md
|
||||
prerelease: true
|
||||
tag_name: beta-media
|
||||
|
||||
- name: Upload assets to github artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: beta-media changelog
|
||||
path: ${{ github.workspace }}/CHANGELOG.md
|
||||
compression-level: 0
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
needs:
|
||||
- changelog
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: "!(*musl*|*windows-arm64*|*windows7-*|*android*|*freebsd*)" # xgo and loongarch
|
||||
hash: "md5"
|
||||
- target: "linux-!(arm*)-musl*" #musl-not-arm
|
||||
hash: "md5-linux-musl"
|
||||
- target: "linux-arm*-musl*" #musl-arm
|
||||
hash: "md5-linux-musl-arm"
|
||||
- target: "windows-arm64" #win-arm64
|
||||
hash: "md5-windows-arm64"
|
||||
- target: "windows7-*" #win7
|
||||
hash: "md5-windows7"
|
||||
- target: "android-*" #android
|
||||
hash: "md5-android"
|
||||
- target: "freebsd-*" #freebsd
|
||||
hash: "md5-freebsd"
|
||||
|
||||
name: Beta Media Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25.0"
|
||||
|
||||
- name: Setup web
|
||||
run: |
|
||||
frontendRepo="${FRONTEND_REPO:-OpenListTeam/OpenList-Frontend}"
|
||||
release_json=$(curl -fsSL --max-time 10 \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/$frontendRepo/releases/tags/beta-media")
|
||||
tar_url=$(echo "$release_json" | jq -r '.assets[].browser_download_url' | grep "openlist-frontend-dist" | grep -v "lite" | grep "\.tar\.gz$")
|
||||
echo "Downloading frontend from: $tar_url"
|
||||
curl -fsSL "$tar_url" -o dist.tar.gz
|
||||
rm -rf public/dist && mkdir -p public/dist
|
||||
tar -zxvf dist.tar.gz -C public/dist
|
||||
rm -rf dist.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
FRONTEND_REPO: ${{ vars.FRONTEND_REPO }}
|
||||
|
||||
- name: Build
|
||||
uses: OpenListTeam/cgo-actions@v1.2.2
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
musl-target-format: $os-$musl-$arch
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
out-dir: build
|
||||
output: openlist-$target$ext
|
||||
musl-base-url: "https://github.com/OpenListTeam/musl-compilers/releases/latest/download/"
|
||||
x-flags: |
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.BuiltAt=$built_at
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.GitAuthor=The OpenList Projects Contributors <noreply@openlist.team>
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.GitCommit=$git_commit
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.Version=$tag
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.WebVersion=rolling
|
||||
|
||||
- name: Compress
|
||||
run: |
|
||||
bash build.sh zip ${{ matrix.hash }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets to beta-media release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
prerelease: true
|
||||
tag_name: beta-media
|
||||
|
||||
- name: Clean illegal characters from matrix.target
|
||||
id: clean_target_name
|
||||
run: |
|
||||
ILLEGAL_CHARS_REGEX='[":<>|*?\\/\r\n]'
|
||||
CLEANED_TARGET=$(echo "${{ matrix.target }}" | sed -E "s/$ILLEGAL_CHARS_REGEX//g")
|
||||
echo "Original target: ${{ matrix.target }}"
|
||||
echo "Cleaned target: $CLEANED_TARGET"
|
||||
echo "cleaned_target=$CLEANED_TARGET" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload assets to github artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: beta-media builds for ${{ env.cleaned_target }}
|
||||
path: ${{ github.workspace }}/build/compress/*
|
||||
compression-level: 0
|
||||
if-no-files-found: error
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.26.4"
|
||||
go-version: "1.25.0"
|
||||
|
||||
- name: Setup web
|
||||
run: bash build.sh dev web
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.26.4"
|
||||
go-version: "1.25.0"
|
||||
|
||||
- name: Setup web
|
||||
run: bash build.sh dev web
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
if: github.event_name == 'issues'
|
||||
steps:
|
||||
- name: Check issue for unchecked tasks and reply
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let comment = "";
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Check PR title for required prefix and comment
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const title = context.payload.pull_request.title || "";
|
||||
|
||||
@@ -46,13 +46,12 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.25.0'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
if: matrix.target-platform == ''
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26'
|
||||
go-version: '1.25.0'
|
||||
|
||||
- name: Cache Musl
|
||||
id: cache-musl
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.25.0'
|
||||
|
||||
- name: Cache Musl
|
||||
id: cache-musl
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.25.0'
|
||||
|
||||
- name: Cache Musl
|
||||
id: cache-musl
|
||||
|
||||
@@ -16,33 +16,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Makefile hash update
|
||||
uses: actions/github-script@v9
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
github-token: ${{ secrets.EXTERNAL_REPO_TOKEN_LUCI_APP_OPENLIST }}
|
||||
script: |
|
||||
const repository = process.env.REPOSITORY;
|
||||
const [owner, repo] = repository.split('/');
|
||||
|
||||
await github.rest.repos.createDispatchEvent({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
event_type: 'update-hashes',
|
||||
client_payload: {
|
||||
source_repository: process.env.SOURCE_REPOSITORY,
|
||||
release_tag: process.env.RELEASE_TAG,
|
||||
release_name: process.env.RELEASE_TAG,
|
||||
release_url: process.env.RELEASE_URL,
|
||||
triggered_by: process.env.ACTOR,
|
||||
trigger_reason: process.env.EVENT_NAME
|
||||
}
|
||||
});
|
||||
env:
|
||||
REPOSITORY: ${{ vars.HOOK_REPO || 'OpenListTeam/OpenList-OpenWRT' }}
|
||||
SOURCE_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
|
||||
RELEASE_URL: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ inputs.tag || github.ref_name }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
token: ${{ secrets.EXTERNAL_REPO_TOKEN_LUCI_APP_OPENLIST }}
|
||||
repository: ${{ vars.HOOK_REPO || 'OpenListTeam/OpenList-OpenWRT' }}
|
||||
event-type: update-hashes
|
||||
client-payload: |
|
||||
{
|
||||
"source_repository": "${{ github.repository }}",
|
||||
"release_tag": "${{ inputs.tag || github.ref_name }}",
|
||||
"release_name": "${{ inputs.tag || github.ref_name }}",
|
||||
"release_url": "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ inputs.tag || github.ref_name }}",
|
||||
"triggered_by": "${{ github.actor }}",
|
||||
"trigger_reason": "${{ github.event_name }}"
|
||||
}
|
||||
|
||||
- name: Log trigger information
|
||||
run: |
|
||||
|
||||
@@ -178,6 +178,7 @@ BuildWin7() {
|
||||
}
|
||||
|
||||
BuildDev() {
|
||||
rm -rf .git/
|
||||
mkdir -p "dist"
|
||||
muslflags="$(GetMuslStaticLdflags)"
|
||||
BASE="https://github.com/OpenListTeam/musl-compilers/releases/latest/download/"
|
||||
@@ -268,6 +269,7 @@ BuildDockerMultiplatform() {
|
||||
}
|
||||
|
||||
BuildRelease() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build"
|
||||
BuildWinArm64 ./build/"$appName"-windows-arm64.exe
|
||||
BuildWin7 ./build/"$appName"-windows7
|
||||
@@ -287,7 +289,7 @@ BuildRelease() {
|
||||
BuildLoongGLIBC() {
|
||||
local target_abi="$2"
|
||||
local output_file="$1"
|
||||
local oldWorldGoVersion="1.26.4"
|
||||
local oldWorldGoVersion="1.25.0"
|
||||
local loong_tags
|
||||
loong_tags=$(GetBuildTagsForTarget "linux-loong64")
|
||||
|
||||
@@ -307,13 +309,13 @@ BuildLoongGLIBC() {
|
||||
|
||||
# Download and setup patched Go compiler for old-world
|
||||
if ! curl -fsSL --retry 3 -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
"https://github.com/loong64-abi1-0/golang/releases/download/go${oldWorldGoVersion}/go${oldWorldGoVersion}.linux-amd64.tar.gz" \
|
||||
"https://github.com/loong64/loong64-abi1.0-toolchains/releases/download/20250821/go${oldWorldGoVersion}.linux-amd64.tar.gz" \
|
||||
-o go-loong64-abi1.0.tar.gz; then
|
||||
echo "Error: Failed to download patched Go compiler for old-world ABI1.0"
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
echo "Error output from curl:"
|
||||
curl -fsSL --retry 3 -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
"https://github.com/loong64-abi1-0/golang/releases/download/go${oldWorldGoVersion}/go${oldWorldGoVersion}.linux-amd64.tar.gz" \
|
||||
"https://github.com/loong64/loong64-abi1.0-toolchains/releases/download/20250821/go${oldWorldGoVersion}.linux-amd64.tar.gz" \
|
||||
-o go-loong64-abi1.0.tar.gz || true
|
||||
fi
|
||||
return 1
|
||||
@@ -440,6 +442,7 @@ BuildLoongGLIBC() {
|
||||
}
|
||||
|
||||
BuildReleaseLinuxMusl() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build"
|
||||
muslflags="$(GetMuslStaticLdflags)"
|
||||
BASE="https://github.com/OpenListTeam/musl-compilers/releases/latest/download/"
|
||||
@@ -468,6 +471,7 @@ BuildReleaseLinuxMusl() {
|
||||
}
|
||||
|
||||
BuildReleaseLinuxMuslArm() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build"
|
||||
muslflags="$(GetMuslStaticLdflags)"
|
||||
BASE="https://github.com/OpenListTeam/musl-compilers/releases/latest/download/"
|
||||
@@ -498,6 +502,7 @@ BuildReleaseLinuxMuslArm() {
|
||||
|
||||
|
||||
BuildReleaseAndroid() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build"
|
||||
wget https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
|
||||
unzip android-ndk-r26b-linux.zip
|
||||
@@ -518,6 +523,7 @@ BuildReleaseAndroid() {
|
||||
}
|
||||
|
||||
BuildReleaseFreeBSD() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build/freebsd"
|
||||
|
||||
# Get latest FreeBSD 14.x release version from GitHub
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,167 @@
|
||||
# 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 永久免版税
|
||||
+1
-30
@@ -262,16 +262,6 @@ func (y *Cloud189PC) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.
|
||||
if err = y.WaitBatchTask("MOVE", resp.TaskID, time.Millisecond*400); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 跟随移动 torrent 文件
|
||||
if !srcObj.IsDir() {
|
||||
var srcFolderId string
|
||||
if f, ok := srcObj.(*Cloud189File); ok {
|
||||
srcFolderId = f.ParentID
|
||||
}
|
||||
y.torrentFollowMove(srcFolderId, srcObj.GetName(), dstDir)
|
||||
}
|
||||
|
||||
return srcObj, nil
|
||||
}
|
||||
|
||||
@@ -308,12 +298,6 @@ func (y *Cloud189PC) Rename(ctx context.Context, srcObj model.Obj, newName strin
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 跟随重命名 torrent 文件
|
||||
if f, ok := srcObj.(*Cloud189File); ok {
|
||||
y.torrentFollowRename(f.ParentID, srcObj.GetName(), newName)
|
||||
}
|
||||
|
||||
switch f := srcObj.(type) {
|
||||
case *Cloud189File:
|
||||
return resp.toFile(f), nil
|
||||
@@ -335,20 +319,7 @@ func (y *Cloud189PC) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = y.WaitBatchTask("COPY", resp.TaskID, time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 跟随复制 torrent 文件
|
||||
if !srcObj.IsDir() {
|
||||
var srcFolderId string
|
||||
if f, ok := srcObj.(*Cloud189File); ok {
|
||||
srcFolderId = f.ParentID
|
||||
}
|
||||
y.torrentFollowCopy(srcFolderId, srcObj.GetName(), dstDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
return y.WaitBatchTask("COPY", resp.TaskID, time.Second)
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) Remove(ctx context.Context, obj model.Obj) error {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package _189pc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
@@ -9,13 +8,10 @@ import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/torrent"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
)
|
||||
@@ -166,36 +162,6 @@ func (y *Cloud189PC) RapidUploadFromTorrent(ctx context.Context, dstDir model.Ob
|
||||
return nil, fmt.Errorf("提交上传失败: %w", err)
|
||||
}
|
||||
|
||||
// 秒传成功后,将 torrent 文件上传到目标目录(异步,不影响秒传结果)
|
||||
if y.Addition.GenerateTorrent {
|
||||
capturedDstDir := dstDir
|
||||
capturedIsFamily := isFamily
|
||||
go func() {
|
||||
torrentName := fileName + ".cas.torrent"
|
||||
infoHash, _ := GetInfoHashHex(torrentData)
|
||||
utils.Log.Infof("秒传成功,上传 torrent: %s (info_hash: %s, size: %d bytes)",
|
||||
torrentName, infoHash, len(torrentData))
|
||||
|
||||
torrentFileStream := &stream.FileStream{
|
||||
Ctx: context.Background(),
|
||||
Obj: &model.Object{
|
||||
Name: torrentName,
|
||||
Size: int64(len(torrentData)),
|
||||
IsFolder: false,
|
||||
},
|
||||
Reader: bytes.NewReader(torrentData),
|
||||
Mimetype: "application/x-bittorrent",
|
||||
}
|
||||
_, uploadErr := y.fastUpload(context.Background(), capturedDstDir, torrentFileStream, func(p float64) {}, capturedIsFamily, false, false)
|
||||
if uploadErr != nil {
|
||||
utils.Log.Warnf("上传 torrent 文件失败: %v", uploadErr)
|
||||
} else {
|
||||
utils.Log.Infof("torrent 文件已上传: %s", torrentName)
|
||||
op.Cache.DeleteDirectory(y, capturedDstDir.GetPath())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return resp.toFile(), nil
|
||||
}
|
||||
|
||||
@@ -292,10 +258,6 @@ func GetInfoHashHex(torrentData []byte) (string, error) {
|
||||
return hex.EncodeToString(t.InfoHash), nil
|
||||
}
|
||||
|
||||
func isCASTorrentFile(fileName string) bool {
|
||||
return strings.HasSuffix(fileName, ".cas.torrent")
|
||||
}
|
||||
|
||||
// ComputeSliceMD5sFromReader 从 reader 中计算每个 10MB 分片的 MD5
|
||||
// 返回:整文件 MD5、分片 MD5 列表
|
||||
func ComputeSliceMD5sFromReader(reader io.Reader, sliceSize int64) (string, []string, error) {
|
||||
@@ -327,123 +289,3 @@ func ComputeSliceMD5sFromReader(reader io.Reader, sliceSize int64) (string, []st
|
||||
fileMD5Hex := strings.ToUpper(hex.EncodeToString(fileMD5Hash.Sum(nil)))
|
||||
return fileMD5Hex, sliceMD5s, nil
|
||||
}
|
||||
|
||||
// torrentFollowCopy 跟随复制 torrent 文件(异步,不影响主操作)
|
||||
// srcFolderId: 源文件所在目录 ID
|
||||
// srcFileName: 源文件名
|
||||
// dstDir: 目标目录
|
||||
func (y *Cloud189PC) torrentFollowCopy(srcFolderId string, srcFileName string, dstDir model.Obj) {
|
||||
if !y.Addition.GenerateTorrent {
|
||||
return
|
||||
}
|
||||
if srcFolderId == "" {
|
||||
return
|
||||
}
|
||||
torrentName := srcFileName + ".cas.torrent"
|
||||
isFamily := y.isFamily()
|
||||
|
||||
go func() {
|
||||
torrentFile, err := y.findFileByName(context.Background(), torrentName, srcFolderId, isFamily)
|
||||
if err != nil {
|
||||
utils.Log.Debugf("查找 torrent 文件失败(跟随复制): %v", err)
|
||||
return
|
||||
}
|
||||
// 复制 torrent 文件到目标目录
|
||||
resp, copyErr := y.CreateBatchTask("COPY", IF(isFamily, y.FamilyID, ""), dstDir.GetID(),
|
||||
map[string]string{"targetFileName": dstDir.GetName()},
|
||||
BatchTaskInfo{
|
||||
FileId: torrentFile.GetID(),
|
||||
FileName: torrentFile.GetName(),
|
||||
IsFolder: 0,
|
||||
})
|
||||
if copyErr != nil {
|
||||
utils.Log.Warnf("跟随复制 torrent 文件失败: %v", copyErr)
|
||||
return
|
||||
}
|
||||
if err = y.WaitBatchTask("COPY", resp.TaskID, time.Second); err != nil {
|
||||
utils.Log.Warnf("等待跟随复制 torrent 文件失败: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// torrentFollowMove 跟随移动 torrent 文件(异步,不影响主操作)
|
||||
// srcFolderId: 源文件所在目录 ID
|
||||
// srcFileName: 源文件名
|
||||
// dstDir: 目标目录
|
||||
func (y *Cloud189PC) torrentFollowMove(srcFolderId string, srcFileName string, dstDir model.Obj) {
|
||||
if !y.Addition.GenerateTorrent {
|
||||
return
|
||||
}
|
||||
if srcFolderId == "" {
|
||||
return
|
||||
}
|
||||
torrentName := srcFileName + ".cas.torrent"
|
||||
isFamily := y.isFamily()
|
||||
|
||||
go func() {
|
||||
torrentFile, err := y.findFileByName(context.Background(), torrentName, srcFolderId, isFamily)
|
||||
if err != nil {
|
||||
utils.Log.Debugf("查找 torrent 文件失败(跟随移动): %v", err)
|
||||
return
|
||||
}
|
||||
// 移动 torrent 文件到目标目录
|
||||
resp, moveErr := y.CreateBatchTask("MOVE", IF(isFamily, y.FamilyID, ""), dstDir.GetID(),
|
||||
map[string]string{"targetFileName": dstDir.GetName()},
|
||||
BatchTaskInfo{
|
||||
FileId: torrentFile.GetID(),
|
||||
FileName: torrentFile.GetName(),
|
||||
IsFolder: 0,
|
||||
})
|
||||
if moveErr != nil {
|
||||
utils.Log.Warnf("跟随移动 torrent 文件失败: %v", moveErr)
|
||||
return
|
||||
}
|
||||
if err = y.WaitBatchTask("MOVE", resp.TaskID, time.Millisecond*400); err != nil {
|
||||
utils.Log.Warnf("等待跟随移动 torrent 文件失败: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// torrentFollowRename 跟随重命名 torrent 文件(异步,不影响主操作)
|
||||
// folderId: 文件所在目录 ID
|
||||
// oldFileName: 原文件名
|
||||
// newFileName: 新文件名
|
||||
func (y *Cloud189PC) torrentFollowRename(folderId string, oldFileName string, newFileName string) {
|
||||
if !y.Addition.GenerateTorrent {
|
||||
return
|
||||
}
|
||||
if folderId == "" {
|
||||
return
|
||||
}
|
||||
oldTorrentName := oldFileName + ".cas.torrent"
|
||||
newTorrentName := newFileName + ".cas.torrent"
|
||||
isFamily := y.isFamily()
|
||||
|
||||
go func() {
|
||||
torrentFile, err := y.findFileByName(context.Background(), oldTorrentName, folderId, isFamily)
|
||||
if err != nil {
|
||||
utils.Log.Debugf("查找 torrent 文件失败(跟随重命名): %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 重命名 torrent 文件
|
||||
queryParam := make(map[string]string)
|
||||
fullUrl := API_URL
|
||||
method := "POST"
|
||||
if isFamily {
|
||||
fullUrl += "/family/file"
|
||||
method = "GET"
|
||||
queryParam["familyId"] = y.FamilyID
|
||||
}
|
||||
fullUrl += "/renameFile.action"
|
||||
queryParam["fileId"] = torrentFile.GetID()
|
||||
queryParam["destFileName"] = newTorrentName
|
||||
|
||||
_, renameErr := y.request(fullUrl, method, func(req *resty.Request) {
|
||||
req.SetContext(context.Background()).SetQueryParams(queryParam)
|
||||
}, nil, &RenameResp{}, isFamily)
|
||||
if renameErr != nil {
|
||||
utils.Log.Warnf("跟随重命名 torrent 文件失败: %v", renameErr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -166,11 +166,10 @@ type FamilyInfoResp struct {
|
||||
/*文件部分*/
|
||||
// 文件
|
||||
type Cloud189File struct {
|
||||
ID String `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Md5 string `json:"md5"`
|
||||
ParentID string `json:"-"` // 由 getFiles 设置,不从 JSON 解析
|
||||
ID String `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Md5 string `json:"md5"`
|
||||
|
||||
LastOpTime Time `json:"lastOpTime"`
|
||||
CreateDate Time `json:"createDate"`
|
||||
|
||||
+1
-63
@@ -210,7 +210,6 @@ func (y *Cloud189PC) getFiles(ctx context.Context, fileId string, isFamily bool)
|
||||
res = append(res, &resp.FileListAO.FolderList[i])
|
||||
}
|
||||
for i := 0; i < len(resp.FileListAO.FileList); i++ {
|
||||
resp.FileListAO.FileList[i].ParentID = fileId
|
||||
res = append(res, &resp.FileListAO.FileList[i])
|
||||
}
|
||||
}
|
||||
@@ -911,11 +910,6 @@ func (y *Cloud189PC) RapidUpload(ctx context.Context, dstDir model.Obj, stream m
|
||||
|
||||
// 快传
|
||||
func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
|
||||
generateTorrent := y.Addition.GenerateTorrent && !isCASTorrentFile(file.GetName())
|
||||
return y.fastUpload(ctx, dstDir, file, up, isFamily, overwrite, generateTorrent)
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) fastUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool, generateTorrent bool) (model.Obj, error) {
|
||||
var (
|
||||
cache = file.GetFile()
|
||||
tmpF *os.File
|
||||
@@ -953,9 +947,6 @@ func (y *Cloud189PC) fastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
if tmpF != nil {
|
||||
writers = append(writers, tmpF)
|
||||
}
|
||||
|
||||
pieceSHA1Hashes := make([]byte, 0, count*20)
|
||||
|
||||
written := int64(0)
|
||||
for i := 1; i <= count; i++ {
|
||||
if utils.IsCanceled(ctx) {
|
||||
@@ -966,17 +957,7 @@ func (y *Cloud189PC) fastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
byteSize = lastSliceSize
|
||||
}
|
||||
|
||||
// 如果需要生成 torrent,同时计算 SHA-1
|
||||
var sha1Writer hash.Hash
|
||||
var multiWriter io.Writer
|
||||
if generateTorrent {
|
||||
sha1Writer = sha1Pkg.New()
|
||||
multiWriter = io.MultiWriter(append(writers, sha1Writer)...)
|
||||
} else {
|
||||
multiWriter = io.MultiWriter(writers...)
|
||||
}
|
||||
|
||||
n, err := utils.CopyWithBufferN(multiWriter, file, byteSize)
|
||||
n, err := utils.CopyWithBufferN(io.MultiWriter(writers...), file, byteSize)
|
||||
written += n
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
@@ -985,11 +966,6 @@ func (y *Cloud189PC) fastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
sliceMd5Hexs = append(sliceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
|
||||
partInfos = append(partInfos, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
|
||||
sliceMd5.Reset()
|
||||
|
||||
// 收集 SHA-1 piece hash(仅在本次分片实际写入了数据时追加)
|
||||
if generateTorrent && n > 0 {
|
||||
pieceSHA1Hashes = append(pieceSHA1Hashes, sha1Writer.Sum(nil)...)
|
||||
}
|
||||
}
|
||||
|
||||
if tmpF != nil {
|
||||
@@ -1105,44 +1081,6 @@ func (y *Cloud189PC) fastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 生成 torrent 文件(异步,不影响上传结果)
|
||||
if generateTorrent && size > 0 && len(pieceSHA1Hashes) > 0 {
|
||||
capturedDstDir := dstDir
|
||||
capturedIsFamily := isFamily
|
||||
capturedFileName := file.GetName()
|
||||
go func() {
|
||||
torrentData, err := GenerateTorrent(capturedFileName, size, fileMd5Hex, sliceMd5Hexs, sliceSize, pieceSHA1Hashes)
|
||||
if err != nil {
|
||||
utils.Log.Warnf("生成 torrent 失败: %v", err)
|
||||
return
|
||||
}
|
||||
infoHash, _ := GetInfoHashHex(torrentData)
|
||||
torrentName := capturedFileName + ".cas.torrent"
|
||||
utils.Log.Infof("已生成 torrent: %s (info_hash: %s, size: %d bytes)",
|
||||
torrentName, infoHash, len(torrentData))
|
||||
|
||||
// 将 torrent 文件上传到同一目录
|
||||
torrentFileStream := &stream.FileStream{
|
||||
Ctx: context.Background(),
|
||||
Obj: &model.Object{
|
||||
Name: torrentName,
|
||||
Size: int64(len(torrentData)),
|
||||
IsFolder: false,
|
||||
},
|
||||
Reader: bytes.NewReader(torrentData),
|
||||
Mimetype: "application/x-bittorrent",
|
||||
}
|
||||
_, uploadErr := y.fastUpload(context.Background(), capturedDstDir, torrentFileStream, func(p float64) {}, capturedIsFamily, false, false)
|
||||
if uploadErr != nil {
|
||||
utils.Log.Warnf("上传 torrent 文件失败: %v", uploadErr)
|
||||
} else {
|
||||
utils.Log.Infof("torrent 文件已上传: %s", torrentName)
|
||||
op.Cache.DeleteDirectory(y, capturedDstDir.GetPath())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return resp.toFile(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ func (d *Wps) Init(ctx context.Context) error {
|
||||
if !resp.IsSuccess() {
|
||||
return fmt.Errorf("failed to check login status, status code: %d, body: %s", resp.StatusCode(), resp.String())
|
||||
}
|
||||
if d.login.CompanyID == 0 {
|
||||
return fmt.Errorf("wps company id is empty, please check business account login")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -249,6 +249,79 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.StreamMaxClientUploadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||||
{Key: conf.StreamMaxServerDownloadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||||
{Key: conf.StreamMaxServerUploadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||||
|
||||
// media settings
|
||||
{Key: conf.MediaTMDBKey, Value: "", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaTMDBAPIURL, Value: "api.themoviedb.org", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaDiscogsToken, Value: "", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaDiscogsAPIURL, Value: "api.discogs.com", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaStoreThumbnail, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaThumbnailMode, Value: "base64", Type: conf.TypeSelect, Options: "base64,local", Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaThumbnailPath, Value: "/imgs", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
{Key: conf.MediaScrapeConcurrency, Value: "5", Type: conf.TypeNumber, Group: model.MEDIA, Flag: model.PRIVATE},
|
||||
|
||||
// transcode settings (FFmpeg 云端/本地转码) - 默认全部关闭
|
||||
{Key: conf.TranscodeEnabled, Value: "false", Type: conf.TypeBool, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `开启后,超过阈值的媒体文件将通过 FFmpeg 转码后再播放;默认关闭`},
|
||||
{Key: conf.TranscodeRunMode, Value: "local", Type: conf.TypeSelect, Options: "local,remote,hybrid", Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `local=仅使用本机内置 worker;remote=只使用远程 Worker 节点;hybrid=本地优先,超载后派发到远程`},
|
||||
{Key: conf.TranscodeMinSizeGB, Value: "5", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `文件大于该 GB 数才走转码(小于则直链播放),0=任意大小都转码`},
|
||||
{Key: conf.TranscodeMinBitrateMbps, Value: "20", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `视频码率超过该 Mbps 才转码,0=不限制`},
|
||||
{Key: conf.TranscodeSourceCodecs, Value: "hevc,h265,av1,vvc,vp9", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `仅对这些源视频编码进行转码(逗号分隔)。常见高码率/兼容性差的编码:hevc,av1,vvc,vp9`},
|
||||
{Key: conf.TranscodeSourceExtensions, Value: "mkv,ts,m2ts,mov,avi,wmv,flv,rmvb,webm", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `仅对这些后缀进行转码(逗号分隔,不带点),mp4 默认不转码可直接播放`},
|
||||
{Key: conf.TranscodeOutputFormat, Value: "hls", Type: conf.TypeSelect, Options: "hls,dash,mp4", Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `输出封装格式,HLS 兼容性最好`},
|
||||
{Key: conf.TranscodeOutputCodec, Value: "h264", Type: conf.TypeSelect, Options: "h264,hevc,av1", Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `重新编码后的视频编码,推荐 h264(最广兼容、互联网分发免授权费)`},
|
||||
{Key: conf.TranscodeOutputBitrate, Value: "4000k", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `输出视频码率,例如 4000k / 6M。建议 1080p:4000k、720p:2500k`},
|
||||
{Key: conf.TranscodeOutputAudioCodec, Value: "aac", Type: conf.TypeSelect, Options: "aac,mp3,opus,copy", Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `输出音频编码,aac 兼容性最好;copy=直接复制源音频流`},
|
||||
{Key: conf.TranscodeOutputAudioBitrate, Value: "160k", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `输出音频码率`},
|
||||
{Key: conf.TranscodeOutputResolution, Value: "1920x1080", Type: conf.TypeSelect, Options: "source,3840x2160,2560x1440,1920x1080,1280x720,854x480", Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `输出分辨率上限,超过该分辨率会下采样;source=保持源分辨率`},
|
||||
{Key: conf.TranscodeSegmentDuration, Value: "6", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `HLS/DASH 切片时长(秒),越小首帧越快但请求数变多,推荐 4-10`},
|
||||
{Key: conf.TranscodeHWAccel, Value: "none", Type: conf.TypeSelect,
|
||||
Options: "none,auto,nvenc,qsv,vaapi,amf,videotoolbox",
|
||||
Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `GPU 硬件加速:
|
||||
none = 纯 CPU(libx264)
|
||||
auto = 自动探测可用加速器
|
||||
nvenc = NVIDIA GPU(GeForce/Tesla/Quadro/RTX,需 NVIDIA 驱动)
|
||||
qsv = Intel 集显/独显 QuickSync(免费,功耗低)
|
||||
vaapi = Linux 通用 VA-API(支持 Intel/AMD)
|
||||
amf = AMD GPU(Windows AMF)
|
||||
videotoolbox = macOS 硬件加速`},
|
||||
{Key: conf.TranscodeFFmpegPath, Value: "ffmpeg", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `FFmpeg 可执行路径,留空则使用 PATH 中的 ffmpeg`},
|
||||
{Key: conf.TranscodeFFprobePath, Value: "ffprobe", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `FFprobe 可执行路径`},
|
||||
{Key: conf.TranscodeWorkerSecret, Value: "", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `远程 Worker 注册时使用的共享密钥,留空则禁用远程 Worker 注册`},
|
||||
{Key: conf.TranscodeCachePath, Value: "data/transcode_cache", Type: conf.TypeString, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `转码切片缓存目录`},
|
||||
{Key: conf.TranscodeCacheMaxGB, Value: "20", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `切片缓存最大容量(GB),超过后按 LRU 清理`},
|
||||
{Key: conf.TranscodeJobTimeoutMin, Value: "120", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `单个转码任务超时分钟数,超时自动失败`},
|
||||
{Key: conf.TranscodeLocalConcurrency, Value: "1", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `本机内置 worker 同时执行的转码任务数(local/hybrid 模式生效)`},
|
||||
{Key: conf.TranscodeIdleTimeoutSec, Value: "90", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `播放端无请求多少秒后自动停止转码(默认90秒,0=禁用)`},
|
||||
{Key: conf.TranscodeChunkDurationSec, Value: "60", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `每个 chunk 时长(秒),越大每段缓冲越长但 ffmpeg 进程切换越少(默认60秒)`},
|
||||
{Key: conf.TranscodeMaxChunkConcurrency, Value: "2", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `单个视频任务最多同时运行的 chunk 数(默认2,越大并发越高但 CPU/GPU 占用越多)`},
|
||||
{Key: conf.TranscodeChunkIdleSec, Value: "60", Type: conf.TypeNumber, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `chunk 多久没被访问就 kill ffmpeg 释放资源(默认60秒)`},
|
||||
{Key: conf.TranscodeChunkPrefetch, Value: "true", Type: conf.TypeBool, Group: model.TRANSCODE, Flag: model.PRIVATE,
|
||||
Help: `当前 chunk 转码进度过半时,是否预启动下一个 chunk(默认开启,可让顺序播放无缝衔接)`},
|
||||
}
|
||||
additionalSettingItems := tool.Tools.Items()
|
||||
// 固定顺序
|
||||
|
||||
@@ -20,12 +20,12 @@ import (
|
||||
func InitDB() {
|
||||
logLevel := logger.Silent
|
||||
if flags.Debug || flags.Dev {
|
||||
logLevel = logger.Info
|
||||
logLevel = logger.Warn // Warn 级别:只输出慢查询和错误,不输出每条 SQL
|
||||
}
|
||||
newLogger := logger.New(
|
||||
stdlog.New(log.StandardLogger().Out, "\r\n", stdlog.LstdFlags),
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second,
|
||||
SlowThreshold: 200 * time.Millisecond, // 超过 200ms 才记录慢查询
|
||||
LogLevel: logLevel,
|
||||
IgnoreRecordNotFoundError: true,
|
||||
Colorful: true,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user