From 6a039dc6a6d71dbc065d15beae059050425586d5 Mon Sep 17 00:00:00 2001 From: Joftal <42807405+Joftal@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:43:44 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E1/4=E3=80=81?= =?UTF-8?q?3/4=E5=BC=B9=E5=B9=95=E5=BD=95=E5=88=B6=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E3=80=82=E4=BC=98=E5=8C=96=E5=BC=B9=E5=B9=95?= =?UTF-8?q?=E9=98=B2=E9=87=8D=E5=8F=A0=E9=80=BB=E8=BE=91=EF=BC=88=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=9F=BA=E4=BA=8E=E6=96=87=E5=AD=97=E5=AE=BD=E5=BA=A6?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=AE=A1=E7=AE=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/configs/config.go | 12 +++-- src/recorders/danmaku/ass.go | 54 +++++++++++++------ .../src/component/danmaku-config/index.tsx | 2 + 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/configs/config.go b/src/configs/config.go index 9d504de..4945cd1 100644 --- a/src/configs/config.go +++ b/src/configs/config.go @@ -79,7 +79,7 @@ func (f *Feature) GetEffectiveDownloaderType() DownloaderType { type DanmakuConfig struct { FontSize int `yaml:"font_size" json:"font_size"` // 字体大小 (12~120) FontName string `yaml:"font_name" json:"font_name"` // 字体名称 - ScrollArea string `yaml:"scroll_area" json:"scroll_area"` // 滚动区域: full(全屏), top(顶部), bottom(底部) + ScrollArea string `yaml:"scroll_area" json:"scroll_area"` // 滚动区域: full(全屏), top(顶部半屏), bottom(底部半屏), quarter(1/4屏), three-quarter(3/4屏) ScrollTime int `yaml:"scroll_time" json:"scroll_time"` // 弹幕滚过屏幕的秒数 (5~20) Resolution string `yaml:"resolution" json:"resolution"` // 播放分辨率 Outline *int `yaml:"outline,omitempty" json:"outline,omitempty"` // 描边粗细 (0~4),nil 表示使用默认值 @@ -115,9 +115,11 @@ var defaultDanmakuConfig = DanmakuConfig{ // validScrollAreas 支持的滚动区域 var validScrollAreas = map[string]bool{ - "full": true, // 全屏滚动 - "top": true, // 仅在屏幕上半部分滚动 - "bottom": true, // 仅在屏幕下半部分滚动 + "full": true, // 全屏滚动 + "top": true, // 仅在屏幕上半部分滚动 + "bottom": true, // 仅在屏幕下半部分滚动 + "quarter": true, // 仅在屏幕上1/4部分滚动 + "three-quarter": true, // 仅在屏幕上3/4部分滚动 } // validResolutions 支持的分辨率列表 @@ -221,7 +223,7 @@ func (d *DanmakuConfig) ValidateWithPlatform(platformKey string) error { return fmt.Errorf("字体名称不能为空") } if !validScrollAreas[d.ScrollArea] { - return fmt.Errorf("不支持的滚动区域: %s,可选值: full, top, bottom", d.ScrollArea) + return fmt.Errorf("不支持的滚动区域: %s,可选值: full, top, bottom, quarter, three-quarter", d.ScrollArea) } if d.ScrollTime < 5 || d.ScrollTime > 20 { return fmt.Errorf("滚动时间必须在 5~20 秒之间,当前值: %d", d.ScrollTime) diff --git a/src/recorders/danmaku/ass.go b/src/recorders/danmaku/ass.go index 1e54003..182cc7e 100644 --- a/src/recorders/danmaku/ass.go +++ b/src/recorders/danmaku/ass.go @@ -28,7 +28,7 @@ type AssWriter struct { laneEnd int // last usable lane index (exclusive) laneNum int // total lanes in the usable range nextLane int - laneLast []int64 // last end time (centiseconds) per lane + laneLast []int64 // last tail-clear time (centiseconds) per lane } func parseResolution(res string) (int, int) { @@ -72,6 +72,10 @@ func NewAssWriter(filePath string, startAt time.Time, cfg configs.DanmakuConfig, laneEnd = totalLanes / 2 case "bottom": laneStart = totalLanes / 2 + case "quarter": + laneEnd = totalLanes / 4 + case "three-quarter": + laneEnd = totalLanes * 3 / 4 } laneNum := laneEnd - laneStart if laneNum < 1 { @@ -84,14 +88,14 @@ func NewAssWriter(filePath string, startAt time.Time, cfg configs.DanmakuConfig, cfg: cfg, title: title, resX: resX, - resY: resY, + resY: resY, scrollTimeMs: scrollTimeMs, - bannerSpeed: bannerSpeed, - laneStart: laneStart, - laneEnd: laneEnd, - laneNum: laneNum, - nextLane: 0, - laneLast: make([]int64, laneNum), + bannerSpeed: bannerSpeed, + laneStart: laneStart, + laneEnd: laneEnd, + laneNum: laneNum, + nextLane: 0, + laneLast: make([]int64, laneNum), } if err := w.writeHeader(); err != nil { @@ -251,7 +255,12 @@ func (w *AssWriter) AddDanmaku(recvAt time.Time, username, text string, color in } endCS := startCS + durationCS - lane := w.assignLane(startCS, endCS) + lane, adjustedStartCS := w.assignLane(startCS, endCS, textWidth) + // 基于文字宽度的防重叠:使用调整后的起始时间 + if adjustedStartCS != startCS { + startCS = adjustedStartCS + endCS = startCS + durationCS + } laneHeight := w.cfg.FontSize + 4 marginV := (lane + w.laneStart) * laneHeight @@ -298,7 +307,12 @@ func (w *AssWriter) AddGift(recvAt time.Time, username, giftName string, num int } endCS := startCS + durationCS - lane := w.assignLane(startCS, endCS) + lane, adjustedStartCS := w.assignLane(startCS, endCS, textWidth) + // 基于文字宽度的防重叠:使用调整后的起始时间 + if adjustedStartCS != startCS { + startCS = adjustedStartCS + endCS = startCS + durationCS + } laneHeight := w.cfg.FontSize + 4 marginV := (lane + w.laneStart) * laneHeight @@ -373,24 +387,34 @@ func (w *AssWriter) AddSuperChat(recvAt time.Time, username, text string, price } } -func (w *AssWriter) assignLane(startCS, endCS int64) int { +// assignLane 分配一个空闲 lane,基于文字宽度的防重叠。 +// 参数:startCS 弹幕开始时间,endCS 弹幕结束时间,textWidth 文字像素宽度。 +// 返回值:lane 索引、调整后的 startCS(可能延迟)。 +func (w *AssWriter) assignLane(startCS, endCS int64, textWidth int) (int, int64) { + // 优先找空闲 lane(前一条弹幕的尾部已离开屏幕右侧) for i := 0; i < w.laneNum; i++ { idx := (w.nextLane + i) % w.laneNum if w.laneLast[idx] <= startCS { - w.laneLast[idx] = endCS + // 存储该弹幕尾部离开右侧的时间点(用于下一条弹幕判断) + tailClearCS := startCS + int64(w.scrollTimeMs)*int64(textWidth)/int64(w.resX)/10 + w.laneLast[idx] = tailClearCS w.nextLane = (idx + 1) % w.laneNum - return idx + return idx, startCS } } + // 所有 lane 都被占用,延迟到最早可用的时间点 earliest := 0 for i := 1; i < w.laneNum; i++ { if w.laneLast[i] < w.laneLast[earliest] { earliest = i } } - w.laneLast[earliest] = endCS + newStartCS := w.laneLast[earliest] + // 存储新弹幕尾部离开右侧的时间点 + tailClearCS := newStartCS + int64(w.scrollTimeMs)*int64(textWidth)/int64(w.resX)/10 + w.laneLast[earliest] = tailClearCS w.nextLane = (earliest + 1) % w.laneNum - return earliest + return earliest, newStartCS } func (w *AssWriter) OutputPath() string { diff --git a/src/webapp/src/component/danmaku-config/index.tsx b/src/webapp/src/component/danmaku-config/index.tsx index ff4c84f..bea9902 100644 --- a/src/webapp/src/component/danmaku-config/index.tsx +++ b/src/webapp/src/component/danmaku-config/index.tsx @@ -173,6 +173,8 @@ const DanmakuParamForm: React.FC<{ { label: '全屏', value: 'full' }, { label: '顶部半屏', value: 'top' }, { label: '底部半屏', value: 'bottom' }, + { label: '1/4屏', value: 'quarter' }, + { label: '3/4屏', value: 'three-quarter' }, ]} />