hihkm / DanmakuFactory

支持特殊弹幕的xml转ass格式转换工具
MIT License
577 stars 32 forks source link

大佬,请问个问题 #68

Closed holwell closed 1 year ago

holwell commented 1 year ago

就是当设置了显示区域后,势必在一个弹幕量多的时点会对弹幕进行显示和屏蔽的判断,也就是在ass中标记为message或者comment,那么这个屏蔽的依据是什么呢?换言之就是弹幕屏蔽和显示的优先级是什么?

  1. 据我所知,xml弹幕有一个字段表示user_level的,您是用这个字段来进行优先级筛选的呢,还是其他逻辑呢。
  2. 之所以有此类需求,是因为我想控制哪些用户的弹幕被优先显示,例如主播弹幕优先,永远不被过滤,这样我就可以在写入xml弹幕文件时,自定义它们的优先级。
hihkm commented 1 year ago

目前是先入为主的原则,没有特别的策略~

holwell commented 1 year ago

目前是先入为主的原则,没有特别的策略~

作者大佬,其实提这个防止屏蔽和优先级的问题,因为那样还可以将sc以弹幕形式的方式嵌入,然后我会基于ass添加一个类似气泡的效果。 我有一个天才想法,虽然我没有看到您的具体实现(看项目级的c语言一坨浆糊),但是我猜测逻辑应该是按时间从上到下渲染列,然后基于每行中弹幕的长度和速度计算下一次该行可出现的弹幕时间,然后以该时间为基准升序的装填下一条弹幕,如果某条弹幕的出现时间小于所有行的可出现弹幕时间,就标记为comment屏蔽掉,当然中间可能还有一些基于弹幕密度进行弹幕时间前后的微调。

如果是这样的话,基于user_level的排序基本不可能实现,也是没有必要的,因为每条弹幕的装填逻辑是基于时间的,基于user_level的防屏蔽则需要回退已装填弹幕,回退又会影响各行原来的时间线,新的时间线又要对user_level判断,太麻烦了。

但是,我前面所说防止屏蔽,应该还是能实现的,就是在出现特殊标记的弹幕时,例如sc弹幕或者特殊字段标记(例如user_level标记为999)的弹幕时,原本的逻辑是进行弹幕时间判断,如果它的时间小于所有行可出现的弹幕时间时,屏蔽掉,但是我们简单的将其与第一行或者最近渲染的弹幕行进行弹幕内容替换即可。这样既做到了防屏蔽,又不会破坏后续碰撞逻辑的计算。

说了这么多,我没有任何想让作者大大进行额外工作的意思!您没义务没责任搭理这些,包括看这篇废话issue!我纯粹是想分享一下自己幼稚的逻辑罢了,再次感谢作者!伟大无需多言!

holwell commented 1 year ago

又或者说其实不需要将标记为防屏蔽的弹幕与该列弹幕中的某行调换,直接将其添加到下一列或者说下一轮中里的可出现弹幕时间最早的行中即可!天才!

hihkm commented 1 year ago

言重啦,这么垃圾的项目都有人看,能跟这么多大佬讨论问题才是我的荣幸~

替换前面弹幕的内容是不合理的,每条弹幕从右侧出现滚动到左边消失这个时间是固定的,所以决定弹幕速度的是弹幕的文本长度,直接替换的要求是替换与被替换的弹幕文本长度相同,否则依然有“碰撞”的可能。

如果是延迟显示的话确实是一个简单有效的方法,但是我在想这会不会导致弹幕会有滞后的问题,如果较多这种high level的弹幕,这种影响被逐渐放大,对于比如同传这类高优先级的弹幕会有比较大的影响。

我觉得可以有一种计算多条弹幕的综合优先级的算法,如果一条弹幕挤不下去的话,按行从上到下找,评估如果要硬塞下这条弹幕需要冲掉前面弹幕的条数,被冲掉的这几条弹幕综合的优先级是多少,找一行影响最小的,把那几条弹幕直接屏蔽掉;如果全部行的综合优先级都大于新来的这条弹幕,那新来的这条弹幕就被屏蔽。

唔,这样效果应该还行,但是回溯的次数有点太多了~

lengyanyu258 commented 1 year ago

我想了一种暴力计算的方式

假定每条弹幕的

  1. 入场时间
  2. 基于 User Level 或其它形式的优先权重

这两点是固定不变的, 那么每条弹幕的排版只与选择出现在哪一行有关,相同的入场时间代表是相同的列。

首先将每条弹幕都重复排版在每一行中,直到 ~从左至右出现在屏幕内的~ 所有弹幕都排版完毕 然后根据弹幕优先级从高到低一个一个计算该优先弹幕在该列中每一行会覆盖掉的弹幕数量, 最终选取计算后数量最少的那行保留下来,其余被覆盖掉的弹幕皆删去, 以此类推,直到每条弹幕都不发生碰撞且只出现一次为止。

