tamakyi 40f4f36aa6 自动合并按文件实际位置找弹幕,修掉「jsonl 文件不存在」
为什么:

录制结束时 moveFile 已经把 .ass / .nick.ass / .jsonl 搬到保存目录,但合并时只有视频
分段走了 findMovedFile 兜底,弹幕 jsonl 一直拿着录制时算出来的旧路径。于是文件明明
安安稳稳躺在保存目录里,burnDanmu 却去旧位置找,自动合并以「jsonl 文件不存在」告终
并推送一条失败通知。

连带还有两处也指向错误位置:tempMergeOutput 用 filepath.Dir(jsonlFile) 决定中间成片
写到哪个目录,mergeDelDanmu 按它去删那三个弹幕文件。

处理方式是新增 resolveAfterMove(path)——找不着就退回原路径,这样报错还能说清本来想
找哪儿——然后在两处归一:prepareForMerge 让返回的 jsonl 是解析后的路径;autoMergeJob
里也要归一,因为中间成片写到哪、事后删哪几个文件都看这条路径。

上一个提交(录制直接写保存目录)落地后这条会退化成空操作,文件压根不再挪窝;留着它
是为了兜住「运行中改了保存目录」「老录播补合并」这类场景。

验证:

- 新增 automerge_test.go 两条:prepareForMerge 返回的视频与 jsonl 都从录制位置解析到
  保存位置;目标目录里没有同名文件时退回原路径而不是返回空串。
- go test ./... 23.6s 与 go test -race ./... 26.4s 均全绿,215 个顶层用例。
- 反向验证新增一项:删掉 prepareForMerge 里那句归一,对应用例确实变红。
- 验证边界说明:测到的是路径解析这一层,没有构造一整场录制跑真 ffmpeg 走完合并流程
  ——那需要一整套直播素材,成本远高于收益。
2026-09-28 02:04:10 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00
2026-09-25 13:16:54 +08:00

acfunlive

AcFun 直播弹幕和录播助手(免登录,只用 uid 列表指定要录制的主播)

依赖

运行依赖

  • ffmpeg(录制直播视频和合并弹幕需要,可以放在本程序所在文件夹、加入 PATH,或在设置里用ffmpegPath指定位置)

编译依赖

  • go
  • GNU Make(Linux 下可选)

编译

使用 GNU Make

# 更新repo需另外运行 git submodule update --remote --merge
git clone --recursive https://github.com/orzogc/acfunlive.git
cd acfunlive
# 编译GUI版本运行 make build-gui ,编译Windows版本运行 make build-windows-gui 或 make build-windows-cli
make

编译好的文件在 bin 文件夹下

不使用 GNU Make

# 更新repo需另外运行 git submodule update --remote --merge
git clone --recursive https://github.com/orzogc/acfunlive.git
cd acfunlive
go build

web UI 已经内嵌在程序里,不需要另外编译前端。

交叉编译 Windows 版本:

GOOS=windows GOARCH=amd64 go build -o acfunlive.exe

配置文件详解

可以先运行一次本程序以生成配置文件。

配置文件config.json默认保存在本程序所在文件夹内,运行时可用参数-config指定配置文件所在文件夹,大部分设置修改后会自动生效,无需重新启动本程序。

