Skip to content

Commit dfa7403

Browse files
committed
添加音频转换工具
1 parent c9ce6b4 commit dfa7403

File tree

7 files changed

+220
-4
lines changed

7 files changed

+220
-4
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"dotrush.roslyn.projectOrSolutionFiles": [
33
"d:\\Projects\\mai\\Sitreamai\\Sitreamai.sln"
4-
]
4+
],
5+
"dotnet.defaultSolution": "Sitreamai.sln"
56
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Sitreamai;
3+
4+
namespace MaiChartManager.Controllers.Tools;
5+
6+
[ApiController]
7+
[Route("MaiChartManagerServlet/[action]Api")]
8+
public class AudioConvertToolController : ControllerBase
9+
{
10+
[HttpPost]
11+
public IActionResult AudioConvertTool()
12+
{
13+
if (AppMain.BrowserWin is null) return BadRequest("浏览器窗口未初始化");
14+
15+
var dialog = new OpenFileDialog()
16+
{
17+
Title = "请选择要转换的音频文件",
18+
Filter = "音频文件|*.wav;*.mp3;*.aac;*.ogg;*.flac;*.m4a;*.wma;*.ape;*.acb;*.awb;*.mp4",
19+
};
20+
21+
if (AppMain.BrowserWin.Invoke(() => dialog.ShowDialog(AppMain.BrowserWin)) != DialogResult.OK)
22+
return BadRequest("未选择文件");
23+
24+
var inputFile = dialog.FileName;
25+
var extension = Path.GetExtension(inputFile).ToLowerInvariant();
26+
var directory = Path.GetDirectoryName(inputFile);
27+
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(inputFile);
28+
29+
try
30+
{
31+
// 检查是否是 ACB 或 AWB 文件 - 转换为 MP3
32+
if (extension == ".acb" || extension == ".awb")
33+
{
34+
return ConvertAcbAwbToMp3(inputFile, directory, fileNameWithoutExt, extension);
35+
}
36+
37+
// 其他格式转换为 ACB/AWB
38+
return ConvertToAcbAwb(inputFile, directory, fileNameWithoutExt, extension);
39+
}
40+
catch (Exception ex)
41+
{
42+
return BadRequest($"转换失败: {ex.Message}");
43+
}
44+
}
45+
46+
/// <summary>
47+
/// 将 ACB/AWB 转换为 MP3
48+
/// </summary>
49+
private IActionResult ConvertAcbAwbToMp3(string inputFile, string directory, string fileNameWithoutExt, string extension)
50+
{
51+
string acbPath;
52+
string awbPath;
53+
54+
// 根据输入文件类型确定 ACB 和 AWB 路径
55+
if (extension == ".acb")
56+
{
57+
acbPath = inputFile;
58+
awbPath = Path.Combine(directory, fileNameWithoutExt + ".awb");
59+
}
60+
else // .awb
61+
{
62+
awbPath = inputFile;
63+
acbPath = Path.Combine(directory, fileNameWithoutExt + ".acb");
64+
}
65+
66+
// 检查配对文件是否存在
67+
if (!System.IO.File.Exists(acbPath))
68+
{
69+
return BadRequest($"找不到配对的 ACB 文件: {acbPath}");
70+
}
71+
72+
if (!System.IO.File.Exists(awbPath))
73+
{
74+
return BadRequest($"找不到配对的 AWB 文件: {awbPath}");
75+
}
76+
77+
// 转换 ACB 到 WAV
78+
byte[] wavData = Audio.AcbToWav(acbPath);
79+
80+
// 生成 MP3 输出路径
81+
string mp3Path = Path.Combine(directory, fileNameWithoutExt + ".mp3");
82+
83+
// 将 WAV 数据转换为 MP3
84+
Audio.ConvertWavBytesToMp3(wavData, mp3Path);
85+
86+
return Ok(new { message = "转换完成!", outputPath = mp3Path });
87+
}
88+
89+
/// <summary>
90+
/// 将音频文件转换为 ACB/AWB
91+
/// </summary>
92+
private IActionResult ConvertToAcbAwb(string inputFile, string directory, string fileNameWithoutExt, string extension)
93+
{
94+
string tempAudioFile = null;
95+
96+
try
97+
{
98+
string actualInputFile = inputFile;
99+
100+
// 如果是 MP4 文件,先提取音轨
101+
if (extension == ".mp4")
102+
{
103+
tempAudioFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".wav");
104+
Audio.ExtractAudioFromMp4(inputFile, tempAudioFile);
105+
actualInputFile = tempAudioFile;
106+
}
107+
108+
// 生成输出路径
109+
string acbPath = Path.Combine(directory, fileNameWithoutExt + ".acb");
110+
string awbPath = Path.Combine(directory, fileNameWithoutExt + ".awb");
111+
112+
// 执行转换
113+
Audio.ConvertToMai(actualInputFile, acbPath);
114+
115+
return Ok(new { message = "转换完成!", acbPath = acbPath, awbPath = awbPath });
116+
}
117+
finally
118+
{
119+
// 删除临时音频文件
120+
if (tempAudioFile != null && System.IO.File.Exists(tempAudioFile))
121+
{
122+
try
123+
{
124+
System.IO.File.Delete(tempAudioFile);
125+
}
126+
catch
127+
{
128+
// 忽略删除错误
129+
}
130+
}
131+
}
132+
}
133+
}

