hihkm / DanmakuFactory

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

非常棒的项目👍,有一些问题想请教下作者大大 #47

Open HFrost0 opened 1 year ago

HFrost0 commented 1 year ago

我做下载工具的时候碰上过python转换弹幕文件太慢的问题,所以自己重写一个C++版本的转换器,但是因为我个人对弹幕和ass格式的了解不够所以很多功能还不完善。今天才看到大大的项目,想请教下:

由于弹幕网站提供的弹幕接口不一样,例如b站就由过去的xml格式变成了现在的protobuf,能否有一个小的c模块来实现核心的转换功能,例如下面的伪代码:

class Ass:
    def __init__(self,
                 width: int,
                 height: int,
                 reserve_blank: int = 0,
                 font_face: str = "sans-serif",
                 font_size: float = 25.0,
                 alpha: float = 1.0,
                 duration_marquee: float = 5.0,
                 duration_still: float = 5.0,
                 filter: str = "",
                 reduced: bool = False): ...

    def add_comment(self, progress: float, ctime: int, content: str, font_size: float, mode: int, color: int) -> bool:
        """add a comment to Ass object, return True if add success"""

    def to_string(self) -> str:
        """return the ass text"""

    def write_to_file(self, str) -> None:
        """direct write to the file to avoid memory cost"""

而解析proto,xml的工作交给py这类语言,因为可以更快适应网站的变化,适配更多网站。画一个简单的图来说就是:

                      py       c
.xml,.json,.proto...----->data----->.ass

我对c和c++包括ass都不够熟悉,所以想问问作者大大抽离出这个功能可行吗

hihkm commented 1 year ago

你好,抽离出这个功能是可行的,为了兼容更多弹幕格式,设计初就有中间 data 一层抽象,以下是一个简单例子,可以按需取其中的一部分或者打成个动态库,理论上并没有什么耦合。

#include "Define/DanmakuDef.h"
#include "Config/Config.h"
#include "CDanmakuFactory.h"
#include "AssFile/AssFile.h"
#include "List/DanmakuFactoryList.h"
int main()
{
    DANMAKU *danmakuPoorHead = NULL;  // 弹幕池的头指针
    CONFIG config = { ... }  // 配置项参照 Config/Config.h 定义

    /* 读 xml */
    readXml("test.xml",  // 要读取的xml文件名
            &danmakuPoorHead,  // 弹幕池头指针的指针,需要把指针的指针传进去(好蠢,不知道为啥要这样做,这玩意儿会在执行的时候被改指向,不能通过赋值来拷贝
            "n",  // 读取方式,"n"清空之前弹幕池的内容并新建 "a"在之前弹幕池的基础上追加
            0.00,  // 时轴偏移量
            NULL  // 处理状态 用于多线程获取进度 可以不传
           );
    /* 排序弹幕池 */
    sortList(&danmakuPoorHead,  // 弹幕池头指针的指针
             NULL // 处理状态 用于多线程获取进度 可以不传
            );
    /* 写 ass */
    writeAss("test.ass",  // 输出文件名
             danmakuPoorHead,  // 弹幕池头指针,注意这里却是一维指针
             config,  // 配置
             NULL,  // 字幕 空表示不插入(年代久远,这个好像只是预留但没有做)
             NULL  // 处理状态 用于多线程获取进度 可以不传
            );
    return 0;
}

关于你刚才说的python转换太慢的问题,可以细说一下是有多慢,慢的地方在哪里吗~ 因为这个项目写的太拉太难维护了,正在用python做重构,全部的业务逻辑都做插件化,开发者可以只实现其关心的任意一小部分。现本体已经写好在做插件了,因此比较关心性能问题qwq

HFrost0 commented 1 year ago

当弹幕数量较少时看不出来,但是现在b站的电影和电视剧弹幕由于时间长数量大所以转换时间成本高,在我的转换器项目README中有做测试,大概50秒左右才能完成,而c++只需要0.2秒左右。

HFrost0 commented 1 year ago

看您的定义似乎没有一条条添加弹幕的接口,在原本我重构的项目中,性能瓶颈主要在于向ass对象添加新的弹幕上

def add_comment(self, progress: float, ctime: int, content: str, font_size: float, mode: int, color: int) -> bool:
        """add a comment to Ass object, return True if add success"""

有这个接口,我们甚至可以混合多个网站的弹幕,而不是转换某个特定网站的xml文件

hihkm commented 1 year ago

了解了~没有定义这个接口,其实可以简单实现一下,弹幕池是一个单向链表,往链表后面补一条就可以达到追加的效果了。

HFrost0 commented 1 year ago

好的,根据您的提示我刚刚粗略看了readXml这个函数,似乎是针对bilibili xml的处理函数,可以把xml中的数据读入链表中。但似乎和b站耦合了,我非常希望抽象出之前我提到的Ass类,Ass类是和平台无关的一个类,上游任务主要是把数据简单处理为Ass.add_comment接受的格式,例如提供时间戳,内容字符串等,这个上游任务是平台相关的,甚至随时受到接口变动影响的,所以交给灵活的胶水语言来做,而c模块做为底层加快转换速度。

您关心的性能瓶颈位置,py执行循环的速度,字符串format的速度都远低于c,所以核心部分非常需要c的介入

不知道这样的思路是否可行,在niconico弹幕和bilibili弹幕上我做了这样的尝试

问题在于我虽然重构了之前其他大佬写的库,但是我本人对其中的逻辑实在是欠缺了解,所以很难维护好它,不知道大佬是否有兴趣提供这类接口,或者为danmakuC提供一些代码完善它,成为它的核心维护者🥹

hihkm commented 1 year ago

是的,readXml()是针对 bilibili 的处理函数,但是sortList()writeAss()是平台无关的,把readXml()换成一个往链表里面 push back 的函数大概就可以实现你想要的功能~ 链表的node结构如下,是很通用的属性:

// file: Define/DanmakuDef.h
struct SingleDanmaku
{/* 弹幕节点定义 */
    float time;            /* 开始时间 */ 
    short type;            /* 弹幕类型 */ 
    short fontSize;        /* 字体大小 */ 
    int color;             /* 文字颜色 */ 
    char *text;            /* 文本内容 */
    struct UserPart *user; /* 用户信息 */
    struct GiftPart *gift;      /* 礼物属性 */
    struct SpecialDanmakuPart *special;  /* 特殊弹幕的额外属性 */ 
    struct SingleDanmaku *next;
};

论性能,这玩意儿绝对快,最接地气的字符数组,链表操作,几乎没有多余拷贝😂 尽管如此,也正因为如此,我不太建议抽出来直接简单封装,因为目前这部分代码已经很难维护了~ 我现在正在用python写一个ass的包,目标是能轻松用ass画出简单图形或者动画,以及实现一个ass parser,与弹幕处理无关,但是能方便之后的开发。我现在纠结是不是应该用C++来做这个事情~ 我看了一下大佬项目的实现,目前的处理是有什么问题嘛~