Skip to content

Commit b893dad

Browse files
committed
通用视频转换为 USM
1 parent 1634574 commit b893dad

File tree

5 files changed

+114
-33
lines changed

5 files changed

+114
-33
lines changed

MaiChartManager/Controllers/Music/MusicTransferController.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -384,25 +384,17 @@ public async Task ExportAsMaidata(int id, string assetDir, bool ignoreVideo = fa
384384
if (!ignoreVideo && StaticSettings.MovieDataMap.TryGetValue(music.NonDxId, out var movieUsmPath))
385385
{
386386
string? pvMp4Path = null;
387-
if (Path.GetExtension(movieUsmPath).Equals(".dat", StringComparison.InvariantCultureIgnoreCase))
387+
var ext = Path.GetExtension(movieUsmPath).ToLowerInvariant();
388+
389+
if (ext == ".dat" || ext == ".usm")
388390
{
389391
var tmpDir = Directory.CreateTempSubdirectory();
390392
logger.LogInformation("Temp dir: {tmpDir}", tmpDir.FullName);
391-
var movieUsm = Path.Combine(tmpDir.FullName, "movie.usm");
392-
FileSystem.CopyFile(movieUsmPath, movieUsm, UIOption.OnlyErrorDialogs);
393-
WannaCRI.WannaCRI.UnpackUsm(movieUsm, Path.Combine(tmpDir.FullName, "output"));
394-
var outputIvfFile = Directory.EnumerateFiles(Path.Combine(tmpDir.FullName, @"output\movie.usm\videos")).FirstOrDefault();
395-
if (outputIvfFile is not null)
396-
{
397-
pvMp4Path = Path.Combine(tmpDir.FullName, "pv.mp4");
398-
await FFmpeg.Conversions.New()
399-
.AddParameter("-i " + outputIvfFile.Escape())
400-
.AddParameter("-c:v copy")
401-
.SetOutput(pvMp4Path)
402-
.Start();
403-
}
393+
pvMp4Path = Path.Combine(tmpDir.FullName, "pv.mp4");
394+
395+
await VideoConvert.ConvertUsmToMp4(movieUsmPath, pvMp4Path);
404396
}
405-
else if (Path.GetExtension(movieUsmPath).Equals(".mp4", StringComparison.InvariantCultureIgnoreCase))
397+
else if (ext == ".mp4")
406398
{
407399
pvMp4Path = movieUsmPath;
408400
}

MaiChartManager/Controllers/Tools/VideoConvertToolController.cs

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public async Task VideoConvertTool([FromQuery] bool noScale, [FromQuery] bool yu
2828

2929
var dialog = new OpenFileDialog()
3030
{
31-
Title = "请选择要转换的 MP4 视频文件",
32-
Filter = "MP4 视频文件|*.mp4",
31+
Title = "请选择要转换的视频文件",
32+
Filter = "视频或者图片|*.mp4;*.mov;*.avi;*.mkv;*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.webp;*.svg;*.dat;*.usm",
3333
};
3434

3535
if (AppMain.BrowserWin.Invoke(() => dialog.ShowDialog(AppMain.BrowserWin)) != DialogResult.OK)
@@ -42,23 +42,46 @@ public async Task VideoConvertTool([FromQuery] bool noScale, [FromQuery] bool yu
4242
var inputFile = dialog.FileName;
4343
var directory = Path.GetDirectoryName(inputFile);
4444
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(inputFile);
45-
var outputDatPath = Path.Combine(directory!, fileNameWithoutExt + ".dat");
45+
var inputExt = Path.GetExtension(inputFile).ToLowerInvariant();
4646

4747
try
4848
{
49-
await VideoConvert.ConvertVideoToUsm(
50-
inputFile,
51-
outputDatPath,
52-
noScale,
53-
yuv420p,
54-
async percent =>
55-
{
56-
await Response.WriteAsync($"event: {VideoConvertEventType.Progress}\ndata: {percent}\n\n");
57-
await Response.Body.FlushAsync();
58-
});
49+
// 检查是否是 USM/DAT 转 MP4
50+
if (inputExt is ".dat" or ".usm")
51+
{
52+
var outputMp4Path = Path.Combine(directory!, fileNameWithoutExt + ".mp4");
53+
54+
await VideoConvert.ConvertUsmToMp4(
55+
inputFile,
56+
outputMp4Path,
57+
async percent =>
58+
{
59+
await Response.WriteAsync($"event: {VideoConvertEventType.Progress}\ndata: {percent}\n\n");
60+
await Response.Body.FlushAsync();
61+
});
62+
63+
await Response.WriteAsync($"event: {VideoConvertEventType.Success}\ndata: {outputMp4Path}\n\n");
64+
await Response.Body.FlushAsync();
65+
}
66+
else
67+
{
68+
// 普通视频转 USM/DAT
69+
var outputDatPath = Path.Combine(directory!, fileNameWithoutExt + ".dat");
70+
71+
await VideoConvert.ConvertVideoToUsm(
72+
inputFile,
73+
outputDatPath,
74+
noScale,
75+
yuv420p,
76+
async percent =>
77+
{
78+
await Response.WriteAsync($"event: {VideoConvertEventType.Progress}\ndata: {percent}\n\n");
79+
await Response.Body.FlushAsync();
80+
});
5981

60-
await Response.WriteAsync($"event: {VideoConvertEventType.Success}\ndata: {outputDatPath}\n\n");
61-
await Response.Body.FlushAsync();
82+
await Response.WriteAsync($"event: {VideoConvertEventType.Success}\ndata: {outputDatPath}\n\n");
83+
await Response.Body.FlushAsync();
84+
}
6285
}
6386
catch (Exception ex)
6487
{

MaiChartManager/Front/src/components/Tools/VideoConvertButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export default defineComponent({
101101
onUpdateShow={() => step.value = STEP.None}
102102
>{{
103103
default: () => <NFlex vertical size="large">
104-
<div>将 MP4 视频转换为 DAT 格式(USM 容器)</div>
104+
<div>以下选项仅对通用格式转换为 USM 生效</div>
105105
<NCheckbox v-model:checked={videoConvertOptions.value.noScale}>
106106
不要缩放视频到 1080 宽度
107107
</NCheckbox>

MaiChartManager/Front/src/components/Tools/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ export default defineComponent({
1616
const videoConvertRef = ref<{ trigger: () => void }>();
1717

1818
const options = [
19-
{ label: "音频转换", key: DROPDOWN_OPTIONS.AudioConvert },
20-
{ label: "视频转换(MP4 转 DAT)", key: DROPDOWN_OPTIONS.VideoConvert },
19+
{ label: "音频转换(ACB + AWB)", key: DROPDOWN_OPTIONS.AudioConvert },
20+
{ label: "视频转换(DAT)", key: DROPDOWN_OPTIONS.VideoConvert },
2121
]
2222

2323
const handleOptionClick = async (key: DROPDOWN_OPTIONS) => {

MaiChartManager/Utils/VideoConvert.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,5 +285,71 @@ await ConvertVideo(new VideoConvertOptions
285285
OnProgress = onProgress
286286
});
287287
}
288+
289+
/// <summary>
290+
/// 将 USM/DAT 文件转换为 MP4
291+
/// </summary>
292+
/// <param name="inputPath">输入的 USM 或 DAT 文件路径</param>
293+
/// <param name="outputPath">输出的 MP4 文件路径</param>
294+
/// <param name="onProgress">进度回调(可选)</param>
295+
public static async Task ConvertUsmToMp4(string inputPath, string outputPath, Action<int>? onProgress = null)
296+
{
297+
var tmpDir = Directory.CreateTempSubdirectory();
298+
try
299+
{
300+
var movieUsm = Path.Combine(tmpDir.FullName, "movie.usm");
301+
var ext = Path.GetExtension(inputPath).ToLowerInvariant();
302+
303+
onProgress?.Invoke(10);
304+
FileSystem.CopyFile(inputPath, movieUsm, UIOption.OnlyErrorDialogs);
305+
306+
// 解包 USM
307+
onProgress?.Invoke(30);
308+
WannaCRI.WannaCRI.UnpackUsm(movieUsm, Path.Combine(tmpDir.FullName, "output"));
309+
310+
// 查找解包后的 IVF 文件
311+
onProgress?.Invoke(50);
312+
var outputIvfFile = Directory.EnumerateFiles(Path.Combine(tmpDir.FullName, @"output\movie.usm\videos")).FirstOrDefault();
313+
if (outputIvfFile is null)
314+
{
315+
throw new Exception("USM 解包失败:未找到视频文件");
316+
}
317+
318+
// 转换为 MP4
319+
var conversion = FFmpeg.Conversions.New()
320+
.AddParameter("-i " + outputIvfFile.Escape())
321+
.AddParameter("-c:v copy")
322+
.SetOutput(outputPath);
323+
324+
if (onProgress != null)
325+
{
326+
conversion.OnProgress += (sender, args) =>
327+
{
328+
// FFmpeg 进度从 50% 开始,映射到 50-100%
329+
var percent = 50 + (int)(args.Percent / 2);
330+
onProgress(percent);
331+
};
332+
}
333+
334+
await conversion.Start();
335+
336+
if (!File.Exists(outputPath) || new FileInfo(outputPath).Length == 0)
337+
{
338+
throw new Exception("转换失败:输出文件不存在或为空");
339+
}
340+
}
341+
finally
342+
{
343+
// 清理临时目录
344+
try
345+
{
346+
tmpDir.Delete(true);
347+
}
348+
catch
349+
{
350+
// 忽略清理错误
351+
}
352+
}
353+
}
288354
}
289355

0 commit comments

Comments
 (0)