Files
MercuryConverter/src/UI/Views/Export/Export.axaml.cs
T

199 lines
6.4 KiB
C#
Raw Normal View History

2025-08-21 01:13:08 -07:00
using System;
2025-08-27 16:11:06 -07:00
using System.Collections.Generic;
2025-08-27 12:44:57 -07:00
using System.Collections.ObjectModel;
2025-08-27 16:11:06 -07:00
using System.IO;
2025-08-27 12:44:57 -07:00
using System.Linq;
using System.Threading;
2025-08-21 01:13:08 -07:00
using System.Threading.Tasks;
2025-08-27 12:44:57 -07:00
using Avalonia;
2025-08-21 01:13:08 -07:00
using Avalonia.Controls;
2025-08-27 16:11:06 -07:00
using Avalonia.Data;
2025-08-21 01:13:08 -07:00
using Avalonia.Interactivity;
2025-08-27 16:11:06 -07:00
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
2025-08-21 01:13:08 -07:00
using Avalonia.Threading;
2025-08-27 12:44:57 -07:00
using CommunityToolkit.Mvvm.ComponentModel;
using MercuryConverter.Data;
using MercuryConverter.ExportOperation;
using MercuryConverter.Utility;
2025-08-27 16:11:06 -07:00
using SaturnData.Notation.Serialization;
2025-08-21 01:13:08 -07:00
namespace MercuryConverter.UI.Views;
2025-08-27 12:44:57 -07:00
public enum ExportStatus
{
NotStarted, Working, Error, Finished, FinishedWithMessages
}
public partial class ExportRow : ObservableObject
{
2025-08-27 16:11:06 -07:00
private static Dictionary<ExportStatus, IImage?> 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;
2025-08-27 12:44:57 -07:00
public required Song Song { get; set; }
2025-08-27 16:11:06 -07:00
public void SetStatus(ExportStatus status)
{
StatusImg = StatusImgs[status];
}
2025-08-27 12:44:57 -07:00
}
2025-08-21 01:13:08 -07:00
public partial class Export : Panel
{
2025-08-27 12:44:57 -07:00
public ObservableCollection<ExportRow> Rows { get; } = new();
2025-08-21 01:13:08 -07:00
public Export()
{
InitializeComponent();
2025-08-27 12:44:57 -07:00
DataContext = this;
RadioShouldAudioConvert.IsCheckedChanged += OnUIChange;
NumThreads.PropertyChanged += OnUIChange;
RadioExportAll.IsCheckedChanged += OnExportSelectionChg;
ListingTable.PointerPressed += OnClick;
2025-08-27 16:11:06 -07:00
NumThreads.Bind(TextBox.TextProperty, new Binding(nameof(Settings.ConcurrentExports))
{
Source = Settings.I!
});
2025-08-27 12:44:57 -07:00
ToolTip.SetTip(TickGroupExports,
"Group exported songs into subfolders named after the version they released in. For example:\n" +
$"\"Exports/{Consts.NUM_SOURCE[5]}/Ado - \"");
Task.Run(async () =>
{
await Task.Delay(100);
MainWindow.Instance!.TabControl.SelectionChanged += OnExportSelectionChg;
});
2025-08-21 01:13:08 -07:00
}
2025-08-27 12:44:57 -07:00
private void OnClick(object? sender, RoutedEventArgs e)
2025-08-21 01:13:08 -07:00
{
2025-08-27 12:44:57 -07:00
ListingTable.SelectedItems.Clear();
}
private void OnUIChange(object? sender, AvaloniaPropertyChangedEventArgs e) => UpdateUIConditions();
private void OnUIChange(object? sender, RoutedEventArgs args) => UpdateUIConditions();
private void OnExportSelectionChg(object? sender, RoutedEventArgs args)
{
UpdateUIConditions();
UpdateRows();
}
public void OnExportClick()
{
Task.Run(ExportFlow);
}
private void UpdateRows()
{
Console.WriteLine("Updating rows!");
Rows.Clear();
if ((bool)RadioExportAll.IsChecked!)
{
Console.WriteLine("Adding DB songs to rows...");
Database.Songs.ToList().ForEach((s) => Rows.Add(new ExportRow { Song = s }));
}
else
{
Console.WriteLine("Adding selections to rows...");
Selection.Selections.ToList().ForEach((s) => Rows.Add(new ExportRow { Song = s }));
}
}
/// <summary>
/// Modify UI as needed; determine if we are in an exportable state to enable the button.
/// </summary>
private void UpdateUIConditions()
{
ListSelectAudioConvertFormat.IsEnabled = (bool)RadioShouldAudioConvert.IsChecked!;
BtnExport.IsEnabled =
( // ensure we have selections
Selection.Selections.Count > 0
|| ((bool)RadioExportAll.IsChecked! && Database.Songs.Count > 0)
) &&
( // ensure audio format is set
!(bool)RadioShouldAudioConvert.IsChecked || ListSelectAudioConvertFormat.SelectedIndex != -1
) &&
(
2025-08-27 16:11:06 -07:00
// enabled export worker count is in good range
int.TryParse(NumThreads.Text, out var thr) && 1 <= thr && thr <= Environment.ProcessorCount
2025-08-27 12:44:57 -07:00
);
2025-08-27 23:03:30 -07:00
var ffmpegAvail = Utils.IsFFMpegAvailable();
Console.WriteLine($"FFMpeg available: {ffmpegAvail}");
if (!ffmpegAvail)
RadioLeaveAudioWAV.IsChecked = true;
RadioLeaveAudioWAV.IsEnabled = ffmpegAvail;
RadioShouldAudioConvert.IsEnabled = ffmpegAvail;
NoFFMpegMessage.IsVisible = !ffmpegAvail;
2025-08-27 12:44:57 -07:00
}
private void UIExportingMode(bool isExporting)
{
Dispatcher.UIThread.Invoke(() =>
{
BtnExport.IsEnabled = !isExporting;
ExportOptionsPane.IsEnabled = !isExporting;
MainWindow.Instance!.TabSelection.IsEnabled = !isExporting;
ExportSelectionPane.IsEnabled = !isExporting;
});
}
private async void ExportFlow()
{
UIExportingMode(true);
2025-08-27 23:03:30 -07:00
var path = await Utils.BeginDirSelection("Choose your export path...", Settings.I!.ExportPath);
if (string.IsNullOrEmpty(path))
{
UIExportingMode(false);
return;
}
Settings.I!.ExportPath = path;
2025-08-27 12:44:57 -07:00
2025-08-27 16:11:06 -07:00
var options = await Dispatcher.UIThread.InvokeAsync(() =>
2025-08-27 12:44:57 -07:00
{
2025-08-27 16:11:06 -07:00
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);
2025-08-27 23:03:30 -07:00
await Dispatcher.UIThread.InvokeAsync(() => row.SetStatus(ExportStatus.Finished));
2025-08-27 16:11:06 -07:00
}
);
2025-08-27 12:44:57 -07:00
UIExportingMode(false);
2025-08-21 01:13:08 -07:00
}
}