Skip to content

Commit 997bf32

Browse files
committed
CLI 基础
1 parent eb0ffb0 commit 997bf32

File tree

6 files changed

+338
-20
lines changed

6 files changed

+338
-20
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using MaiChartManager.Utils;
2+
using Spectre.Console;
3+
using Spectre.Console.Cli;
4+
using System.ComponentModel;
5+
6+
namespace MaiChartManager.CLI.Commands;
7+
8+
public class MakeAcbCommand : AsyncCommand<MakeAcbCommand.Settings>
9+
{
10+
public class Settings : CommandSettings
11+
{
12+
[CommandArgument(0, "<sources>")]
13+
[Description("要转换的源音频文件")]
14+
public string[] Sources { get; set; } = [];
15+
16+
[CommandOption("-O|--output")]
17+
[Description("输出文件路径(仅单文件时可用)")]
18+
public string? Output { get; set; }
19+
20+
[CommandOption("-p|--padding")]
21+
[Description("音频填充(秒),正数为前置静音,负数为裁剪开头")]
22+
[DefaultValue(0f)]
23+
public float Padding { get; set; }
24+
25+
public override ValidationResult Validate()
26+
{
27+
if (Sources.Length == 0)
28+
{
29+
return ValidationResult.Error("至少需要一个源文件");
30+
}
31+
32+
if (Sources.Length > 1 && !string.IsNullOrEmpty(Output))
33+
{
34+
return ValidationResult.Error("多文件转换时不能使用 -O 选项");
35+
}
36+
37+
foreach (var source in Sources)
38+
{
39+
if (!File.Exists(source))
40+
{
41+
return ValidationResult.Error($"源文件不存在: {source}");
42+
}
43+
}
44+
45+
return ValidationResult.Success();
46+
}
47+
}
48+
49+
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
50+
{
51+
try
52+
{
53+
if (settings.Sources.Length == 1)
54+
{
55+
var source = settings.Sources[0];
56+
var output = settings.Output ?? Path.ChangeExtension(source, "");
57+
await ConvertSingleFile(source, output, settings);
58+
}
59+
else
60+
{
61+
await ConvertMultipleFiles(settings);
62+
}
63+
64+
AnsiConsole.MarkupLine("[green]✓ 所有转换已成功完成![/]");
65+
return 0;
66+
}
67+
catch (Exception ex)
68+
{
69+
AnsiConsole.MarkupLine($"[red]✗ 错误: {ex.Message}[/]");
70+
return 1;
71+
}
72+
}
73+
74+
private async Task ConvertSingleFile(string source, string output, Settings settings)
75+
{
76+
AnsiConsole.MarkupLine($"[yellow]正在转换:[/] {Path.GetFileName(source)}{Path.GetFileName(output)}.acb");
77+
78+
await AnsiConsole.Status()
79+
.Spinner(Spinner.Known.Dots)
80+
.StartAsync($"转换 {Path.GetFileName(source)}...", async ctx =>
81+
{
82+
await Task.Run(() =>
83+
{
84+
Audio.ConvertToMai(
85+
srcPath: source,
86+
savePath: output,
87+
padding: settings.Padding
88+
);
89+
});
90+
});
91+
92+
AnsiConsole.MarkupLine($"[green]✓ 已保存到: {output}[/]");
93+
}
94+
95+
private async Task ConvertMultipleFiles(Settings settings)
96+
{
97+
AnsiConsole.MarkupLine($"[yellow]正在转换 {settings.Sources.Length} 个文件...[/]");
98+
99+
var table = new Table();
100+
table.AddColumn("文件");
101+
table.AddColumn("状态");
102+
103+
foreach (var source in settings.Sources)
104+
{
105+
var output = Path.ChangeExtension(source, "");
106+
107+
try
108+
{
109+
await Task.Run(() =>
110+
{
111+
Audio.ConvertToMai(
112+
srcPath: source,
113+
savePath: output,
114+
padding: settings.Padding
115+
);
116+
});
117+
118+
table.AddRow(
119+
Path.GetFileName(source),
120+
$"[green]✓ → {Path.GetFileName(output)}.acb[/]"
121+
);
122+
}
123+
catch (Exception ex)
124+
{
125+
table.AddRow(
126+
Path.GetFileName(source),
127+
$"[red]✗ {ex.Message}[/]"
128+
);
129+
}
130+
}
131+
132+
AnsiConsole.Write(table);
133+
}
134+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using MaiChartManager.Utils;
2+
using Spectre.Console;
3+
using Spectre.Console.Cli;
4+
using System.ComponentModel;
5+
6+
namespace MaiChartManager.CLI.Commands;
7+
8+
public class MakeUsmCommand : AsyncCommand<MakeUsmCommand.Settings>
9+
{
10+
public class Settings : CommandSettings
11+
{
12+
[CommandArgument(0, "<sources>")]
13+
[Description("要转换的源视频文件")]
14+
public string[] Sources { get; set; } = [];
15+
16+
[CommandOption("-O|--output")]
17+
[Description("输出文件路径(仅单文件时可用)")]
18+
public string? Output { get; set; }
19+
20+
[CommandOption("--no-scale")]
21+
[Description("禁用视频缩放")]
22+
[DefaultValue(false)]
23+
public bool NoScale { get; set; }
24+
25+
[CommandOption("--yuv420p")]
26+
[Description("使用 YUV420p 色彩空间")]
27+
[DefaultValue(false)]
28+
public bool UseYuv420p { get; set; }
29+
30+
public override ValidationResult Validate()
31+
{
32+
if (Sources.Length == 0)
33+
{
34+
return ValidationResult.Error("至少需要一个源文件");
35+
}
36+
37+
if (Sources.Length > 1 && !string.IsNullOrEmpty(Output))
38+
{
39+
return ValidationResult.Error("多文件转换时不能使用 -O 选项");
40+
}
41+
42+
foreach (var source in Sources)
43+
{
44+
if (!File.Exists(source))
45+
{
46+
return ValidationResult.Error($"源文件不存在: {source}");
47+
}
48+
}
49+
50+
return ValidationResult.Success();
51+
}
52+
}
53+
54+
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
55+
{
56+
try
57+
{
58+
await AnsiConsole.Status()
59+
.Spinner(Spinner.Known.Dots)
60+
.StartAsync("正在检测硬件加速...", async ctx =>
61+
{
62+
await VideoConvert.CheckHardwareAcceleration();
63+
});
64+
65+
AnsiConsole.MarkupLine($"[green]硬件加速: {VideoConvert.HardwareAcceleration}[/]");
66+
AnsiConsole.MarkupLine($"[green]H264 编码器: {VideoConvert.H264Encoder}[/]");
67+
68+
if (settings.Sources.Length == 1)
69+
{
70+
var source = settings.Sources[0];
71+
var output = settings.Output ?? Path.ChangeExtension(source, ".dat");
72+
await ConvertSingleFile(source, output, settings);
73+
}
74+
else
75+
{
76+
await ConvertMultipleFiles(settings);
77+
}
78+
79+
AnsiConsole.MarkupLine("[green]✓ 所有转换已成功完成![/]");
80+
return 0;
81+
}
82+
catch (Exception ex)
83+
{
84+
AnsiConsole.MarkupLine($"[red]✗ 错误: {ex.Message}[/]");
85+
return 1;
86+
}
87+
}
88+
89+
private async Task ConvertSingleFile(string source, string output, Settings settings)
90+
{
91+
AnsiConsole.MarkupLine($"[yellow]正在转换:[/] {Path.GetFileName(source)}{Path.GetFileName(output)}");
92+
93+
await AnsiConsole.Progress()
94+
.AutoClear(false)
95+
.Columns(
96+
new TaskDescriptionColumn(),
97+
new ProgressBarColumn(),
98+
new PercentageColumn(),
99+
new SpinnerColumn())
100+
.StartAsync(async ctx =>
101+
{
102+
var task = ctx.AddTask($"[green]转换 {Path.GetFileName(source)}[/]");
103+
task.MaxValue = 100;
104+
105+
await VideoConvert.ConvertVideoToUsm(
106+
source,
107+
output,
108+
noScale: settings.NoScale,
109+
yuv420p: settings.UseYuv420p,
110+
onProgress: percent => task.Value = percent
111+
);
112+
113+
task.Value = 100;
114+
});
115+
116+
AnsiConsole.MarkupLine($"[green]✓ 已保存到: {output}[/]");
117+
}
118+
119+
private async Task ConvertMultipleFiles(Settings settings)
120+
{
121+
AnsiConsole.MarkupLine($"[yellow]正在转换 {settings.Sources.Length} 个文件...[/]");
122+
123+
await AnsiConsole.Progress()
124+
.AutoClear(false)
125+
.Columns(
126+
new TaskDescriptionColumn(),
127+
new ProgressBarColumn(),
128+
new PercentageColumn(),
129+
new SpinnerColumn())
130+
.StartAsync(async ctx =>
131+
{
132+
foreach (var source in settings.Sources)
133+
{
134+
var output = Path.ChangeExtension(source, ".dat");
135+
var task = ctx.AddTask($"[green]{Path.GetFileName(source)}[/]");
136+
task.MaxValue = 100;
137+
138+
try
139+
{
140+
await VideoConvert.ConvertVideoToUsm(
141+
source,
142+
output,
143+
noScale: settings.NoScale,
144+
yuv420p: settings.UseYuv420p,
145+
onProgress: percent => task.Value = percent
146+
);
147+
148+
task.Value = 100;
149+
AnsiConsole.MarkupLine($"[green]✓ {Path.GetFileName(source)}{Path.GetFileName(output)}[/]");
150+
}
151+
catch (Exception ex)
152+
{
153+
task.StopTask();
154+
AnsiConsole.MarkupLine($"[red]✗ 转换失败 {Path.GetFileName(source)}: {ex.Message}[/]");
155+
}
156+
}
157+
});
158+
}
159+
}
Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net9.0-windows10.0.17763.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8+
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
9+
<SelfContained>true</SelfContained>
10+
<ValidateExecutableReferencesMatchSelfContained>False</ValidateExecutableReferencesMatchSelfContained>
811
</PropertyGroup>
912

13+
<ItemGroup>
14+
<ProjectReference Include="..\MaiChartManager\MaiChartManager.csproj"/>
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<Reference Include="System.Console">
19+
<HintPath>..\MaiChartManager\bin\x64\Debug\net9.0-windows10.0.17763.0\win-x64\System.Console.dll</HintPath>
20+
</Reference>
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<PackageReference Include="Spectre.Console.Cli" Version="0.53.0"/>
25+
</ItemGroup>
26+
1027
</Project>

MaiChartManager.CLI/Program.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1-
// See https://aka.ms/new-console-template for more information
1+
using MaiChartManager.CLI.Commands;
2+
using Spectre.Console.Cli;
23

3-
Console.WriteLine("Hello, World!");
4+
var app = new CommandApp();
5+
6+
app.Configure(config =>
7+
{
8+
config.SetApplicationName("MaiChartManager CLI");
9+
10+
config.AddCommand<MakeUsmCommand>("makeusm")
11+
.WithDescription("将视频文件转换为 USM 格式")
12+
.WithExample("makeusm", "video.mp4")
13+
.WithExample("makeusm", "video.mp4", "-O", "output.dat")
14+
.WithExample("makeusm", "video1.mp4", "video2.mp4", "video3.mp4");
15+
16+
config.AddCommand<MakeAcbCommand>("makeacb")
17+
.WithDescription("将音频文件转换为 ACB 格式")
18+
.WithExample("makeacb", "audio.wav")
19+
.WithExample("makeacb", "audio.mp3", "-O", "output.acb")
20+
.WithExample("makeacb", "audio1.wav", "audio2.mp3", "--padding", "0.5");
21+
});
22+
23+
return await app.RunAsync(args);

MaiChartManager/AppMain.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace MaiChartManager;
1111

1212
public class AppMain : ISingleInstance
1313
{
14-
public const string Version = "1.6.0";
14+
public const string Version = "1.6.1";
1515
public static Browser? BrowserWin { get; set; }
1616

1717
private Launcher _launcher;
@@ -74,7 +74,7 @@ public void Run()
7474
// 首次启动,从系统语言检测
7575
var systemCulture = System.Globalization.CultureInfo.CurrentUICulture;
7676
var cultureName = systemCulture.Name;
77-
77+
7878
// 检测语言:简体中文、繁体中文、英文
7979
if (cultureName.StartsWith("zh-CN") || cultureName.StartsWith("zh-Hans") || cultureName == "zh")
8080
{
@@ -89,7 +89,7 @@ public void Run()
8989
// 非中文系统默认英文
9090
StaticSettings.CurrentLocale = "en";
9191
}
92-
92+
9393
StaticSettings.Config.Locale = StaticSettings.CurrentLocale;
9494
// 保存配置
9595
try

0 commit comments

Comments
 (0)