档位改成「自动·第二高 + 实际档位」的下拉;手动合并也推送;头像缓存挪到程序目录
三件事: 1) 主播列表的录制档位 - 一律是下拉,不再有手填输入框。第一项「自动 · 第二高」(值 0)= 按这次读到的 档位排序自上往下取第二高(只有一档就用它,读不到档位表才退回最高档); 后面依次列出实际档位。选最高/最低/中间某一档直接点它,手动选过之后 「自动」仍留在列表里,随时能改回去。 - 语义变化:0 不再被写死成某个具体码率。原来记录档位时会把「没选过」的主播 按第二档定下来(写进 config.json),现在只在取流时解析,所以主播换了档位表 也能跟着走;app 启动时的补齐逻辑(applyCachedDefaultQualities)随之去掉。 - 解析规则统一到 resolveQualityBitrate:0 = 第二高,>0 = 不超过它的最高档, 都比它高时退回最低档;「自动」项会把解析到的档位名带出来 (自动 · 第二高(当前 = 蓝光 4M))。/api/status 和档位接口都多了 qualityBitrate。 - 点「档位」按钮时先给一句「正在读取码率档位…」:主播没开播时服务端要重试三次 才放弃,约 5 秒,之前这段时间界面没有任何反应。 2) 手动合并完成也推送(这就是「MeoW 只在测试时成功」的原因) - 原来只有自动合并调 sendNotify,手动合并从头到尾没推过,所以事件开着、渠道也通 却一直收不到。现在手动合并完成后同样按 mergeDone 事件推送两个渠道。 - 手动合并没有 uid,会拿成片名(再退到视频名)去对已有主播名,只有一个能对上时 才在文案里带上主播名,对不上就用「手动合并完成」。 3) 头像缓存挪到程序所在目录 - 原来存在「录播目录/avatar_cache」,换一次保存目录(甚至换盘)就要把同一批观众 的头像重新下一遍(一个头像可能 0.5MB)。现在统一放程序目录下的 avatar_cache。 - 旧位置作为只读回退:命中就搬过来(同盘改名,跨盘退回复制),不重复下载; 没被用到的旧文件留在原地,用到哪个搬哪个。 验证: - 新增 avatar_test.go 两个用例(缓存路径、旧位置搬家)和 notify_test.go 的 TestManualMergeSendsMergeDoneNotify(真跑一次合并,断言 webhook 与 MeoW 各收到 一条 mergeDone,且从文件名认出主播);qualities_test.go 改成新语义,新增 resolveQualityBitrate(7 种档位/码率组合)和 guessUIDByFileName。 - 端到端:把旧位置的头像缓存放在录播目录、头像地址指向连不通的端口,合并仍然成功 且画面里有那张头像(纯红像素 400 个),证明是搬来的而不是重新下载的;文件也从 录播目录搬到了程序目录。 - 浏览器:verify_streamer_br.py 重写成档位下拉的 20 项断言,verify_qualities.py 更新为新语义,两个都全过;verify_settings.py / verify_pick.py / verify_danmu_match.py 无回归;go test ./...、gofmt、go vet 干净。
This commit is contained in:
+46
-8
@@ -34,6 +34,15 @@ import (
|
||||
// 头像缓存目录名
|
||||
const avatarCacheDir = "avatar_cache"
|
||||
|
||||
// 头像缓存放程序所在目录:换保存目录(甚至换盘)都还能复用,
|
||||
// 不用为同一批观众重新下一次。旧版本放在弹幕文件同目录,读取时会搬过来。
|
||||
func avatarCachePath() string {
|
||||
if exeDir == "" {
|
||||
return avatarCacheDir
|
||||
}
|
||||
return filepath.Join(exeDir, avatarCacheDir)
|
||||
}
|
||||
|
||||
// 命令行 -vbitrate 指定的合成目标码率(Kbps),配合 config.MergeBitrate 使用
|
||||
var burnBitrate int
|
||||
|
||||
@@ -222,8 +231,10 @@ func burnDanmu(jsonlFile, videoFile, outFile string, offset float64) error {
|
||||
|
||||
// 下载并圆形裁切头像
|
||||
avatarPaths := make(map[int64]string)
|
||||
dir := filepath.Dir(jsonlFile)
|
||||
cacheDir := filepath.Join(dir, avatarCacheDir)
|
||||
// 缓存放在程序所在目录,换保存目录(甚至换盘)都不用重新下载;
|
||||
// 旧版本放在弹幕文件同目录,作为回退来源读一次并搬过去
|
||||
cacheDir := avatarCachePath()
|
||||
legacyCache := filepath.Join(filepath.Dir(jsonlFile), avatarCacheDir)
|
||||
// 先去重,顺便记下每个用户自带的头像链接
|
||||
urlByUID := make(map[int64]string)
|
||||
uids := make([]int64, 0, 64)
|
||||
@@ -254,7 +265,7 @@ func burnDanmu(jsonlFile, videoFile, outFile string, offset float64) error {
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
if path := ensureAvatar(uid, url, cacheDir); path != "" {
|
||||
if path := ensureAvatar(uid, url, cacheDir, legacyCache); path != "" {
|
||||
avatarPaths[uid] = path
|
||||
}
|
||||
}
|
||||
@@ -284,7 +295,7 @@ func burnDanmu(jsonlFile, videoFile, outFile string, offset float64) error {
|
||||
meta: meta,
|
||||
avatars: avatarPaths,
|
||||
bitrate: bitrate,
|
||||
dir: dir,
|
||||
dir: filepath.Dir(jsonlFile),
|
||||
}
|
||||
|
||||
chunks := splitDanmuChunks(records, offset, videoDuration)
|
||||
@@ -842,11 +853,13 @@ func formatSeconds(sec float64) string {
|
||||
return strconv.FormatFloat(sec, 'f', 3, 64)
|
||||
}
|
||||
|
||||
// 下载头像并圆形裁切,失败自动重试,返回缓存的 PNG 路径,失败返回空字符串
|
||||
func ensureAvatar(uid int64, url, cacheDir string) string {
|
||||
// 下载头像并圆形裁切,失败自动重试,返回缓存的 PNG 路径,失败返回空字符串。
|
||||
// legacyDirs 是旧版本放缓存的位置,命中就搬过来用,省得换保存目录后重新下一次。
|
||||
func ensureAvatar(uid int64, url, cacheDir string, legacyDirs ...string) string {
|
||||
// 文件名里带上头像链接的摘要,主播换头像后不会一直用着旧缓存
|
||||
sum := sha1.Sum([]byte(url))
|
||||
path := filepath.Join(cacheDir, fmt.Sprintf("%d_%x.png", uid, sum[:6]))
|
||||
name := fmt.Sprintf("%d_%x.png", uid, sum[:6])
|
||||
path := filepath.Join(cacheDir, name)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
@@ -854,8 +867,33 @@ func ensureAvatar(uid int64, url, cacheDir string) string {
|
||||
lPrintWarnf("创建头像缓存文件夹 %s 失败,弹幕只烧录文字不带头像:%v", cacheDir, err)
|
||||
return ""
|
||||
}
|
||||
// 旧位置(以前存在录播目录里)有同一个头像就直接搬过来,不重复下载
|
||||
for _, dir := range legacyDirs {
|
||||
if dir == "" || dir == cacheDir {
|
||||
continue
|
||||
}
|
||||
old := filepath.Join(dir, name)
|
||||
if _, err := os.Stat(old); err != nil {
|
||||
continue
|
||||
}
|
||||
if err := os.Rename(old, path); err == nil {
|
||||
return path
|
||||
}
|
||||
// 跨盘改名会失败,退回复制
|
||||
if copyFile(old, path) == nil {
|
||||
_ = os.Remove(old)
|
||||
return path
|
||||
}
|
||||
}
|
||||
// 清掉这个用户已经失效的旧头像
|
||||
if olds, err := filepath.Glob(filepath.Join(cacheDir, strconv.FormatInt(uid, 10)+"_*.png")); err == nil {
|
||||
for _, dir := range append([]string{cacheDir}, legacyDirs...) {
|
||||
if dir == "" {
|
||||
continue
|
||||
}
|
||||
olds, err := filepath.Glob(filepath.Join(dir, strconv.FormatInt(uid, 10)+"_*.png"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, p := range olds {
|
||||
if p != path {
|
||||
_ = os.Remove(p)
|
||||
|
||||
Reference in New Issue
Block a user