MaiChartManager/Front/src/client/apiGen.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,20 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
810810
...params,
811811
}),
812812

813+
/**
814+
* No description
815+
*
816+
* @tags AudioConvertTool
817+
* @name AudioConvertTool
818+
* @request POST:/MaiChartManagerServlet/AudioConvertToolApi
819+
*/
820+
AudioConvertTool: (params: RequestParams = {}) =>
821+
this.request<void, any>({
822+
path: `/MaiChartManagerServlet/AudioConvertToolApi`,
823+
method: "POST",
824+
...params,
825+
}),
826+
813827
/**
814828
* No description
815829
*
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import api from '@/client/api';
2+
import { NDropdown, NButton, useMessage } from 'naive-ui';
3+
import { defineComponent, PropType, ref, computed, watch } from 'vue';
4+
5+
enum DROPDOWN_OPTIONS {
6+
AudioConvert,
7+
}
8+
9+
export default defineComponent({
10+
// props: {
11+
// },
12+
setup(props, { emit }) {
13+
const message = useMessage();
14+
const options = [
15+
{ label: "音频转换", key: DROPDOWN_OPTIONS.AudioConvert },
16+
]
17+
18+
const handleOptionClick = async (key: DROPDOWN_OPTIONS) => {
19+
switch (key) {
20+
case DROPDOWN_OPTIONS.AudioConvert: {
21+
const res = await api.AudioConvertTool();
22+
if (res.status === 200) {
23+
message.success("转换成功");
24+
} else {
25+
message.error("转换失败");
26+
}
27+
break;
28+
}
29+
}
30+
}
31+
32+
return () => (location.hostname === 'mcm.invalid' || import.meta.env.DEV) && <NDropdown options={options} trigger="click" onSelect={handleOptionClick}>
33+
<NButton secondary>
34+
工具
35+
</NButton>
36+
</NDropdown>;
37+
},
38+
});

MaiChartManager/Front/src/views/Index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import ImportCreateChartButton from "@/components/ImportCreateChartButton";
1313
import { HardwareAccelerationStatus, LicenseStatus } from "@/client/apiGen";
1414
import CopyToButton from "@/components/CopyToButton";
1515
import TransitionOpacity from '@/components/TransitionOpacity';
16+
import Tools from '@/components/Tools';
1617

1718
export default defineComponent({
1819
setup() {
@@ -92,6 +93,7 @@ export default defineComponent({
9293
<MusicSelectedTopRightToolbar />
9394
<ImportCreateChartButton />
9495
</>}
96+
<Tools />
9597
<VersionInfo />
9698
</NFlex>
9799
<NScrollbar class="grow-1">

Sitreamai/Audio.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Reflection;
4+
using NAudio.Lame;
45
using NAudio.Wave;
56
using VGAudio;
67
using VGAudio.Cli;
@@ -94,11 +95,15 @@ public static Stream ConvertToWav(Stream src, bool isOgg, float padding = 0)
9495

9596
var stream = new MemoryStream();
9697
WaveFileWriter.WriteWavFileToStream(stream, sample.ToWaveProvider16()); // 淦
97-
stream.Position = 0; // 淦 x2
98+
stream.Position = 0; // 淦 x2
9899
return stream;
99100
}
100101

101-
public static byte[] ConvertFile(Stream s, FileType encodeType, FileType convertToType, bool loop,
102+
public static byte[] ConvertFile(
103+
Stream s,
104+
FileType encodeType,
105+
FileType convertToType,
106+
bool loop,
102107
ulong encryptionKey = 0)
103108
{
104109
ConvertStatics.SetLoop(loop, 0, 0);
@@ -148,4 +153,26 @@ public static byte[] AcbToWav(string acbPath)
148153
using MemoryStream stream = new MemoryStream(entry.bytes);
149154
return ConvertStream.ConvertFile(new Options(), stream, GetFileType(wave.EncodeType), FileType.Wave);
150155
}
151-
}
156+
157+
// 从MP4视频文件中提取音频轨道并保存为WAV文件
158+
public static void ExtractAudioFromMp4(string mp4Path, string outputWavPath)
159+
{
160+
using (var reader = new MediaFoundationReader(mp4Path))
161+
{
162+
// MediaFoundationReader 会自动解码视频中的音频流(如AAC)为PCM
163+
WaveFileWriter.CreateWaveFile(outputWavPath, reader);
164+
}
165+
}
166+
167+
// 将 WAV 字节数据转换为 MP3 文件
168+
public static void ConvertWavBytesToMp3(byte[] wavData, string mp3Path)
169+
{
170+
// 将 WAV 字节数据写入内存流
171+
using var wavStream = new MemoryStream(wavData);
172+
using var reader = new WaveFileReader(wavStream);
173+
174+
// 创建 MP3 文件并编码
175+
using var writer = new LameMP3FileWriter(mp3Path, reader.WaveFormat, 256);
176+
reader.CopyTo(writer);
177+
}
178+
}

Sitreamai/Sitreamai.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8"/>
2525
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.8"/>
2626
<PackageReference Include="NAudio" Version="2.2.1" />
27+
<PackageReference Include="NAudio.Lame" Version="2.1.0"/>
2728
<PackageReference Include="NAudio.Vorbis" Version="1.5.0" />
2829
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
2930
<PackageReference Include="YAXLib" Version="2.15.0" />

0 commit comments

Comments
 (0)