{
    "uids": [                   // 要录制的主播列表
        {
            "uid": 23682490,    // 主播的 uid
            "danmu": true,      // 是否录制弹幕
            "record": true,     // 是否录制直播视频
            "qualityPref": "second", // 档位偏好:top(最高档)/ second(第二高)/ lowest(最低档)/ custom(自定义)
            "bitrate": 0,       // 只在 qualityPref 为 custom 时有意义:不超过这个码率(Kbps)的最高档
            "qualities": []     // 开播时读到的码率档位列表,缓存下来让没开播时也能显示对应关系
        }
    ],
    "directory": "",            // 录播和弹幕文件的保存根目录,录制时就直接写在这里;留空用运行时的 -record 文件夹
    "webPort": 51880,           // web UI 的端口,为 0 时不启动 web UI

    "autoMerge": false,         // 直播结束后自动把弹幕层合并进录播
    "mergeDelVideo": false,     // 合并完成后删除源视频
    "mergeDelDanmu": false,     // 合并完成后删除弹幕文件(ass、nick.ass、jsonl)
    "mergeOutputDir": "",       // 合并完成后成片移动到该文件夹,留空则不移动
    "mergeBitrate": 0,          // 合并成片的目标码率(Kbps),0 表示按「源片码率 + 500」自动计算
    "danmuFontSize": 0,         // 合并时弹幕文字的字号(像素),0 表示按画面分辨率自动(短边 × 5.56%,横屏取高、竖屏取宽);头像随字号等比缩放
    "defaultQualityPref": "second", // 新添加主播的默认档位偏好,取值同上面的 qualityPref
    "defaultBitrate": 0,        // 只在上面为 custom 时有意义:默认的自定义码率上限(Kbps)

    "webExternal": false,       // 是否允许外网访问 web UI,false 时只监听本机 127.0.0.1
    "authToken": "…",           // web UI 管理接口的访问口令,首次启动自动生成

    "notifyEnabled": true,      // QQ 机器人(webhook)推送的渠道开关,关掉后即使填了地址也不推送
    "notifyUrl": "",            // 开播/下播等事件推送到该地址(HTTP POST JSON),留空不推送
    "notifyToken": "",          // 推送时附带的 Bearer 口令,留空不带
    "notifyGroup": "",          // 推送时带上的群号,机器人可以直接用它决定发到哪个群
    "notifyLiveOn": false,      // 开播时推送
    "notifyLiveOff": false,     // 下播时推送
    "notifyRecordStart": false, // 开始录制直播视频时推送
    "notifyMergeDone": false,   // 合并完成时推送(自动合并和手动合并都会推)

    "meowEnabled": false,       // MeoW 推送的渠道开关
    "meowNickname": "",         // MeoW 昵称,推送地址里的路径参数
    "meowMsgType": "text",      // MeoW 消息类型:text(纯文本)或 html
    "meowHtmlHeight": 200,      // MeoW 在 msgType 为 html 时的显示高度(像素)
    "meowImgUrl": "",           // MeoW 通知图标 URL,建议 216×216 的 PNG,留空用默认图标

    "ffmpegPath": "",           // ffmpeg 可执行文件(或它所在文件夹)的路径,留空则自动查找
    "recordFormat": "mp4",      // 录播文件的封装格式:mp4、flv、ts、mkv
    "recordSubdir": "record",   // 录播原片和弹幕文件存放在保存目录下的子文件夹名
    "mergeSubdir": "merged"     // 合并成片存放在保存目录下的子文件夹名
}

uids也兼容旧的纯数字数组格式,如"uids": [23682490, 12345],此时弹幕和录播都默认开启、码率按实际档位取第二档。

qualityPref是按档位里的第几档来选的,不是写死某个码率 —— 因为每场直播的档位表不一定一样(不一定都有蓝光 8M):

取值 页面上的选项 含义
top 最高档 这次能读到的最高档
second 第二高(默认) 按实际档位排序自上往下第二高,只有一档时用它
lowest 最低档 这次能读到的最低档
custom 自定义 取不超过bitrate(Kbps)的最高档,都比它高时退回最低档

四种偏好永远都在下拉里,所以还没开播、连档位表都没读到的时候也能先设好;读到档位表以后,每个选项后面会标出它现在对应哪个档位(例如第二高(蓝光 4M)、自定义(≤2500K = 高清)),选完立即生效,正在录制时换档会自动重录。选自定义时会出现一个码率框,初始值就是当前实际在用的那一档,填完回车即生效。

档位表要开播后读直播源列表才知道(写在config.json的qualities里缓存)。没读到过的行,选项后面不会带档位名,旁边的档位按钮可以去直播间接一次(主播没开播时会失败并提示,服务端本身要重试三次、约 5 秒,所以点下去会先给一句正在读取码率档位…)。读档位的时机是:录制开始取流时、页面上点档位按钮、以及发现主播在直播但还没有档位缓存时自动读一次(同一个主播 10 秒内只读一次)。录制始终是原画质直接保存(-c copy),不会重新编码,所以只能在这几个档位里挑。

录播原片和弹幕文件(.ass、.nick.ass、.jsonl)保存在保存目录下的recordSubdir子文件夹里(默认record),录制过程中 ffmpeg 就直接写在这里,不会再先落到别处中转;两者同名配对才能自动合并。保存目录的优先级是:directory > 命令行-record指定的文件夹 > 程序所在目录。合并后的成片保存在mergeSubdir子文件夹里(默认merged),留空则用默认值,两个名字不能相同。如果设置了mergeOutputDir,成片优先移动到那里。

合成先在录播文件夹里做完,写完了才落到成片文件夹(同一个盘时是直接改名,几乎瞬时;跨盘会自动拷贝):成片文件夹是网络盘、外接盘或者中途断线时,不会在里面留下一个写到一半、看起来却像正常成片的文件。合成失败或中途强杀程序时,临时文件(acfunlive_chunk_*、*.acfunlive.part)不会出现在文件管理的列表里,程序下次启动会自动清掉。

