转码的源文件不再限死在录播/成片目录,可以填任意路径
为什么
转码页的多选框只列 /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:
@@ -1,10 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -477,6 +481,94 @@ func has(args []string, v string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ==================== 源文件定位 ====================
|
||||
|
||||
// 填了路径就按路径找,录播/成片文件夹之外的文件也要能转;没填才回落到 dir + name
|
||||
func TestResolveTranscodeJobByPath(t *testing.T) {
|
||||
setupMergeTest(t)
|
||||
outside := t.TempDir()
|
||||
video := filepath.Join(outside, "别处的录播.mp4")
|
||||
writeTestFile(t, video, "x")
|
||||
|
||||
// 绝对路径要认,从资源管理器复制来的带引号路径也要认
|
||||
for _, in := range []string{video, `"` + video + `"`} {
|
||||
got, err := resolveTranscodeJob(transcodeJobReq{Path: in})
|
||||
if err != nil {
|
||||
t.Fatalf("%q 应当能解析:%v", in, err)
|
||||
}
|
||||
if normalizePath(got) != normalizePath(video) {
|
||||
t.Errorf("%q 解析成了 %q,想要 %q", in, got, video)
|
||||
}
|
||||
}
|
||||
|
||||
// 不填路径时还是按录播目录 + 文件名找,老写法不能坏
|
||||
rec := filepath.Join(recordSaveDir(), "录播里的.mp4")
|
||||
writeTestFile(t, rec, "x")
|
||||
got, err := resolveTranscodeJob(transcodeJobReq{Dir: "record", Name: "录播里的.mp4"})
|
||||
if err != nil {
|
||||
t.Fatalf("dir + name 应当能解析:%v", err)
|
||||
}
|
||||
if normalizePath(got) != normalizePath(rec) {
|
||||
t.Errorf("dir + name 解析成了 %q,想要 %q", got, rec)
|
||||
}
|
||||
}
|
||||
|
||||
// 路径这条能指向磁盘上任何文件,格式白名单必须照样过一遍
|
||||
func TestResolveTranscodeJobRejectsBadPath(t *testing.T) {
|
||||
setupMergeTest(t)
|
||||
outside := t.TempDir()
|
||||
danmu := filepath.Join(outside, "某场.jsonl")
|
||||
writeTestFile(t, danmu, "{}")
|
||||
sub := filepath.Join(outside, "子文件夹")
|
||||
if err := os.MkdirAll(sub, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bad := map[string]transcodeJobReq{
|
||||
"不是视频后缀": {Path: danmu},
|
||||
"指向文件夹": {Path: sub},
|
||||
"路径不存在": {Path: filepath.Join(outside, "没有这个文件.mp4")},
|
||||
"路径和名字都没填": {},
|
||||
}
|
||||
for name, j := range bad {
|
||||
if _, err := resolveTranscodeJob(j); err == nil {
|
||||
t.Errorf("%s:应当被挡住,实际通过了", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 探测接口要认 {path} —— 这是「转码任意文件」的实际入口,只测 resolve 不足以证明接线对了。
|
||||
// 这里故意用一个不是视频的文件:探测拿不到流信息,但接口仍要 200 并把路径原样回显
|
||||
func TestTranscodeProbeHandlerAcceptsPath(t *testing.T) {
|
||||
setupMergeTest(t)
|
||||
outside := t.TempDir()
|
||||
video := filepath.Join(outside, "别处.mp4")
|
||||
writeTestFile(t, video, "not a real video")
|
||||
|
||||
body := `{"files":[{"path":` + strconv.Quote(video) + `}]}`
|
||||
req := httptest.NewRequest("POST", "/api/transcode/probe", strings.NewReader(body))
|
||||
rec := httptest.NewRecorder()
|
||||
transcodeProbeHandler(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("按路径探测应当 200,实际 %d:%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var resp struct {
|
||||
Files []struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
} `json:"files"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Files) != 1 || resp.Files[0].Name != "别处.mp4" {
|
||||
t.Fatalf("探测结果不对:%+v", resp.Files)
|
||||
}
|
||||
if normalizePath(resp.Files[0].Path) != normalizePath(video) {
|
||||
t.Errorf("回显的 path 是 %q,想要 %q", resp.Files[0].Path, video)
|
||||
}
|
||||
}
|
||||
|
||||
func indexOf(args []string, v string) int {
|
||||
for i, a := range args {
|
||||
if a == v {
|
||||
|
||||
Reference in New Issue
Block a user