转码的源文件不再限死在录播/成片目录,可以填任意路径
为什么
转码页的多选框只列 /api/files 的内容,也就是录播目录和成片目录两处;后端的
resolveTranscodeInput 走 resolveFile,也只认 record/merge 两个目录名。结果是
录播原片被合并且清掉之后(或者录播目录本来就空),转码页里只剩合并结果可挑,
磁盘上别处的视频一个都进不来。
怎么改
- transcodeJobReq 加 Path 字段,与 dir/name 二选一;新增 resolveTranscodeJob()
把「填了路径就按路径找(认绝对路径和从资源管理器复制来的带引号路径,走
resolveMergeInput)、否则回落 dir+name」收在一处,两条路最后统一过
mergeVideoExts 白名单 —— 路径这条能指向磁盘上任何文件,不挡的话手滑把 .jsonl
丢进来要等 ffmpeg 跑起来才报错。三个调用点和 runTranscode 全部改走它。
- transcodeProbe 回显绝对路径,页面拿它当任务身份:同名文件在不同目录里不会再
被认成一个,重复加入也因此挡得住。
- 转码卡片改成两栏:左边保留原来的文件列表多选,右边加「一行一个路径」的输入框
和「浏览…」按钮(复用已有的目录选择器,可多选、可跨文件夹跨盘攒,选完只追加不
覆盖)。openPick 多了个可选的第 5 参数用来指定起始目录,浏览转码文件时从成片
目录开始(录播目录可能是空的)。
- 报错前缀改用 transcodeJobReq.label():按路径进来时 Name 是空的,直接拼 j.Name
会变成「:参数不合法」。
验证
gofmt -l 干净;go vet 干净;Windows 与 GOOS=linux 编译均通过。
go test ./... 全绿;go test -race -count=1 ./... 27.2s 全绿;顶层用例 209 个
(含子测试 226),本次新增 3 个:-run 单独跑过 TestResolveTranscodeJobByPath /
TestResolveTranscodeJobRejectsBadPath / TestTranscodeProbeHandlerAcceptsPath。
反向验证 44/44:把 resolveTranscodeJob 的路径分支退回 resolveFile 后,上面两个
用例确实变红。
转码接口冒烟 40/40(新增 11 项按路径的:探测回显路径、估算、不存在 400、非视频
后缀 400、指向文件夹 400、真排队转出成片)。
页面 jsdom 冒烟 49/49 且无 JS 报错(新增 12 项:路径框存在、空框不给加入、手填能
进列表并带上绝对路径、探测请求按 path 发、重复加入不重复、浏览弹窗打开/追加不
覆盖/关闭/清空)。
This commit is contained in:
+33
-11
@@ -66,17 +66,29 @@ type transcodeOptions struct {
|
||||
Audio string `json:"audio"` // 音频处理,空 = 直接拷贝
|
||||
}
|
||||
|
||||
// 一个待转码的文件
|
||||
// 一个待转码的文件。
|
||||
// 定位源文件有两种写法,二选一:Path 填了就用它(绝对路径、相对路径都行,可以是从资源管理器
|
||||
// 复制来的带引号路径),没填才按 Dir + Name 在录播/成片文件夹里找
|
||||
type transcodeJobReq struct {
|
||||
Path string `json:"path"`
|
||||
Dir string `json:"dir"` // record / merge
|
||||
Name string `json:"name"` // 文件名
|
||||
OutName string `json:"outName"` // 输出文件名,留空自动
|
||||
Options transcodeOptions `json:"options"`
|
||||
}
|
||||
|
||||
// 报错时给用户看的名字:按路径传进来的 Name 是空的,直接拼进消息会变成「:参数不合法」
|
||||
func (j transcodeJobReq) label() string {
|
||||
if p := strings.TrimSpace(j.Path); p != "" {
|
||||
return filepath.Base(strings.Trim(p, `"'`))
|
||||
}
|
||||
return j.Name
|
||||
}
|
||||
|
||||
// 探测到的源文件信息,页面用它显示原始参数和算预估体积
|
||||
type transcodeProbe struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"` // 源文件绝对路径,页面拿它当任务的身份(同名文件在不同目录不会认错)
|
||||
Dir string `json:"dir"`
|
||||
Size int64 `json:"size"`
|
||||
Duration float64 `json:"duration"`
|
||||
@@ -531,9 +543,19 @@ func formatByteSize(n int64) string {
|
||||
return fmt.Sprintf("%.1f %s", v, units[i])
|
||||
}
|
||||
|
||||
// 转码任务的输入必须来自文件管理里的两个文件夹,和手动合并一致
|
||||
func resolveTranscodeInput(dir, name string) (string, error) {
|
||||
full, err := resolveFile(dir, name)
|
||||
// 转码任务的输入:填了路径就按路径找(任意文件夹都行),否则按文件名在录播/成片文件夹里找。
|
||||
// 两条路都要过一遍格式白名单 —— 路径这条能指向磁盘上任何文件,不挡的话
|
||||
// 用户手滑把 .jsonl 弹幕丢进来,要等 ffmpeg 跑起来才报一句看不懂的错
|
||||
func resolveTranscodeJob(j transcodeJobReq) (string, error) {
|
||||
var (
|
||||
full string
|
||||
err error
|
||||
)
|
||||
if p := strings.TrimSpace(j.Path); p != "" {
|
||||
full, err = resolveMergeInput(p)
|
||||
} else {
|
||||
full, err = resolveFile(j.Dir, j.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -650,7 +672,7 @@ func runTranscode(job transcodeJobReq, outDir string, idx, total int) (string, e
|
||||
if ffmpegFile == "" {
|
||||
return "", errors.New("找不到 ffmpeg,请先在设置里填写 ffmpeg 路径")
|
||||
}
|
||||
in, err := resolveTranscodeInput(job.Dir, job.Name)
|
||||
in, err := resolveTranscodeJob(job)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -803,7 +825,7 @@ func transcodeProbeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
out := make([]transcodeProbe, 0, len(req.Files))
|
||||
for _, f := range req.Files {
|
||||
full, err := resolveTranscodeInput(f.Dir, f.Name)
|
||||
full, err := resolveTranscodeJob(f)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -815,7 +837,7 @@ func transcodeProbeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
m := probeMediaCached(ffmpegFile, full)
|
||||
out = append(out, transcodeProbe{
|
||||
Name: filepath.Base(full), Dir: f.Dir, Size: st.Size(),
|
||||
Name: filepath.Base(full), Path: full, Dir: f.Dir, Size: st.Size(),
|
||||
Duration: m.duration, Width: m.width, Height: m.height, Fps: m.fps,
|
||||
// 视频码率在这里就扣掉音轨:后面预估体积还要单独加一次音频,
|
||||
// 直接用容器总码率会把音轨算成两份
|
||||
@@ -852,10 +874,10 @@ func transcodeEstimateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
out := make([]transcodeEstimate, 0, len(req.Jobs))
|
||||
for _, j := range req.Jobs {
|
||||
if msg := j.Options.validate(ffmpegFile); msg != "" {
|
||||
http.Error(w, j.Name+":"+msg, http.StatusBadRequest)
|
||||
http.Error(w, j.label()+":"+msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
full, err := resolveTranscodeInput(j.Dir, j.Name)
|
||||
full, err := resolveTranscodeJob(j)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -907,10 +929,10 @@ func transcodeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 探测走缓存,刚才「加入列表」和「估算体积」时已经探过,这里不会再多起 ffmpeg
|
||||
for _, j := range req.Jobs {
|
||||
if msg := j.Options.validate(ffmpegFile); msg != "" {
|
||||
http.Error(w, j.Name+":"+msg, http.StatusBadRequest)
|
||||
http.Error(w, j.label()+":"+msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
full, err := resolveTranscodeInput(j.Dir, j.Name)
|
||||
full, err := resolveTranscodeJob(j)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user