recordFormat决定录播原片的封装格式,默认为mp4(和原版config.Output的默认值一致),也可以改成flv、ts、mkv,在 web UI 的录制分区用下拉框修改。录制始终是-c copy直接封装直播源、不重新编码,所以四种格式的画质和码率完全一样,区别只在容器。

正常停止(web UI 的停止按钮、stoprecord、Ctrl+C、quit)会先向 ffmpeg 发q让它正常收尾,四种格式都能正常播放——本机 ffmpeg 6.0 实测录 5 秒后发q:mp4 161 KB、flv 163 KB、ts 191 KB、mkv 160 KB,全部可读且时长 5.14 秒。但如果进程被强杀(任务管理器结束进程、断电、系统崩溃),差别就很大了——同样录制 8 秒后强杀:

格式 兼容性 强杀后
mp4(默认) 最好,播放器、剪辑软件、手机都认 整个文件报废(48 字节,moov atom not found)
flv 一般,浏览器不能直接播 整个文件报废(0 字节,输出还在缓冲区里)
ts 较好,剪辑软件普遍支持 能读出已录的部分(7.06 秒可播)
mkv 一般,部分移动端不支持 整个文件报废(0 字节)

也就是说,如果这台机器可能被强制关机、或者你习惯直接结束进程,把recordFormat改成ts更保险(后续合并、播放都不受影响);追求兼容性就用默认的mp4。合并出来的成片始终是mp4(弹幕层需要重新编码)。

同一主播下播后 20 秒内又上播会被判定为网络波动:程序不会结束本场录制,而是接着录到同一对文件里,最终只合成一个成片(视频分段会先拼接,弹幕时间轴自动对齐)。超过 20 秒才算真的换场次,会重新开始一场录制,下播通知也是等过了这个窗口确认后才发,避免网络抖动导致误报。

webExternal为 false 时 web UI 只监听127.0.0.1,只有本机能打开;设为 true 后监听所有网卡,同一局域网的设备可以用本机 IP 加端口访问。这项设置改动后 web 服务会自动重启。

authToken是管理接口的访问口令,首次启动时自动生成(12 字节随机数的十六进制)并打印在运行日志里,忘记时可以打开config.json查看,也能在 web UI 的设置分区查看、复制和更换。所有/api/接口都必须带口令,页面本身可以匿名打开,打开后会要求输入口令并保存在浏览器本地。口令可以用三种方式提供:X-Auth-Token请求头、Authorization: Bearer <口令>、或者?token=<口令>查询参数。口令可以用任意字符,包括中文。注意 HTTP 请求头只接受 ISO-8859-1,浏览器遇到中文口令会直接拒绝发请求,所以页面发给服务端的X-Auth-Token是百分号编码过的口令,服务端原样和解码后的都认;用 curl 或脚本调接口时按原样填即可,不用自己编码。同一来源连续输错时每次会增加最多 2 秒延迟,避免被暴力尝试。手工把config.json里的authToken清空保存后,程序会自动重新生成一个,不会把自己锁在外面。

defaultQualityPref/defaultBitrate是新主播的默认档位:在 web UI 里新增主播、且没有单独指定档位时用它,录制分区可以随时改,只对新添加的主播生效,已有主播保持各自选的。默认是第二高,也就是按主播实际能用的档位自上往下取第二档(比如有蓝光 8M / 蓝光 4M / 高清就录蓝光 4M),只有一档时就用那一档;档位表要开播后才读得到,读到以后会记进config.json,没开播时页面也能显示和修改。

ffmpegPath用来指定 ffmpeg 的位置,可以填可执行文件本身(如D:\ffmpeg\bin\ffmpeg.exe)或它所在的文件夹,留空时按「PATH → 程序所在文件夹 → 程序所在文件夹的 bin、ffmpeg、ffmpeg/bin 子目录」的顺序自动查找。填相对路径时按程序所在目录解析,不会因为合并时会切换工作目录而失效(这是ffmpeg.exe这种只写文件名的写法能正常工作的原因)。录播和弹幕合并都依赖 ffmpeg,找不到时启动日志里会有明确提示。

每个主播的danmu和record开关可以在 web UI 里随时修改,也可以用命令行命令修改,输入help查看全部命令。

使用方法

运行时可用参数-record指定录制文件保存的文件夹,用-config指定配置文件所在文件夹。

web UI 默认通过http://localhost:51880访问(端口对应webPort),可以在网页里增删主播、切换每个主播的弹幕和录播开关、选择录制的码率档位、修改保存目录和自动合并设置。

