Skip to content

Commit e5a2d5d

Browse files
committed
init: ChuChartManager
0 parents  commit e5a2d5d

148 files changed

Lines changed: 20589 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: Build & Release
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
workflow_dispatch:
7+
8+
jobs:
9+
build-frontend:
10+
name: Build Frontend
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
with:
16+
submodules: recursive
17+
18+
- name: Setup Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: '22'
22+
23+
- name: Install pnpm
24+
uses: pnpm/action-setup@v4
25+
with:
26+
version: 10
27+
28+
- name: Install and Build
29+
working-directory: ChuChartManager/Front
30+
run: |
31+
pnpm install
32+
pnpm build
33+
34+
- name: Upload wwwroot
35+
uses: actions/upload-artifact@v4
36+
with:
37+
name: wwwroot
38+
path: ChuChartManager/wwwroot
39+
40+
build-backend:
41+
name: Build Backend
42+
runs-on: windows-latest
43+
needs: build-frontend
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v4
47+
with:
48+
submodules: recursive
49+
50+
- name: Setup .NET
51+
uses: actions/setup-dotnet@v4
52+
with:
53+
dotnet-version: '10.0.x'
54+
dotnet-quality: 'preview'
55+
56+
- name: Download wwwroot
57+
uses: actions/download-artifact@v4
58+
with:
59+
name: wwwroot
60+
path: ChuChartManager/wwwroot
61+
62+
- name: Publish
63+
run: |
64+
dotnet publish ChuChartManager/ChuChartManager.csproj -c Release -r win-x64 --self-contained -o publish /p:PublishSingleFile=false
65+
66+
- name: Package
67+
shell: pwsh
68+
run: |
69+
Compress-Archive -Path publish/* -DestinationPath ChuChartManager-win-x64.zip
70+
71+
- name: Upload artifact
72+
uses: actions/upload-artifact@v4
73+
with:
74+
name: ChuChartManager-win-x64
75+
path: ChuChartManager-win-x64.zip
76+
77+
release:
78+
name: Create Release
79+
runs-on: ubuntu-latest
80+
needs: build-backend
81+
if: startsWith(github.ref, 'refs/tags/v')
82+
permissions:
83+
contents: write
84+
steps:
85+
- name: Download artifact
86+
uses: actions/download-artifact@v4
87+
with:
88+
name: ChuChartManager-win-x64
89+
90+
- name: Create Release
91+
uses: softprops/action-gh-release@v2
92+
with:
93+
files: ChuChartManager-win-x64.zip
94+
generate_release_notes: true
95+
prerelease: true

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# .NET
2+
bin/
3+
obj/
4+
.vs/
5+
*.user
6+
*.suo
7+
*.cache
8+
packages/
9+
nul
10+
11+
# 运行时工具(构建后生成)
12+
ChuChartManager/tools/
13+
14+
# 前端
15+
node_modules/
16+
ChuChartManager/wwwroot/
17+
pnpm-lock.yaml
18+
19+
# 临时文件
20+
*.psb
21+
*.pure.psb
22+
*.emtbytes
23+

.gitmodules

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[submodule "SonicAudioTools"]
2+
path = SonicAudioTools
3+
url = https://github.com/clansty/SonicAudioTools.git
4+
[submodule "MuNET-UI"]
5+
path = MuNET-UI
6+
url = https://github.com/MuNET-OSS/MuNET-UI.git
7+
[submodule "XV2-Tools"]
8+
path = XV2-Tools
9+
url = https://github.com/Clansty/XV2-Tools.git
10+
[submodule "MuConvert"]
11+
path = MuConvert
12+
url = https://github.com/MuNET-OSS/MuConvert
13+
[submodule "DDSExtractor"]
14+
path = DDSExtractor
15+
url = https://github.com/Applesaber/DDSExtractor.git
16+
[submodule "FreeMote"]
17+
path = FreeMote
18+
url = https://github.com/UlyssesWu/FreeMote.git
19+
[submodule "FreeMote-SDK"]
20+
path = FreeMote-SDK
21+
url = https://github.com/Project-AZUSA/FreeMote-SDK.git
22+
[submodule "ChuModLoader"]
23+
path = ChuModLoader
24+
url = https://github.com/MuNET-OSS/ChuModLoader.git
25+
[submodule "AppleChu"]
26+
path = AppleChu
27+
url = https://github.com/MuNET-OSS/AppleChu.git

AppleChu

Submodule AppleChu added at dda5ec7
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0-windows10.0.17763.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
<UseWPF>true</UseWPF>
10+
<ApplicationIcon>icon.ico</ApplicationIcon>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\ChuChartManager\ChuChartManager.csproj" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<Using Include="System.IO" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Spectre.Console.Cli;
2+
3+
namespace ChuChartManager.CLI.Commands;
4+
5+
public class DebugCommand : Command
6+
{
7+
public override int Execute(CommandContext context)
8+
{
9+
Log.EnableConsoleOutput();
10+
Log.Info("以 debug 模式启动");
11+
12+
Exception? exception = null;
13+
var thread = new Thread(() =>
14+
{
15+
try
16+
{
17+
var app = new App();
18+
app.InitializeComponent();
19+
app.Run();
20+
}
21+
catch (Exception e)
22+
{
23+
exception = e;
24+
}
25+
});
26+
thread.SetApartmentState(ApartmentState.STA);
27+
thread.Start();
28+
thread.Join();
29+
30+
if (exception != null)
31+
{
32+
Console.Error.WriteLine($"发生错误: {exception}");
33+
throw exception;
34+
}
35+
return 0;
36+
}
37+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System.ComponentModel;
2+
using System.Windows.Media;
3+
using System.Windows.Media.Imaging;
4+
using ChuChartManager.CLI.Utils;
5+
using Spectre.Console;
6+
using Spectre.Console.Cli;
7+
8+
namespace ChuChartManager.CLI.Commands;
9+
10+
public class ExportJacketCommand : AsyncCommand<ExportJacketCommand.Settings>
11+
{
12+
public class Settings : GameSettings
13+
{
14+
[CommandOption("-i|--id <ID>")]
15+
[Description("导出指定 ID 的封面")]
16+
[DefaultValue(-1)]
17+
public int MusicId { get; set; }
18+
19+
[CommandOption("-a|--all")]
20+
[Description("导出全部封面")]
21+
[DefaultValue(false)]
22+
public bool All { get; set; }
23+
24+
[CommandOption("-o|--output <DIR>")]
25+
[Description("输出目录(默认当前目录)")]
26+
public string? OutputDir { get; set; }
27+
28+
public override ValidationResult Validate()
29+
{
30+
var baseResult = base.Validate();
31+
if (!baseResult.Successful) return baseResult;
32+
33+
if (MusicId < 0 && !All)
34+
return ValidationResult.Error("请指定 -i <ID> 导出单曲封面,或 -a 导出全部");
35+
36+
return ValidationResult.Success();
37+
}
38+
}
39+
40+
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
41+
{
42+
var outputDir = settings.OutputDir ?? Directory.GetCurrentDirectory();
43+
Directory.CreateDirectory(outputDir);
44+
45+
var scanner = new MusicScanner(settings.GamePath);
46+
AnsiConsole.MarkupLine("[dim]正在扫描...[/]");
47+
await Task.Run(scanner.ScanAll);
48+
49+
var allMusic = scanner.MusicBySource.Values.SelectMany(x => x);
50+
var targets = settings.All
51+
? allMusic.ToList()
52+
: allMusic.Where(m => m.Id == settings.MusicId).ToList();
53+
54+
if (targets.Count == 0)
55+
{
56+
AnsiConsole.MarkupLine(settings.All
57+
? "[red]扫描完成但未找到任何曲目[/]"
58+
: $"[red]未找到 ID={settings.MusicId} 的曲目[/]");
59+
return 1;
60+
}
61+
62+
int done = 0, failed = 0;
63+
foreach (var music in targets)
64+
{
65+
try
66+
{
67+
TerminalProgress.Set(done * 100 / targets.Count);
68+
var jacketPath = music.GetJacketFullPath();
69+
if (jacketPath == null)
70+
{
71+
AnsiConsole.MarkupLine($"[yellow]跳过[/] #{music.Id:D4} {Markup.Escape(music.Name)} — 未找到封面文件");
72+
failed++;
73+
continue;
74+
}
75+
76+
var outPath = Path.Combine(outputDir, $"{music.Id:D4}.png");
77+
await Task.Run(() => ConvertToPng(jacketPath, outPath));
78+
AnsiConsole.MarkupLine($"[green]✓[/] #{music.Id:D4} {Markup.Escape(music.Name)}{outPath}");
79+
done++;
80+
}
81+
catch (Exception ex)
82+
{
83+
AnsiConsole.MarkupLine($"[red]✗[/] #{music.Id:D4} {Markup.Escape(music.Name)}: {Markup.Escape(ex.Message)}");
84+
failed++;
85+
}
86+
}
87+
88+
TerminalProgress.Clear();
89+
AnsiConsole.MarkupLine($"\n[green]完成: {done} 张导出[/]{(failed > 0 ? $", [yellow]{failed} 张失败[/]" : "")}");
90+
return failed > 0 ? 1 : 0;
91+
}
92+
93+
private static void ConvertToPng(string ddsPath, string outPath)
94+
{
95+
BitmapSource bmp;
96+
if (ddsPath.EndsWith(".dds", StringComparison.OrdinalIgnoreCase))
97+
{
98+
using var pfim = Pfim.Pfimage.FromFile(ddsPath);
99+
if (pfim.Compressed) pfim.Decompress();
100+
var fmt = pfim.Format == Pfim.ImageFormat.Rgba32
101+
? PixelFormats.Bgra32 : PixelFormats.Bgr24;
102+
bmp = BitmapSource.Create(
103+
pfim.Width, pfim.Height, 96, 96, fmt, null,
104+
pfim.Data, pfim.Stride);
105+
}
106+
else
107+
{
108+
var img = new BitmapImage(new Uri(Path.GetFullPath(ddsPath)));
109+
img.Freeze();
110+
bmp = img;
111+
}
112+
113+
var encoder = new PngBitmapEncoder();
114+
encoder.Frames.Add(BitmapFrame.Create(bmp));
115+
using var fs = File.Create(outPath);
116+
encoder.Save(fs);
117+
}
118+
}

0 commit comments

Comments
 (0)