Refine WinUI shell and improve render diagnostics

This commit is contained in:
2026-08-11 16:27:18 +02:00
parent d62ae0b9fb
commit d744056583
11 changed files with 397 additions and 107 deletions
+6 -2
View File
@@ -4,7 +4,8 @@
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>AmiReel_WinUI</RootNamespace>
<AssemblyName>AmiReel.WinUI</AssemblyName>
<AssemblyName>AmiReel</AssemblyName>
<ApplicationIcon>..\assets\AmiReel.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant())</RuntimeIdentifier>
@@ -12,6 +13,8 @@
<UseWinUI>true</UseWinUI>
<WinUISDKReferences>false</WinUISDKReferences>
<EnableMsixTooling>true</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -79,6 +82,7 @@
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
<IncludeAllContentForSelfExtract Condition="'$(Configuration)' != 'Debug'">True</IncludeAllContentForSelfExtract>
</PropertyGroup>
</Project>
+90 -2
View File
@@ -8,9 +8,97 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="PageBrush" Color="#0E1525"/>
<SolidColorBrush x:Key="PanelBrush" Color="#162033"/>
<SolidColorBrush x:Key="PanelAltBrush" Color="#1A2740"/>
<SolidColorBrush x:Key="FieldBrush" Color="#1D2940"/>
<SolidColorBrush x:Key="BorderBrush" Color="#2B3A58"/>
<SolidColorBrush x:Key="AccentBrush" Color="#4AB8FF"/>
<SolidColorBrush x:Key="AccentSoftBrush" Color="#13324F"/>
<SolidColorBrush x:Key="TextBrush" Color="#F5F7FB"/>
<SolidColorBrush x:Key="MutedBrush" Color="#9FB1CC"/>
<SolidColorBrush x:Key="ButtonBrush" Color="#243554"/>
<SolidColorBrush x:Key="ButtonHoverBrush" Color="#2E446C"/>
<SolidColorBrush x:Key="PrimaryTextBrush" Color="#07111D"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="PageBrush" Color="#F4F7FB"/>
<SolidColorBrush x:Key="PanelBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="PanelAltBrush" Color="#EEF4FB"/>
<SolidColorBrush x:Key="FieldBrush" Color="#F7FAFD"/>
<SolidColorBrush x:Key="BorderBrush" Color="#C7D3E3"/>
<SolidColorBrush x:Key="AccentBrush" Color="#0E9AEF"/>
<SolidColorBrush x:Key="AccentSoftBrush" Color="#D6EEFF"/>
<SolidColorBrush x:Key="TextBrush" Color="#122033"/>
<SolidColorBrush x:Key="MutedBrush" Color="#5F7390"/>
<SolidColorBrush x:Key="ButtonBrush" Color="#E5EDF7"/>
<SolidColorBrush x:Key="ButtonHoverBrush" Color="#D7E4F4"/>
<SolidColorBrush x:Key="PrimaryTextBrush" Color="#FFFFFF"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style TargetType="Page">
<Setter Property="Background" Value="{ThemeResource PageBrush}" />
</Style>
<Style x:Key="CardBorderStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource PanelBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="16" />
</Style>
<Style x:Key="SectionTitleStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
<Setter Property="FontSize" Value="24" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
<Style x:Key="MutedTextStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource MutedBrush}" />
</Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{ThemeResource ButtonBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
</Style>
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource AccentBrush}" />
<Setter Property="Foreground" Value="{ThemeResource PrimaryTextBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource AccentBrush}" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
</Style>
<Style TargetType="ComboBox">
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
</Style>
<Style TargetType="ProgressBar">
<Setter Property="Foreground" Value="{ThemeResource AccentBrush}" />
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
</Style>
<Style TargetType="ListView">
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
+56 -48
View File
@@ -7,33 +7,43 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid x:Name="RootGrid" Padding="20" RowSpacing="16" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="RootGrid" Padding="20" RowSpacing="16" Background="{ThemeResource PageBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="0.9*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="16">
<Border Padding="18" CornerRadius="16" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Grid.Column="0" Padding="18" Style="{StaticResource CardBorderStyle}">
<StackPanel Spacing="12">
<TextBlock Text="Source recordings" FontSize="28" FontWeight="SemiBold" />
<TextBlock Text="Source recordings" Style="{StaticResource SectionTitleStyle}" />
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Content="Add AVI files..." Click="AddInputs_Click" />
<Button Content="Clear" Click="ClearInputs_Click" />
</StackPanel>
<ListView x:Name="InputList" Height="220" SelectionMode="None" />
<ListView x:Name="InputList"
Height="120"
SelectionMode="Single"
SelectionChanged="InputList_SelectionChanged" />
<Button x:Name="PreviewSourceButton"
Content="Preview selected"
Click="PreviewSource_Click"
HorizontalAlignment="Left"
IsEnabled="False" />
</StackPanel>
</Border>
<Border Padding="18" CornerRadius="16" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Grid.Column="1" Padding="18" Style="{StaticResource CardBorderStyle}">
<StackPanel Spacing="12">
<TextBlock Text="End card and output" FontSize="28" FontWeight="SemiBold" />
<TextBlock Text="Leave blank to use a built-in black end card." Opacity="0.8" />
<TextBlock Text="End card and output" Style="{StaticResource SectionTitleStyle}" />
<TextBlock Text="Leave blank to use a built-in black end card." Style="{StaticResource MutedTextStyle}" />
<Grid ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions>
@@ -46,63 +56,61 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox x:Name="EndCardBox" Grid.Row="0" Header="End card image" />
<Button Grid.Row="0" Grid.Column="1" Content="Browse..." Click="BrowseEndCard_Click" VerticalAlignment="Bottom" />
<TextBox x:Name="EndCardBox" Grid.Row="0" MinHeight="48" />
<Button Grid.Row="0" Grid.Column="1" Content="Browse..." Click="BrowseEndCard_Click" VerticalAlignment="Stretch" MinWidth="120" />
<TextBox x:Name="OutputFolderBox" Grid.Row="1" Header="Output folder" />
<Button Grid.Row="1" Grid.Column="1" Content="Browse..." Click="BrowseOutput_Click" VerticalAlignment="Bottom" />
<TextBox x:Name="OutputFolderBox" Grid.Row="1" MinHeight="48" />
<Button Grid.Row="1" Grid.Column="1" Content="Browse..." Click="BrowseOutput_Click" VerticalAlignment="Stretch" MinWidth="120" />
<TextBox x:Name="OutputNameBox" Grid.Row="2" Grid.ColumnSpan="2" Header="Output filename prefix" />
<TextBox x:Name="OutputNameBox" Grid.Row="2" Grid.ColumnSpan="2" MinHeight="48" />
</Grid>
</StackPanel>
</Border>
</StackPanel>
</Grid>
<StackPanel Grid.Row="0" Grid.Column="2" Spacing="16">
<Border Padding="18" CornerRadius="16" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Grid.Row="1" Padding="18" Style="{StaticResource CardBorderStyle}">
<StackPanel Spacing="12">
<TextBlock Text="Render progress" FontSize="28" FontWeight="SemiBold" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Render progress" Style="{StaticResource SectionTitleStyle}" />
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock x:Name="StageText" Text="Ready" FontSize="18" FontWeight="SemiBold" />
<TextBlock x:Name="PercentText" Grid.Column="1" Text="0%" FontSize="18" FontWeight="SemiBold" />
</Grid>
<TextBlock x:Name="PercentText" Text="0%" FontSize="18" FontWeight="SemiBold" Foreground="{ThemeResource AccentBrush}" />
</StackPanel>
<ProgressBar x:Name="RenderProgressBar" Minimum="0" Maximum="100" Height="10" />
<TextBlock x:Name="StatusText" Text="Select the recordings and start the render. The end card is optional." TextWrapping="WrapWholeWords" Opacity="0.85" />
<TextBlock x:Name="StatusText" Text="Select the recordings and start the render. The end card is optional." TextWrapping="WrapWholeWords" Style="{StaticResource MutedTextStyle}" />
</StackPanel>
</Border>
<Border Padding="18" CornerRadius="16" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Grid.Row="2" Padding="18" Style="{StaticResource CardBorderStyle}">
<StackPanel Spacing="12">
<TextBlock Text="FFmpeg log" FontSize="28" FontWeight="SemiBold" />
<TextBlock Text="FFmpeg log" Style="{StaticResource SectionTitleStyle}" />
<TextBox x:Name="LogBox"
Height="360"
IsReadOnly="True"
AcceptsReturn="True"
TextWrapping="Wrap"
TextWrapping="NoWrap"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
FontFamily="Consolas"
FontSize="12" />
FontSize="12"
VerticalAlignment="Stretch"
Height="260" />
</StackPanel>
</Border>
</StackPanel>
<Grid Grid.Row="1" Grid.ColumnSpan="3">
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button x:Name="OpenOutputButton" Grid.Column="0" Content="Open output folder" Click="OpenOutput_Click" IsEnabled="False" HorizontalAlignment="Left" />
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="10">
<Button x:Name="OpenOutputButton" Content="Open output folder" Click="OpenOutput_Click" IsEnabled="False" HorizontalAlignment="Left" />
<Button x:Name="PreviewRenderedButton" Content="Preview render" Click="PreviewRendered_Click" IsEnabled="False" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10">
<Button x:Name="OpenSettingsButton" Content="Settings" Click="OpenSettings_Click" />
<Button x:Name="CancelButton" Content="Cancel" Click="Cancel_Click" IsEnabled="False" />
<Button x:Name="RenderButton" Content="Start render" Click="Render_Click" />
<Button x:Name="RenderButton" Content="Start render" Click="Render_Click" Style="{StaticResource PrimaryButtonStyle}" />
</StackPanel>
</Grid>
@@ -113,9 +121,9 @@
DefaultButton="Primary">
<ScrollViewer MaxHeight="560" VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="16">
<Border Padding="14" CornerRadius="12" Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Padding="14" Background="{ThemeResource PanelAltBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1" CornerRadius="12">
<StackPanel Spacing="10">
<TextBlock Text="Appearance" FontSize="24" FontWeight="SemiBold" />
<TextBlock Text="Appearance" Style="{StaticResource SectionTitleStyle}" />
<ComboBox x:Name="ThemeBox" Header="Theme" SelectionChanged="ThemeBox_SelectionChanged">
<ComboBoxItem Content="Dark" Tag="Dark" />
<ComboBoxItem Content="Light" Tag="Light" />
@@ -123,9 +131,9 @@
</StackPanel>
</Border>
<Border Padding="14" CornerRadius="12" Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Padding="14" Background="{ThemeResource PanelAltBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1" CornerRadius="12">
<StackPanel Spacing="10">
<TextBlock Text="Timing and screenshots" FontSize="24" FontWeight="SemiBold" />
<TextBlock Text="Timing and screenshots" Style="{StaticResource SectionTitleStyle}" />
<Grid ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -143,22 +151,22 @@
</StackPanel>
</Border>
<Border Padding="14" CornerRadius="12" Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Padding="14" Background="{ThemeResource PanelAltBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1" CornerRadius="12">
<StackPanel Spacing="10">
<TextBlock Text="Video encoding" FontSize="24" FontWeight="SemiBold" />
<TextBlock Text="Video encoding" Style="{StaticResource SectionTitleStyle}" />
<ComboBox x:Name="EncoderBox" Header="Encoder">
<ComboBoxItem Content="Auto (NVENC → CPU fallback)" Tag="Auto" />
<ComboBoxItem Content="NVIDIA NVENC" Tag="NvidiaNvenc" />
<ComboBoxItem Content="CPU libx264" Tag="CpuX264" />
</ComboBox>
<TextBlock Text="Output: 3840 × 2160 · 50 FPS" Opacity="0.8" />
<TextBlock Text="Output: 3840 × 2160 · 50 FPS" Style="{StaticResource MutedTextStyle}" />
</StackPanel>
</Border>
<Border Padding="14" CornerRadius="12" Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1">
<Border Padding="14" Background="{ThemeResource PanelAltBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1" CornerRadius="12">
<StackPanel Spacing="10">
<TextBlock Text="FFmpeg tools" FontSize="24" FontWeight="SemiBold" />
<TextBlock Text="Optional override. Leave blank to use embedded FFmpeg and FFprobe." TextWrapping="WrapWholeWords" Opacity="0.8" />
<TextBlock Text="FFmpeg tools" Style="{StaticResource SectionTitleStyle}" />
<TextBlock Text="Optional override. Leave blank to auto-detect FFmpeg and FFprobe from PATH, then use the embedded fallback." TextWrapping="WrapWholeWords" Style="{StaticResource MutedTextStyle}" />
<Grid ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
+55 -3
View File
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using AmigaDB.VideoRenderer.Models;
using AmigaDB.VideoRenderer.Services;
using Microsoft.UI.Xaml;
@@ -15,7 +16,11 @@ public sealed partial class MainPage : Page
{
private readonly ObservableCollection<string> _inputs = [];
private readonly AppSettings _loadedSettings;
private readonly StringBuilder _pendingLog = new();
private readonly object _logLock = new();
private CancellationTokenSource? _renderCancellation;
private string? _lastRenderedFile;
private bool _logFlushScheduled;
public MainPage()
{
@@ -107,7 +112,9 @@ public sealed partial class MainPage : Page
_renderCancellation = new CancellationTokenSource();
Progress<RenderProgress> progress = new(UpdateProgress);
await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token);
_lastRenderedFile = Path.Combine(settings.OutputDirectory, settings.OutputName + "_final.mp4");
OpenOutputButton.IsEnabled = true;
PreviewRenderedButton.IsEnabled = File.Exists(_lastRenderedFile);
await ShowMessageAsync("Render complete", "The AmiReel render completed successfully.");
}
catch (OperationCanceledException)
@@ -118,7 +125,7 @@ public sealed partial class MainPage : Page
{
AppendLog("ERROR: " + exception);
StageText.Text = "Failed";
await ShowMessageAsync("Render failed", exception.Message);
await ShowMessageAsync("Render failed", UserFacingErrors.Summarize(exception));
}
finally
{
@@ -141,6 +148,18 @@ public sealed partial class MainPage : Page
Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true });
}
private void PreviewSource_Click(object sender, RoutedEventArgs e)
{
if (InputList.SelectedItem is string path)
OpenPreview(path);
}
private void PreviewRendered_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(_lastRenderedFile) && File.Exists(_lastRenderedFile))
OpenPreview(_lastRenderedFile);
}
private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!IsLoaded) return;
@@ -191,11 +210,39 @@ public sealed partial class MainPage : Page
StatusText.Text = value.Message;
}
private void OpenPreview(string mediaPath)
{
if (!File.Exists(mediaPath))
return;
Process.Start(new ProcessStartInfo(mediaPath) { UseShellExecute = true });
}
private void AppendLog(string line)
{
lock (_logLock)
{
_pendingLog.AppendLine(line);
if (_logFlushScheduled)
return;
_logFlushScheduled = true;
}
_ = DispatcherQueue.TryEnqueue(() =>
{
LogBox.Text += line + Environment.NewLine;
string chunk;
lock (_logLock)
{
chunk = _pendingLog.ToString();
_pendingLog.Clear();
_logFlushScheduled = false;
}
if (chunk.Length == 0)
return;
LogBox.Text += chunk;
LogBox.Select(LogBox.Text.Length, 0);
});
}
@@ -207,7 +254,7 @@ public sealed partial class MainPage : Page
EndCardBox.Text.Trim(),
FfmpegPathBox.Text.Trim(),
FfprobePathBox.Text.Trim(),
string.Empty,
_loadedSettings.PreviewPlayerPath,
SelectedTheme(),
SelectedEncoder(),
TrimBox.Text.Trim(),
@@ -248,6 +295,11 @@ public sealed partial class MainPage : Page
EncoderBox.SelectedIndex = 0;
}
private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
PreviewSourceButton.IsEnabled = InputList.SelectedItem is string;
}
private void ApplyTheme(string theme)
{
RequestedTheme = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase)
+11 -5
View File
@@ -18,11 +18,17 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="AppTitleBar" Title="AmiReel">
<TitleBar.IconSource>
<ImageIconSource ImageSource="Assets/AppIcon.ico" />
</TitleBar.IconSource>
</TitleBar>
<Grid x:Name="AppTitleBar" Height="44" Background="Transparent">
<StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center" Margin="14,0,0,0">
<Image Source="ms-appx:///Assets/Square44x44Logo.scale-200.png"
Width="20"
Height="20"
Stretch="Uniform" />
<TextBlock Text="AmiReel"
FontSize="16"
VerticalAlignment="Center" />
</StackPanel>
</Grid>
<!--
The Frame hosts pages for your application content. Add your UI to
+5 -1
View File
@@ -1,4 +1,6 @@
using Microsoft.UI.Xaml;
using System;
using System.IO;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -19,7 +21,9 @@ public sealed partial class MainWindow : Window
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar);
AppWindow.SetIcon("Assets/AppIcon.ico");
string? executablePath = Environment.ProcessPath;
if (!string.IsNullOrWhiteSpace(executablePath) && File.Exists(executablePath))
AppWindow.SetIcon(executablePath);
// Navigate the root frame to the main page on startup.
RootFrame.Navigate(typeof(MainPage));
+3 -3
View File
@@ -16,7 +16,7 @@
<mp:PhoneIdentity PhoneProductId="F004AE73-5989-46F3-B913-E9A3A355713F" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>AmiReel.WinUI</DisplayName>
<DisplayName>AmiReel</DisplayName>
<PublisherDisplayName>AppPublisher</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
@@ -35,8 +35,8 @@
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="AmiReel.WinUI"
Description="AmiReel.WinUI"
DisplayName="AmiReel"
Description="AmiReel"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
+1 -1
View File
@@ -43,7 +43,7 @@
<Button Content="Add AVI files…" Click="AddInputs_Click" Margin="0,0,8,0"/>
<Button Content="Clear" Click="ClearInputs_Click"/>
</StackPanel>
<ListBox x:Name="InputList" Grid.Row="1" SelectionChanged="InputList_SelectionChanged" MouseDoubleClick="InputList_MouseDoubleClick"/>
<ListBox x:Name="InputList" Grid.Row="1" SelectionChanged="InputList_SelectionChanged"/>
<Button x:Name="PreviewSourceButton" Grid.Row="2" Content="Preview selected" Click="PreviewSource_Click"
HorizontalAlignment="Left" Margin="0,10,0,0" IsEnabled="False"/>
</Grid>
+27 -9
View File
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using AmigaDB.VideoRenderer.Models;
@@ -15,8 +16,11 @@ public partial class MainWindow : Window
{
private readonly ObservableCollection<string> _inputs = [];
private readonly AppSettings _loadedSettings;
private readonly StringBuilder _pendingLog = new();
private readonly object _logLock = new();
private CancellationTokenSource? _renderCancellation;
private string? _lastRenderedFile;
private bool _logFlushScheduled;
public MainWindow()
{
@@ -110,7 +114,7 @@ public partial class MainWindow : Window
catch (Exception exception)
{
AppendLog("ERROR: " + exception);
MessageBox.Show(this, exception.Message, "Render failed", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(this, UserFacingErrors.Summarize(exception), "Render failed", MessageBoxButton.OK, MessageBoxImage.Error);
StageText.Text = "Failed";
}
finally
@@ -165,12 +169,6 @@ public partial class MainWindow : Window
private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> PreviewSourceButton.IsEnabled = InputList.SelectedItem is string;
private void InputList_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (InputList.SelectedItem is string path)
OpenPreview(path);
}
private void SetRendering(bool rendering)
{
RenderButton.IsEnabled = !rendering;
@@ -187,9 +185,29 @@ public partial class MainWindow : Window
private void AppendLog(string line)
{
Dispatcher.Invoke(() =>
lock (_logLock)
{
LogBox.AppendText(line + Environment.NewLine);
_pendingLog.AppendLine(line);
if (_logFlushScheduled)
return;
_logFlushScheduled = true;
}
_ = Dispatcher.BeginInvoke(() =>
{
string chunk;
lock (_logLock)
{
chunk = _pendingLog.ToString();
_pendingLog.Clear();
_logFlushScheduled = false;
}
if (chunk.Length == 0)
return;
LogBox.AppendText(chunk);
LogBox.ScrollToEnd();
});
}
+76
View File
@@ -0,0 +1,76 @@
namespace AmigaDB.VideoRenderer.Services;
public static class UserFacingErrors
{
public static string Summarize(Exception exception)
{
string message = exception.Message.Trim();
if (string.IsNullOrWhiteSpace(message))
return "An unexpected error occurred.";
string[] lines = message
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (lines.Length == 0)
return "An unexpected error occurred.";
if (!lines[0].StartsWith("Process exited with code", StringComparison.OrdinalIgnoreCase))
return message;
List<string> important = [];
foreach (string line in lines.Skip(1))
{
if (IsNoise(line))
continue;
important.Add(line);
}
if (important.Count == 0)
return lines[0];
List<string> summary = [lines[0]];
foreach (string line in important.TakeLast(3))
{
if (!summary.Contains(line, StringComparer.Ordinal))
summary.Add(line);
}
return string.Join(Environment.NewLine, summary);
}
private static bool IsNoise(string line)
{
return line.StartsWith("ffmpeg version ", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("built with ", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("configuration:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libavutil", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libavcodec", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libavformat", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libavdevice", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libavfilter", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libswscale", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("libswresample", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Input #", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Output #", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Stream mapping:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Stream #", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Metadata:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("major_brand", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("minor_version", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("compatible_brands", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("encoder", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Duration:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Press [q] to stop", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("[in#", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("[out#", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Last message repeated", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("frame=", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("video:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("audio:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("subtitle:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("other streams:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("global headers:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("muxing overhead:", StringComparison.OrdinalIgnoreCase);
}
}
+48 -14
View File
@@ -1,19 +1,57 @@
[CmdletBinding()]
param(
[ValidateSet('WinUI', 'Wpf')]
[string]$Target = 'WinUI'
)
$ErrorActionPreference = 'Stop'
$projectDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ffmpeg = Join-Path $projectDir 'ThirdParty\ffmpeg.exe'
$ffprobe = Join-Path $projectDir 'ThirdParty\ffprobe.exe'
$releaseExe = Join-Path $projectDir 'bin\Release\net8.0-windows\win-x64\AmiReel.exe'
$publishDir = Join-Path $projectDir 'bin\Release\net8.0-windows\win-x64\publish'
$publishExe = Join-Path $publishDir 'AmiReel.exe'
if (-not (Test-Path $ffmpeg) -or -not (Test-Path $ffprobe)) {
throw 'Add ffmpeg.exe and ffprobe.exe to ThirdParty before publishing.'
}
switch ($Target) {
'WinUI' {
$projectPath = Join-Path $projectDir 'AmiReel.WinUI\AmiReel.WinUI.csproj'
$releaseDir = Join-Path $projectDir 'AmiReel.WinUI\bin\Release\net10.0-windows10.0.26100.0\win-x64'
$releaseExe = Join-Path $releaseDir 'AmiReel.exe'
$publishDir = Join-Path $releaseDir 'publish'
$publishExe = Join-Path $publishDir 'AmiReel.exe'
$publishArguments = @(
'publish', $projectPath,
'-c', 'Release',
'-r', 'win-x64',
'--self-contained', 'true',
'-p:WindowsPackageType=None',
'-p:PublishSingleFile=true',
'-p:IncludeNativeLibrariesForSelfExtract=true',
'-p:EnableCompressionInSingleFile=true'
)
}
'Wpf' {
$projectPath = Join-Path $projectDir 'AmigaDB.VideoRenderer.csproj'
$releaseDir = Join-Path $projectDir 'bin\Release\net8.0-windows\win-x64'
$releaseExe = Join-Path $releaseDir 'AmiReel.exe'
$publishDir = Join-Path $releaseDir 'publish'
$publishExe = Join-Path $publishDir 'AmiReel.exe'
$publishArguments = @(
'publish', $projectPath,
'-c', 'Release',
'-r', 'win-x64',
'--self-contained', 'true',
'-p:PublishSingleFile=true',
'-p:IncludeNativeLibrariesForSelfExtract=true',
'-p:EnableCompressionInSingleFile=true'
)
}
}
if ((Test-Path $releaseExe) -or (Test-Path $publishExe)) {
$runningReleaseInstances = Get-Process AmiReel -ErrorAction SilentlyContinue |
Where-Object {
$runningReleaseInstances = Get-Process | Where-Object {
$_.Path -and (
[string]::Equals($_.Path, $releaseExe, [System.StringComparison]::OrdinalIgnoreCase) -or
[string]::Equals($_.Path, $publishExe, [System.StringComparison]::OrdinalIgnoreCase)
@@ -21,7 +59,7 @@ if ((Test-Path $releaseExe) -or (Test-Path $publishExe)) {
}
if ($runningReleaseInstances) {
Write-Host 'Stopping running AmiReel build before publish...' -ForegroundColor Yellow
Write-Host "Stopping running $Target build before publish..." -ForegroundColor Yellow
$runningReleaseInstances | Stop-Process -Force
Start-Sleep -Milliseconds 800
}
@@ -31,12 +69,8 @@ if (Test-Path $publishDir) {
Remove-Item -LiteralPath $publishDir -Recurse -Force -ErrorAction SilentlyContinue
}
dotnet publish (Join-Path $projectDir 'AmigaDB.VideoRenderer.csproj') `
-c Release `
-r win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true
& dotnet @publishArguments
Write-Host 'Published to bin\Release\net8.0-windows\win-x64\publish' -ForegroundColor Green
Write-Host "Published $Target to $publishDir" -ForegroundColor Green
Write-Host "Release folder: $publishDir" -ForegroundColor Cyan
Write-Host "Executable: $publishExe" -ForegroundColor Cyan