页面按用途分成七个分区,顶部一条导航切换,地址栏会跟着带上#分区名,所以刷新、收藏、直接把链接发给别人都能落到同一个分区:

分区 内容
监控 当前转换任务的进度卡片、添加主播、主播列表(默认打开的分区),以及运行日志
录制 保存目录和子文件夹、录播格式、默认码率上限、ffmpeg 路径
合并 自动合并开关、成片输出目录和合成码率、手动合并(可一次排多个任务,每个任务单独配参数)
转码 把录播、成片或磁盘上任意位置的视频重新压一遍:每个文件单独配置编码器、码率、帧率、分辨率、音频,并预估产物大小
通知 推送哪些事件,以及 QQ 机器人 / MeoW 两个渠道的配置和测试连通
文件 磁盘剩余容量、录播和成片的文件列表
设置 是否允许外网访问、管理口令的查看与更换

转换任务进度

自动合并、手动合并和转码跑起来之后,监控分区顶部会出现一张转换任务卡片,页面最顶上还有一条很细的全局进度条,切到哪个分区都能看到。卡片里给的是:

  • 阶段步骤条:合并是准备 → 分段拼接 → 获取头像 → 画面渲染 → 拼接与落盘,转码是准备 → 转码 → 落盘,走过的打勾、当前的高亮(暂停时变红);一次排了多个任务时标题上还会标第 n/N 个;
  • 总进度条和百分比:渲染阶段按「已渲染时长 ÷ 视频总时长」算,其余阶段按各自的完成比例折算后加权;
  • 实时指标:渲染倍速、帧率、已处理时长 / 总时长、已用时间、已写出多少;
  • 预计剩余时间:渲染阶段用「剩余时长 ÷ 当前倍速」,别的阶段按已用时间线性外推;
  • 体积对比条:源片大小和预计成片大小并排显示两段,预计值先按目标码率估,渲染过 5% 之后换成按已写出字节外推(这时候标注会从按目标码率估算变成按已渲染部分推算);
  • 渲染速度折线:最近两分钟、每秒一个采样点,能看出硬件编码和 CPU 渲染有没有被别的任务拖慢。

任务跑着的时候可以直接暂停和停止:暂停会挂起 ffmpeg 进程(Windows 用NtSuspendProcess、Linux 用SIGSTOP),CPU 占用归零、进度冻结,点继续从原地接着跑;不在 ffmpeg 环节(比如正在下载头像)时,暂停和停止会在下一个检查点生效。停止会中断这次合并并丢弃已经渲染的部分——成片目录不会留下半成品,录播目录里的分块临时文件也会一并清掉,所以需要重新跑一次。

开关和下拉框改完立即保存;文本框(目录、地址、口令这些)填完按回车或点右上角的保存设置才生效,这一条在每个有文本框的分区里也写了提示。

目录和文件两条路都行:直接在框里填,或者点框右边的浏览逐级点选。弹窗里可以一路点进子文件夹、点C:/D:这类按钮(Linux 上是/、/home、/mnt 这些常用位置)换根位置,地址栏本身也能粘贴路径回车跳转,随时在点选和手填之间切换;选好点选用这个文件夹填回设置框,仍然是按回车或点保存才生效。填的是文件时(比如 ffmpeg 路径)列出的是文件,双击即选中并填入。默认不显示以点开头的隐藏项,需要时勾上显示隐藏项。

要选的是一组文件时(手动合并里的视频框,一行一个)用的是同一套弹窗,区别只是能多选:点一下文件名即勾选、再点一下取消(手机上也一样点,不用按 Ctrl),可以换到别的文件夹继续勾,底部显示已选 N 项、点清空能重来,双击某个文件则是「把这一个一起追加进去并关窗」。选完点选用这 N 个文件是追加到框里,手填的内容和其他已经选好的行都不会被清掉,重复的不会加两遍。填入时录播目录下的文件只写文件名(跟着保存目录走,以后改了保存目录也还认得),别的文件夹里的文件写绝对路径。这个视频框和旁边的弹幕框都只服务于一次手动合并,不属于设置项,所以选完不会有有未保存的修改提示,也不用去点保存设置,直接点开始合并即可。

页面每 3 秒从服务端取一次状态回填表单,但不会覆盖你改过还没提交的字段,所以填好的数字不会过几秒又被刷回旧值;有没提交的内容时顶栏会显示有未保存的修改(保存按钮右上角也有个红点),此时离开或刷新页面浏览器会再问一次。保存失败(比如成片目录填了个不存在的路径)时提示里会带上原因,并且输入框里填的内容会保留,改对了再存即可;开关和下拉框的立即保存不会带上前述还没提交的文本框内容,所以某个框填错也不会连累开关保存失败。

