弹幕字号按画面短边自动定,竖屏成片不再字大到铺满整行
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:
@@ -9,6 +9,8 @@ acfunlive
|
|||||||
/bin
|
/bin
|
||||||
/merged
|
/merged
|
||||||
/record
|
/record
|
||||||
|
# 头像缓存放在程序所在目录,程序正好跑在仓库里时会落在这里,别提交进去
|
||||||
|
/avatar_cache
|
||||||
|
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ GOOS=windows GOARCH=amd64 go build -o acfunlive.exe
|
|||||||
"mergeDelDanmu": false, // 合并完成后删除弹幕文件(ass、nick.ass、jsonl)
|
"mergeDelDanmu": false, // 合并完成后删除弹幕文件(ass、nick.ass、jsonl)
|
||||||
"mergeOutputDir": "", // 合并完成后成片移动到该文件夹,留空则不移动
|
"mergeOutputDir": "", // 合并完成后成片移动到该文件夹,留空则不移动
|
||||||
"mergeBitrate": 0, // 合并成片的目标码率(Kbps),0 表示按「源片码率 + 500」自动计算
|
"mergeBitrate": 0, // 合并成片的目标码率(Kbps),0 表示按「源片码率 + 500」自动计算
|
||||||
"danmuFontSize": 0, // 合并时弹幕文字的字号(像素),0 表示跟随弹幕文件;头像随字号等比缩放
|
"danmuFontSize": 0, // 合并时弹幕文字的字号(像素),0 表示按画面分辨率自动(短边 × 5.56%,横屏取高、竖屏取宽);头像随字号等比缩放
|
||||||
"defaultQualityPref": "second", // 新添加主播的默认档位偏好,取值同上面的 qualityPref
|
"defaultQualityPref": "second", // 新添加主播的默认档位偏好,取值同上面的 qualityPref
|
||||||
"defaultBitrate": 0, // 只在上面为 custom 时有意义:默认的自定义码率上限(Kbps)
|
"defaultBitrate": 0, // 只在上面为 custom 时有意义:默认的自定义码率上限(Kbps)
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ type configData struct {
|
|||||||
MergeDelDanmu bool `json:"mergeDelDanmu"` // 合并完成后删除弹幕文件
|
MergeDelDanmu bool `json:"mergeDelDanmu"` // 合并完成后删除弹幕文件
|
||||||
MergeOutputDir string `json:"mergeOutputDir"` // 合并完成后成片移动到该文件夹,留空不移动
|
MergeOutputDir string `json:"mergeOutputDir"` // 合并完成后成片移动到该文件夹,留空不移动
|
||||||
MergeBitrate int `json:"mergeBitrate"` // 合并成片的目标码率(Kbps),0 表示使用默认质量
|
MergeBitrate int `json:"mergeBitrate"` // 合并成片的目标码率(Kbps),0 表示使用默认质量
|
||||||
DanmuFontSize int `json:"danmuFontSize"` // 合并时弹幕文字的字号(头像随字号等比缩放),0 表示跟随弹幕文件
|
DanmuFontSize int `json:"danmuFontSize"` // 合并时弹幕文字的字号(像素,头像随字号等比缩放),0 表示按画面分辨率自动
|
||||||
|
|
||||||
// 新添加主播的默认录制档位(留空/非法按第二高)与自定义时的码率上限
|
// 新添加主播的默认录制档位(留空/非法按第二高)与自定义时的码率上限
|
||||||
DefaultQualityPref string `json:"defaultQualityPref,omitempty"`
|
DefaultQualityPref string `json:"defaultQualityPref,omitempty"`
|
||||||
@@ -419,7 +419,7 @@ func checkConfig() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if config.DanmuFontSize < 0 {
|
if config.DanmuFontSize < 0 {
|
||||||
lPrintErrf("%s里的danmuFontSize不能小于 0(0 表示跟随弹幕文件)", configFile)
|
lPrintErrf("%s里的danmuFontSize不能小于 0(0 表示按画面分辨率自动)", configFile)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if config.DefaultBitrate < 0 {
|
if config.DefaultBitrate < 0 {
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 不同的视频分辨率对应的弹幕字幕设置
|
// 不同的视频分辨率对应的弹幕字幕设置
|
||||||
|
// 字号一律取「短边 × 60/1080」(横屏取高、竖屏取宽),和合并端的自动字号一致
|
||||||
var subConfigs = map[int]acfundanmu.SubConfig{
|
var subConfigs = map[int]acfundanmu.SubConfig{
|
||||||
0: {PlayResX: 720, PlayResY: 1280, FontSize: 60}, // 这是手机直播,有一些是 540X960
|
0: {PlayResX: 720, PlayResY: 1280, FontSize: 40}, // 这是手机直播,有一些是 540X960
|
||||||
540: {PlayResX: 960, PlayResY: 540, FontSize: 30},
|
540: {PlayResX: 960, PlayResY: 540, FontSize: 30},
|
||||||
720: {PlayResX: 1280, PlayResY: 720, FontSize: 40},
|
720: {PlayResX: 1280, PlayResY: 720, FontSize: 40},
|
||||||
1080: {PlayResX: 1920, PlayResY: 1080, FontSize: 60},
|
1080: {PlayResX: 1920, PlayResY: 1080, FontSize: 60},
|
||||||
|
|||||||
+59
-20
@@ -94,6 +94,58 @@ func avatarGap(fontSize int) int {
|
|||||||
return fontSize / 6
|
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 再合成)
|
// 弹幕文件可以给 .jsonl,也可以给 .ass(先还原成 jsonl 再合成)
|
||||||
func runDanmuburn(danmuFile, videoFile, outFile string, offset float64) {
|
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);
|
// 弹幕文件的坐标系和视频对不上时,统一到视频坐标系(见 unifyDanmuSpace);
|
||||||
// 顺便拿到源片码率,用于「合成码率填 0」时的自动码率
|
// 顺便拿到源片码率,用于「合成码率填 0」时的自动码率
|
||||||
sourceKbps := 0
|
sourceKbps := 0
|
||||||
if burnMode {
|
if burnMode {
|
||||||
var vw, vh int
|
var vw, vh int
|
||||||
vw, vh, sourceKbps = probeVideoStream(ffmpegFile, videoFile)
|
vw, vh, sourceKbps = probeVideoStream(ffmpegFile, videoFile)
|
||||||
unifyDanmuSpace(&meta, vw, vh, config.DanmuFontSize)
|
unifyDanmuSpace(&meta, vw, vh)
|
||||||
lPrintf("视频编码器优先顺序:%s", strings.Join(availableEncoders(ffmpegFile), " → "))
|
lPrintf("视频编码器优先顺序:%s", strings.Join(availableEncoders(ffmpegFile), " → "))
|
||||||
}
|
}
|
||||||
|
// 字号在坐标系统一之后再定,此时 meta.PlayRes 就是成片画布
|
||||||
|
applyFontSize(&meta, config.DanmuFontSize)
|
||||||
// 弹道按最终字号统一重排,避免还原弹幕或改字号后同弹道追尾
|
// 弹道按最终字号统一重排,避免还原弹幕或改字号后同弹道追尾
|
||||||
relayoutLanes(records, meta)
|
relayoutLanes(records, meta)
|
||||||
|
|
||||||
@@ -365,24 +414,14 @@ func earliestLane(lanes []laneState) int {
|
|||||||
// (例如 960x540 的弹幕文件配 720x1280 的竖屏视频)。libass 会按「视频高 / PlayResY」
|
// (例如 960x540 的弹幕文件配 720x1280 的竖屏视频)。libass 会按「视频高 / PlayResY」
|
||||||
// 缩放文字和它的坐标,而头像叠加用的是视频像素坐标,两者不在同一个坐标系里 ——
|
// 缩放文字和它的坐标,而头像叠加用的是视频像素坐标,两者不在同一个坐标系里 ——
|
||||||
// 字号越大越明显:头像显得小,而且每往下一行错得越多(看起来和文字分开了)。
|
// 字号越大越明显:头像显得小,而且每往下一行错得越多(看起来和文字分开了)。
|
||||||
// 这里把 PlayRes 改成视频宽高:跟随文件时字号按高度比换算(保持占画面的比例),
|
// 这里只负责改 PlayRes,字号交给 applyFontSize 按最终画布重算。返回是否做过调整。
|
||||||
// 用户显式填了字号就直接用(他看的是成片上的像素)。返回是否做过调整。
|
func unifyDanmuSpace(meta *danmuMeta, vw, vh int) bool {
|
||||||
func unifyDanmuSpace(meta *danmuMeta, vw, vh, fontSizeOverride int) bool {
|
|
||||||
if vw <= 0 || vh <= 0 || (vw == meta.PlayResX && vh == meta.PlayResY) {
|
if vw <= 0 || vh <= 0 || (vw == meta.PlayResX && vh == meta.PlayResY) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
oldX, oldY, oldFs := meta.PlayResX, meta.PlayResY, meta.FontSize
|
oldX, oldY := meta.PlayResX, meta.PlayResY
|
||||||
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
|
|
||||||
}
|
|
||||||
meta.PlayResX, meta.PlayResY = vw, vh
|
meta.PlayResX, meta.PlayResY = vw, vh
|
||||||
lPrintf("弹幕文件的坐标系是 %dx%d(字号 %d),视频是 %dx%d,改按视频坐标系渲染,字号 %d",
|
lPrintf("弹幕文件的坐标系是 %dx%d,视频是 %dx%d,改按视频坐标系渲染", oldX, oldY, vw, vh)
|
||||||
oldX, oldY, oldFs, vw, vh, meta.FontSize)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -103,7 +103,7 @@ web UI(`-webui`,端口看`webPort`)用的是另一套`/api/`接口,全
|
|||||||
|
|
||||||
合并状态通过`GET /api/status`查看:`mergeBusy`(是否在跑)、`mergeOutName`(当前成片名)、`mergeErr`(失败原因)、`mergeDoneAt`(上次完成时间)。合并期间成片会出现在`GET /api/files`里且`inUse`为 true,此时`POST /api/file/remove`会返回 409 拒绝删除。
|
合并状态通过`GET /api/status`查看:`mergeBusy`(是否在跑)、`mergeOutName`(当前成片名)、`mergeErr`(失败原因)、`mergeDoneAt`(上次完成时间)。合并期间成片会出现在`GET /api/files`里且`inUse`为 true,此时`POST /api/file/remove`会返回 409 拒绝删除。
|
||||||
|
|
||||||
`POST /api/config`里的`danmuFontSize`控制合并时弹幕文字的字号(像素),头像按同一比例缩放,填 0 表示沿用弹幕文件里记的字号。渲染前会按最终字号重排弹道:从上游原版`.ass`还原出来的弹幕只按内容宽度占位,而渲染用的是「昵称(uid):内容 + 头像」的完整宽度,占位时间更长,照搬原弹道会追尾叠字;重排按完整宽度重新分配,同一条弹道上不会再有两条弹幕叠在一起。
|
`POST /api/config`里的`danmuFontSize`控制合并时弹幕文字的字号(像素),头像按同一比例缩放,填 0 表示按画面分辨率自动:字号取画面**短边**的 5.56%(横屏取高、竖屏取宽),所以 1920x1080 是 60、1280x720 是 40、720x1280 也是 40 —— 竖屏画面窄,若按高度算会得到 71,一条弹幕就铺满整行。显式填写则按填的像素值直接用。渲染前会按最终字号重排弹道:从上游原版`.ass`还原出来的弹幕只按内容宽度占位,而渲染用的是「昵称(uid):内容 + 头像」的完整宽度,占位时间更长,照搬原弹道会追尾叠字;重排按完整宽度重新分配,同一条弹道上不会再有两条弹幕叠在一起。
|
||||||
|
|
||||||
`mergeBitrate`填 0(默认)时按「源片码率 + 500 Kbps」自动计算:先探测视频流的码率,加 500 留给烧进去的弹幕;探测不到就退回质量模式。填大于 0 的值则按该码率固定输出。编码器优先硬件 HEVC(`hevc_qsv`,同画质比 H.264 省约三成体积,mp4 里打`hvc1`标记),不支持时回退`h264_qsv`、`libx264`,日志里会打印实际用的那一个。
|
`mergeBitrate`填 0(默认)时按「源片码率 + 500 Kbps」自动计算:先探测视频流的码率,加 500 留给烧进去的弹幕;探测不到就退回质量模式。填大于 0 的值则按该码率固定输出。编码器优先硬件 HEVC(`hevc_qsv`,同画质比 H.264 省约三成体积,mp4 里打`hvc1`标记),不支持时回退`h264_qsv`、`libx264`,日志里会打印实际用的那一个。
|
||||||
|
|
||||||
|
|||||||
+112
-13
@@ -123,32 +123,131 @@ func TestRelayoutFixesConcatenatedSegmentConflict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 弹幕坐标系(PlayRes)和视频分辨率不一致时统一到视频坐标系:
|
// 画布短边:横屏取高、竖屏取宽
|
||||||
// 跟随文件时字号按高度比换算,用户填了字号就按成片像素直接用
|
func TestShortSide(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
w, h, want int
|
||||||
|
}{
|
||||||
|
{1920, 1080, 1080},
|
||||||
|
{1080, 1920, 1080},
|
||||||
|
{720, 1280, 720},
|
||||||
|
{1280, 720, 720},
|
||||||
|
{1080, 1080, 1080},
|
||||||
|
{0, 1080, 0},
|
||||||
|
{1920, -1, 0},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := shortSide(c.w, c.h); got != c.want {
|
||||||
|
t.Errorf("shortSide(%d,%d) 应为 %d,实际 %d", c.w, c.h, c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动字号 = 画布短边的 5.56%。竖屏必须按宽度算 ——
|
||||||
|
// 按高度算的话 720x1280 会得到 71,一条弹幕就铺满整行。
|
||||||
|
func TestAutoFontSize(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
vw, vh, want int
|
||||||
|
}{
|
||||||
|
{1920, 1080, 60},
|
||||||
|
{1280, 720, 40},
|
||||||
|
{960, 540, 30},
|
||||||
|
{720, 1280, 40},
|
||||||
|
{640, 1280, 36},
|
||||||
|
{1080, 1920, 60},
|
||||||
|
{1080, 1080, 60}, // 方形按宽算
|
||||||
|
{0, 1080, 0}, // 拿不到尺寸
|
||||||
|
{1920, 0, 0},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := autoFontSize(c.vw, c.vh); got != c.want {
|
||||||
|
t.Errorf("autoFontSize(%d,%d) 应为 %d,实际 %d", c.vw, c.vh, c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 录制端的档位预设必须和自动字号规则一致,否则录出来的弹幕文件和成片对不上
|
||||||
|
func TestSubConfigsMatchAutoFontSize(t *testing.T) {
|
||||||
|
for key, cfg := range subConfigs {
|
||||||
|
want := autoFontSize(cfg.PlayResX, cfg.PlayResY)
|
||||||
|
if cfg.FontSize != want {
|
||||||
|
t.Errorf("档位 %d(%dx%d)字号应为 %d,实际 %d",
|
||||||
|
key, cfg.PlayResX, cfg.PlayResY, want, cfg.FontSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主流程:先把坐标系统一到视频分辨率,再按画布定字号
|
||||||
|
func TestApplyFontSize(t *testing.T) {
|
||||||
|
// 横屏弹幕文件 + 竖屏成片:以前按高度比会放大到 71,现在按短边取 40
|
||||||
|
meta := danmuMeta{PlayResX: 1920, PlayResY: 1080, FontSize: 60}
|
||||||
|
unifyDanmuSpace(&meta, 720, 1280)
|
||||||
|
applyFontSize(&meta, 0)
|
||||||
|
if meta.FontSize != 40 {
|
||||||
|
t.Errorf("竖屏成片字号应为 40,实际 %d", meta.FontSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 横屏成片和弹幕文件一致时保持原样
|
||||||
|
meta = danmuMeta{PlayResX: 1920, PlayResY: 1080, FontSize: 60}
|
||||||
|
unifyDanmuSpace(&meta, 1920, 1080)
|
||||||
|
applyFontSize(&meta, 0)
|
||||||
|
if meta.FontSize != 60 {
|
||||||
|
t.Errorf("1080p 横屏字号应为 60,实际 %d", meta.FontSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 竖屏弹幕文件 + 同尺寸竖屏成片:老文件写的 60 要归一到 40
|
||||||
|
meta = danmuMeta{PlayResX: 720, PlayResY: 1280, FontSize: 60}
|
||||||
|
unifyDanmuSpace(&meta, 720, 1280)
|
||||||
|
applyFontSize(&meta, 0)
|
||||||
|
if meta.FontSize != 40 {
|
||||||
|
t.Errorf("竖屏弹幕文件应归一到 40,实际 %d", meta.FontSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置里填了数就按填的来(成片像素)
|
||||||
|
meta = danmuMeta{PlayResX: 1920, PlayResY: 1080, FontSize: 60}
|
||||||
|
unifyDanmuSpace(&meta, 720, 1280)
|
||||||
|
applyFontSize(&meta, 33)
|
||||||
|
if meta.FontSize != 33 {
|
||||||
|
t.Errorf("填了 33 就该用 33,实际 %d", meta.FontSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拿不到画布尺寸时保留弹幕文件里的字号
|
||||||
|
meta = danmuMeta{FontSize: 48}
|
||||||
|
applyFontSize(&meta, 0)
|
||||||
|
if meta.FontSize != 48 {
|
||||||
|
t.Errorf("拿不到尺寸时应保留 48,实际 %d", meta.FontSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 低分辨率也别算出 0 号字
|
||||||
|
meta = danmuMeta{PlayResX: 4, PlayResY: 4, FontSize: 30}
|
||||||
|
applyFontSize(&meta, 0)
|
||||||
|
if meta.FontSize < 1 {
|
||||||
|
t.Errorf("字号不能小于 1,实际 %d", meta.FontSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕坐标系(PlayRes)和视频分辨率不一致时统一到视频坐标系;
|
||||||
|
// 字号不在这里改,由 applyFontSize 按最终画布决定
|
||||||
func TestUnifyDanmuSpace(t *testing.T) {
|
func TestUnifyDanmuSpace(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
resX, resY, fontSize int
|
resX, resY, fontSize int
|
||||||
vw, vh int
|
vw, vh int
|
||||||
override int
|
|
||||||
wantChanged bool
|
wantChanged bool
|
||||||
wantFontSize int
|
|
||||||
}{
|
}{
|
||||||
{"坐标系一致不动", 1920, 1080, 60, 1920, 1080, 0, false, 60},
|
{"坐标系一致不动", 1920, 1080, 60, 1920, 1080, false},
|
||||||
{"960x540 弹幕配 720x1280 视频(跟随文件)", 960, 540, 30, 720, 1280, 0, true, 71},
|
{"960x540 弹幕配 720x1280 视频", 960, 540, 30, 720, 1280, true},
|
||||||
{"同上但用户填了 42", 960, 540, 30, 720, 1280, 42, true, 42},
|
{"探测不到视频尺寸就不动", 960, 540, 30, 0, 0, false},
|
||||||
{"手机档 720x1280 弹幕配 540x960 视频", 720, 1280, 60, 540, 960, 0, true, 45},
|
{"相同宽高比但不同分辨率也要按视频走", 1280, 720, 40, 1920, 1080, true},
|
||||||
{"探测不到视频尺寸就不动", 960, 540, 30, 0, 0, 0, false, 30},
|
|
||||||
{"相同宽高比但不同分辨率也要按视频走", 1280, 720, 40, 1920, 1080, 0, true, 60},
|
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
meta := danmuMeta{PlayResX: c.resX, PlayResY: c.resY, FontSize: c.fontSize}
|
meta := danmuMeta{PlayResX: c.resX, PlayResY: c.resY, FontSize: c.fontSize}
|
||||||
changed := unifyDanmuSpace(&meta, c.vw, c.vh, c.override)
|
changed := unifyDanmuSpace(&meta, c.vw, c.vh)
|
||||||
if changed != c.wantChanged {
|
if changed != c.wantChanged {
|
||||||
t.Errorf("%s:changed 应为 %v,实际 %v", c.name, c.wantChanged, changed)
|
t.Errorf("%s:changed 应为 %v,实际 %v", c.name, c.wantChanged, changed)
|
||||||
}
|
}
|
||||||
if meta.FontSize != c.wantFontSize {
|
if meta.FontSize != c.fontSize {
|
||||||
t.Errorf("%s:字号应为 %d,实际 %d", c.name, c.wantFontSize, meta.FontSize)
|
t.Errorf("%s:统一坐标系不该动字号,应为 %d,实际 %d", c.name, c.fontSize, meta.FontSize)
|
||||||
}
|
}
|
||||||
if changed {
|
if changed {
|
||||||
if meta.PlayResX != c.vw || meta.PlayResY != c.vh {
|
if meta.PlayResX != c.vw || meta.PlayResY != c.vh {
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ const webuiHTML = `<!DOCTYPE html>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="f"><label>合成码率(Kbps,0 = 源片码率 + 500)</label><input type="text" id="mergeBitrate" inputmode="numeric" placeholder="例如 1500,填 0 自动"></div>
|
<div class="f"><label>合成码率(Kbps,0 = 源片码率 + 500)</label><input type="text" id="mergeBitrate" inputmode="numeric" placeholder="例如 1500,填 0 自动"></div>
|
||||||
<div class="f"><label>弹幕字号(像素,0 = 跟随弹幕文件)</label><input type="text" id="danmuFontSize" inputmode="numeric" placeholder="例如 42,头像随字号一起缩放"></div>
|
<div class="f"><label>弹幕字号(像素,0 = 自动按画面分辨率)</label><input type="text" id="danmuFontSize" inputmode="numeric" placeholder="0 自动,或填例如 42;头像随字号一起缩放"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">文本框改完按回车或点右上角「保存设置」生效,不做这一步的话 3 秒后会被刷回原值。</div>
|
<div class="hint">文本框改完按回车或点右上角「保存设置」生效,不做这一步的话 3 秒后会被刷回原值。</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1641,7 +1641,7 @@ func webConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.DanmuFontSize < 0 {
|
if req.DanmuFontSize < 0 {
|
||||||
http.Error(w, "danmuFontSize 不能小于 0(0 表示跟随弹幕文件)", http.StatusBadRequest)
|
http.Error(w, "danmuFontSize 不能小于 0(0 表示按画面分辨率自动)", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.DefaultBitrate < 0 {
|
if req.DefaultBitrate < 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user