弹幕字号按画面短边自动定,竖屏成片不再字大到铺满整行
1. 字号基准从「画面高度」改成「画面短边」 以前合并时只用高度比换算字号(字号 × 视频高 ÷ 弹幕文件高)。横屏弹幕文件 (1920x1080、60 号)配竖屏视频(720x1280)时会被放大到 71 号,而画面只有 720 宽,一条 10 字弹幕就铺满整行。横竖屏互换时高度不是约束文字宽度的维度, 短边才是:横屏取高、竖屏取宽。 2. 「弹幕字号」填 0 的含义从「跟随弹幕文件」改成「自动按画面分辨率」 取画布短边的 5.56%(60/1080)。于是 1920x1080 → 60、1280x720 → 40、 960x540 → 30,横屏行为与以前完全一致;720x1280 → 40(以前 60)、 640x1280 → 36。填大于 0 仍是精确像素覆盖。 3. 录制端竖屏档位预设 720x1280 的字号从 60 改成 40 原来横屏三档都是短边的 5.56%,只有竖屏这档是 8.33%,比横屏大 50%。 改完四档口径一致,新录的弹幕文件和成片也对得上。 4. 坐标系和字号拆开 unifyDanmuSpace 只负责把 PlayRes 统一到视频分辨率,字号统一交给 applyFontSize 按最终画布算,避免两处各改一次。 5. .gitignore 加上 /avatar_cache 头像缓存挪到程序目录后,程序正好跑在仓库里,缓存目录会被 git 看见, git add . 会把那 336 个 PNG 一起提交进去。 已验证: - 新增 TestShortSide / TestAutoFontSize / TestSubConfigsMatchAutoFontSize / TestApplyFontSize;TestUnifyDanmuSpace 改成只校验 PlayRes,并且新增断言 确认它不再动字号。 - 反向验证:把短边规则临时退回「按高度」,TestAutoFontSize 报 「720x1280 应为 40,实际 71」「1080x1920 应为 60,实际 107」, TestApplyFontSize、TestSubConfigsMatchAutoFontSize 同步失败。 - 端到端:造 720x1280 竖屏视频 + 1920x1080/60 号的 jsonl,字号填 0 真跑一次合并, 日志打出「竖屏画布 720x1280,弹幕字号自动取 40」;同一输入再用旧规则跑一次对照, 抽同一时刻的帧量墨迹高度:改前 46px、改后 25px(1.84 倍)。 - gofmt / go vet / go build / GOOS=linux go build / go test 全过。
This commit is contained in:
+59
-20
@@ -94,6 +94,58 @@ func avatarGap(fontSize int) int {
|
||||
return fontSize / 6
|
||||
}
|
||||
|
||||
// 画布短边:横屏取高、竖屏取宽。弹幕文字和头像的尺寸都以它为准 ——
|
||||
// 竖屏画面窄,按高度算出来的字会大到一条弹幕就铺满整行。
|
||||
func shortSide(w, h int) int {
|
||||
if w <= 0 || h <= 0 {
|
||||
return 0
|
||||
}
|
||||
if w < h {
|
||||
return w
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// 自动字号的比例:短边 × 60/1080 ≈ 5.56%,
|
||||
// 和录制端的档位预设一致(1920x1080 → 60、1280x720 → 40、960x540 → 30)
|
||||
const autoFontRatioNum, autoFontRatioDen = 60, 1080
|
||||
|
||||
// 按画布短边算自动字号,拿不到尺寸时返回 0
|
||||
func autoFontSize(w, h int) int {
|
||||
short := shortSide(w, h)
|
||||
if short <= 0 {
|
||||
return 0
|
||||
}
|
||||
fs := int(math.Round(float64(short) * autoFontRatioNum / autoFontRatioDen))
|
||||
if fs < 1 {
|
||||
fs = 1
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// 定成片字号。设置里填了 >0 就按填的(成片像素);填 0(默认)按画布短边自动算,
|
||||
// 这样横屏和竖屏的字看起来一样大,竖屏成片不会字大到铺满整行。头像随字号等比缩放。
|
||||
func applyFontSize(meta *danmuMeta, override int) {
|
||||
if override > 0 {
|
||||
if override != meta.FontSize {
|
||||
lPrintf("弹幕字号按设置使用 %d(弹幕文件里是 %d)", override, meta.FontSize)
|
||||
}
|
||||
meta.FontSize = override
|
||||
return
|
||||
}
|
||||
auto := autoFontSize(meta.PlayResX, meta.PlayResY)
|
||||
if auto <= 0 || auto == meta.FontSize {
|
||||
return
|
||||
}
|
||||
orient := "横屏"
|
||||
if meta.PlayResY > meta.PlayResX {
|
||||
orient = "竖屏"
|
||||
}
|
||||
lPrintf("%s画布 %dx%d,弹幕字号自动取 %d(短边 × %d/%d,弹幕文件里是 %d)",
|
||||
orient, meta.PlayResX, meta.PlayResY, auto, autoFontRatioNum, autoFontRatioDen, meta.FontSize)
|
||||
meta.FontSize = auto
|
||||
}
|
||||
|
||||
// 弹幕合成入口(命令行)
|
||||
// 弹幕文件可以给 .jsonl,也可以给 .ass(先还原成 jsonl 再合成)
|
||||
func runDanmuburn(danmuFile, videoFile, outFile string, offset float64) {
|
||||
@@ -212,20 +264,17 @@ func burnDanmu(jsonlFile, videoFile, outFile string, offset float64) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 弹幕字号可被设置覆盖(0 = 跟随弹幕文件),头像尺寸随字号等比缩放
|
||||
if config.DanmuFontSize > 0 {
|
||||
lPrintf("弹幕字号按设置使用 %d(弹幕文件里是 %d)", config.DanmuFontSize, meta.FontSize)
|
||||
meta.FontSize = config.DanmuFontSize
|
||||
}
|
||||
// 弹幕文件的坐标系和视频对不上时,统一到视频坐标系(见 unifyDanmuSpace);
|
||||
// 顺便拿到源片码率,用于「合成码率填 0」时的自动码率
|
||||
sourceKbps := 0
|
||||
if burnMode {
|
||||
var vw, vh int
|
||||
vw, vh, sourceKbps = probeVideoStream(ffmpegFile, videoFile)
|
||||
unifyDanmuSpace(&meta, vw, vh, config.DanmuFontSize)
|
||||
unifyDanmuSpace(&meta, vw, vh)
|
||||
lPrintf("视频编码器优先顺序:%s", strings.Join(availableEncoders(ffmpegFile), " → "))
|
||||
}
|
||||
// 字号在坐标系统一之后再定,此时 meta.PlayRes 就是成片画布
|
||||
applyFontSize(&meta, config.DanmuFontSize)
|
||||
// 弹道按最终字号统一重排,避免还原弹幕或改字号后同弹道追尾
|
||||
relayoutLanes(records, meta)
|
||||
|
||||
@@ -365,24 +414,14 @@ func earliestLane(lanes []laneState) int {
|
||||
// (例如 960x540 的弹幕文件配 720x1280 的竖屏视频)。libass 会按「视频高 / PlayResY」
|
||||
// 缩放文字和它的坐标,而头像叠加用的是视频像素坐标,两者不在同一个坐标系里 ——
|
||||
// 字号越大越明显:头像显得小,而且每往下一行错得越多(看起来和文字分开了)。
|
||||
// 这里把 PlayRes 改成视频宽高:跟随文件时字号按高度比换算(保持占画面的比例),
|
||||
// 用户显式填了字号就直接用(他看的是成片上的像素)。返回是否做过调整。
|
||||
func unifyDanmuSpace(meta *danmuMeta, vw, vh, fontSizeOverride int) bool {
|
||||
// 这里只负责改 PlayRes,字号交给 applyFontSize 按最终画布重算。返回是否做过调整。
|
||||
func unifyDanmuSpace(meta *danmuMeta, vw, vh int) bool {
|
||||
if vw <= 0 || vh <= 0 || (vw == meta.PlayResX && vh == meta.PlayResY) {
|
||||
return false
|
||||
}
|
||||
oldX, oldY, oldFs := meta.PlayResX, meta.PlayResY, meta.FontSize
|
||||
if fontSizeOverride > 0 {
|
||||
meta.FontSize = fontSizeOverride
|
||||
} else {
|
||||
meta.FontSize = int(math.Round(float64(oldFs) * float64(vh) / float64(oldY)))
|
||||
}
|
||||
if meta.FontSize < 1 {
|
||||
meta.FontSize = 1
|
||||
}
|
||||
oldX, oldY := meta.PlayResX, meta.PlayResY
|
||||
meta.PlayResX, meta.PlayResY = vw, vh
|
||||
lPrintf("弹幕文件的坐标系是 %dx%d(字号 %d),视频是 %dx%d,改按视频坐标系渲染,字号 %d",
|
||||
oldX, oldY, oldFs, vw, vh, meta.FontSize)
|
||||
lPrintf("弹幕文件的坐标系是 %dx%d,视频是 %dx%d,改按视频坐标系渲染", oldX, oldY, vw, vh)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user