顶栏右上角一直显示当前状态(录制中 N / 直播中 N / 空闲)。窄屏上按钮收起成短文案、标题居中,手机上也能正常用;页面跟随系统的深浅色设置,深色模式下日志和表单自动变深色,不用额外配置。

主播列表每行右侧的停止和重新开始按钮控制该主播当前的录制:停止会结束这一场的弹幕和录播(同时把该主播的弹幕、录播开关关掉,避免被监控循环自动重启),重新开始会重新开启开关并立刻开始新的一组录制文件。这两个按钮同时作用于弹幕和录播,只控制其中一项可以用对应的勾选框。

命令行模式运行时可以输入命令控制本程序,输入help查看全部命令的解释,输入quit退出程序。

录制直播视频时用ffmpeg -c copy直接保存直播源,不转码,分辨率和码率跟随直播源,档位就是上面说的那个(默认选第二档,而不是吃满最高档)。

录制弹幕时会同时生成三个文件:

  • .ass:原始弹幕字幕
  • .nick.ass:带头像占位和昵称(UID):前缀的弹幕字幕
  • .jsonl:弹幕记录(时间、uid、昵称、头像链接、内容、弹道),供弹幕合成使用

弹幕合成

录制生成的jsonl文件可以合成带头像的弹幕层,需要 ffmpeg:

# 把弹幕压进录播视频,输出 xxx_弹幕合并.mp4
acfunlive -danmuburn xxx.jsonl -video xxx.flv
# 只输出带透明通道的头像弹幕层(ProRes 4444 的 mov),可叠到剪辑软件里
acfunlive -danmuburn xxx.jsonl
# 指定输出文件、时间偏移和目标码率(录播比弹幕晚开始 3 秒时用 -offset 对齐)
acfunlive -danmuburn xxx.jsonl -video xxx.flv -out out.mp4 -offset 3 -vbitrate 2000
# 弹幕文件也可以是上游原版的 .ass,会先自动还原成 jsonl 再合并
acfunlive -danmuburn "2025-04-09 20-52-05 AC娘本体 标题.ass" -video xxx.flv
# ffmpeg 既不在 PATH 也不在程序目录时,用 -ffmpeg 指定它的位置
acfunlive -danmuburn xxx.jsonl -video xxx.flv -ffmpeg D:\tools\ffmpeg.exe

-danmuburn的弹幕文件支持.jsonl和.ass两种。给的是.ass时,如果同目录下已经有同名的非空.jsonl就直接用,否则按 ASS 里的PlayResX/PlayResY、字号和\move弹道把弹幕还原成 jsonl,并把还原结果写回弹幕文件旁边,下次再合就不用重新解析。-out不指定时输出到弹幕文件同目录,重名会自动加(2)之类的后缀,不会覆盖已有文件。ffmpeg 的路径除了-ffmpeg参数,也会读设置文件里的ffmpegPath;填相对路径时按程序所在目录解析,也可以填它所在的文件夹。

头像会按 uid 缓存到程序所在目录下的avatar_cache里(不放在录播目录,所以换保存目录、甚至换盘都不用重新下载);程序启动合并时会顺手把旧位置(以前存在录播目录下的avatar_cache)里已有的头像搬过来。圆形裁切后的直径与弹幕文字的墨迹高度一致。头像图片支持 jpg、png、gif 和静态 webp:gif(包括多帧动图)取第一帧来裁切,动画 webp 解不开、遇到就只烧文字。上游原版的.ass里没有头像链接(nick.ass只有昵称和 uid),遇到这种情况会按 uid 向 AcFun 的userInfo接口反查头像,查不到的只烧文字。反查走网络且有超时,所以离线环境下合并会退化成纯文字弹幕并给出警告。

有 Intel 核显时优先用 QSV 硬件编码(h264_qsv),不可用或运行失败时自动回退到libx264。

每条带头像的弹幕都要生成一个叠加滤镜,而它们全部塞在同一条 ffmpeg 命令里,命令总长在 Windows 上不能超过 32767 字符(Linux 是单个参数 128KB),弹幕一多就会超限失败。所以弹幕数量超过一定值时会自动分块渲染:按时段切成若干块,每块单独渲染成一个小片段,最后拼接成完整成片。断流空档以外的视频不会缺帧,音轨在分块时会重新编码以保证拼接后不跑偏。

自动合并

开启autoMerge后,主播下播且弹幕和录播都自然结束时,会自动把该场录播和弹幕合成为xxx_弹幕合并.mp4,之后按设置删除源视频和弹幕文件,并把成片移动到mergeOutputDir。

