diff --git a/src/Assets/awb.csv b/src/Assets/awb.csv new file mode 100644 index 0000000..d874507 --- /dev/null +++ b/src/Assets/awb.csv @@ -0,0 +1,395 @@ +songID,awb +3,MER_30 +4,MER_32 +5,MER_34 +6,MER_36 +7,MER_38 +8,MER_40 +9,MER_42 +10,MER_44 +20,MER_46 +21,MER_48 +23,MER_50 +24,MER_52 +25,MER_54 +26,MER_56 +27,MER_58 +30,MER_60 +31,MER_62 +32,MER_64 +33,MER_524 +1001,MER_66 +1002,MER_68 +1003, +1004,MER_70 +1005, +1006,MER_72 +1008,MER_372 +1009,MER_74 +1010,MER_76 +1011,MER_78 +1013,MER_80 +1014,MER_82 +1015,MER_84 +1016,MER_86 +1017,MER_88 +1018,MER_90 +1020,MER_374 +1021,MER_92 +1026,MER_94 +1027,MER_96 +1030,MER_98 +1031,MER_100 +1032,02_0 +1035,MER_102 +1036,MER_104 +1037,MER_106 +1041,MER_108 +1042,MER_110 +1043,MER_112 +1044,MER_114 +1045,MER_116 +1047,MER_118 +1049, +1050,MER_120 +1051,MER_122 +1052,MER_124 +1054,MER_126 +1055,MER_128 +1056,MER_130 +1057,MER_132 +1058,MER_134 +1059,MER_136 +1060,MER_138 +1061,MER_140 +1062,MER_142 +1063,MER_144 +1064,MER_146 +1065,MER_148 +1068,MER_150 +1069,MER_152 +1070,MER_154 +1071,MER_156 +1072,MER_158 +1073,MER_160 +1076,MER_162 +1077,MER_164 +1078,MER_166 +1079,MER_168 +1080,MER_170 +1081,MER_172 +1082,MER_174 +1083,MER_176 +1084,MER_178 +1085,MER_180 +1086,MER_182 +1087,MER_184 +1088,MER_186 +1089,MER_188 +1090,MER_190 +1091,MER_192 +1092,MER_194 +1093,MER_196 +1094,MER_198 +1095,MER_200 +1097,MER_202 +1098,MER_204 +1099,MER_206 +1100,MER_208 +1101,MER_210 +1102,MER_212 +1104,MER_214 +1105,MER_216 +1107,MER_218 +1108,MER_220 +1109,MER_222 +1110,MER_224 +1111,MER_226 +1113,MER_250 +1114,MER_252 +1115,MER_254 +1116,MER_256 +1117,MER_228 +1118,MER_230 +1119,MER_232 +1121,MER_234 +1122,MER_236 +1123,MER_238 +1124,MER_240 +1125,MER_242 +1126,MER_244 +1127,MER_246 +1128,MER_248 +1203,MER_258 +1204,MER_376 +1205,MER_260 +1206,MER_262 +1207,MER_264 +1208,MER_266 +1209,MER_526 +1210,MER_268 +1211,MER_270 +1212,MER_272 +1213,MER_274 +1214,MER_276 +1215,MER_278 +1216,MER_280 +1217,MER_282 +1218,MER_284 +1219,MER_528 +1221,MER_286 +1222,MER_288 +1224,MER_290 +1226,MER_292 +1227,MER_294 +1228,MER_296 +1229,MER_298 +1230,MER_300 +1231,MER_302 +1232,MER_304 +1236,MER_308 +1237,MER_310 +1238,MER_312 +1239,MER_378 +1240,MER_380 +1241,MER_530 +1242,MER_314 +1243,MER_316 +1244,MER_318 +1245,MER_320 +1246,MER_322 +1247,MER_382 +1248,MER_384 +1249,MER_386 +1250,MER_324 +1251,MER_388 +1254,MER_326 +1255,MER_328 +1256,MER_390 +1258,MER_330 +1259,MER_332 +1260,MER_334 +1261,MER_336 +1262,MER_338 +1263,MER_340 +1264,MER_392 +1265,MER_342 +1267,MER_346 +1268,MER_348 +1269,MER_350 +1270,MER_352 +1272,MER_354 +1273,MER_356 +1274,MER_358 +1275,MER_360 +1276,MER_362 +1278,MER_364 +1279,MER_366 +2001,MER_394 +2002,MER_396 +2003,MER_398 +2004,MER_400 +2005,MER_402 +2006,MER_404 +2007,MER_406 +2008,MER_408 +2009,MER_410 +2010,MER_412 +2011,MER_414 +2012,MER_416 +2013,MER_418 +2014,MER_420 +2015,MER_422 +2016,MER_424 +2017,MER_426 +2019,MER_428 +2021,MER_430 +2022,MER_432 +2023,MER_434 +2024,MER_436 +2027,MER_438 +2028,MER_440 +2029,MER_442 +2030,MER_444 +2031,MER_446 +2032,MER_448 +2033,MER_450 +2034,MER_452 +2036,MER_306 +2037,MER_456 +2038,MER_532 +2039,MER_534 +2040,MER_458 +2042,MER_460 +2043,MER_462 +2046,MER_464 +2047,MER_466 +2048,MER_468 +2049,MER_470 +2053,MER_472 +2054,MER_474 +2055,MER_476 +2056,MER_478 +2058,MER_536 +2059,MER_480 +2060,MER_482 +2062,MER_484 +2065,MER_486 +2066,MER_488 +2067,MER_490 +2068,MER_492 +2069,MER_494 +2070,MER_496 +2071,MER_498 +2072,MER_500 +2073,MER_502 +2074,MER_504 +2075,MER_506 +2076,MER_508 +2077,MER_510 +2078,MER_512 +2079,MER_538 +2080,MER_540 +2081,MER_542 +2082,MER_544 +2083,MER_546 +2084,MER_514 +2085,MER_548 +2088,MER_516 +2089,MER_518 +2090,MER_520 +2091,MER_522 +2092,MER_550 +2202,MER_552 +2203,MER_554 +2204,MER_556 +2205,MER_558 +2206,MER_560 +2207,MER_562 +2208,MER_564 +2209,MER_566 +2210,MER_568 +2211,MER_570 +2212,MER_572 +2213,MER_574 +2214,MER_576 +2215,MER_578 +2216,MER_580 +2217,MER_582 +2218,MER_584 +2219,MER_586 +2220,MER_588 +2221,MER_590 +2222,MER_592 +2223,MER_594 +2224,MER_596 +2225,MER_598 +2226,MER_600 +2227,MER_602 +2228,MER_652 +2229,MER_604 +2230,MER_606 +2231,MER_608 +2232,MER_610 +2234,MER_612 +2235,MER_614 +2236,MER_616 +2238,MER_618 +2239,MER_620 +2240,MER_622 +2243,MER_624 +2244,MER_626 +2245,MER_628 +2246,MER_630 +2247,MER_632 +2248,MER_634 +2249,MER_636 +2250,MER_638 +2251,MER_640 +2252,MER_642 +2253,MER_644 +2254,MER_646 +2255,MER_648 +2256,MER_650 +3003,MER_654 +3004,05_0 +3005,MER_656 +3006,MER_658 +3007,MER_660 +3008,MER_662 +3009,MER_664 +3011,MER_666 +3012,MER_668 +3013,MER_670 +3015,MER_672 +3016,MER_674 +3017,MER_676 +3018,MER_678 +3019,MER_680 +3020,MER_682 +3021,MER_684 +3022,MER_686 +3023,MER_688 +3024,01_0 +3025,02_2 +3026,01_2 +3027,03_0 +3028,MER_690 +3029,MER_692 +3030,03_2 +3031,01_4 +3032,06_0 +3033,01_6 +3034,01_8 +3035,01_10 +3036,02_4 +3037,02_6 +3038,02_8 +3039,02_10 +3040,02_12 +3041,02_14 +3042,03_4 +3044,04_0 +3045,04_2 +3046,04_4 +3047,04_6 +3048,04_8 +3049,04_10 +3050,04_12 +3052,05_2 +3053,05_4 +3055,04_14 +3056,04_16 +3057,04_18 +3058,05_6 +3059,05_8 +3060,05_10 +3061,05_12 +3062,06_2 +3063,06_4 +3064,06_6 +3070,05_14 +3071,07_0 +3072,07_2 +3073,07_4 +3074,07_6 +3075,07_8 +3076,07_10 +3077,07_12 +3078,07_14 +3079,07_16 +3080,07_18 +3081,07_20 +3082,07_22 +3083,07_24 +3084,07_26 +3085,07_28 +3086,07_30 +3087,07_32 +3088,07_34 +3089,07_36 +3090,07_38 +3091,07_40 +3092,07_42 +3093,07_44 +3094,07_46 diff --git a/src/Assets/imgs/status/indeterminate_spinner.png b/src/Assets/imgs/status/indeterminate_spinner.png new file mode 100644 index 0000000..32967e8 Binary files /dev/null and b/src/Assets/imgs/status/indeterminate_spinner.png differ diff --git a/src/Assets/imgs/status/task_alert.png b/src/Assets/imgs/status/task_alert.png new file mode 100644 index 0000000..0a49c61 Binary files /dev/null and b/src/Assets/imgs/status/task_alert.png differ diff --git a/src/Assets/imgs/status/task_complete.png b/src/Assets/imgs/status/task_complete.png new file mode 100644 index 0000000..66fed37 Binary files /dev/null and b/src/Assets/imgs/status/task_complete.png differ diff --git a/src/Assets/imgs/status/task_error.png b/src/Assets/imgs/status/task_error.png new file mode 100644 index 0000000..bd80d5e Binary files /dev/null and b/src/Assets/imgs/status/task_error.png differ diff --git a/src/ExportOperation/Exporter.cs b/src/ExportOperation/Exporter.cs index 3cd6d8e..91e013f 100644 --- a/src/ExportOperation/Exporter.cs +++ b/src/ExportOperation/Exporter.cs @@ -3,9 +3,21 @@ using System.Diagnostics.Contracts; using System.IO; using MercuryConverter.Data; using MercuryConverter.UI.Views; +using SaturnData.Notation.Serialization; namespace MercuryConverter.ExportOperation; +public enum AudioFormat { WAV, MP3, OGG } + +public class ExportOptions +{ + + public required FormatVersion ChartFormat; + public required AudioFormat AudioFormat; + public required bool ExcludeVideo; + public required bool SourceSubdir; +} + public class ExportResult { public enum Status @@ -21,10 +33,11 @@ public class ExportResult public class Exporter { - public static ExportResult Run(string outputPath, Song song) + public static ExportResult Run(string outputPath, Song song, ExportOptions options) { - var exportPath = Path.Combine(outputPath, song.FolderName); - Console.WriteLine($"Exporting to {exportPath}..."); + var exportPath = Path.Combine(outputPath, options.SourceSubdir ? song.SourceName : "", song.FolderName); + + Console.WriteLine($"Exporting {song.Id} to {exportPath}"); return new ExportResult { status = ExportResult.Status.Failed, message = "Unimplemented" }; } } \ No newline at end of file diff --git a/src/Settings.cs b/src/Settings.cs index bd5b41a..5fc72cb 100644 --- a/src/Settings.cs +++ b/src/Settings.cs @@ -23,6 +23,11 @@ public partial class Settings : ObservableObject [ObservableProperty] private string dataPath = ""; + [ObservableProperty] + private string exportPath = ""; + [ObservableProperty] + private string concurrentExports = (Environment.ProcessorCount/2).ToString(); + [ObservableProperty] private Theme theme = Theme.System; @@ -34,6 +39,12 @@ public partial class Settings : ObservableObject case nameof(DataPath): Console.WriteLine(DataPath); break; + case nameof(ExportPath): + Console.WriteLine(ExportPath); + break; + case nameof(ConcurrentExports): + Console.WriteLine(ConcurrentExports); + break; default: Console.WriteLine("unknown variable"); break; @@ -66,7 +77,12 @@ public partial class Settings : ObservableObject private void SaveToIni() { var data = new IniData(); + data["paths"]["data"] = DataPath; + data["paths"]["export"] = ExportPath; + + data["export"]["concurrentOperations"] = ConcurrentExports; + data["ui"]["theme"] = Theme.ToString(); try @@ -88,6 +104,9 @@ public partial class Settings : ObservableObject var iniData = parser.ReadFile(iniPath); DataPath = iniData["paths"]["data"]; + ExportPath = iniData["paths"]["export"]; + ConcurrentExports = iniData["export"]["concurrentOperations"]; + if (Enum.TryParse(iniData["ui"]["theme"], out Theme loadedTheme)) Theme = loadedTheme; } diff --git a/src/UI/Views/Export/Export.axaml b/src/UI/Views/Export/Export.axaml index 97b98eb..bf9cb71 100644 --- a/src/UI/Views/Export/Export.axaml +++ b/src/UI/Views/Export/Export.axaml @@ -10,6 +10,7 @@ xmlns:views="clr-namespace:MercuryConverter.UI.Views" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:util="clr-namespace:MercuryConverter.Utility" + xmlns:merc="clr-namespace:MercuryConverter" > @@ -20,11 +21,11 @@ - + + - @@ -64,7 +65,14 @@ CanUserResizeColumns="True" CanUserSortColumns="False" > - + + + + + + + + diff --git a/src/UI/Views/Export/Export.axaml.cs b/src/UI/Views/Export/Export.axaml.cs index b9f2fae..0a69ff3 100644 --- a/src/UI/Views/Export/Export.axaml.cs +++ b/src/UI/Views/Export/Export.axaml.cs @@ -1,16 +1,23 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; +using Avalonia.Data; using Avalonia.Interactivity; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using MercuryConverter.Data; using MercuryConverter.ExportOperation; using MercuryConverter.Utility; +using SaturnData.Notation.Serialization; namespace MercuryConverter.UI.Views; @@ -21,9 +28,22 @@ public enum ExportStatus public partial class ExportRow : ObservableObject { + private static Dictionary StatusImgs = new() + { + { ExportStatus.NotStarted, null }, + { ExportStatus.Working, new Bitmap(Utils.AssetPath("imgs/status/indeterminate_spinner.png")) }, + { ExportStatus.Error, new Bitmap(Utils.AssetPath("imgs/status/task_error.png")) }, + { ExportStatus.Finished, new Bitmap(Utils.AssetPath("imgs/status/task_complete.png")) }, + { ExportStatus.FinishedWithMessages, new Bitmap(Utils.AssetPath("imgs/status/task_alert.png")) }, + }; + + [ObservableProperty] + private IImage? statusImg = null; public required Song Song { get; set; } - public ExportStatus Status { get; set; } = ExportStatus.NotStarted; - public string? Message { get; set; } + public void SetStatus(ExportStatus status) + { + StatusImg = StatusImgs[status]; + } } public partial class Export : Panel @@ -39,7 +59,10 @@ public partial class Export : Panel RadioExportAll.IsCheckedChanged += OnExportSelectionChg; ListingTable.PointerPressed += OnClick; - NumThreads.Text = (Environment.ProcessorCount / 2).ToString(); + NumThreads.Bind(TextBox.TextProperty, new Binding(nameof(Settings.ConcurrentExports)) + { + Source = Settings.I! + }); ToolTip.SetTip(TickGroupExports, "Group exported songs into subfolders named after the version they released in. For example:\n" + @@ -70,7 +93,6 @@ public partial class Export : Panel public void OnExportClick() { - Console.WriteLine("export clicked!"); Task.Run(ExportFlow); } @@ -98,7 +120,6 @@ public partial class Export : Panel { ListSelectAudioConvertFormat.IsEnabled = (bool)RadioShouldAudioConvert.IsChecked!; - Console.WriteLine($"input threads: {NumThreads.Text}"); BtnExport.IsEnabled = ( // ensure we have selections Selection.Selections.Count > 0 @@ -108,7 +129,8 @@ public partial class Export : Panel !(bool)RadioShouldAudioConvert.IsChecked || ListSelectAudioConvertFormat.SelectedIndex != -1 ) && ( - int.TryParse(NumThreads.Text, out var thr) && thr <= Environment.ProcessorCount + // enabled export worker count is in good range + int.TryParse(NumThreads.Text, out var thr) && 1 <= thr && thr <= Environment.ProcessorCount ); } @@ -128,15 +150,34 @@ public partial class Export : Panel UIExportingMode(true); string path = await Utils.BeginDirSelection("Choose your export path..."); - Console.WriteLine($"Exporting to {path}"); - int i = 0; - var mtx = new Mutex(); - - foreach (var r in Rows) + var options = await Dispatcher.UIThread.InvokeAsync(() => { - Exporter.Run(path, r.Song); - } + AudioFormat audFor; + if (!(bool)RadioShouldAudioConvert.IsChecked!) + audFor = AudioFormat.WAV; + else + audFor = (AudioFormat)ListSelectAudioConvertFormat.SelectedIndex + 1; + + return new ExportOptions + { + ChartFormat = (FormatVersion)ListSelectChartFormat.SelectedIndex, + AudioFormat = audFor, + ExcludeVideo = (bool)TickExcludeVideos.IsChecked!, + SourceSubdir = (bool)TickGroupExports.IsChecked! + }; + }); + + // process each song in parallel (for audio conversion) + Parallel.ForEach( + Rows, + new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(Settings.I!.ConcurrentExports) }, + async row => + { + await Dispatcher.UIThread.InvokeAsync(() => row.SetStatus(ExportStatus.Working)); + Exporter.Run(path, row.Song, options); + } + ); UIExportingMode(false); } diff --git a/src/Utility/Utils.cs b/src/Utility/Utils.cs index 556791f..0406921 100644 --- a/src/Utility/Utils.cs +++ b/src/Utility/Utils.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Threading; using MercuryConverter.UI; @@ -38,4 +40,11 @@ public static class Utils return dirSelection!.First().TryGetLocalPath()!; } + + /// + /// Get an AvaloniaResource asset. + /// + /// Forward-slash (/)-separated path to asset. + /// + public static Stream AssetPath(string path) => AssetLoader.Open(new Uri("avares://MercuryConverter/Assets/" + path)); } \ No newline at end of file