这套东西由三个仓库组成,解决的是同一个问题:给 Beat Saber 的自定义歌曲配背景视频,并且能一口气连着打下去,不用每首之间回菜单。

0. 先说结论

能实现的是「自动跳结算的连播」,不是真正的零间隔无缝。歌与歌之间仍会退出关卡、重新加载下一首,通常有 1~数秒的黑屏或加载时间。原因见第 6 节。

1. 三个仓库分别管什么

  • beat-saber-video-tool(网页版)—— 主控台。浏览器里管理全部歌曲的视频状态、补 YouTube 链接、批量下载、试看调偏移、写 Cinema / Theater 配置,顺带生成连打挑战的 JSON。
  • yt-pot-helper(可选)—— 专治 YouTube 的「Sign in to confirm you’re not a bot」。起一个本地 PoToken 服务;另外带 Cookie Sync,能把浏览器里的 YouTube 登录态一键推到网页版。
  • SetlistChallenge(游戏 mod)—— 真正在游戏里生效的部分:按预设顺序连打、自动跳过结算、NoFail 式继续。

数据流大概是这样:

YouTube ──yt-dlp──▶ 本地视频 ──ffmpeg──▶ 歌曲文件夹
                                        │
                        网页版写配置 ────┴──▶ cinema-video.json / theater-video.json
                                                (Cinema / Theater mod 读取)