成片码率默认(mergeBitrate填 0)按源片码率 + 500 Kbps算 —— 烧进去的文字和头像都是高频细节,和源片同码率会糊,所以多给一点余量;源片码率探不到时退回质量模式(QSV 用 ICQ 22、软编用 CRF 18)。想固定体积就自己填一个值。编码器优先用硬件 HEVC(hevc_qsv),同画质比 H.264 省三成左右体积、耗时几乎一样,成片在 mp4 里打hvc1标记以便 Apple 系播放;显卡不支持时依次回退到h264_qsv、libx264。注意 Windows 自带播放器要装 HEVC 扩展才能放,PotPlayer、VLC、mpv 不用。

直播流中途中断时录制会自动重连并产生新的视频分段,合并会把所有分段拼成一个完整视频,弹幕时间轴也按各段时长重新对齐,断流期间的弹幕挪到下一段开头,所以后半场不会丢弹幕。分段时长探测不到时会退化为只合并第一段并给出提示。

手动停止录制(web UI 的停止按钮或命令行里停止)不会触发自动合并,重新开始录制后新的一组文件仍会在直播结束时自动合并。这一场想补出成片的话,用下面的手动合并。合并需要 ffmpeg,且会占用 CPU 或显卡资源,长录播的合并耗时较长。

弹幕字号默认沿用弹幕文件里记的字号(1080p 下通常是 48~60),嫌大可以调弹幕字号,头像会跟着等比缩小。合并前会按最终字号把弹幕重新排一遍弹道:从上游原版的.ass还原出来的弹幕,原本是按「只有内容」的宽度占位的,而渲染用的是「昵称(uid):内容 + 头像」的宽度,占位更久,照搬原弹道会追尾叠字——重排按完整宽度重新分配,同一条弹道上不会再有两条弹幕叠在一起。

弹幕文件里的坐标系(PlayResX/PlayResY)记的也是录制当时的画面尺寸,老文件常和现在的视频对不上(例如 960x540 的弹幕文件配 720x1280 的竖屏视频)。这种情况下字幕渲染器会按「视频高 ÷ 弹幕高」缩放文字和坐标,而头像叠加用的是视频像素坐标,两边不在一个坐标系里:头像显得小,而且越往下的弹幕错得越多,看起来像和文字分开了。合并时会先探测视频分辨率,不一致就把坐标系统一到视频上(字号按高度比换算,跟画面的相对大小不变;弹幕字号填了值就按成片像素直接用),头像和文字因此始终对得上。

停止录播时会向 ffmpeg 发送正常退出的指令,让它把文件正常收尾(直接杀进程会在文件尾部留下损坏的数据包,mp4 甚至会因为缺少 moov atom 而整个读不出来)。

autoMerge、mergeDelVideo、mergeDelDanmu三项互不影响,可以同时开启:第一项决定是否自动合并,后两项是合并成功后的清理动作。

手动合并

web UI 的合并分区里的手动合并用来自己挑视频和弹幕出片,适合手动停止录制过的一场、或者手里只有零散文件的情况。可以一次排多个任务,每个任务一套自己的文件和参数。

选好视频后会按文件名自动归组:文件名是2026-09-25 01-00-00 主播名 标题.mp4这种格式,去掉开头的时间和扩展名后剩下的(主播名 + 标题)相同、而且相邻两段录制时间差在 2 小时以内的算同一场——同一场断流分成的几段归到一个任务里、先拼起来再合,不同场次各排一个任务、依次合出多个成片。归错了(比如同一天连着两场、标题又一样但间隔在 2 小时内)可以点任务行上的拆成单独任务把它们拆开。

每个任务行里能单独改:

  • 视频:行里列出这一组的文件,可以整组移除或拆成一人一个任务。既可以从录播目录的下拉列表里多选,也可以点浏览从别的文件夹挑。填的名字不带路径就按录播目录找,带路径的相对和绝对都能用。
  • 弹幕:支持.jsonl和.ass。填.ass时会先还原成 jsonl 再合并(规则和-danmuburn一样,优先用同名的 jsonl)。选好视频会自动找同名弹幕文件(同目录下依次找.jsonl、.ass、.nick.ass,同目录没有再去录播目录找一次),找到就自动填上;找不到就保持空着,用行里的…按钮手选。
  • 时间偏移:留空时按文件名里的录制时间自动推算(视频时间 = 弹幕时间 + 偏移),也可以自己填,比如录播比弹幕晚开始 3 秒就填3。
  • 输出文件名:留空时按弹幕文件名加_弹幕合并自动命名,重名会自动加(2)之类的后缀;自己填了名字就按填的来(与已有文件同名时会被覆盖,注意别填错)。
  • 本次的合成参数:码率(Kbps)、输出帧率、弹幕字号(像素)、编码器。留空就沿用成片输出里的全局设置,填了就只对这一个任务生效——比如某一场要压小一点、另一场保持原码率。
  • 先预览再合并:检查按钮会把每个任务的分段顺序、各段时长、总时长、弹幕条数和时间范围、推算出的偏移、以及要用的弹幕文件逐段列出来,确认没问题再点开始合并。

