mirror of
https://github.com/muskit/MercuryConverter.git
synced 2026-06-02 20:24:26 -07:00
we can export!!
This commit is contained in:
+44
-10
@@ -1,31 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
using Avalonia.Threading;
|
||||
using MercuryConverter.UI.Views;
|
||||
using FFMpegCore.Arguments;
|
||||
using MercuryConverter.Utility;
|
||||
using SaturnData.Notation.Core;
|
||||
using SaturnData.Notation.Serialization;
|
||||
using SaturnData.Notation.Serialization.Mer;
|
||||
using Tmds.DBus.Protocol;
|
||||
using UAssetAPI;
|
||||
using UAssetAPI.ExportTypes;
|
||||
using UAssetAPI.PropertyTypes.Objects;
|
||||
using UAssetAPI.PropertyTypes.Structs;
|
||||
using UAssetAPI.UnrealTypes;
|
||||
|
||||
namespace MercuryConverter.Data;
|
||||
|
||||
public static class Database
|
||||
{
|
||||
public static ObservableCollection<Song> Songs = new();
|
||||
public static Dictionary<string, string> AudioPaths { get; } = new();
|
||||
public static ObservableCollection<Song> Songs { get; } = new();
|
||||
|
||||
public static void SetupNew(string dataPath)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => Songs.Clear());
|
||||
SetupAudio();
|
||||
SetupSongs(dataPath);
|
||||
}
|
||||
|
||||
private static void SetupAudio()
|
||||
{
|
||||
AudioPaths.Clear();
|
||||
|
||||
using (var reader = new StreamReader(Utils.AssetPath("awb.csv")))
|
||||
{
|
||||
string? line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
// skip header
|
||||
if (line.Contains("songID")) continue;
|
||||
|
||||
var tokens = line.Split(",");
|
||||
var id = tokens[0];
|
||||
var path = tokens[1];
|
||||
|
||||
if (path.Length <= 0)
|
||||
{
|
||||
// TODO: warn of missing audio
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = Utils.IIDToMusicFilePath(Convert.ToUInt32(id));
|
||||
|
||||
var audFilePath = path.Split("_");
|
||||
var audPath = Path.Combine(Settings.I!.DataPath, "MER_BGM", audFilePath[0], $"{audFilePath[1]}.wav");
|
||||
AudioPaths[key] = audPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupSongs(string dataPath)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(Songs.Clear);
|
||||
|
||||
var metadataTablePath = Path.Combine(dataPath, "MusicParameterTable.uasset");
|
||||
var metadataAsset = new UAsset(metadataTablePath, EngineVersion.VER_UE4_19);
|
||||
@@ -50,6 +83,7 @@ public static class Database
|
||||
var song = new Song
|
||||
{
|
||||
Id = data["AssetDirectory"].ToString()!,
|
||||
Uid = ((UInt32PropertyData)data["UniqueID"]).Value,
|
||||
Rubi = data["Rubi"].ToString()!,
|
||||
Name = data["MusicMessage"].ToString()!,
|
||||
Artist = data["ArtistMessage"].ToString()!,
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace MercuryConverter.Data;
|
||||
public class Song
|
||||
{
|
||||
public required string Id { get; set; } // Snn-nnn
|
||||
public required uint Uid { get; set; } // nnnn
|
||||
public required string Name { get; set; }
|
||||
public required string Artist { get; set; }
|
||||
public required uint Source { get; set; }
|
||||
@@ -53,7 +54,7 @@ public class Song
|
||||
========= Entry.Guid format =========
|
||||
MERCURY_[SONGID]_[DIFF] (each var is int)
|
||||
*/
|
||||
public IEnumerable<(Entry, Chart)> GetEntryCharts(string dataPath)
|
||||
public IEnumerable<(Entry, Chart)> GetEntryCharts()
|
||||
{
|
||||
List<(Entry, Chart)> ret = new();
|
||||
|
||||
@@ -64,7 +65,7 @@ public class Song
|
||||
|
||||
var diff = (Difficulty)i;
|
||||
|
||||
var chartFilePath = Path.Combine(dataPath, "MusicData", Id, $"{Id}_{Consts.DIFF_FILENAME_APPEND[diff]}.mer");
|
||||
var chartFilePath = Path.Combine(Settings.I!.DataPath, "MusicData", Id, $"{Id}_{Consts.DIFF_FILENAME_APPEND[diff]}.mer");
|
||||
var clearThreshold = ((FloatPropertyData)assetData[Consts.DIFF_CLEAR_KEY[diff]]).Value;
|
||||
|
||||
var e = NotationSerializer.ToEntry(chartFilePath, new NotationReadArgs
|
||||
@@ -81,12 +82,11 @@ public class Song
|
||||
e.Difficulty = diff;
|
||||
e.Level = l.Value.Item1;
|
||||
e.NotesDesigner = l.Value.Item2;
|
||||
e.JacketPath = Path.GetFileName(Jacket)!;
|
||||
e.JacketPath = "jacket.png";
|
||||
|
||||
// TODO: video
|
||||
|
||||
var uid = ((UInt32PropertyData)assetData["UniqueID"]).Value;
|
||||
e.Guid = $"MERCURY_{uid}_0{(int)diff}";
|
||||
e.Guid = $"MERCURY_{Uid}_0{(int)diff}";
|
||||
|
||||
if (new List<uint> { 1, 2 }.Contains(Source))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using MercuryConverter.Data;
|
||||
@@ -35,9 +36,60 @@ public class Exporter
|
||||
{
|
||||
public static ExportResult Run(string outputPath, Song song, ExportOptions options)
|
||||
{
|
||||
var exportPath = Path.Combine(outputPath, options.SourceSubdir ? song.SourceName : "", song.FolderName);
|
||||
var exportDir = Path.Combine(outputPath, options.SourceSubdir ? song.SourceName : "");
|
||||
var exportSongPath = Path.Combine(exportDir, song.FolderName);
|
||||
Directory.CreateDirectory(exportSongPath);
|
||||
|
||||
Console.WriteLine($"Exporting {song.Id} to {exportSongPath}");
|
||||
|
||||
var entryCharts = song.GetEntryCharts();
|
||||
HashSet<string> processedAudio = new();
|
||||
|
||||
foreach (var ec in entryCharts)
|
||||
{
|
||||
/// AUDIO ///
|
||||
var audioKey = ec.Item1.AudioPath;
|
||||
var audioExportFileName = $"{audioKey}.{options.AudioFormat.ToString().ToLower()}";
|
||||
if (!processedAudio.Contains(audioKey) && Database.AudioPaths.ContainsKey(audioKey))
|
||||
{
|
||||
var audioSourcePath = Database.AudioPaths[audioKey];
|
||||
|
||||
// Copy/convert audio -- TODO
|
||||
switch (options.AudioFormat)
|
||||
{
|
||||
case AudioFormat.MP3:
|
||||
break;
|
||||
case AudioFormat.OGG:
|
||||
break;
|
||||
default:
|
||||
File.Copy(audioSourcePath, Path.Combine(exportSongPath, audioExportFileName), true);
|
||||
break;
|
||||
}
|
||||
processedAudio.Add(audioKey);
|
||||
}
|
||||
ec.Item1.AudioPath = audioExportFileName;
|
||||
|
||||
/// CHART ///
|
||||
var chartExt =
|
||||
options.ChartFormat == FormatVersion.SatV1 ||
|
||||
options.ChartFormat == FormatVersion.SatV2 ||
|
||||
options.ChartFormat == FormatVersion.SatV3 ?
|
||||
"sat" : "mer";
|
||||
|
||||
NotationSerializer.ToFile(
|
||||
Path.Combine(exportSongPath, $"{(int)ec.Item1.Difficulty}.{chartExt}"),
|
||||
ec.Item1, ec.Item2,
|
||||
new NotationWriteArgs { FormatVersion = options.ChartFormat }
|
||||
);
|
||||
|
||||
// restore audio key in db AFTER exporting metadata / chart
|
||||
ec.Item1.AudioPath = audioKey;
|
||||
}
|
||||
|
||||
/// JACKET ///
|
||||
if (song.Jacket != null)
|
||||
File.Copy(song.Jacket, Path.Combine(exportSongPath, "jacket.png"));
|
||||
|
||||
Console.WriteLine($"Exporting {song.Id} to {exportPath}");
|
||||
return new ExportResult { status = ExportResult.Status.Failed, message = "Unimplemented" };
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,14 @@
|
||||
|
||||
<TextBlock Text="Audio Format" FontWeight="Medium" Margin="0 0 0 4"/>
|
||||
<StackPanel Margin="10 0 0 42">
|
||||
<RadioButton GroupName="AudioFormat" IsChecked="true" Content="Leave as WAV"/>
|
||||
<RadioButton Name="RadioLeaveAudioWAV" GroupName="AudioFormat" IsChecked="true" Content="Leave as WAV"/>
|
||||
<RadioButton Name="RadioShouldAudioConvert" GroupName="AudioFormat">
|
||||
<ComboBox Name="ListSelectAudioConvertFormat" IsEnabled="false" PlaceholderText="Convert to...">
|
||||
<ComboBoxItem Content="mp3"/>
|
||||
<ComboBoxItem Content="ogg"/>
|
||||
</ComboBox>
|
||||
</RadioButton>
|
||||
<TextBlock Name="NoFFMpegMessage" Text="Could not find FFmpeg. Make sure it is installed and on PATH!" TextWrapping="Wrap" Foreground="Red" FontWeight="DemiBold" Opacity="0.8" FontSize="12"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Name="TickExcludeVideos" Content="Exclude videos"/>
|
||||
|
||||
@@ -132,6 +132,14 @@ public partial class Export : Panel
|
||||
// enabled export worker count is in good range
|
||||
int.TryParse(NumThreads.Text, out var thr) && 1 <= thr && thr <= Environment.ProcessorCount
|
||||
);
|
||||
|
||||
var ffmpegAvail = Utils.IsFFMpegAvailable();
|
||||
Console.WriteLine($"FFMpeg available: {ffmpegAvail}");
|
||||
if (!ffmpegAvail)
|
||||
RadioLeaveAudioWAV.IsChecked = true;
|
||||
RadioLeaveAudioWAV.IsEnabled = ffmpegAvail;
|
||||
RadioShouldAudioConvert.IsEnabled = ffmpegAvail;
|
||||
NoFFMpegMessage.IsVisible = !ffmpegAvail;
|
||||
}
|
||||
|
||||
private void UIExportingMode(bool isExporting)
|
||||
@@ -149,7 +157,13 @@ public partial class Export : Panel
|
||||
{
|
||||
UIExportingMode(true);
|
||||
|
||||
string path = await Utils.BeginDirSelection("Choose your export path...");
|
||||
var path = await Utils.BeginDirSelection("Choose your export path...", Settings.I!.ExportPath);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
UIExportingMode(false);
|
||||
return;
|
||||
}
|
||||
Settings.I!.ExportPath = path;
|
||||
|
||||
var options = await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
@@ -176,6 +190,7 @@ public partial class Export : Panel
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() => row.SetStatus(ExportStatus.Working));
|
||||
Exporter.Run(path, row.Song, options);
|
||||
await Dispatcher.UIThread.InvokeAsync(() => row.SetStatus(ExportStatus.Finished));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using FFMpegCore;
|
||||
using FFMpegCore.Arguments;
|
||||
using MercuryConverter.UI;
|
||||
|
||||
namespace MercuryConverter.Utility;
|
||||
@@ -47,4 +50,19 @@ public static class Utils
|
||||
/// <param name="path">Forward-slash (/)-separated path to asset.</param>
|
||||
/// <returns></returns>
|
||||
public static Stream AssetPath(string path) => AssetLoader.Open(new Uri("avares://MercuryConverter/Assets/" + path));
|
||||
|
||||
public static string IIDToMusicFilePath(uint id)
|
||||
{
|
||||
return $"MER_BGM_S{id / 1000:D2}_{id % 1000:D3}";
|
||||
}
|
||||
|
||||
public static bool IsFFMpegAvailable()
|
||||
{
|
||||
// FFMpegArguments
|
||||
// .FromFileInput("dummy_input.mp4") // Use a dummy input, as it won't be processed
|
||||
// .OutputToFile("dummy_output.mp4", true, options => options.WithArgument(new CustomArgument("-version"))) // Request FFmpeg version
|
||||
// .ProcessSynchronously();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user