当然细节上还有许多可讨论的余地。

简化一下这就是带有权重的长方条在长幕布中的排版优化问题, 每条弹幕的首字开头与尾字结束就是它的首尾坐标, 幕布总长度是最后一条弹幕的尾坐标, 在保证高权重弹幕存活的情况下填充进去最多数量的长方形。 不晓得有没有相关的最优化算法。

hihkm commented 1 year ago

是的,其实我感觉差不多是一个意思。

目前的弹幕转换过程就是类似你说的排版的逻辑,但是对于弹幕优先级的处理,等全部弹幕排版完成再进行是不合适的,因为当某一行的一条或多条弹幕被抹除后,后面时轴的全部弹幕都是需要重新排版的。

换个说法,某一行的某个时间窗口用来“显示一条弹幕”还是“两条弹幕”对后面弹幕的排版有巨大影响。

比如一共有两行,第一行显示两条弹幕,第二行显示两条弹幕,但是跟第一行重叠的还有一条优先级高的弹幕,为了显示这条弹幕,把第一行原有的两条都屏蔽了,这种情况下,第二行的某条弹幕也许是可以塞回第一行而没有冲突的。

原则上是能尽量用一行排下来的,绝对不用两行,不然看上去就是弹幕零散地在整个屏幕里面飘。

还有一个就是目前的弹幕排版逻辑是像素级的,也就说“一行”的概念其实是一像素,因为弹幕的文字大小会不一样,用尽量少的行还需要考虑文字的大小。

lengyanyu258 commented 1 year ago

目前的弹幕转换过程就是类似你说的排版的逻辑,但是对于弹幕优先级的处理,等全部弹幕排版完成再进行是不合适的,因为当某一行的一条或多条弹幕被抹除后,后面时轴的全部弹幕都是需要重新排版的。

这里我说的『全部排版完成』指的是无视重复、无视冲突的情况下先进行有冲突的排版,然后再根据情况消去冲突,保留优先级更高的弹幕。

因此我假定了每条弹幕首先会占据一整列——即该列的所有行都重复出现了这条弹幕。 不过我前文的描述有漏洞:

然后根据弹幕优先级从高到低一个一个计算该优先弹幕在该列中每一行会覆盖掉的弹幕数量

在这条假定下『覆盖掉的弹幕数量』是不会出现有所不同的,而是都会相同(想象一下只有一行高的幕布)。所以在这种情况下只会出现高优先级弹幕仅在第一行保留并且除去了其余被覆盖的弹幕。

按照这个逻辑,其实现的结果必然是第一行最先被排满,其次为第二行、第三行……

比如一共有两行,第一行显示两条弹幕,第二行显示两条弹幕,但是跟第一行重叠的还有一条优先级高的弹幕,为了显示这条弹幕,把第一行原有的两条都屏蔽了,这种情况下,第二行的某条弹幕也许是可以塞回第一行而没有冲突的。

按照这个例子说明: 假定一共有 S、A、B、C、D 5 条弹幕,优先级从高到低,其中 S 弹幕为最高优先级。

你的例子是在说明,在完成了

A B
C D

排版时,插入 S 弹幕后会变成

S
C D

但实际上需要调整为

S D
C

而我的算法是,首先完成有冲突的全部弹幕排版

(S A C) (B D)
(S A C) (B D)

再保留最高优先级的弹幕,变为

 S    (B D)
(A C) (B D)

最后依此类推:

 S    (B D) ->  S    B -> S B
(A C) (B D) -> (A C) D -> A D

这种做法等价于, (需要预先按优先级排好序) 先固定将最高优先级的排列在第一行,若排不下则下移一行, 按照优先级顺序以此类推,若优先级相同,则从上往下依次插空排进去 最后排满整列。 其流程为:

? ? -> S ? -> S ? -> S B -> S B
? ? -> ? ? -> A ? -> A ? -> A D

其中计算量最大的地方就是每个弹幕长条的首尾坐标碰撞检测。 难点在于优先级相同时的排列策略。 选择更多的弹幕数量还是选择恰好的弹幕长度,怎样排列使得在长幕布中排进去的数目最多。 不过在限定了优先排满前排行数的条件下,这个问题的解法可能就没那么困难了。


然而我这里有个漏洞, 由于弹幕的出现与消失时间是固定的,导致长弹幕的移动速度更快, 所以套用在长幕布中填充长方条的模型是不准确的。 但假如我们认为出现与消失的固定时间是单个字而非整条弹幕, 那么所有弹幕的运动速度就会是一样的,从而长幕布模型能准确描述了。 这也导致不同字数的弹幕时长不同,需要能精确计算运行一个字宽的长度所花费的时间。


实际试着修改时感到工程量与困难度还是蛮大的,估计跟这次修改消息显示功能一样需要近一个月时间。所以先溜了等明年有空时再来改吧……(逃