一次只跑一个合并任务,排着的按列表顺序来;重复提交会被拒绝并提示现在这个成片的名字。合并期间成片会标记写入中,和录制一样不能删除;合并失败的原因会显示在页面上,不用去翻日志。同一组里的多个视频会按文件名里的录制时间自动排序再拼接,所以下拉里选出来的顺序不影响结果,弹幕时间轴会按各段时长重新对齐。

转码

转码分区用来把视频重新压一遍:体积太大想压小、想换成 HEVC、想降分辨率或帧率、想换音轨,都在这里做。源文件不限于本程序录的录播和成片,磁盘上任何位置的文件都能丢进来。转码不动原文件,成片另存到输出目录(留空就是成片文件夹)。

源文件有两个入口,挑哪个都行:

  • 从文件列表挑:录播和成片文件夹里的文件列在左边,按 Ctrl 点可以多选,选中后点加入列表。
  • 自己填路径:右边的输入框一行一个路径,可以是从资源管理器复制来的绝对路径,也可以是录播/成片文件夹里的文件名(只写文件名就按这两个文件夹找)。点浏览…可以从磁盘上任意位置挑,支持多选、可以跨文件夹跨盘攒,选完只往输入框里追加,不会清掉已经填好的。

两个入口进来的文件进的是同一个待转码列表,每条互不影响。

每个文件一行,参数各自独立:

参数 说明
输出文件名 默认是原名_转码.mp4;这个默认名重名时会自动加(2)之类的后缀,自己改成别的名字就按填的来(与已有文件同名时会被覆盖)
编码器 自动按本机可用性挑(优先 Intel QSV 的 HEVC),也可以指定 HEVC 硬件 / H.264 硬件 / H.264 软件;本机没有的编码器会被挡住
码率控制 源片码率的比例(默认 60%)/ 指定目标码率 / 质量模式(CRF 或 ICQ,数值越小画质越好)/ 跟随源片码率
编码速度 veryfast ~ slower,慢一点压得小一点
输出帧率 0 表示跟随源片;转码场景下可以按填的值升也可以降
分辨率 按高度缩放,只列出比源片低的档,不会把小图放大
音频 直接拷贝 / 重编码 128k 或 192k / 去掉音轨

每行下面实时显示预估产物大小和相对源片的百分比(体积是服务端按「码率 × 时长」算的,目标码率模式是准数,质量模式只给粗估并标注依据),列表底部还有一整批的合计。调好第一个之后点把第一个的参数套到全部可以一次性铺给其它行。

转码和合并共用同一张进度卡片、同一个任务位,所以一次只跑一个文件,排着的按列表顺序来,也能在监控页暂停和停止;停止只丢当前这个,已经在输出目录里落地的成品不受影响。

通知推送

开播、下播、开始录制、合并完成这四个事件可以推到两个渠道,两个渠道各推各的、互不影响:

渠道 接收方 需要的配置
QQ 机器人 自己的机器人(NoneBot、Koishi 等)或任何 HTTP 服务 notifyUrl(+ 可选的notifyToken、notifyGroup)
MeoW 手机上 MeoW App 的通知 meowNickname

「推哪些事件」只有一套勾选(notifyLiveOn等四个),两个渠道共用;每个渠道另有自己的开关(notifyEnabled、meowEnabled)。关掉开关只是暂停推送,地址、昵称这些配置都会留着,随时可以再打开。没勾选的事件、或两个渠道都没启用时,程序什么都不做。

渠道一:QQ 机器人

填好notifyUrl并勾选需要的事件后,程序会把事件以 JSON POST 到该地址。

推送的请求体:

{
    "event": "liveOn",          // liveOn 开播、liveOff 下播、recordStart 开始录制、mergeDone 合并完成
    "uid": 280178,              // 主播 uid
    "name": "Xsphere",          // 主播名字
    "title": "今天画点啥",       // 直播标题
    "url": "https://…",         // 直播间地址
    "file": "…",                // 相关文件路径(开始录制时的视频、合并完成时的成片)
    "group": "777163254",       // 通知群号(web UI 里配的),机器人可以直接用它决定发到哪个群
    "text": "Xsphere 开播了…",   // 已经拼好的文本,机器人可以直接发出去
    "time": "2026/09/25 10:59:29"
}

