mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2026-11-12 13:40:47 +08:00
* feat(media): scan every folder level, save incrementally, TV tags, seed task progress Media library - Merge mode now walks every folder level instead of only the first one. A folder that directly contains media files is one collection and is not split further; a folder without media is descended into. Loose files in the scan root stay individual items. Depth is capped at 16. - Scanning no longer buffers the whole tree before writing: items are flushed to the database every 20 items or 3 seconds, for all four media types, so results appear while the scan is still running. Skipped and failed items still advance the progress counter. - The scan path type tag now decides movie vs. series. Tags such as TV series / anime / variety / documentary mark every video under the path as "tv" (shown as series in Emby, also for single files); movie tags force "movie" even when merge mode is on. Unrecognised tags keep the old rule (folder items are series). Emby virtual folders use the same rule. - Fix: a music folder containing a non-audio file was dropped entirely. Playback URL - When no site_url is configured and the request host is a loopback address (127.0.0.1 / localhost / ::1), stream_url now returns relative /d and /p paths instead of absolute loopback URLs. An explicit site_url or a forwarded host still produces absolute URLs. Seed tasks - Seed generation reports progress by bytes hashed plus a stage message and is exposed at /task/seed_generate. - Seed save, rapid upload and transit transfer register a display-only task at /task/seed_save (the operation stays synchronous) so progress is visible in the task list. Tests cover tag classification, index-length checks, level-by-level merge scanning (stop at collection, list errors, depth limit) and loopback detection. Co-Authored-By: Claude <noreply@anthropic.com> * fix(server): keep site_url path prefix in playback base url site_url 允许配置成 /openlist 这样的路径前缀;原先只有以 http 开头才走绝对地址,前缀模式下播放地址会丢掉前缀导致 404。改为配置了 site_url 就统一用 GetApiUrlFromRequest 生成。 * feat(media): season-aware episodes, album admin api, cover upload, zip export - 扫描:季目录(第N季/Season N/S01)解析季号写入 EpisodeInfo.season,Emby 与播放器可按季分组;显式电影标签不再被季目录反推成剧集;merge_depth 语义固定为「第 N 层=一条记录」,与模型注释、UI 一致 - 刮削:音乐条目的文件自带封面(data:/thumb:/站内地址)不再被在线封面覆盖 - 后台新增:封面 URL/上传(/admin/media/items/cover)、专辑聚合列表与批量编辑(/admin/media/albums、/album/items、/album/update)、条目可编辑字段扩充(选集/视频类型/季集/时长/外部ID) - 导入导出升级为 zip(media_export.json + covers/),导入兼容旧 JSON - Emby:多季合集按每集 season 拆分 Season,集 ID 带季号避免跨季重号 - fix(seed):中转转存部分文件失败时任务按失败收尾,不再一律标记成功 * fix(media): patch semantics for item update, transactional album update, concurrent cover export - UpdateMediaItemAdmin 改为 PATCH 语义(字段全部指针,只写出现过的键),修复只传 hidden 也会清空 cover/plot 的问题;cover 增加 12MB 上限 - 专辑批量更新走单事务(新增 db.UpdateMediaItems),失败整批回滚;旧封面文件在事务成功后才删除 - 导出封面改为并发读取(4 并发,单张 8MB / 总量 512MB / 5000 张上限),避免大库导出逐张串行 * fix(media,seed): unify episode ids, season fallback, cancellable seed tasks, periodic flush - Emby 集 ID 统一走 parseEpisodeID(新格式 ep-item-season-num,兼容旧两段),修掉封面/刷新/续播列表里仍假设旧格式的三处;续播能唯一定位时补季号 - 季号兜底 seasonFromText 与前端 episodeSeasonOf 规则对齐,老数据不会在 Emby 和网页端分到不同季 - 种子任务真正可取消:任务 ctx 重新绑定(tache Add 会覆盖)、保存 IO 改用任务 ctx、逐文件与哈希读取检查取消、取消后状态为 Canceled - mediaSaver 增加定时落盘协程,Close 时停表并收尾 Flush * feat(media): artist aggregation api and image exif lookup - 新增 GET /fs/media/artists(db.ListArtists):按 album_artist 聚合专辑数/曲目数/封面,供音乐库「按艺术家」分类 - /fs/media/albums 支持 album_artist 过滤,艺术家下钻时只取该艺术家的专辑 - 新增 GET /fs/media/exif:按需读取图片文件头解析 EXIF(设备/镜头/曝光/焦距/时间/GPS)并缓存,解析失败返回 available=false 而不是 500 * fix(emby): return full UserDto from /Users/Public (issue #3301) 严格类型的客户端会把 user['Policy'] / user['Configuration'] 直接强转成 Map, 之前 /Users/Public 只返回 5 个标量字段,转 null 时抛异常并中断渲染(封面全挂)。 - 删除 embyPublicUserDto,/Users/Public 直接复用 embyUserDtoOf,与登录接口结构一致 - 新增 embyUserConfiguration(),按官方 UserConfiguration 给全默认值(空 map 同样是缺键) - 补 TestEmbyUserDtoHasPolicyAndConfiguration 回归测试 * feat(media): pure-go pdf cover rendering, keep existing cover by default, same-dir thumbnails - PDF 封面改用纯 Go 光栅化(github.com/go-pdfkit/render,BSD-3、无 cgo):渲染第 1 页再缩到 300px,矢量/扫描件/Flate 压缩图都能出图;失败时仍退回原来的内嵌 JPEG 扫描。渲染带 MaxPixels/MaxDuration 上限,防止恶意或超大页面拖住扫描 - 新增设置 media_overwrite_cover(默认关闭):刮削不再覆盖条目已有封面,只负责补空;需要替换时手动打开。原先只对音乐做了保护,现在统一到所有媒体类型 - 缩略图存储新增 samedir 模式:封面写到媒体文件同目录,文件名为「原文件名.图片后缀」(song.flac.jpg),不覆盖原文件,也不需要配置 VFS 目录 - 依赖代价:go 指令 1.26.0 -> 1.26.4,新增 render 及其纯 Go 依赖(go-gfx/opentype 等),go.sum 由 tidy 收敛 * feat(media): add skip-file-metadata switch (default off) - 新设置 media_skip_file_metadata(默认关闭):开启后扫描与刮削都不读取媒体文件内容 - 音乐:扫描与刮削均跳过 ID3/Vorbis 解析,退回目录名/文件名,内嵌封面与内嵌歌词一并跳过 - 书籍:刮削时跳过 EPUB/PDF 内嵌封面提取 - 图片:刮削时跳过 EXIF(拍摄时间/地点/设备),缩略图仍生成 - 书籍封面依旧只在刮削阶段提取,扫描阶段完全不读文件 * feat(seed): support folder expansion, split mode and structure-preserving paths - 右键目录也能生成种子:后端递归展开目录下所有文件(深度/数量上限,逐目录校验权限) - 新增 preserve_structure(默认关闭):开启后种子内保留相对目录结构,关闭时拍平成文件名,重名自动加序号 - 新增 split(默认关闭):每个文件生成一个种子;关闭时全部合并(默认) - 默认命名:单文件/单目录用其名字;多选合并固定为 torrent - 能力探测接口同步支持目录,前端按展开后的文件列表展示哈希/流量 - 单测覆盖命名规则、保留/拍平路径与重名处理 * feat(seed): compute missing hashes from source files when converting - ConvertSeed 新增 compute_missing / file_base_path:缺哈希时按种子内相对路径读取源目录文件补算,而不是直接返回不可转换 - torrent 目标按整包拼接计算 SHA-1 pieces(解决 piece boundary crosses the next file),CAS 目标按 10 MiB 分片补 MD5 - 补算后仍不满足时返回 400 并带具体原因,避免前端误判成功 * feat(seed): persist overall comment when converting - /fs/seed/convert 新增 comment 字段(指针,允许清空) - 转换与保存注释合并为一次请求,前端不必先保存再转换 * feat(seed): convert to multiple target formats in one request - /fs/seed/convert 新增 formats 数组(去重、bt 别名、最多 3 个),逐个编码并写入目标目录,返回 artifacts - 每个目标独立解码,避免 CAS 补算修改 PieceSize 后污染 torrent pieces - 预检仍保留「不可转换 + 原因」的响应;单目标响应字段保持兼容 - 新增 TestSeedConvertTargets * fix(seed): identity conversion is always feasible and folder roots no longer nest twice - seedConversionStates 现在接收源格式:目标等于源格式时直接可行,不再用「按文件 pieces」去诊断整包结构。合法 torrent(pieces 跨文件)之前被误报 piece boundary crosses the next file,导致预览页对当前格式打叉\n- ConvertSeed 对同一格式直接复制原始字节,跳过诊断与重新编码\n- 保留目录结构时,只选一个目录以该目录为根,修复「剧/剧/S01/...」重复嵌套\n- 新增回归测试:身份转换可行、跨格式仍诊断、单目录/多目录保留结构路径 * fix(seed): keep the torrent's global pieces so it can be re-encoded - Seed 新增运行时字段 GlobalPieces:解析 .torrent 时保留整包 SHA-1 pieces(含 x-openlist 种子),重新编码直接复用,不再按文件拼接\n- DiagnoseConversion 在存在整包 pieces 时跳过「piece 跨文件」检查;否则合法 torrent 会被判成不可转换(Cannot convert to torrent: piece boundary crosses the next file)\n- TorrentFromSeed 优先使用整包 pieces,并对无效哈希返回诊断而不是静默拼接\n- 移除文件 / 重算哈希后清空 GlobalPieces,避免用对不上的旧 pieces 编码\n- 新增往返测试:跨文件 pieces 编码→解析→再编码字节一致;逐文件 pieces 仍拒绝跨文件拼接 * fix(seed): expose seed-level hash availability for torrents BT 的 pieces 是整包的,跨文件时无法拆到单个文件(ValidateSeed 要求每文件片数 = ceil(size/piece)),此前这些文件拿不到 pieces,前端按文件判断就全显示 needs file;现在 SeedFromTorrent 汇总 available_hashes / available_piece_hashes,前端优先按种子级标记显示,并补生成到解析的往返测试 * fix(seed): accept single-file torrent name differing from x-openlist 单文件 torrent 的 info.name 是文件名(带扩展名),x-openlist 的 seed.name 可以不带扩展名;一致性校验此前直接比较两者,判定种子损坏,导致解析 400、前端兜底本地解析(无 conversions/无哈希,全部显示 needs file)。现在单文件允许 info.name == base(seed.Files[0].Path),并补回归测试 * fix(media-seed): address audit findings and review feedback - Enforce seed source visibility, bounded hashing, and stream cleanup. - Apply conversion selections and hash matrices, preserve comments and global pieces, and restore legacy episode IDs. - Correct task cancellation and retries, scan persistence, and cover protection. - Bound and validate media ZIP import/export, authorize public aggregates, and handle cover edit patches. - Add regression tests and extend the CI test scope. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: pikachuren <pikachuren@example.com>
392 lines
32 KiB
Go
392 lines
32 KiB
Go
package data
|
||
|
||
import (
|
||
"fmt"
|
||
"sort"
|
||
"strconv"
|
||
|
||
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||
"github.com/OpenListTeam/OpenList/v4/internal/db"
|
||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||
"github.com/OpenListTeam/OpenList/v4/internal/offline_download/tool"
|
||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||
"github.com/OpenListTeam/OpenList/v4/pkg/utils/random"
|
||
"github.com/pkg/errors"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
func initSettings() {
|
||
initialSettingItems := InitialSettings()
|
||
isActive := func(key string) bool {
|
||
for _, item := range initialSettingItems {
|
||
if item.Key == key {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
// check deprecated
|
||
settings, err := op.GetSettingItems()
|
||
if err != nil {
|
||
utils.Log.Fatalf("failed get settings: %+v", err)
|
||
}
|
||
settingMap := map[string]*model.SettingItem{}
|
||
for _, v := range settings {
|
||
if v.Key == "" {
|
||
err := db.DeleteSettingItemByKey(v.Key)
|
||
if err != nil {
|
||
utils.Log.Errorf("failed delete setting with empty key: %+v", err)
|
||
}
|
||
continue
|
||
}
|
||
if !isActive(v.Key) && v.Flag != model.DEPRECATED {
|
||
v.Flag = model.DEPRECATED
|
||
err = op.SaveSettingItem(&v)
|
||
if err != nil {
|
||
utils.Log.Fatalf("failed save setting: %+v", err)
|
||
}
|
||
}
|
||
settingMap[v.Key] = &v
|
||
}
|
||
op.MigrationSettingItems = map[string]op.MigrationValueItem{}
|
||
// create or save setting
|
||
var saveItems []model.SettingItem
|
||
for i := range initialSettingItems {
|
||
item := &initialSettingItems[i]
|
||
item.Index = uint(i)
|
||
migrationValue := item.MigrationValue
|
||
if len(migrationValue) > 0 {
|
||
op.MigrationSettingItems[item.Key] = op.MigrationValueItem{MigrationValue: item.MigrationValue, Value: item.Value}
|
||
item.MigrationValue = ""
|
||
}
|
||
// err
|
||
stored, ok := settingMap[item.Key]
|
||
if !ok {
|
||
stored, err = op.GetSettingItemByKey(item.Key)
|
||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
utils.Log.Fatalf("failed get setting: %+v", err)
|
||
continue
|
||
}
|
||
}
|
||
if item.Key != conf.VERSION && stored != nil &&
|
||
(len(migrationValue) == 0 || stored.Value != migrationValue) {
|
||
item.Value = stored.Value
|
||
}
|
||
_, err = op.HandleSettingItemHook(item)
|
||
if err != nil {
|
||
utils.Log.Errorf("failed to execute hook on %s: %+v", item.Key, err)
|
||
continue
|
||
}
|
||
if stored == nil || *item != *stored {
|
||
saveItems = append(saveItems, *item)
|
||
}
|
||
}
|
||
if len(saveItems) > 0 {
|
||
err = db.SaveSettingItems(saveItems)
|
||
if err != nil {
|
||
utils.Log.Fatalf("failed save setting: %+v", err)
|
||
} else {
|
||
op.SettingCacheUpdate()
|
||
}
|
||
}
|
||
}
|
||
|
||
func InitialSettings() []model.SettingItem {
|
||
var token string
|
||
if flags.Dev {
|
||
token = "dev_token"
|
||
} else {
|
||
token = random.Token()
|
||
}
|
||
siteVersion := fmt.Sprintf("%s (Commit: %s) - Frontend: %s - Build at: %s", conf.Version, conf.GitCommit, conf.WebVersion, conf.BuiltAt)
|
||
initialSettingItems := []model.SettingItem{
|
||
// site settings
|
||
{Key: conf.VERSION, Value: siteVersion, Type: conf.TypeString, Group: model.SITE, Flag: model.READONLY},
|
||
//{Key: conf.ApiUrl, Value: "", Type: conf.TypeString, Group: model.SITE},
|
||
//{Key: conf.BasePath, Value: "", Type: conf.TypeString, Group: model.SITE},
|
||
{Key: conf.SiteTitle, Value: "OpenList", Type: conf.TypeString, Group: model.SITE},
|
||
{Key: conf.Announcement, Value: "Welcome to the OpenList project!\nFor the latest updates, to contribute code, or to submit suggestions and issues, please visit our [project repository](https://github.com/OpenListTeam/OpenList).", Type: conf.TypeText, Group: model.SITE},
|
||
{Key: "pagination_type", Value: "all", Type: conf.TypeSelect, Options: "all,pagination,load_more,auto_load_more", Group: model.SITE},
|
||
{Key: "default_page_size", Value: "30", Type: conf.TypeNumber, Group: model.SITE},
|
||
{Key: conf.AllowIndexed, Value: "false", Type: conf.TypeBool, Group: model.SITE},
|
||
{Key: conf.AllowMounted, Value: "true", Type: conf.TypeBool, Group: model.SITE},
|
||
{Key: conf.RobotsTxt, Value: "User-agent: *\nAllow: /", Type: conf.TypeText, Group: model.SITE},
|
||
// style settings
|
||
{Key: conf.Logo, Value: "https://res.oplist.org/logo/logo.svg", MigrationValue: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeText, Group: model.STYLE},
|
||
{Key: conf.Favicon, Value: "https://res.oplist.org/logo/logo.svg", MigrationValue: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
||
{Key: conf.MainColor, Value: "#1890ff", Type: conf.TypeString, Group: model.STYLE},
|
||
{Key: "home_icon", Value: "🏠", Type: conf.TypeString, Group: model.STYLE},
|
||
{Key: "share_icon", Value: "🎁", Type: conf.TypeString, Group: model.STYLE},
|
||
{Key: "home_container", Value: "max_980px", Type: conf.TypeSelect, Options: "max_980px,hope_container", Group: model.STYLE},
|
||
{Key: "settings_layout", Value: "list", Type: conf.TypeSelect, Options: "list,responsive", Group: model.STYLE},
|
||
{Key: conf.HideStorageDetails, Value: "true", Type: conf.TypeBool, Group: model.STYLE, Flag: model.PRIVATE},
|
||
{Key: conf.HideStorageDetailsInManagePage, Value: "true", Type: conf.TypeBool, Group: model.STYLE, Flag: model.PRIVATE},
|
||
{Key: "show_disk_usage_in_plain_text", Value: "false", Type: conf.TypeBool, Group: model.STYLE, Flag: model.PUBLIC},
|
||
// preview settings
|
||
{Key: conf.TextTypes, Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp,tsx,vtt,srt,ass,rs,lrc,strm", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
{Key: conf.AudioTypes, Value: "mp3,flac,ogg,m4a,wav,opus,wma", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
{Key: conf.VideoTypes, Value: "mp4,mkv,avi,mov,rmvb,webm,flv,m3u8", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
{Key: conf.ImageTypes, Value: "jpg,tiff,jpeg,png,gif,bmp,svg,ico,swf,webp,avif", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
//{Key: conf.OfficeTypes, Value: "doc,docx,xls,xlsx,ppt,pptx", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
{Key: conf.ProxyTypes, Value: "m3u8,url", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
{Key: conf.ProxyIgnoreHeaders, Value: "authorization,referer", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE},
|
||
{Key: "external_previews", Value: `{}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||
{Key: "iframe_previews", Value: `{
|
||
"doc,docx,xls,xlsx,ppt,pptx": {
|
||
"Microsoft":"https://view.officeapps.live.com/op/view.aspx?src=$e_url",
|
||
"Google":"https://docs.google.com/gview?url=$e_url&embedded=true"
|
||
},
|
||
"pdf": {
|
||
"PDF.js":"https://res.oplist.org/pdf.js/web/viewer.html?file=$e_url"
|
||
},
|
||
"epub": {
|
||
"EPUB.js":"https://res.oplist.org/epub.js/viewer.html?url=$e_url"
|
||
}
|
||
}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||
// {Key: conf.OfficeViewers, Value: `{
|
||
// "Microsoft":"https://view.officeapps.live.com/op/view.aspx?src=$url",
|
||
// "Google":"https://docs.google.com/gview?url=$url&embedded=true",
|
||
//}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||
// {Key: conf.PdfViewers, Value: `{
|
||
// "pdf.js":"https://openlistteam.github.io/pdf.js/web/viewer.html?file=$url"
|
||
//}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||
{Key: "audio_cover", Value: "https://res.oplist.org/logo/logo.svg", MigrationValue: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeString, Group: model.PREVIEW},
|
||
{Key: conf.AudioAutoplay, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.VideoAutoplay, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.PreviewDownloadByDefault, Value: "false", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.PreviewArchivesByDefault, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.SharePreviewDownloadByDefault, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.SharePreviewArchivesByDefault, Value: "false", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.ReadMeAutoRender, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||
{Key: conf.FilterReadMeScripts, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW}, // frontend
|
||
{Key: conf.NonEFSZipEncoding, Value: "IBM437", Type: conf.TypeString, Group: model.PREVIEW},
|
||
// global settings
|
||
{Key: conf.HideFiles, Value: "/\\/README.md/i", Type: conf.TypeText, Group: model.GLOBAL},
|
||
{Key: "package_download", Value: "true", Type: conf.TypeBool, Group: model.GLOBAL},
|
||
{Key: conf.CustomizeHead, MigrationValue: `<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.SignAll, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{
|
||
Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
||
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
||
(?U)access_token=(.*)&`,
|
||
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE,
|
||
},
|
||
{Key: conf.OcrApi, Value: "https://openlistteam-ocr-api-server.hf.space/ocr/file/json", MigrationValue: "https://api.example.com/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL}, // TODO: This can be replace by a community-hosted endpoint, see https://github.com/OpenListTeam/ocr_api_server
|
||
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
||
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,openlist_ts,raw", Type: conf.TypeString, Group: model.GLOBAL},
|
||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||
{Key: conf.SharePreview, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||
{Key: conf.ShareArchivePreview, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||
{Key: conf.ShareForceProxy, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.ShareSummaryContent, Value: "@{{creator}} shared {{#each files}}{{#if @first}}\"{{filename this}}\"{{/if}}{{#if @last}}{{#unless (eq @index 0)}} and {{@index}} more files{{/unless}}{{/if}}{{/each}} from {{site_title}}: {{base_url}}/@s/{{id}}{{#if pwd}} , the share code is {{pwd}}{{/if}}{{#if expires}}, please access before {{dateLocaleString expires}}.{{/if}}", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||
{Key: conf.HandleHookAfterWriting, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.HandleHookRateLimit, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.IgnoreSystemFiles, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE, Help: `When enabled, ignores common system files during upload (.DS_Store, desktop.ini, Thumbs.db, and files starting with ._)`},
|
||
{Key: conf.SeedSiteURL, Value: "", Type: conf.TypeString, Group: model.GLOBAL, Flag: model.PRIVATE, Help: `Public base URL embedded in generated transfer seed sources when configured`},
|
||
{Key: conf.SeedDefaultMatrix, Value: `{"md5":{"whole":true,"pieces":false},"sha1":{"whole":true,"pieces":false},"sha256":{"whole":true,"pieces":false}}`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE, Help: `Default right-click hash matrix for transfer seed generation`},
|
||
{Key: conf.SeedFormatPolicies, Value: `{"oss":"off","torrent":"off","cas":"off"}`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.SeedDefaultFormat, Value: "oss", Type: conf.TypeSelect, Options: "oss,torrent,cas", Group: model.GLOBAL, Flag: model.PRIVATE},
|
||
{Key: conf.SeedSingleDirectPreview, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||
{Key: conf.SeedCASDirectAccess, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC, Help: `When opening a single-file CAS seed, immediately rapid-upload it into the same folder and preview the restored file`},
|
||
{Key: conf.SeedAutoGeneratePolicy, Value: "off", Type: conf.TypeSelect, Options: "off,on", Group: model.GLOBAL, Flag: model.PRIVATE, Help: `Global upload sidecar policy; storage-specific inheritance can be layered without changing the safe default`},
|
||
{Key: conf.SeedDefaultTrackers, Value: "", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE, Help: `Default tracker list offered when generating torrent seeds (one tracker per line)`},
|
||
|
||
// single settings
|
||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||
{Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,database_non_full_text,bleve,meilisearch,none", Group: model.INDEX},
|
||
{Key: conf.AutoUpdateIndex, Value: "false", Type: conf.TypeBool, Group: model.INDEX},
|
||
{Key: conf.IgnorePaths, Value: "", Type: conf.TypeText, Group: model.INDEX, Flag: model.PRIVATE, Help: `one path per line`},
|
||
{Key: conf.MaxIndexDepth, Value: "20", Type: conf.TypeNumber, Group: model.INDEX, Flag: model.PRIVATE, Help: `max depth of index`},
|
||
{Key: conf.IndexProgress, Value: "{}", Type: conf.TypeText, Group: model.SINGLE, Flag: model.PRIVATE},
|
||
|
||
// SSO settings
|
||
{Key: conf.SSOLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
||
{Key: conf.SSOLoginPlatform, Type: conf.TypeSelect, Options: "Casdoor,Github,Microsoft,Google,Dingtalk,OIDC", Group: model.SSO, Flag: model.PUBLIC},
|
||
{Key: conf.SSOClientId, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOClientSecret, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOOIDCUsernameKey, Value: "name", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOOrganizationName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOApplicationName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOEndpointName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOJwtPublicKey, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOExtraScopes, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOAutoRegister, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSODefaultDir, Value: "/", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSODefaultPermission, Value: "0", Type: conf.TypeNumber, Group: model.SSO, Flag: model.PRIVATE},
|
||
{Key: conf.SSOCompatibilityMode, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
||
{Key: conf.SSOPostMessageOrigin, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PUBLIC},
|
||
|
||
// ldap settings
|
||
{Key: conf.LdapLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.LDAP, Flag: model.PUBLIC},
|
||
{Key: conf.LdapServer, Value: "", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapSkipTlsVerify, Value: "false", Type: conf.TypeBool, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapManagerDN, Value: "", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapManagerPassword, Value: "", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapUserSearchBase, Value: "", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapUserSearchFilter, Value: "(uid=%s)", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapDefaultDir, Value: "/", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapDefaultPermission, Value: "0", Type: conf.TypeNumber, Group: model.LDAP, Flag: model.PRIVATE},
|
||
{Key: conf.LdapLoginTips, Value: "login with ldap", Type: conf.TypeString, Group: model.LDAP, Flag: model.PUBLIC},
|
||
|
||
// s3 settings
|
||
{Key: conf.S3AccessKeyId, Value: "", Type: conf.TypeString, Group: model.S3, Flag: model.PRIVATE},
|
||
{Key: conf.S3SecretAccessKey, Value: "", Type: conf.TypeString, Group: model.S3, Flag: model.PRIVATE},
|
||
{Key: conf.S3Buckets, Value: "[]", Type: conf.TypeString, Group: model.S3, Flag: model.PRIVATE},
|
||
|
||
// ftp settings
|
||
{Key: conf.FTPPublicHost, Value: "127.0.0.1", Type: conf.TypeString, Group: model.FTP, Flag: model.PRIVATE},
|
||
{Key: conf.FTPPasvPortMap, Value: "", Type: conf.TypeText, Group: model.FTP, Flag: model.PRIVATE},
|
||
{Key: conf.FTPMandatoryTLS, Value: "false", Type: conf.TypeBool, Group: model.FTP, Flag: model.PRIVATE},
|
||
{Key: conf.FTPImplicitTLS, Value: "false", Type: conf.TypeBool, Group: model.FTP, Flag: model.PRIVATE},
|
||
{Key: conf.FTPTLSPrivateKeyPath, Value: "", Type: conf.TypeString, Group: model.FTP, Flag: model.PRIVATE},
|
||
{Key: conf.FTPTLSPublicCertPath, Value: "", Type: conf.TypeString, Group: model.FTP, Flag: model.PRIVATE},
|
||
{Key: conf.SFTPDisablePasswordLogin, Value: "false", Type: conf.TypeBool, Group: model.FTP, Flag: model.PRIVATE},
|
||
|
||
// traffic settings
|
||
{Key: conf.TaskOfflineDownloadThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.Download.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.TaskOfflineDownloadTransferThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.Transfer.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.TaskUploadThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.Upload.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.TaskCopyThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.Copy.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.TaskMoveThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.Move.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.TaskDecompressDownloadThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.Decompress.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.TaskDecompressUploadThreadsNum, Value: strconv.Itoa(conf.Conf.Tasks.DecompressUpload.Workers), Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{Key: conf.StreamMaxClientDownloadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||
{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: "runtime", Type: conf.TypeSelect, Options: "base64,runtime,local,samedir", Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `缩略图存储方式:base64 存入数据库;runtime 存入程序数据目录(data/imgs);local 写入挂载目录下的 VFS 路径;samedir 保存在媒体文件同目录(<原文件名>.jpg/png)`},
|
||
{Key: conf.MediaThumbnailPath, Value: "/imgs", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE},
|
||
{Key: conf.MediaOverwriteCover, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `刮削时是否用刮削到的封面覆盖条目已有封面。默认关闭:文件自带/手动设置的封面优先,刮削只补空`},
|
||
{Key: conf.MediaSkipFileMetadata, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `开启后扫描/刮削跳过音乐 ID3/Vorbis 标签、图片 EXIF、EPUB/PDF 书籍内嵌封面读取,使用文件名与目录名推断元数据。图片缩略图生成与同目录外部 .lrc 歌词仍可读取文件内容,不等于禁止所有文件读取。默认关闭`},
|
||
{Key: conf.MediaScrapeConcurrency, Value: "5", Type: conf.TypeNumber, Group: model.MEDIA, Flag: model.PRIVATE},
|
||
{Key: conf.MediaScrapeLanguage, Value: "zh-CN", Type: conf.TypeSelect, Options: "zh-CN,zh-TW,en-US,ja-JP,ko-KR,fr-FR,de-DE,es-ES,ru-RU,pt-BR", Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `首选刮削语言:影响 TMDB 的标题/简介/海报文字与 Google Books 的检索语言;豆瓣固定为中文`},
|
||
{Key: conf.MediaBookSource, Value: "douban", Type: conf.TypeSelect, Options: "douban,google,openlibrary", Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `书籍刮削数据源:douban(豆瓣)、google(Google Books)、openlibrary(OpenLibrary)`},
|
||
{Key: conf.MediaGoogleBooksKey, Value: "", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `Google Books API Key,可留空(无 Key 时配额较低);申请地址 https://developers.google.com/books/docs/v1/using#APIKey`},
|
||
{Key: conf.MediaDownloadCover, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `开启后把 TMDB / Google Books / OpenLibrary 等返回的封面链接下载到下方缩略图存储路径,而不是直接保存图片链接`},
|
||
{Key: conf.MediaEnabled, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PUBLIC,
|
||
Help: `媒体库总开关。关闭时媒体库相关页面、API 与协议端点全部不可用(返回 404),与未安装媒体库时表现一致`},
|
||
{Key: conf.MediaEmbyEnabled, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `开启后提供 Emby 兼容 API(/@emby),可供 Infuse、VidHub、MoviePilot 等客户端直接接入`},
|
||
{Key: conf.MediaSubsonicEnabled, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `开启后提供 OpenSubsonic API(/@rest),可供 Symfonium、Feishin、Amperfy 等音乐客户端接入`},
|
||
{Key: conf.MediaSubsonicAllowPwd, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `允许 Subsonic 客户端用 OpenList 登录密码(u+p 参数)认证。该方式会把主账号密码暴露给第三方客户端,默认关闭,推荐使用应用密钥(apiKey 或 t+s)`},
|
||
{Key: conf.MediaSubsonicProxyStream, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `Subsonic 播放默认返回 302 直链;若客户端不跟随重定向,可开启此项改为服务端代理转发(占用服务器带宽)`},
|
||
{Key: conf.MediaProxyCover, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `开启后由服务端代理 TMDB/豆瓣等外部封面图片,适合直连图片域名受限的网络;默认关闭,直接使用原始图片地址`},
|
||
{Key: conf.MediaCoverGateway, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `封面代理直连失败时,回退到第三方图片网关 wsrv.nl 重试。开启后封面地址会发送给该第三方服务;默认关闭`},
|
||
{Key: conf.MediaStrmOnScan, Value: "false", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `扫描完成后自动把媒体条目生成为 .strm 文件并写入下方目录`},
|
||
{Key: conf.MediaStrmOverwrite, Value: "true", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `生成 STRM 时覆盖已存在的同名文件`},
|
||
{Key: conf.MediaStrmSameDir, Value: "true", Type: conf.TypeBool, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `开启时 .strm 与媒体文件放在同一目录且同名(仅扩展名不同);关闭时统一输出到下方指定目录`},
|
||
{Key: conf.MediaStrmPath, Value: "/strm", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `STRM 输出目录(VFS 路径,需位于可写存储中),仅在关闭「与媒体文件同目录」时使用`},
|
||
{Key: conf.MediaStrmTemplate, Value: "{site_url}/d{path}?sign={sign}", Type: conf.TypeString, Group: model.MEDIA, Flag: model.PRIVATE,
|
||
Help: `STRM 内容模板,可用变量:{site_url} 站点地址、{path} 文件虚拟路径、{sign} 签名、{name} 文件名`},
|
||
|
||
// 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/ctmp", 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(默认开启,可让顺序播放无缝衔接)`},
|
||
|
||
// multipart upload settings (from main)
|
||
{Key: conf.MultipartEnabled, Value: "true", Type: conf.TypeBool, Group: model.TRAFFIC, Flag: model.PUBLIC},
|
||
{Key: conf.MultipartChunkSize, Value: "10", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PUBLIC, Help: `chunk size of multipart upload in MB (positive integer), keep it under your CDN's request body limit; each active session buffers up to 8 chunks on the server's disk`},
|
||
}
|
||
additionalSettingItems := tool.Tools.Items()
|
||
// 固定顺序
|
||
sort.Slice(additionalSettingItems, func(i, j int) bool {
|
||
return additionalSettingItems[i].Key < additionalSettingItems[j].Key
|
||
})
|
||
initialSettingItems = append(initialSettingItems, additionalSettingItems...)
|
||
if flags.Dev {
|
||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||
{Key: "test_deprecated", Value: "test_value", Type: conf.TypeString, Flag: model.DEPRECATED},
|
||
{Key: "test_options", Value: "a", Type: conf.TypeSelect, Options: "a,b,c"},
|
||
{Key: "test_help", Type: conf.TypeString, Help: "this is a help message"},
|
||
}...)
|
||
}
|
||
return initialSettingItems
|
||
}
|