合并前重排弹道并支持自定义弹幕字号
用户反馈两件事:弹幕想再小些;某场合并的 1:19:25 处弹幕糊在一起(把成片抽帧 出来一看,是同一弹道上两条弹幕追尾叠字)。 重叠的根因:弹幕从上游原版的 .ass 还原成 jsonl 时,lane 是按「只有内容」的 宽度反推的(原格式的 x/y 就是这么摆的),而渲染用的是「昵称(uid):内容 + 头像」 的完整宽度 —— 后者占位时间长得多,照搬原弹道必然追尾。用用户那份 3011 条的真实 jsonl 验过:按渲染公式统计,重排前 960 条(31.9%)追尾,重排后 0 条。 - 新增 relayoutLanes:渲染前按最终字号统一重排弹道并重算宽度,公式和录制时的 placeLane 完全一致,所以录制下来的 jsonl 重排后弹道不变(已加用例覆盖)。 - 空弹道用 math.MinInt64 占位:placeLane 的条件是「本条出现时间晚于上一条的让位 时刻」,零值下 0 > 0 不成立,第一条弹幕(还原出来的时间可能正好是 0)会占不上 弹道、占用状态丢失,后面又跟着乱排。录制的弹幕时间总大于 0,所以原逻辑没暴露。 - 弹道全满(100 条都占着)时退到最早让出来的那条:录制时是直接不写这条,合并时 丢一条等于凭空少一句弹幕,压一点也比丢了强。 - 新增 danmuFontSize 设置(合并分区,0 = 跟随弹幕文件):覆盖渲染字号,头像按 同一比例缩放;改了字号会在重排时一并生效,不会因为宽度变了又追尾。 验证:layout_test.go 5 个用例;用新 ffmpeg(用户已自行升级到 n9.0.2)在隔离实例上 跑了真合并 —— 字号 95 以上的对照(60px → 42px:抽帧量到的弹幕墨迹高度 48px → 36px)、 按 1:19:00 裁出的 130 秒片段合并前后追尾 25 条 → 0 条;重跑 verify_settings.py (新增第 8 节覆盖字号字段)、verify_pick.py、verify_streamer_br.py 无回归; go test ./...、gofmt、go vet 干净。
This commit is contained in:
@@ -308,6 +308,7 @@ const webuiHTML = `<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f"><label>合成码率(Kbps,0 = 默认质量)</label><input type="text" id="mergeBitrate" inputmode="numeric" placeholder="例如 1500"></div>
|
||||
<div class="f"><label>弹幕字号(像素,0 = 跟随弹幕文件)</label><input type="text" id="danmuFontSize" inputmode="numeric" placeholder="例如 42,头像随字号一起缩放"></div>
|
||||
</div>
|
||||
<div class="hint">文本框改完按回车或点右上角「保存设置」生效,不做这一步的话 3 秒后会被刷回原值。</div>
|
||||
</div>
|
||||
@@ -477,8 +478,8 @@ var token = localStorage.getItem("acfunlive_token") || ""; // 管理口令,存
|
||||
var authed = false; // 是否已通过口令校验,未通过时停止轮询
|
||||
var tokenLoaded = false;
|
||||
// 设置表单里的字段,元素 id 与 /api/config 的字段名一致
|
||||
var settingFields = ["directory","autoMerge","mergeDelVideo","mergeDelDanmu","mergeOutputDir","mergeBitrate","defaultBitrate","recordSubdir","mergeSubdir","ffmpegPath","recordFormat","notifyEnabled","notifyUrl","notifyToken","notifyGroup","notifyLiveOn","notifyLiveOff","notifyRecordStart","notifyMergeDone","meowEnabled","meowNickname","meowMsgType","meowHtmlHeight","meowImgUrl","webExternal"];
|
||||
var numberFields = {mergeBitrate:1, defaultBitrate:1, meowHtmlHeight:1};
|
||||
var settingFields = ["directory","autoMerge","mergeDelVideo","mergeDelDanmu","mergeOutputDir","mergeBitrate","danmuFontSize","defaultBitrate","recordSubdir","mergeSubdir","ffmpegPath","recordFormat","notifyEnabled","notifyUrl","notifyToken","notifyGroup","notifyLiveOn","notifyLiveOff","notifyRecordStart","notifyMergeDone","meowEnabled","meowNickname","meowMsgType","meowHtmlHeight","meowImgUrl","webExternal"];
|
||||
var numberFields = {mergeBitrate:1, danmuFontSize:1, defaultBitrate:1, meowHtmlHeight:1};
|
||||
// 用户改过、还没提交的字段。定时刷新会拿服务端的值填表单,不记下这些字段的话,
|
||||
// 刚填好的数字 3 秒后会被刷回旧值,看起来就像「改了又自己恢复」
|
||||
var dirty = {};
|
||||
@@ -642,6 +643,7 @@ async function refresh(){
|
||||
setVal("directory", st.directory||"");
|
||||
setVal("mergeOutputDir", st.mergeOutputDir||"");
|
||||
setVal("mergeBitrate", st.mergeBitrate||0);
|
||||
setVal("danmuFontSize", st.danmuFontSize||0);
|
||||
setVal("defaultBitrate", st.defaultBitrate||0);
|
||||
setVal("recordSubdir", st.recordSubdir||"record");
|
||||
setVal("mergeSubdir", st.mergeSubdir||"merged");
|
||||
@@ -1261,6 +1263,7 @@ type webStatus struct {
|
||||
MergeDelDanmu bool `json:"mergeDelDanmu"`
|
||||
MergeOutputDir string `json:"mergeOutputDir"`
|
||||
MergeBitrate int `json:"mergeBitrate"`
|
||||
DanmuFontSize int `json:"danmuFontSize"`
|
||||
DefaultBitrate int `json:"defaultBitrate"`
|
||||
WebExternal bool `json:"webExternal"`
|
||||
RecordSubdir string `json:"recordSubdir"`
|
||||
@@ -1321,6 +1324,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
MergeDelDanmu: config.MergeDelDanmu,
|
||||
MergeOutputDir: config.MergeOutputDir,
|
||||
MergeBitrate: config.MergeBitrate,
|
||||
DanmuFontSize: config.DanmuFontSize,
|
||||
WebExternal: config.WebExternal,
|
||||
RecordSubdir: config.RecordSubdir,
|
||||
MergeSubdir: config.MergeSubdir,
|
||||
@@ -1398,6 +1402,7 @@ func webConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
MergeDelDanmu bool `json:"mergeDelDanmu"`
|
||||
MergeOutputDir string `json:"mergeOutputDir"`
|
||||
MergeBitrate int `json:"mergeBitrate"`
|
||||
DanmuFontSize int `json:"danmuFontSize"`
|
||||
DefaultBitrate int `json:"defaultBitrate"`
|
||||
WebExternal bool `json:"webExternal"`
|
||||
RecordSubdir string `json:"recordSubdir"`
|
||||
@@ -1435,6 +1440,10 @@ func webConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "mergeBitrate 不能小于 0", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.DanmuFontSize < 0 {
|
||||
http.Error(w, "danmuFontSize 不能小于 0(0 表示跟随弹幕文件)", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.DefaultBitrate < 0 {
|
||||
http.Error(w, "defaultBitrate 不能小于 0", http.StatusBadRequest)
|
||||
return
|
||||
@@ -1503,6 +1512,7 @@ func webConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
config.MergeDelDanmu = req.MergeDelDanmu
|
||||
config.MergeOutputDir = req.MergeOutputDir
|
||||
config.MergeBitrate = req.MergeBitrate
|
||||
config.DanmuFontSize = req.DanmuFontSize
|
||||
config.DefaultBitrate = req.DefaultBitrate
|
||||
restartWeb := config.WebExternal != req.WebExternal
|
||||
config.WebExternal = req.WebExternal
|
||||
|
||||
Reference in New Issue
Block a user