notifyToken非空时会以Authorization: Bearer <口令>和X-Auth-Token两个头一起发出去。推送在后台进行且有 10 秒超时,失败只在日志里记警告,不会影响录制。同一场直播的开播通知只会推送一次。

notifyGroup是随推送下发的群号,留空则不带这个字段。它只是原样透传给机器人,程序自己不做任何判断——好处是群号、口令这些都能在 web UI 里改,不用去动机器人那边的配置。

接 NoneBot 的例子见doc/momoko-acfunlive-notify.py:把这个文件复制到 NoneBot 项目的插件目录即可,收到后直接把text发到群里。插件会优先使用推送里的group,只有推送没带时才回退到环境变量ACFUNLIVE_NOTIFY_GROUP,所以群号在 acfunlive 页面上改完立即生效,不用重启机器人。

渠道二:MeoW

MeoW 是个消息推送 App,手机上装好、注册自己的昵称后,把昵称填进meowNickname打开开关,就能直接收到通知,不用自己架机器人。

  • 消息内容用事件正文(就是上面 webhook 里的text),标题按事件取「开播提醒 / 下播提醒 / 开始录制 / 合并完成」
  • 开播、开始录制这类事件会带上直播间地址,MeoW 里点通知就能跳回直播间;合并完成带的是本地成片路径,手机上打不开,所以不带跳转链接
  • meowMsgType为html时会渲染成 HTML(第一行加粗、链接可点),meowHtmlHeight控制显示高度(默认 200,范围 40–2000);为text时就是纯文本,也是默认值
  • meowImgUrl可以换掉通知图标,官方建议用 216×216 的 PNG

注意 MeoW 的频控和封禁规则:非会员是 3 秒 1 条、每分钟 15 条、每小时 60 条,超了返回 429;更麻烦的是一天内累计 3 次推给不存在的昵称,它会把你的 IP 封掉。所以程序做了保护:一旦某个昵称被 MeoW 判定为「昵称未注册」,就不再向它推送,页面上会红字提示原因,改掉昵称(或重启程序)才恢复。请先把昵称填对再打开开关。

测试连通

两个渠道各有一个测试连通按钮,就在各自的配置下面:

  • 用页面上当前填的值直接测,不用先保存;留空的项自动用已保存的值补齐
  • 测试不受渠道开关和事件勾选影响,关着开关也能测(本来就是为了在打开之前先确认配置对不对)
  • 成功后会显示实际结果:QQ 渠道显示 webhook 返回的 HTTP 状态码和耗时,MeoW 渠道显示它返回的status和耗时;失败则显示具体原因,比如MeoW 返回 status 404(昵称未注册)

webhook 返回 200 只代表地址和口令没问题,能不能真的收到还要看机器人那边有没有正常处理——这一点测试结果里也会写明。

文件管理

web UI 的文件分区可以列出录播文件夹和成片文件夹里的文件(名称、大小、修改时间),并显示保存目录所在磁盘的剩余和总容量。

  • 下载:以附件形式下载,中文文件名用 RFC 5987 编码,不会乱码
  • 播放:只对浏览器能直接播的格式(mp4、m4v、webm、ogv)显示,在新标签页里内联播放;.flv 浏览器放不了,只提供下载
  • 删除:会二次确认,删除后不可恢复
  • 正在录制或正在合并的文件会标记写入中且不显示删除按钮,后端也会拒绝删除这类文件(返回 409),避免删掉还没写完的录播或成片。录制侧和接口侧的路径会先归一成绝对路径再比较,所以-record .这类相对路径的部署方式下判断同样有效

文件管理只暴露这两个文件夹,接口不接受带路径分隔符的文件名,也不能用相对路径跳出文件夹。下载链接带?token=,因为浏览器直接跳转无法携带请求头,页面响应已设置Referrer-Policy: no-referrer避免口令随来源页泄露。

Docker

git clone --recursive https://github.com/orzogc/acfunlive.git
cd acfunlive
docker build -t acfunlive .
# configDir是配置文件所在文件夹,recordDir是录播和弹幕保存所在文件夹
docker run -i -v configDir:/acfunlive/config -v recordDir:/acfunlive/record -p 51880:51880 -u `id -u`:`id -g` acfunlive:latest
S
Description
No description provided
Readme GPL-3.0
11 MiB
Languages
Go 99.8%
Dockerfile 0.1%
Makefile 0.1%