网页版「挑战」页 ──▶ UserData/SetlistChallenge/*.json ──▶ SetlistChallenge mod

2. 准备工作

  • Beat Saber 1.44.1,已装 BSIPA
  • 视频播放 mod:Cinema 全系,或 Theater v1.17(1.27 之后 Cinema 停更,后继是 Theater)
  • Node.js 18+(网页版)
  • yt-dlp.exe 和 ffmpeg.exe(下载与转码;网页版设置页里填路径)
  • Windows 为主(工具本身也支持 macOS / Linux,只是启动脚本是 .bat)

3. 第一步:跑起网页版

下载 release 包(或直接克隆仓库),进入目录后:

  1. 双击 启动网页版.bat
  2. 浏览器会自动打开 http://127.0.0.1:8770;如果 8770 被占用,会自动换成 8771、8772……以黑色窗口里显示的地址为准
  3. 关掉那个黑色窗口 = 停止服务

服务只监听本机 127.0.0.1,不对局域网或外网开放,这点可以放心。

首次打开要填的三样东西

  • yt-dlp 路径 和 ffmpeg 路径:不填的话下载会直接失败
  • 代理:默认直连;YouTube 连不上时再填本机或局域网代理(例如 http://127.0.0.1:7890)
  • Cookies:启动时会自动探测本机浏览器,也可以在设置页手动导入

设置页

歌曲总览

打开就是全部自定义歌曲的清单,按状态分成「已下载 / 待下载 / 需补链接」,每首显示时长、YouTube 链接和偏移值。

歌曲总览

常用按钮:

  • 🎬 试看:有视频时打开试看弹窗,顺便调偏移
  • 🎧 试听:没视频时只听歌曲音频,用来确认是哪首歌
  • 📁 本地:直接用本地已有的视频
  • 下载 / 重下:单个下载或强制覆盖
  • 📂:打开这首歌的文件夹

试看弹窗:调偏移的地方

点「🎬 试看」打开。左边是视频、右边是 3D 谱面预览,底部是两条频谱(视频声 vs 谱面声)。

试看弹窗演示

  • 偏移:-1000 到 +1000 毫秒的粗调,配合 ms 微调按钮
  • 🪄 自动对齐:解码两条音轨自动算建议偏移,并立即应用到试看(不会自动保存)
  • 💾 保存偏移:满意后写入配置
  • 🎧 左耳 / 右耳 / 立体声:左耳只有视频声、右耳只有谱面声,方便听感对拍
  • 🔇 删除音频 / 🗑 删除视频:清掉不需要的音轨或视频

偏移调好之后点保存,游戏里选中这首歌就能看到视频了。

4. YouTube 下载:Cookies 与 PoToken

批量下载报这个错,说明 YouTube 要你证明自己是人:

Sign in to confirm you’re not a bot. Use --cookies-from-browser or --cookies for the authentication.

两种解法,按情况选:

方案 A:给 cookies(大多数情况够用)

浏览器里已经登录 YouTube 的话,在网页版「设置 → Cookies」里:

  • 点「检测」看当前生效的来源(浏览器 / cookies.txt / 手动)
  • 或者直接粘贴 Netscape 格式的 cookies.txt 内容导入
  • 也可以填一个 URL 让工具去拉

方案 B:yt-pot-helper(cookies 也不管用时)

它在本机起一个 PoToken 服务给 yt-dlp 用:

powershell -ExecutionPolicy Bypass -File .\setup-pot.ps1

按提示走完,保持窗口开着,再回网页版重新下载。

Cookie Sync:抓完自动推给网页版

以前要在浏览器导出 cookies.txt、再手动粘贴到网页版,现在一条命令:

# 默认从 Edge 导出,推送到 http://127.0.0.1:8770
powershell -ExecutionPolicy Bypass -File .\sync-cookies.ps1

# 换浏览器 / 换工具地址
.\sync-cookies.ps1 -Browser firefox -ToolUrl http://127.0.0.1:8770

# 已经有 cookies.txt,直接上传
.\sync-cookies.ps1 -CookiesFile .\cookies.txt

也可以直接双击 sync-cookies.bat。脚本会:

  1. 用 yt-dlp 从浏览器导出 YouTube cookies
  2. POST 到网页版的 /api/cookies/import(就是设置页「导入」用的同一个接口)
  3. 自动调用 /api/cookies/detect 做一次有效性检测并打印结果
  4. 临时文件默认用完即删

参数一览:

  • -Browser:firefox / edge / chrome / brave / opera / vivaldi / chromium,默认 edge
  • -ToolUrl:网页版地址,默认 http://127.0.0.1:8770
  • -CookiesFile:直接上传已有文件,跳过导出
  • -Ytdlp:指定 yt-dlp 路径,默认自动找 .\bin\yt-dlp.exe
  • -KeepFile:保留导出的临时文件

安全:cookies 等价于登录态。脚本只会 POST 到你本机运行的网页版,不会发给任何第三方。

5. 装 SetlistChallenge

依赖

  • BeatSaberMarkupLanguage ^1.12.0
  • SiraUtil ^3.0.0
  • SongCore ^3.14.0

把 SetlistChallenge.dll 放进:

<Beat Saber>\Plugins\SetlistChallenge.dll

游戏内使用

  1. 进游戏 → 设置 → Mods 设置 → 无缝挑战
  2. 选一个挑战,设定「失败即结束 / 跳过结算 / 进度 HUD」
  3. 点「▶ 开始挑战」

挑战配置格式

配置读自 UserData/SetlistChallenge/*.json:

{
  "title": "周末挑战",
  "mode": "setlist",
  "failEndsRun": false,
  "skipResults": true,
  "shuffle": false,
  "entries": [
    { "folder": "13b80 (Emir - Hitchcock)", "characteristic": "Standard", "difficulty": "ExpertPlus" }
  ]
}
  • mode:setlist 按 entries 顺序;random 按筛选条件随机抽
  • failEndsRun:true 失败即结束;false 开 NoFail 继续打
  • skipResults:结算界面出现后自动按掉
  • shuffle:把 entries 顺序打乱
  • entries[].folder:曲目文件夹名,必须和游戏里的目录名一致

6. 用网页版生成挑战配置

手写 JSON 容易把文件夹名拼错,网页版的「🎬 无缝挑战」页就是干这个的:

  • 新建挑战,或者从现有播放列表一键导入
  • 设置「失败即结束 / 跳过结算」
  • 保存时直接写进 UserData/SetlistChallenge/*.json,SetlistChallenge 读的就是这个格式

播放列表

7. 它到底怎么「连播」(重要)

标题里一直写「无缝挑战」,但这里必须说清楚:它不是真正的无缝换谱。

最初的目标是 Shaffuru 那种「关卡内直接换谱」:上一首快结束时溶解音符、改写活的谱面数据、接着播下一首,全程不退出关卡。这个在 1.44 上没做成——谱面数据能插进去,但游戏不会据此生成新的音符和物块,结果只是音乐换了、谱面还是旧的。

所以现在的实现换了一条稳的路:

  1. 一首正常打完 → 游戏进入 Results(结算)界面
  2. SkipResultsPatch 在界面激活约 2 帧后自动触发「继续」,人几乎看不到结算
  3. StartStandardLevel 的回调拿到本首结果,立即开始下一首
  4. 下一首重新加载整个关卡(正常读谱、载入场景、重新起播)

实际体验:

  • ✅ 不用回菜单、不用手点,歌与歌之间不需要任何人工操作
  • ❌ 不是零间隔:每首之间仍有关卡加载,会有可见的停顿或黑屏,通常 1~数秒
  • ❌ 不是场景内换谱:音乐和谱面都是重新加载的

代码里场景内换谱那条路径仍然保留着,但 SeamlessSwapEnabled = false,默认关闭。等 1.44 的换谱问题解决了才会打开。

8. 常见问题

游戏里看不到视频

  • 确认 Cinema 或 Theater 已装、且版本匹配游戏 1.44.1
  • 确认写的是对应的配置文件:Cinema 用 cinema-video.json,Theater 用 theater-video.json
  • 改完配置要重启游戏才生效
  • 歌曲文件夹名变了的话,配置里的路径会失效,重新跑一次下载/本地关联

试看没有声音

浏览器要求先有用户操作才允许出声,点一下「▶ 播放」就好。

视频和谱面对不上

在试看弹窗里调偏移:先用「🪄 自动对齐」拿建议值,再用左右耳分离听感微调,满意后点「💾 保存偏移」。偏移单位是毫秒,和游戏内 Cinema 的 offset 一致。

下载报 bot 检查 / 403

见第 4 节:先补 cookies,还不行就上 yt-pot-helper 的 PoToken。

连打时每首之间黑屏几秒

这是预期行为,属于关卡重新加载,不是 bug。想彻底消除只能等场景内换谱方案成熟。

连打里的成绩算不算

当前每首都走普通的 StartStandardLevel,成绩按游戏默认规则提交。但连续高强度打容易失误,想刷分还是单曲打更稳。

mod 版本不匹配崩溃

SetlistChallenge 的 manifest.json 固定 gameVersion 1.44.1。游戏升版本后要等 mod 更新,别硬装。

9. 文件位置速查

  • 视频配置:<Beat Saber>\UserData\cinema-video.json / theater-video.json
  • 挑战配置:<Beat Saber>\UserData\SetlistChallenge\*.json
  • mod 本体:<Beat Saber>\Plugins\SetlistChallenge.dll
  • 歌曲:<Beat Saber>\Beat Saber_Data\CustomLevels\<文件夹>(视频一般放在歌曲文件夹里)

10. 仓库地址

三个都是 MIT 协议,随便改随便用。遇到问题优先看各仓库的 README,里面写得更细。