Repository navigation
feat(tools): 新增开场视频与曲目伪装的编辑工具 - #78
Conversation
- 后端新增 FancyToolController,配合 AquaMai 的 Fancy.CustomIntroCinematic / Fancy.TrackCamouflage: - 开场视频:GetIntroMovie / GetIntroMovieFile(支持 Range)/ GetIntroMovieList / SetIntroMovie / DeleteIntroMovie - 上传视频统一转 H.264 MP4,并按当前编码器给画质参数(h264_mf 默认画质极低,实测同源 2.86 Mbps → 13 Mbps) - 支持左右屏同一视频(L=R)与左右分开;替换单侧时自动补齐另一侧 - 音轨自动抽取成 Enter_<id>.acb / .awb(游戏里由 acb/awb 发声);另提供 SetIntroAudio / DeleteIntroAudio 单独替换音频 - 曲目伪装:GetCamouflage / GetCamouflageJacket / GetCamouflageList / SetCamouflage(Toml)/ SetCamouflageJacket / DeleteCamouflage - 删除与覆盖统一走占用重试(DeleteFilesWithRetry / MoveFileWithRetry),文件被占用时不会卡死或误报成功 - VideoConvert 新增 KeepAudio / ExtraArgs 选项与 H264QualityArgs,其他调用方行为不变 - 前端:工具页新增入口 + IntroCamouflageModal - 使用 MuNET-UI 的 Tabs / Select / TextInput / CheckBox,曲目选择带搜索框过滤 - 左右分屏 1:2 预览,播放/暂停/拖动进度双向同步,显示实际分辨率与文件路径 - 未选择乐曲时所有输入与按钮禁用 - 已有列表显示真实曲名,载入时清空搜索并定位到该曲目
- 预览里左右两个 <video> 都带音轨时会同时出声(重声)。现在视频一律 muted, 声音只播一路:新增 GetIntroAudioFileApi 把 Enter_<id>.acb/.awb 解成 wav (按 acb 修改时间缓存到临时目录,支持 Range),前端用隐藏的 <audio> 与视频 同步播放/暂停/拖动进度 - SetIntroMovie 不再保留视频音轨(KeepAudio=false),mp4 回归纯画面, 与游戏内一致(AquaMai 的 VideoPlayer 本来就不输出视频音轨) - 重新生成 apiGen
审阅者指南本 PR 在工具页新增开场视频与曲目伪装编辑能力:后端负责媒体转换、音频封装、TOML/封面文件管理及占用重试,前端通过统一弹窗提供曲目选择、分屏同步预览、编辑上传和删除,并同步扩展视频转换器与 API/多语言支持。 开场视频上传与音频提取时序图sequenceDiagram
participant User
participant Modal as IntroCamouflageModal
participant API as FancyToolController
participant Converter as VideoConvert
participant Audio as Audio
participant Assets as IntroMovies
User->>Modal: chooseIntroMovie()
Modal->>API: SetIntroMovie(id, side, file)
API->>Converter: ConvertVideo(KeepAudio=false, ExtraArgs)
Converter-->>API: silent H.264 MP4
API->>Audio: ExtractAudioFromMp4(inputPath, wavPath)
Audio-->>API: extracted WAV
API->>Audio: ConvertToMai(wavPath, acbPath)
Audio-->>API: ACB and AWB
API->>Assets: MoveFileWithRetry(video, target)
API->>Assets: MoveFileWithRetry(acb, awb)
API-->>Modal: FancyToolResult
Modal->>API: GetIntroMovie(id)
API-->>Modal: video and audio availability
开场视频同步预览时序图sequenceDiagram
participant User
participant Modal as IntroCamouflageModal
participant VideoL as Left video
participant VideoR as Right video
participant Audio as Decoded WAV audio
User->>VideoL: play or pause
VideoL->>VideoR: syncPlay()
VideoL->>Audio: playAudio()
User->>VideoL: seek
VideoL->>VideoR: syncSeek()
VideoL->>Audio: syncAudioSeek()
User->>VideoR: play or pause
VideoR->>VideoL: syncPlay()
VideoR->>Audio: playAudio()
AquaMai 本地资源实体关系图erDiagram
INTRO_MOVIE {
int id PK
file single_mp4
file left_mp4
file right_mp4
file acb
file awb
}
CAMOUFLAGE {
int id PK
string toml
string name
string artist
string noteDesigner
boolean hideNoteDesigner
double unlockScore
file jacket
}
INTRO_MOVIE ||--|| CAMOUFLAGE : "may share track id"
文件级变更
提示与命令与 Sourcery 交互
自定义使用体验访问你的控制面板来:
获取帮助Original review guide in EnglishReviewer's Guide本 PR 在工具页新增开场视频与曲目伪装编辑能力:后端负责媒体转换、音频封装、TOML/封面文件管理及占用重试,前端通过统一弹窗提供曲目选择、分屏同步预览、编辑上传和删除,并同步扩展视频转换器与 API/多语言支持。 Sequence diagram for intro video upload and audio extractionsequenceDiagram
participant User
participant Modal as IntroCamouflageModal
participant API as FancyToolController
participant Converter as VideoConvert
participant Audio as Audio
participant Assets as IntroMovies
User->>Modal: chooseIntroMovie()
Modal->>API: SetIntroMovie(id, side, file)
API->>Converter: ConvertVideo(KeepAudio=false, ExtraArgs)
Converter-->>API: silent H.264 MP4
API->>Audio: ExtractAudioFromMp4(inputPath, wavPath)
Audio-->>API: extracted WAV
API->>Audio: ConvertToMai(wavPath, acbPath)
Audio-->>API: ACB and AWB
API->>Assets: MoveFileWithRetry(video, target)
API->>Assets: MoveFileWithRetry(acb, awb)
API-->>Modal: FancyToolResult
Modal->>API: GetIntroMovie(id)
API-->>Modal: video and audio availability
Sequence diagram for synchronized intro previewsequenceDiagram
participant User
participant Modal as IntroCamouflageModal
participant VideoL as Left video
participant VideoR as Right video
participant Audio as Decoded WAV audio
User->>VideoL: play or pause
VideoL->>VideoR: syncPlay()
VideoL->>Audio: playAudio()
User->>VideoL: seek
VideoL->>VideoR: syncSeek()
VideoL->>Audio: syncAudioSeek()
User->>VideoR: play or pause
VideoR->>VideoL: syncPlay()
VideoR->>Audio: playAudio()
Entity relationship diagram for AquaMai local assetserDiagram
INTRO_MOVIE {
int id PK
file single_mp4
file left_mp4
file right_mp4
file acb
file awb
}
CAMOUFLAGE {
int id PK
string toml
string name
string artist
string noteDesigner
boolean hideNoteDesigner
double unlockScore
file jacket
}
INTRO_MOVIE ||--|| CAMOUFLAGE : "may share track id"
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
嗨——我已审阅你的更改,整体看起来很棒!
Sourcery 评估
需要人工审核。 替换片头视频或封面会覆盖现有的游戏文件,而删除操作可能会移除这些资源及其配置;回滚应用程序并不能恢复之前的文件。影响仅限于所选曲目,但恢复可能需要使用备份或重新创建这些资源。
Original comment in English
Hey - I've reviewed your changes and they look great!
Sourcery assessment
Needs a human reviewer. Replacing an intro video or jacket overwrites existing game files, and the delete actions can remove those assets and their configuration; reverting the application will not restore the previous files. The impact is limited to the selected track, but recovery may require a backup or recreating the assets.
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
- VideoConvert:删除 KeepAudio 选项(IVF/USM 放不下音频,正数 Padding 路径也会忽略它),
恢复统一的 -map 0:0;RunConcatenate 的输出参数也带上 ExtraArgs
- FancyToolController:
- 开场音频 acb/awb 成对替换:先备份旧文件,任一移动失败就回滚
- SetIntroMovie 先清理与新布局冲突的旧文件,清理失败直接返回 delete-failed,
避免单文件与 L/R 同时存在
- SetCamouflageJacket 先写临时文件再覆盖目标,写入失败不会破坏已有封面
- Delete* 接口补 EnsureGamePath 守卫,避免 GamePath 为空时删到工作目录下的相对路径
- 前端 IntroCamouflageModal:
- 替换音频后 bump previewToken,避免 <audio> 继续用旧 URL 播缓存
- loadIntroMovie / loadCamouflage / refreshCamouflageJacket 加竞态保护,丢弃过期响应
- en.yaml:pickMusicFirst 改为 Select a song first,camoUnlockScore 明确为 Score
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
View guided diff | Re-trigger cubic
- SetIntroMovie 把「转码 → 清理旧布局 → 落地新视频 → 落地音频」做成一个事务: 操作前备份 Enter_<id>.mp4 / _L / _R / .acb / .awb,任一步失败整体回滚, 不会出现「新音频配旧画面」「旧布局已删但新视频没落地」这类半状态 - 抽出 BuildIntroAudio(只生成到临时目录)与 IntroFileTransaction, SetIntroAudio 复用同一套事务;删除不再需要的 CreateIntroAudio / RestoreOrDelete - VideoConvert:-map 0:0 改为 -map 0:v:0,避免「第 0 个流其实是音频」的容器选错流导致转换失败
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
- 新增按 id 的 SemaphoreSlim(IntroFileGates):SetIntroMovie / SetIntroAudio 与 DeleteIntroMovie / DeleteIntroAudio / DeleteCamouflage 都先拿锁再执行,finally 释放, 不同曲目互不阻塞 - 原因:上传视频时会顺带重写 acb/awb,两个并发请求交错提交可能出现 「mp4 来自请求 A、acb/awb 来自请求 B」的音画不匹配;事务本身无法防止交错
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
- 用固定 64 条 SemaphoreSlim 的分段锁替换按 id 建字典的 IntroFileGates: 内存恒定、不需要回收,id 校验也提前到加锁之前 - DeleteIntroMovie / DeleteIntroAudio / DeleteCamouflage 改为 async action + await WaitAsync(),避免转码长时间持锁时阻塞线程池线程 - SetCamouflage / SetCamouflageJacket 也拿同一把锁(按 id), 删除不会再与伪装配置 / 封面上传交错,留下不完整的伪装文件
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.
View guided diff | Re-trigger cubic
概述
配合 AquaMai 的
Fancy.CustomIntroCinematic/Fancy.TrackCamouflage,在「工具」页新增编辑工具:LocalAssets/IntroMovies/Enter_<id>.mp4(或_L/_R分屏),并自动生成Enter_<id>.acb/.awbLocalAssets/Camouflages/<id>.toml与<id>_jacket.*,含封面预览后端
Controllers/Tools/FancyToolController.csGetIntroMovie/GetIntroMovieFile(PhysicalFile + Range)/GetIntroMovieList/SetIntroMovie/DeleteIntroMovieEnter_<id>.acb/.awb(复用Audio.ExtractAudioFromMp4+Audio.ConvertToMai),游戏里由它们发声SetIntroAudio/DeleteIntroAudioGetIntroAudioFile把 acb/awb 解成 wav(按 acb mtime 缓存、支持 Range),供前端试听GetCamouflage/GetCamouflageJacket/GetCamouflageList/SetCamouflage(Tomlyn + 原子写入)/SetCamouflageJacket/DeleteCamouflageDeleteFilesWithRetry/MoveFileWithRetry:文件被占用(例如还在预览)时先探测再重试并最终兜底,不会卡死也不会误报成功VideoConvert(新增可选参数,其他调用方行为不变)KeepAudio:是否保留音轨(默认丢弃)ExtraArgs+H264QualityArgs(codec):按编码器给画质参数。本机探测到h264_mf,默认设置码率极低(720×720 仅 1.0 Mbps),加参数后同源 1280×1280 可到 13 Mbps前端
views/Tools/IntroCamouflageModal.tsxL=R/L/R标记),载入时清空搜索并定位到该曲目测试
dotnet build -c Crack、pnpm typecheck、pnpm build全部通过side=L/R分别写入并补齐另一侧;mp3 替换音频正常;GetIntroAudioFile200/206 正常delete-failed(约 3s),解锁后正常删除Sourcery 摘要
在工具页加入开场视频与曲目伪装的完整编辑工作流,并将相关媒体处理能力整合到后端与前端。
新功能:
错误修复:
改进:
日常维护:
Original summary in English
Sourcery 摘要
在 Tools 页面中集成 AquaMai 自定义内容的片头影片和曲目伪装编辑器。
新功能:
错误修复:
增强功能:
测试:
杂项:
Original summary in English
Sourcery 总结
在工具页集成 AquaMai 开场视频与曲目伪装的完整编辑工作流。
新功能:
问题修复:
功能增强:
文档:
测试:
杂项:
Original summary in English
Sourcery 总结
将 AquaMai 开场影片和曲目伪装编辑功能集成到 Tools 页面。
新功能:
错误修复:
增强功能:
构建:
测试:
Original summary in English
Sourcery 摘要
将 AquaMai 片头影片和曲目伪装编辑功能集成到 Tools 页面。
新功能:
错误修复:
增强功能:
构建:
测试:
Original summary in English
Summary by Sourcery
Integrate AquaMai intro movie and track camouflage editing into the Tools page.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests: