合并时把弹幕坐标系统一到视频分辨率;修「改设置会删掉正在合并的临时文件」
用户手动合并一场竖屏录播(视频 720x1280,弹幕文件 960x540、字号 30)时发现: 字号改大以后文字变大了,但头像还是小小的,而且越往下的弹幕头像和文字错得越远。 原因:弹幕文件里的 PlayResX/PlayResY 是录制当时的画面尺寸,和现在的视频对不上时, libass 会按「视频高 ÷ PlayResY」缩放文字和它的坐标(这里 1280/540 ≈ 2.37), 而头像叠加用的是视频像素坐标(头像直径 = 字号×4/5、行位置 = 字号×弹道号), 两者不在同一个坐标系里。实测同一帧(720x1280 竖屏): 修复前:文字墨迹高 54px、头像直径 24px,第 4 行文字在 y=226 而它的头像在 y=92 修复后:文字墨迹高 54px、头像直径 56px,头像与对应文字行一一对齐 做法是合并前探测视频分辨率,不一致就把 PlayRes 改成视频宽高:跟随弹幕文件时字号 按高度比换算(占画面的比例不变),用户填了 danmuFontSize 就按成片像素直接用 —— 因为用户看到的本来就是成片上的像素。只在有视频时做,透明头像层模式不变; 探测不到分辨率就保持原样。坐标系一致时(1920x1080 配 1920x1080)完全不改动, 所以正常录播的合并结果和以前一样。验证方式是拿一份「原生就对」的弹幕文件做对照, 自动统一后的成片与它在文字行位置、头像直径和位置上逐项相同。 顺带修一个真 bug(上一轮引入):checkConfig() 里清了合成临时文件,而改设置会重新 读取配置并调用 checkConfig(),于是合并途中改任何设置都会把正在用的分块字幕/临时 成片删掉,合并当场失败(日志:清理上次留下的临时文件 acfunlive_chunk_single.ass → Unable to open ...)。清理挪到只在启动时执行的 cleanLeftoverTempFiles()。 新增 config_test.go(改设置不能删掉在用的临时文件,反向验证过:把清理塞回 checkConfig 会让它失败)和 unifyDanmuSpace / 分辨率解析的用例。
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 改设置会重新读取配置(web UI 保存、或直接改 config.json),
|
||||
// 这一步绝不能删掉正在进行的合并的临时文件 —— 否则合并会当场失败。
|
||||
func TestReloadConfigKeepsMergeTempFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
savedConfig := config
|
||||
savedLocation := configFileLocation
|
||||
oldDir := configDir
|
||||
if configDir == nil {
|
||||
configDir = new(string)
|
||||
}
|
||||
defer func() {
|
||||
config = savedConfig
|
||||
configFileLocation = savedLocation
|
||||
configDir = oldDir
|
||||
}()
|
||||
|
||||
*configDir = dir
|
||||
configFileLocation = filepath.Join(dir, configFile)
|
||||
|
||||
recDir := filepath.Join(dir, defaultRecordSubdir)
|
||||
if err := os.MkdirAll(recDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 假装一个合并正在跑:录播目录里有分块字幕和先本地合成的成片
|
||||
inUse := []string{
|
||||
filepath.Join(recDir, chunkTempPrefix+"single.ass"),
|
||||
filepath.Join(recDir, chunkTempPrefix+"merging_x.mp4"),
|
||||
}
|
||||
for _, p := range inUse {
|
||||
if err := os.WriteFile(p, []byte("x"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
content, _ := json.Marshal(map[string]string{"directory": filepath.ToSlash(dir)})
|
||||
if err := os.WriteFile(configFileLocation, content, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 重新读取配置的真实路径:loadNewConfig() 就是 loadConfig() + checkConfig()
|
||||
loadConfig()
|
||||
checkConfig()
|
||||
|
||||
for _, p := range inUse {
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
t.Errorf("重新读取配置把正在用的临时文件删了:%s", filepath.Base(p))
|
||||
}
|
||||
}
|
||||
|
||||
// 启动时的兜底清理还得照旧能清掉上次残留
|
||||
cleanLeftoverTempFiles()
|
||||
for _, p := range inUse {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
t.Errorf("启动时的清理没清掉残留:%s", filepath.Base(p))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user