224 lines
8.5 KiB
C#
224 lines
8.5 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using AmigaDB.VideoRenderer.Models;
|
|
using AmigaDB.VideoRenderer.Services;
|
|
using Microsoft.Win32;
|
|
using System.Windows.Media;
|
|
|
|
namespace AmigaDB.VideoRenderer;
|
|
|
|
public partial class MainWindow : Window
|
|
{
|
|
private readonly ObservableCollection<string> _inputs = [];
|
|
private readonly AppSettings _loadedSettings;
|
|
private CancellationTokenSource? _renderCancellation;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
InputList.ItemsSource = _inputs;
|
|
_loadedSettings = AppSettingsStore.Load();
|
|
OutputFolderBox.Text = _loadedSettings.OutputFolder;
|
|
EndCardBox.Text = _loadedSettings.EndCardPath;
|
|
FfmpegPathBox.Text = _loadedSettings.FfmpegPath;
|
|
FfprobePathBox.Text = _loadedSettings.FfprobePath;
|
|
SelectTheme(_loadedSettings.Theme);
|
|
ApplyTheme(_loadedSettings.Theme);
|
|
Closing += (_, _) => SaveSettings();
|
|
}
|
|
|
|
private void AddInputs_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OpenFileDialog dialog = new() { Filter = "AVI recordings (*.avi)|*.avi", Multiselect = true };
|
|
if (dialog.ShowDialog(this) != true) return;
|
|
foreach (string file in dialog.FileNames.OrderBy(NaturalKey))
|
|
if (!_inputs.Contains(file, StringComparer.OrdinalIgnoreCase)) _inputs.Add(file);
|
|
}
|
|
|
|
private void ClearInputs_Click(object sender, RoutedEventArgs e) => _inputs.Clear();
|
|
|
|
private void BrowseEndCard_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OpenFileDialog dialog = new() { Filter = "Images|*.png;*.jpg;*.jpeg;*.webp;*.bmp|All files|*.*" };
|
|
if (dialog.ShowDialog(this) == true) EndCardBox.Text = dialog.FileName;
|
|
}
|
|
|
|
private void BrowseOutput_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OpenFolderDialog dialog = new() { InitialDirectory = OutputFolderBox.Text };
|
|
if (dialog.ShowDialog(this) == true) OutputFolderBox.Text = dialog.FolderName;
|
|
}
|
|
|
|
private void BrowseFfmpeg_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OpenFileDialog dialog = new() { Filter = "FFmpeg executable (ffmpeg.exe)|ffmpeg.exe|Executable files (*.exe)|*.exe|All files|*.*" };
|
|
if (dialog.ShowDialog(this) == true) FfmpegPathBox.Text = dialog.FileName;
|
|
}
|
|
|
|
private void BrowseFfprobe_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OpenFileDialog dialog = new() { Filter = "FFprobe executable (ffprobe.exe)|ffprobe.exe|Executable files (*.exe)|*.exe|All files|*.*" };
|
|
if (dialog.ShowDialog(this) == true) FfprobePathBox.Text = dialog.FileName;
|
|
}
|
|
|
|
private async void Render_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
RenderSettings settings = ReadSettings();
|
|
SaveSettings();
|
|
SetRendering(true);
|
|
LogBox.Clear();
|
|
_renderCancellation = new CancellationTokenSource();
|
|
Progress<RenderProgress> progress = new(UpdateProgress);
|
|
await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token);
|
|
OpenOutputButton.IsEnabled = true;
|
|
MessageBox.Show(this, "The AmiReel render completed successfully.", "Render complete",
|
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
UpdateProgress(new(RenderProgress.Value, "Cancelled", "The render was cancelled."));
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
AppendLog("ERROR: " + exception);
|
|
MessageBox.Show(this, exception.Message, "Render failed", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
StageText.Text = "Failed";
|
|
}
|
|
finally
|
|
{
|
|
_renderCancellation?.Dispose();
|
|
_renderCancellation = null;
|
|
SetRendering(false);
|
|
}
|
|
}
|
|
|
|
private RenderSettings ReadSettings()
|
|
{
|
|
static double Number(string text, string name)
|
|
{
|
|
if (!double.TryParse(text.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out double value) || value < 0)
|
|
throw new ArgumentException($"Enter a valid non-negative value for {name}.");
|
|
return value;
|
|
}
|
|
if (!int.TryParse(IntervalBox.Text, out int interval) || interval < 1)
|
|
throw new ArgumentException("Thumbnail interval must be at least one second.");
|
|
EncoderMode encoder = Enum.Parse<EncoderMode>(((ComboBoxItem)EncoderBox.SelectedItem).Tag!.ToString()!);
|
|
return new(_inputs.ToList(), EndCardBox.Text.Trim(), OutputFolderBox.Text.Trim(), OutputNameBox.Text.Trim(),
|
|
FfmpegPathBox.Text.Trim(), FfprobePathBox.Text.Trim(),
|
|
encoder, Number(TrimBox.Text, "trim start"), Number(FadeBox.Text, "fade"), Number(HoldBox.Text, "end-card hold"), interval);
|
|
}
|
|
|
|
private void Cancel_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
CancelButton.IsEnabled = false;
|
|
StatusText.Text = "Stopping FFmpeg…";
|
|
_renderCancellation?.Cancel();
|
|
}
|
|
|
|
private void OpenOutput_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (Directory.Exists(OutputFolderBox.Text))
|
|
Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true });
|
|
}
|
|
|
|
private void SetRendering(bool rendering)
|
|
{
|
|
RenderButton.IsEnabled = !rendering;
|
|
CancelButton.IsEnabled = rendering;
|
|
}
|
|
|
|
private void UpdateProgress(RenderProgress value)
|
|
{
|
|
RenderProgress.Value = value.Percent;
|
|
PercentText.Text = $"{value.Percent:0}%";
|
|
StageText.Text = value.Stage;
|
|
StatusText.Text = value.Message;
|
|
}
|
|
|
|
private void AppendLog(string line)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
LogBox.AppendText(line + Environment.NewLine);
|
|
LogBox.ScrollToEnd();
|
|
});
|
|
}
|
|
|
|
private static string NaturalKey(string path)
|
|
{
|
|
string name = Path.GetFileNameWithoutExtension(path);
|
|
int underscore = name.LastIndexOf('_');
|
|
return underscore >= 0 && int.TryParse(name[(underscore + 1)..], out int number)
|
|
? name[..underscore] + number.ToString("D10")
|
|
: name;
|
|
}
|
|
|
|
private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (!IsLoaded) return;
|
|
string theme = SelectedTheme();
|
|
ApplyTheme(theme);
|
|
SaveSettings();
|
|
}
|
|
|
|
private void SaveSettings()
|
|
{
|
|
AppSettingsStore.Save(new AppSettings(
|
|
OutputFolderBox.Text.Trim(),
|
|
EndCardBox.Text.Trim(),
|
|
FfmpegPathBox.Text.Trim(),
|
|
FfprobePathBox.Text.Trim(),
|
|
SelectedTheme()));
|
|
}
|
|
|
|
private string SelectedTheme() => ((ComboBoxItem)ThemeBox.SelectedItem).Tag!.ToString()!;
|
|
|
|
private void SelectTheme(string theme)
|
|
{
|
|
foreach (ComboBoxItem item in ThemeBox.Items)
|
|
{
|
|
if (string.Equals(item.Tag?.ToString(), theme, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ThemeBox.SelectedItem = item;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ThemeBox.SelectedIndex = 0;
|
|
}
|
|
|
|
private void ApplyTheme(string theme)
|
|
{
|
|
bool light = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase);
|
|
|
|
SetBrush("PageBrush", light ? "#F4F7FB" : "#0E1525");
|
|
SetBrush("PanelBrush", light ? "#FFFFFF" : "#162033");
|
|
SetBrush("PanelAltBrush", light ? "#EEF4FB" : "#1A2740");
|
|
SetBrush("FieldBrush", light ? "#F7FAFD" : "#1D2940");
|
|
SetBrush("BorderBrush", light ? "#C7D3E3" : "#2B3A58");
|
|
SetBrush("AccentBrush", light ? "#0E9AEF" : "#4AB8FF");
|
|
SetBrush("AccentSoftBrush", light ? "#D6EEFF" : "#13324F");
|
|
SetBrush("TextBrush", light ? "#122033" : "#F5F7FB");
|
|
SetBrush("MutedBrush", light ? "#5F7390" : "#9FB1CC");
|
|
SetBrush("ButtonBrush", light ? "#E5EDF7" : "#243554");
|
|
SetBrush("ButtonHoverBrush", light ? "#D7E4F4" : "#2E446C");
|
|
SetBrush("ButtonDisabledBrush", light ? "#EEF2F7" : "#2A3140");
|
|
SetBrush("ButtonDisabledTextBrush", light ? "#8C99AB" : "#7E8BA1");
|
|
SetBrush("PrimaryTextBrush", light ? "#FFFFFF" : "#07111D");
|
|
SetBrush("SelectionBrush", light ? "#BEE4FF" : "#295C87");
|
|
SetBrush("SelectionTextBrush", light ? "#122033" : "#FFFFFF");
|
|
}
|
|
|
|
private void SetBrush(string key, string color)
|
|
{
|
|
if (Application.Current.Resources[key] is SolidColorBrush brush)
|
|
brush.Color = (Color)ColorConverter.ConvertFromString(color);
|
|
}
|
|
}
|