# acfunlive AcFun 直播弹幕和录播助手(免登录,只用 uid 列表指定要录制的主播) - [acfunlive](#acfunlive) - [依赖](#依赖) - [运行依赖](#运行依赖) - [编译依赖](#编译依赖) - [编译](#编译) - [使用 GNU Make](#使用-gnu-make) - [不使用 GNU Make](#不使用-gnu-make) - [配置文件详解](#配置文件详解) - [使用方法](#使用方法) - [弹幕合成](#弹幕合成) - [自动合并](#自动合并) - [Docker](#docker) ### 依赖 #### 运行依赖 - 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, // 是否录制直播视频 "bitrate": 0 // 录制的直播源最高码率(Kbps),0 表示码率最高的直播源 } ], "directory": "", // 录制文件保存的文件夹,留空是程序运行时的 -record 文件夹 "webPort": 51880, // web UI 的端口,为 0 时不启动 web UI "autoMerge": false, // 直播结束后自动把弹幕层合并进录播 "mergeDelVideo": false, // 合并完成后删除源视频 "mergeDelDanmu": false, // 合并完成后删除弹幕文件(ass、nick.ass、jsonl) "mergeOutputDir": "", // 合并完成后成片移动到该文件夹,留空则不移动 "mergeBitrate": 0, // 合并成片的目标码率(Kbps),0 表示使用默认质量 "defaultBitrate": 0, // 新添加主播的默认 bitrate,0 表示码率最高的直播源 "webExternal": false, // 是否允许外网访问 web UI,false 时只监听本机 127.0.0.1 "authToken": "…", // web UI 管理接口的访问口令,首次启动自动生成 "notifyUrl": "", // 开播/下播等事件推送到该地址(HTTP POST JSON),留空不推送 "notifyToken": "", // 推送时附带的 Bearer 口令,留空不带 "notifyGroup": "", // 推送时带上的群号,机器人可以直接用它决定发到哪个群 "notifyLiveOn": false, // 开播时推送 "notifyLiveOff": false, // 下播时推送 "notifyRecordStart": false, // 开始录制直播视频时推送 "notifyMergeDone": false, // 自动合并完成时推送 "ffmpegPath": "", // ffmpeg 可执行文件(或它所在文件夹)的路径,留空则自动查找 "recordFormat": "mp4", // 录播文件的封装格式:mp4、flv、ts、mkv "recordSubdir": "record", // 录播原片和弹幕文件存放在保存目录下的子文件夹名 "mergeSubdir": "merged" // 合并成片存放在保存目录下的子文件夹名 } ``` `uids`也兼容旧的纯数字数组格式,如`"uids": [23682490, 12345]`,此时弹幕和录播都默认开启、码率取最高。 `bitrate`为 0 时录制码率最高的直播源,设置为其他数字时录制码率不超过该值的最高档(如 2000 大约是超清档),可以在 web UI 的主播列表里随时修改。录制始终是原画质直接保存(`-c copy`),不会重新编码,所以`bitrate`只能在这几个档位里挑选。 录播原片和弹幕文件(`.ass`、`.nick.ass`、`.jsonl`)保存在保存目录下的`recordSubdir`子文件夹里(默认`record`),两者同名配对才能自动合并;合并后的成片保存在`mergeSubdir`子文件夹里(默认`merged`),留空则用默认值,两个名字不能相同。如果设置了`mergeOutputDir`,成片优先移动到那里。 `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=<口令>`查询参数。同一来源连续输错时每次会增加最多 2 秒延迟,避免被暴力尝试。手工把`config.json`里的`authToken`清空保存后,程序会自动重新生成一个,不会把自己锁在外面。 `defaultBitrate`是下载码率的默认值:在 web UI 里新增主播、且不单独指定码率时使用它,也可以在 web UI 设置区随时修改,只对新添加的主播生效,已有主播保持各自的值。 `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`),可以在网页里增删主播、切换每个主播的弹幕和录播开关、修改码率上限、修改保存目录和自动合并设置。 主播列表每行右侧的`停止`和`重新开始`按钮控制该主播当前的录制:`停止`会结束这一场的弹幕和录播(同时把该主播的弹幕、录播开关关掉,避免被监控循环自动重启),`重新开始`会重新开启开关并立刻开始新的一组录制文件。这两个按钮同时作用于弹幕和录播,只控制其中一项可以用对应的勾选框。 命令行模式运行时可以输入命令控制本程序,输入`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`里,圆形裁切后的直径与弹幕文字的墨迹高度一致。上游原版的`.ass`里没有头像链接(`nick.ass`只有昵称和 uid),遇到这种情况会按 uid 向 AcFun 的`userInfo`接口反查头像,查不到的只烧文字。反查走网络且有超时,所以离线环境下合并会退化成纯文字弹幕并给出警告。 有 Intel 核显时优先用 QSV 硬件编码(`h264_qsv`),不可用或运行失败时自动回退到`libx264`。 每条带头像的弹幕都要生成一个叠加滤镜,而它们全部塞在同一条 ffmpeg 命令里,命令总长在 Windows 上不能超过 32767 字符(Linux 是单个参数 128KB),弹幕一多就会超限失败。所以弹幕数量超过一定值时会自动**分块渲染**:按时段切成若干块,每块单独渲染成一个小片段,最后拼接成完整成片。断流空档以外的视频不会缺帧,音轨在分块时会重新编码以保证拼接后不跑偏。 ### 自动合并 开启`autoMerge`后,主播下播且弹幕和录播都自然结束时,会自动把该场录播和弹幕合成为`xxx_弹幕合并.mp4`,之后按设置删除源视频和弹幕文件,并把成片移动到`mergeOutputDir`。`mergeBitrate`大于 0 时成片使用指定目标码率,可以明显减小成片体积。 直播流中途中断时录制会自动重连并产生新的视频分段,合并会把**所有分段拼成一个完整视频**,弹幕时间轴也按各段时长重新对齐,断流期间的弹幕挪到下一段开头,所以后半场不会丢弹幕。分段时长探测不到时会退化为只合并第一段并给出提示。 手动停止录制(web UI 的`停止`按钮或命令行里停止)不会触发自动合并,重新开始录制后新的一组文件仍会在直播结束时自动合并。这一场想补出成片的话,用下面的`手动合并`。合并需要 ffmpeg,且会占用 CPU 或显卡资源,长录播的合并耗时较长。 停止录播时会向 ffmpeg 发送正常退出的指令,让它把文件正常收尾(直接杀进程会在文件尾部留下损坏的数据包,mp4 甚至会因为缺少 moov atom 而整个读不出来)。 `autoMerge`、`mergeDelVideo`、`mergeDelDanmu`三项互不影响,可以同时开启:第一项决定是否自动合并,后两项是合并成功后的清理动作。 ### 手动合并 web UI 的`手动合并`区用来自己挑视频和弹幕出片,适合手动停止录制过的一场、或者手里只有零散文件的情况。 - **视频**:既可以在文本框里一行一个地填,也可以从录播目录的下拉列表里多选(选完自动填进文本框)。填的名字不带路径就按录播目录找,带路径的相对和绝对都能用,所以录播目录以外的文件也合得来。粘贴路径时带上的引号会被自动去掉。 - **弹幕**:支持`.jsonl`和`.ass`。填`.ass`时会先还原成 jsonl 再合并(规则和`-danmuburn`一样,优先用同名的 jsonl),还原结果写回录播目录,页面的下拉列表里也会出现。 - **时间偏移**:留空时按文件名里的录制时间自动推算(视频时间 = 弹幕时间 + 偏移),也可以自己填,比如录播比弹幕晚开始 3 秒就填`3`。 - **输出文件名**:可以改,重名时自动加`(2)`之类的后缀,不会覆盖已有文件。 - **先预览再合并**:`预览`按钮会先把分段顺序、各段时长、总时长、弹幕条数和时间范围、推算出的偏移、以及要用的弹幕文件列出来,确认没问题再点`开始合并`。 一次只允许跑一个手动合并任务,重复提交会被拒绝并提示现在这个成片的名字。合并期间成片会标记`写入中`,和录制一样不能删除;合并失败的原因会显示在页面上,不用去翻日志。多个视频会按文件名里的录制时间自动排序再拼接,所以下拉里选出来的顺序不影响结果,弹幕时间轴会按各段时长重新对齐。 ### 通知推送 填好`notifyUrl`并勾选需要的事件后,程序会把事件以 JSON POST 到该地址,可以用来接自己的机器人(NoneBot、Koishi 等)或任何 HTTP 服务。 推送的请求体: ``` { "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 秒超时,失败只在日志里记警告,不会影响录制。同一场直播的开播通知只会推送一次。web UI 设置区的`发送测试通知`按钮可以用当前填写的地址直接测试(不需要先保存)。 `notifyGroup`是随推送下发的群号,留空则不带这个字段。它只是原样透传给机器人,程序自己不做任何判断——好处是群号、口令这些都能在 web UI 里改,不用去动机器人那边的配置。 接 NoneBot 的例子见`doc/momoko-acfunlive-notify.py`:把这个文件复制到 NoneBot 项目的插件目录即可,收到后直接把`text`发到群里。插件会优先使用推送里的`group`,只有推送没带时才回退到环境变量`ACFUNLIVE_NOTIFY_GROUP`,所以群号在 acfunlive 页面上改完立即生效,不用重启机器人。 ### 文件管理 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 ```