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> <TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>AmiReel_WinUI</RootNamespace> <RootNamespace>AmiReel_WinUI</RootNamespace>
<AssemblyName>AmiReel.WinUI</AssemblyName> <AssemblyName>AmiReel</AssemblyName>
<ApplicationIcon>..\assets\AmiReel.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms> <Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant())</RuntimeIdentifier> <RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant())</RuntimeIdentifier>
@@ -12,6 +13,8 @@
<UseWinUI>true</UseWinUI> <UseWinUI>true</UseWinUI>
<WinUISDKReferences>false</WinUISDKReferences> <WinUISDKReferences>false</WinUISDKReferences>
<EnableMsixTooling>true</EnableMsixTooling> <EnableMsixTooling>true</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -79,6 +82,7 @@
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun> <PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun> <PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed> <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> </PropertyGroup>
</Project> </Project>
+90 -2
View File
@@ -8,9 +8,97 @@
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries> </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> </ResourceDictionary>
</Application.Resources> </Application.Resources>
</Application> </Application>
+70 -62
View File
@@ -7,33 +7,43 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> 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> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="0.9*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="16"> <Grid Grid.Row="0" ColumnSpacing="16">
<Border Padding="18" CornerRadius="16" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Padding="18" Style="{StaticResource CardBorderStyle}">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<TextBlock Text="Source recordings" FontSize="28" FontWeight="SemiBold" /> <TextBlock Text="Source recordings" Style="{StaticResource SectionTitleStyle}" />
<StackPanel Orientation="Horizontal" Spacing="10"> <StackPanel Orientation="Horizontal" Spacing="10">
<Button Content="Add AVI files..." Click="AddInputs_Click" /> <Button Content="Add AVI files..." Click="AddInputs_Click" />
<Button Content="Clear" Click="ClearInputs_Click" /> <Button Content="Clear" Click="ClearInputs_Click" />
</StackPanel> </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> </StackPanel>
</Border> </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"> <StackPanel Spacing="12">
<TextBlock Text="End card and output" FontSize="28" FontWeight="SemiBold" /> <TextBlock Text="End card and output" Style="{StaticResource SectionTitleStyle}" />
<TextBlock Text="Leave blank to use a built-in black end card." Opacity="0.8" /> <TextBlock Text="Leave blank to use a built-in black end card." Style="{StaticResource MutedTextStyle}" />
<Grid ColumnSpacing="10" RowSpacing="10"> <Grid ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -46,63 +56,61 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBox x:Name="EndCardBox" Grid.Row="0" Header="End card image" /> <TextBox x:Name="EndCardBox" Grid.Row="0" MinHeight="48" />
<Button Grid.Row="0" Grid.Column="1" Content="Browse..." Click="BrowseEndCard_Click" VerticalAlignment="Bottom" /> <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" /> <TextBox x:Name="OutputFolderBox" Grid.Row="1" MinHeight="48" />
<Button Grid.Row="1" Grid.Column="1" Content="Browse..." Click="BrowseOutput_Click" VerticalAlignment="Bottom" /> <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> </Grid>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </Grid>
<StackPanel Grid.Row="0" Grid.Column="2" Spacing="16"> <Border Grid.Row="1" Padding="18" Style="{StaticResource CardBorderStyle}">
<Border Padding="18" CornerRadius="16" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"> <StackPanel Spacing="12">
<StackPanel Spacing="12"> <TextBlock Text="Render progress" Style="{StaticResource SectionTitleStyle}" />
<TextBlock Text="Render progress" FontSize="28" FontWeight="SemiBold" /> <StackPanel Orientation="Horizontal" Spacing="6">
<Grid> <TextBlock x:Name="StageText" Text="Ready" FontSize="18" FontWeight="SemiBold" />
<Grid.ColumnDefinitions> <TextBlock x:Name="PercentText" Text="0%" FontSize="18" FontWeight="SemiBold" Foreground="{ThemeResource AccentBrush}" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="StageText" Text="Ready" FontSize="18" FontWeight="SemiBold" />
<TextBlock x:Name="PercentText" Grid.Column="1" Text="0%" FontSize="18" FontWeight="SemiBold" />
</Grid>
<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" />
</StackPanel> </StackPanel>
</Border> <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" 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"> <StackPanel Spacing="12">
<TextBlock Text="FFmpeg log" FontSize="28" FontWeight="SemiBold" /> <TextBlock Text="FFmpeg log" Style="{StaticResource SectionTitleStyle}" />
<TextBox x:Name="LogBox" <TextBox x:Name="LogBox"
Height="360" IsReadOnly="True"
IsReadOnly="True" AcceptsReturn="True"
AcceptsReturn="True" TextWrapping="NoWrap"
TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" FontFamily="Consolas"
FontFamily="Consolas" FontSize="12"
FontSize="12" /> VerticalAlignment="Stretch"
</StackPanel> Height="260" />
</Border> </StackPanel>
</StackPanel> </Border>
<Grid Grid.Row="1" Grid.ColumnSpan="3"> <Grid Grid.Row="3">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </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"> <StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10">
<Button x:Name="OpenSettingsButton" Content="Settings" Click="OpenSettings_Click" /> <Button x:Name="OpenSettingsButton" Content="Settings" Click="OpenSettings_Click" />
<Button x:Name="CancelButton" Content="Cancel" Click="Cancel_Click" IsEnabled="False" /> <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> </StackPanel>
</Grid> </Grid>
@@ -113,9 +121,9 @@
DefaultButton="Primary"> DefaultButton="Primary">
<ScrollViewer MaxHeight="560" VerticalScrollBarVisibility="Auto"> <ScrollViewer MaxHeight="560" VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="16"> <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"> <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"> <ComboBox x:Name="ThemeBox" Header="Theme" SelectionChanged="ThemeBox_SelectionChanged">
<ComboBoxItem Content="Dark" Tag="Dark" /> <ComboBoxItem Content="Dark" Tag="Dark" />
<ComboBoxItem Content="Light" Tag="Light" /> <ComboBoxItem Content="Light" Tag="Light" />
@@ -123,9 +131,9 @@
</StackPanel> </StackPanel>
</Border> </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"> <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 ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -143,22 +151,22 @@
</StackPanel> </StackPanel>
</Border> </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"> <StackPanel Spacing="10">
<TextBlock Text="Video encoding" FontSize="24" FontWeight="SemiBold" /> <TextBlock Text="Video encoding" Style="{StaticResource SectionTitleStyle}" />
<ComboBox x:Name="EncoderBox" Header="Encoder"> <ComboBox x:Name="EncoderBox" Header="Encoder">
<ComboBoxItem Content="Auto (NVENC → CPU fallback)" Tag="Auto" /> <ComboBoxItem Content="Auto (NVENC → CPU fallback)" Tag="Auto" />
<ComboBoxItem Content="NVIDIA NVENC" Tag="NvidiaNvenc" /> <ComboBoxItem Content="NVIDIA NVENC" Tag="NvidiaNvenc" />
<ComboBoxItem Content="CPU libx264" Tag="CpuX264" /> <ComboBoxItem Content="CPU libx264" Tag="CpuX264" />
</ComboBox> </ComboBox>
<TextBlock Text="Output: 3840 × 2160 · 50 FPS" Opacity="0.8" /> <TextBlock Text="Output: 3840 × 2160 · 50 FPS" Style="{StaticResource MutedTextStyle}" />
</StackPanel> </StackPanel>
</Border> </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"> <StackPanel Spacing="10">
<TextBlock Text="FFmpeg tools" FontSize="24" FontWeight="SemiBold" /> <TextBlock Text="FFmpeg tools" Style="{StaticResource SectionTitleStyle}" />
<TextBlock Text="Optional override. Leave blank to use embedded FFmpeg and FFprobe." TextWrapping="WrapWholeWords" Opacity="0.8" /> <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 ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
+55 -3
View File
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Text;
using AmigaDB.VideoRenderer.Models; using AmigaDB.VideoRenderer.Models;
using AmigaDB.VideoRenderer.Services; using AmigaDB.VideoRenderer.Services;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
@@ -15,7 +16,11 @@ public sealed partial class MainPage : Page
{ {
private readonly ObservableCollection<string> _inputs = []; private readonly ObservableCollection<string> _inputs = [];
private readonly AppSettings _loadedSettings; private readonly AppSettings _loadedSettings;
private readonly StringBuilder _pendingLog = new();
private readonly object _logLock = new();
private CancellationTokenSource? _renderCancellation; private CancellationTokenSource? _renderCancellation;
private string? _lastRenderedFile;
private bool _logFlushScheduled;
public MainPage() public MainPage()
{ {
@@ -107,7 +112,9 @@ public sealed partial class MainPage : Page
_renderCancellation = new CancellationTokenSource(); _renderCancellation = new CancellationTokenSource();
Progress<RenderProgress> progress = new(UpdateProgress); Progress<RenderProgress> progress = new(UpdateProgress);
await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token); await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token);
_lastRenderedFile = Path.Combine(settings.OutputDirectory, settings.OutputName + "_final.mp4");
OpenOutputButton.IsEnabled = true; OpenOutputButton.IsEnabled = true;
PreviewRenderedButton.IsEnabled = File.Exists(_lastRenderedFile);
await ShowMessageAsync("Render complete", "The AmiReel render completed successfully."); await ShowMessageAsync("Render complete", "The AmiReel render completed successfully.");
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -118,7 +125,7 @@ public sealed partial class MainPage : Page
{ {
AppendLog("ERROR: " + exception); AppendLog("ERROR: " + exception);
StageText.Text = "Failed"; StageText.Text = "Failed";
await ShowMessageAsync("Render failed", exception.Message); await ShowMessageAsync("Render failed", UserFacingErrors.Summarize(exception));
} }
finally finally
{ {
@@ -141,6 +148,18 @@ public sealed partial class MainPage : Page
Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true }); 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) private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (!IsLoaded) return; if (!IsLoaded) return;
@@ -191,11 +210,39 @@ public sealed partial class MainPage : Page
StatusText.Text = value.Message; 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) private void AppendLog(string line)
{ {
lock (_logLock)
{
_pendingLog.AppendLine(line);
if (_logFlushScheduled)
return;
_logFlushScheduled = true;
}
_ = DispatcherQueue.TryEnqueue(() => _ = 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); LogBox.Select(LogBox.Text.Length, 0);
}); });
} }
@@ -207,7 +254,7 @@ public sealed partial class MainPage : Page
EndCardBox.Text.Trim(), EndCardBox.Text.Trim(),
FfmpegPathBox.Text.Trim(), FfmpegPathBox.Text.Trim(),
FfprobePathBox.Text.Trim(), FfprobePathBox.Text.Trim(),
string.Empty, _loadedSettings.PreviewPlayerPath,
SelectedTheme(), SelectedTheme(),
SelectedEncoder(), SelectedEncoder(),
TrimBox.Text.Trim(), TrimBox.Text.Trim(),
@@ -248,6 +295,11 @@ public sealed partial class MainPage : Page
EncoderBox.SelectedIndex = 0; EncoderBox.SelectedIndex = 0;
} }
private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
PreviewSourceButton.IsEnabled = InputList.SelectedItem is string;
}
private void ApplyTheme(string theme) private void ApplyTheme(string theme)
{ {
RequestedTheme = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase) RequestedTheme = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase)
+11 -5
View File
@@ -18,11 +18,17 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TitleBar x:Name="AppTitleBar" Title="AmiReel"> <Grid x:Name="AppTitleBar" Height="44" Background="Transparent">
<TitleBar.IconSource> <StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center" Margin="14,0,0,0">
<ImageIconSource ImageSource="Assets/AppIcon.ico" /> <Image Source="ms-appx:///Assets/Square44x44Logo.scale-200.png"
</TitleBar.IconSource> Width="20"
</TitleBar> 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 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 Microsoft.UI.Xaml;
using System;
using System.IO;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info. // 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; ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar); 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. // Navigate the root frame to the main page on startup.
RootFrame.Navigate(typeof(MainPage)); 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"/> <mp:PhoneIdentity PhoneProductId="F004AE73-5989-46F3-B913-E9A3A355713F" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties> <Properties>
<DisplayName>AmiReel.WinUI</DisplayName> <DisplayName>AmiReel</DisplayName>
<PublisherDisplayName>AppPublisher</PublisherDisplayName> <PublisherDisplayName>AppPublisher</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo> <Logo>Assets\StoreLogo.png</Logo>
</Properties> </Properties>
@@ -35,8 +35,8 @@
Executable="$targetnametoken$.exe" Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$"> EntryPoint="$targetentrypoint$">
<uap:VisualElements <uap:VisualElements
DisplayName="AmiReel.WinUI" DisplayName="AmiReel"
Description="AmiReel.WinUI" Description="AmiReel"
BackgroundColor="transparent" BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png" Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.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="Add AVI files…" Click="AddInputs_Click" Margin="0,0,8,0"/>
<Button Content="Clear" Click="ClearInputs_Click"/> <Button Content="Clear" Click="ClearInputs_Click"/>
</StackPanel> </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" <Button x:Name="PreviewSourceButton" Grid.Row="2" Content="Preview selected" Click="PreviewSource_Click"
HorizontalAlignment="Left" Margin="0,10,0,0" IsEnabled="False"/> HorizontalAlignment="Left" Margin="0,10,0,0" IsEnabled="False"/>
</Grid> </Grid>
+27 -9
View File
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using AmigaDB.VideoRenderer.Models; using AmigaDB.VideoRenderer.Models;
@@ -15,8 +16,11 @@ public partial class MainWindow : Window
{ {
private readonly ObservableCollection<string> _inputs = []; private readonly ObservableCollection<string> _inputs = [];
private readonly AppSettings _loadedSettings; private readonly AppSettings _loadedSettings;
private readonly StringBuilder _pendingLog = new();
private readonly object _logLock = new();
private CancellationTokenSource? _renderCancellation; private CancellationTokenSource? _renderCancellation;
private string? _lastRenderedFile; private string? _lastRenderedFile;
private bool _logFlushScheduled;
public MainWindow() public MainWindow()
{ {
@@ -110,7 +114,7 @@ public partial class MainWindow : Window
catch (Exception exception) catch (Exception exception)
{ {
AppendLog("ERROR: " + 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"; StageText.Text = "Failed";
} }
finally finally
@@ -165,12 +169,6 @@ public partial class MainWindow : Window
private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e) private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> PreviewSourceButton.IsEnabled = InputList.SelectedItem is string; => 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) private void SetRendering(bool rendering)
{ {
RenderButton.IsEnabled = !rendering; RenderButton.IsEnabled = !rendering;
@@ -187,9 +185,29 @@ public partial class MainWindow : Window
private void AppendLog(string line) 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(); 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);
}
}
+53 -19
View File
@@ -1,27 +1,65 @@
[CmdletBinding()]
param(
[ValidateSet('WinUI', 'Wpf')]
[string]$Target = 'WinUI'
)
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$projectDir = Split-Path -Parent $MyInvocation.MyCommand.Path $projectDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ffmpeg = Join-Path $projectDir 'ThirdParty\ffmpeg.exe' $ffmpeg = Join-Path $projectDir 'ThirdParty\ffmpeg.exe'
$ffprobe = Join-Path $projectDir 'ThirdParty\ffprobe.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)) { if (-not (Test-Path $ffmpeg) -or -not (Test-Path $ffprobe)) {
throw 'Add ffmpeg.exe and ffprobe.exe to ThirdParty before publishing.' 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)) { if ((Test-Path $releaseExe) -or (Test-Path $publishExe)) {
$runningReleaseInstances = Get-Process AmiReel -ErrorAction SilentlyContinue | $runningReleaseInstances = Get-Process | Where-Object {
Where-Object { $_.Path -and (
$_.Path -and ( [string]::Equals($_.Path, $releaseExe, [System.StringComparison]::OrdinalIgnoreCase) -or
[string]::Equals($_.Path, $releaseExe, [System.StringComparison]::OrdinalIgnoreCase) -or [string]::Equals($_.Path, $publishExe, [System.StringComparison]::OrdinalIgnoreCase)
[string]::Equals($_.Path, $publishExe, [System.StringComparison]::OrdinalIgnoreCase) )
) }
}
if ($runningReleaseInstances) { 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 $runningReleaseInstances | Stop-Process -Force
Start-Sleep -Milliseconds 800 Start-Sleep -Milliseconds 800
} }
@@ -31,12 +69,8 @@ if (Test-Path $publishDir) {
Remove-Item -LiteralPath $publishDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $publishDir -Recurse -Force -ErrorAction SilentlyContinue
} }
dotnet publish (Join-Path $projectDir 'AmigaDB.VideoRenderer.csproj') ` & dotnet @publishArguments
-c Release `
-r win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true
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