Bring WinUI app to feature/visual parity with WPF, fix drag-and-drop and branding assets
- Restyle Fluent controls (buttons, fields, dialogs, ListView) to match the app's navy dark/light palette and consistent corner radii/sizing - Fix ContentDialog (Settings, exit-confirm, error) styling by binding PrimaryButtonStyle/CloseButtonStyle explicitly via new DialogHelper, since ContentDialog ignores implicit Button styles and forces accent color onto whichever button is DefaultButton - Remove MicaBackdrop and theme the title bar directly so it no longer shows a gray tint mismatched with the app's navy background - Replace placeholder Assets/*.png (unused VS template art) with the real AmiReel logo at all required tile/splash/store sizes - Set explicit default window size (1280x860, matching the old WPF app) with DPI-aware centering and a minimum size, instead of sizing to content - Fix drag-and-drop silently failing: InputList_Drop needs a DragOperationDeferral before the first await or the DataView is torn down before GetStorageItemsAsync completes - Add Preview player path setting (was present in WPF, missing in WinUI) and error handling for preview playback - Accept all ffmpeg-readable video formats (mp4, mov, mkv, webm, ...) for file picker and drag-and-drop, not just .avi - Add exit-confirmation dialog to WinUI, mirroring the WPF app Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@@ -23,6 +23,54 @@
|
||||
<SolidColorBrush x:Key="ButtonBrush" Color="#243554"/>
|
||||
<SolidColorBrush x:Key="ButtonHoverBrush" Color="#2E446C"/>
|
||||
<SolidColorBrush x:Key="PrimaryTextBrush" Color="#07111D"/>
|
||||
|
||||
<!-- Fluent control theme-brush overrides so built-in controls match the palette -->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#1D2940"/>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#233252"/>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#1D2940"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#2B3A58"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#4AB8FF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#4AB8FF"/>
|
||||
<SolidColorBrush x:Key="TextControlForeground" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="TextControlHeaderForeground" Color="#9FB1CC"/>
|
||||
<SolidColorBrush x:Key="TextControlPlaceholderForeground" Color="#7488A6"/>
|
||||
|
||||
<SolidColorBrush x:Key="ComboBoxBackground" Color="#1D2940"/>
|
||||
<SolidColorBrush x:Key="ComboBoxBackgroundPointerOver" Color="#233252"/>
|
||||
<SolidColorBrush x:Key="ComboBoxBorderBrush" Color="#2B3A58"/>
|
||||
<SolidColorBrush x:Key="ComboBoxForeground" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="ComboBoxHeaderForeground" Color="#9FB1CC"/>
|
||||
<SolidColorBrush x:Key="ComboBoxItemBackgroundSelected" Color="#13324F"/>
|
||||
<SolidColorBrush x:Key="ComboBoxDropDownBackground" Color="#10192B"/>
|
||||
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="#243554"/>
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="#2E446C"/>
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="#1B2A44"/>
|
||||
<SolidColorBrush x:Key="ButtonForeground" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="ButtonBorderBrush" Color="#2B3A58"/>
|
||||
|
||||
<SolidColorBrush x:Key="ContentDialogBackground" Color="#162033"/>
|
||||
<SolidColorBrush x:Key="ContentDialogForeground" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="ContentDialogBorderBrush" Color="#35507D"/>
|
||||
|
||||
<SolidColorBrush x:Key="ListViewItemBackgroundSelected" Color="#13324F"/>
|
||||
<SolidColorBrush x:Key="ListViewItemBackgroundPointerOver" Color="#1A2740"/>
|
||||
<SolidColorBrush x:Key="ListViewItemForeground" Color="#F5F7FB"/>
|
||||
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillUnchecked" Color="#1D2940"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeUnchecked" Color="#2B3A58"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillChecked" Color="#4AB8FF"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeChecked" Color="#4AB8FF"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckGlyphForegroundChecked" Color="#07111D"/>
|
||||
<SolidColorBrush x:Key="CheckBoxForegroundUnchecked" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="CheckBoxForegroundChecked" Color="#F5F7FB"/>
|
||||
|
||||
<SolidColorBrush x:Key="ProgressBarForeground" Color="#4AB8FF"/>
|
||||
<SolidColorBrush x:Key="ProgressBarBackground" Color="#1D2940"/>
|
||||
|
||||
<SolidColorBrush x:Key="HeroBorderBrush" Color="#35507D"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="PageBrush" Color="#F4F7FB"/>
|
||||
@@ -37,11 +85,65 @@
|
||||
<SolidColorBrush x:Key="ButtonBrush" Color="#E5EDF7"/>
|
||||
<SolidColorBrush x:Key="ButtonHoverBrush" Color="#D7E4F4"/>
|
||||
<SolidColorBrush x:Key="PrimaryTextBrush" Color="#FFFFFF"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#F7FAFD"/>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#EEF4FB"/>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#C7D3E3"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#0E9AEF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#0E9AEF"/>
|
||||
<SolidColorBrush x:Key="TextControlForeground" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="TextControlHeaderForeground" Color="#5F7390"/>
|
||||
<SolidColorBrush x:Key="TextControlPlaceholderForeground" Color="#8194AC"/>
|
||||
|
||||
<SolidColorBrush x:Key="ComboBoxBackground" Color="#F7FAFD"/>
|
||||
<SolidColorBrush x:Key="ComboBoxBackgroundPointerOver" Color="#EEF4FB"/>
|
||||
<SolidColorBrush x:Key="ComboBoxBorderBrush" Color="#C7D3E3"/>
|
||||
<SolidColorBrush x:Key="ComboBoxForeground" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="ComboBoxHeaderForeground" Color="#5F7390"/>
|
||||
<SolidColorBrush x:Key="ComboBoxItemBackgroundSelected" Color="#D6EEFF"/>
|
||||
<SolidColorBrush x:Key="ComboBoxDropDownBackground" Color="#FFFFFF"/>
|
||||
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="#E5EDF7"/>
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="#D7E4F4"/>
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="#C7D9EE"/>
|
||||
<SolidColorBrush x:Key="ButtonForeground" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="ButtonBorderBrush" Color="#C7D3E3"/>
|
||||
|
||||
<SolidColorBrush x:Key="ContentDialogBackground" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ContentDialogForeground" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="ContentDialogBorderBrush" Color="#C7D3E3"/>
|
||||
|
||||
<SolidColorBrush x:Key="ListViewItemBackgroundSelected" Color="#D6EEFF"/>
|
||||
<SolidColorBrush x:Key="ListViewItemBackgroundPointerOver" Color="#EEF4FB"/>
|
||||
<SolidColorBrush x:Key="ListViewItemForeground" Color="#122033"/>
|
||||
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillUnchecked" Color="#F7FAFD"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeUnchecked" Color="#C7D3E3"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillChecked" Color="#0E9AEF"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeChecked" Color="#0E9AEF"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckGlyphForegroundChecked" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="CheckBoxForegroundUnchecked" Color="#122033"/>
|
||||
<SolidColorBrush x:Key="CheckBoxForegroundChecked" Color="#122033"/>
|
||||
|
||||
<SolidColorBrush x:Key="ProgressBarForeground" Color="#0E9AEF"/>
|
||||
<SolidColorBrush x:Key="ProgressBarBackground" Color="#F7FAFD"/>
|
||||
|
||||
<SolidColorBrush x:Key="HeroBorderBrush" Color="#A9C4E4"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<!-- Global corner rounding so built-in Fluent controls match the WPF app's rounded look -->
|
||||
<CornerRadius x:Key="ControlCornerRadius">10</CornerRadius>
|
||||
<CornerRadius x:Key="OverlayCornerRadius">18</CornerRadius>
|
||||
|
||||
<x:Double x:Key="ContentDialogMaxWidth">460</x:Double>
|
||||
|
||||
<Style TargetType="Page">
|
||||
<Setter Property="Background" Value="{ThemeResource PageBrush}" />
|
||||
<Setter Property="FontFamily" Value="Bahnschrift" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CardBorderStyle" TargetType="Border">
|
||||
@@ -53,51 +155,114 @@
|
||||
|
||||
<Style x:Key="SectionTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MutedTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource MutedBrush}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="FieldLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource MutedBrush}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Margin" Value="0,0,0,4" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource ButtonBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
|
||||
<Setter Property="Padding" Value="16,9" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
</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}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="22,10" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="IconButtonStyle" TargetType="Button">
|
||||
<Setter Property="Width" Value="40" />
|
||||
<Setter Property="Height" Value="40" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- Explicit styles bound to ContentDialog.PrimaryButtonStyle / CloseButtonStyle so its
|
||||
built-in buttons match the app's own Button look instead of the default Fluent
|
||||
accent-pill / plain styles. -->
|
||||
<Style x:Key="DialogPrimaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource AccentBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource PrimaryTextBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource AccentBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Padding" Value="22,10" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="MinWidth" Value="110" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DialogCloseButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource ButtonBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Padding" Value="16,9" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="MinWidth" Value="110" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="38" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="38" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ProgressBar">
|
||||
<Setter Property="Foreground" Value="{ThemeResource AccentBrush}" />
|
||||
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
|
||||
<Setter Property="MinHeight" Value="8" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ListView">
|
||||
<Setter Property="Background" Value="{ThemeResource FieldBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource BorderBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Padding" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextBrush}" />
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="MinHeight" Value="32" />
|
||||
<Setter Property="Padding" Value="8,6" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ContentDialog">
|
||||
<Setter Property="CornerRadius" Value="18" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -24,6 +24,7 @@ public partial class App : Application
|
||||
{
|
||||
private Window? _window;
|
||||
public static IntPtr MainWindowHandle { get; private set; }
|
||||
public static MainWindow? MainWindowInstance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
|
||||
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,33 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace AmiReel_WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Builds ContentDialogs styled to match the app's own palette and button shapes,
|
||||
/// since ContentDialog's default Fluent look (accent-pill primary button, plain
|
||||
/// square close button, ungrouped theme resources) does not follow our custom brushes.
|
||||
/// </summary>
|
||||
internal static class DialogHelper
|
||||
{
|
||||
public static ContentDialog CreateStyled(XamlRoot xamlRoot, ElementTheme theme)
|
||||
{
|
||||
string themeKey = theme == ElementTheme.Dark ? "Dark" : "Light";
|
||||
ResourceDictionary themeDictionary = (ResourceDictionary)Application.Current.Resources.ThemeDictionaries[themeKey];
|
||||
|
||||
return new ContentDialog
|
||||
{
|
||||
XamlRoot = xamlRoot,
|
||||
RequestedTheme = theme,
|
||||
Background = (Brush)themeDictionary["PanelBrush"],
|
||||
Foreground = (Brush)themeDictionary["TextBrush"],
|
||||
BorderBrush = (Brush)themeDictionary["HeroBorderBrush"],
|
||||
BorderThickness = new Thickness(1.5),
|
||||
CornerRadius = new CornerRadius(18),
|
||||
PrimaryButtonStyle = (Style)Application.Current.Resources["DialogPrimaryButtonStyle"],
|
||||
SecondaryButtonStyle = (Style)Application.Current.Resources["DialogCloseButtonStyle"],
|
||||
CloseButtonStyle = (Style)Application.Current.Resources["DialogCloseButtonStyle"]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,17 @@
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Source recordings" Style="{StaticResource SectionTitleStyle}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Button Content="Add AVI files..." Click="AddInputs_Click" />
|
||||
<Button Content="Add video files..." Click="AddInputs_Click" />
|
||||
<Button Content="Clear" Click="ClearInputs_Click" />
|
||||
</StackPanel>
|
||||
<ListView x:Name="InputList"
|
||||
Height="120"
|
||||
Height="130"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="InputList_SelectionChanged"
|
||||
AllowDrop="True"
|
||||
DragOver="InputList_DragOver"
|
||||
Drop="InputList_Drop" />
|
||||
Drop="InputList_Drop"
|
||||
ToolTipService.ToolTip="Drag and drop video files here to add them" />
|
||||
<Button x:Name="PreviewSourceButton"
|
||||
Content="Preview selected"
|
||||
Click="PreviewSource_Click"
|
||||
@@ -59,13 +60,13 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox x:Name="EndCardBox" Grid.Row="0" MinHeight="48" />
|
||||
<TextBox x:Name="EndCardBox" Grid.Row="0" MinHeight="40" />
|
||||
<Button Grid.Row="0" Grid.Column="1" Content="Browse..." Click="BrowseEndCard_Click" VerticalAlignment="Stretch" MinWidth="120" />
|
||||
|
||||
<TextBox x:Name="OutputFolderBox" Grid.Row="1" MinHeight="48" />
|
||||
<TextBox x:Name="OutputFolderBox" Grid.Row="1" MinHeight="40" />
|
||||
<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" MinHeight="48" />
|
||||
<TextBox x:Name="OutputNameBox" Grid.Row="2" Grid.ColumnSpan="2" MinHeight="40" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -75,10 +76,10 @@
|
||||
<StackPanel Spacing="12">
|
||||
<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" Text="0%" FontSize="18" FontWeight="SemiBold" Foreground="{ThemeResource AccentBrush}" />
|
||||
<TextBlock x:Name="StageText" Text="Ready" FontSize="15" FontWeight="SemiBold" />
|
||||
<TextBlock x:Name="PercentText" Text="0%" FontSize="15" FontWeight="SemiBold" Foreground="{ThemeResource AccentBrush}" />
|
||||
</StackPanel>
|
||||
<ProgressBar x:Name="RenderProgressBar" Minimum="0" Maximum="100" Height="10" />
|
||||
<ProgressBar x:Name="RenderProgressBar" Minimum="0" Maximum="100" Height="8" />
|
||||
<TextBlock x:Name="StatusText" Text="Select the recordings and start the render. The end card is optional." TextWrapping="WrapWholeWords" Style="{StaticResource MutedTextStyle}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -111,7 +112,8 @@
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10">
|
||||
<Button x:Name="OpenSettingsButton" Content="Settings" Click="OpenSettings_Click" />
|
||||
<Button x:Name="OpenSettingsButton" Content="" FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
Click="OpenSettings_Click" Style="{StaticResource IconButtonStyle}" ToolTipService.ToolTip="Settings" />
|
||||
<Button x:Name="CancelButton" Content="Cancel" Click="Cancel_Click" IsEnabled="False" />
|
||||
<Button x:Name="RenderButton" Content="Start render" Click="Render_Click" Style="{StaticResource PrimaryButtonStyle}" />
|
||||
</StackPanel>
|
||||
@@ -121,7 +123,14 @@
|
||||
Title="Settings"
|
||||
PrimaryButtonText="Done"
|
||||
CloseButtonText="Close"
|
||||
DefaultButton="Primary">
|
||||
DefaultButton="Primary"
|
||||
Background="{ThemeResource PanelBrush}"
|
||||
Foreground="{ThemeResource TextBrush}"
|
||||
BorderBrush="{ThemeResource HeroBorderBrush}"
|
||||
BorderThickness="1.5"
|
||||
CornerRadius="18"
|
||||
PrimaryButtonStyle="{StaticResource DialogPrimaryButtonStyle}"
|
||||
CloseButtonStyle="{StaticResource DialogCloseButtonStyle}">
|
||||
<ScrollViewer MaxHeight="560" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="16">
|
||||
<Border Padding="14" Background="{ThemeResource PanelAltBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1" CornerRadius="12">
|
||||
@@ -190,11 +199,14 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox x:Name="FfmpegPathBox" Grid.Row="0" Header="ffmpeg.exe path" />
|
||||
<Button Grid.Row="0" Grid.Column="1" Content="Browse..." Click="BrowseFfmpeg_Click" VerticalAlignment="Bottom" />
|
||||
<TextBox x:Name="FfprobePathBox" Grid.Row="1" Header="ffprobe.exe path" />
|
||||
<Button Grid.Row="1" Grid.Column="1" Content="Browse..." Click="BrowseFfprobe_Click" VerticalAlignment="Bottom" />
|
||||
<TextBox x:Name="PreviewPlayerPathBox" Grid.Row="2" Header="Preview player path (optional, e.g. mpv.exe)" />
|
||||
<Button Grid.Row="2" Grid.Column="1" Content="Browse..." Click="BrowsePreviewPlayer_Click" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -33,6 +33,7 @@ public sealed partial class MainPage : Page
|
||||
EndCardBox.Text = _loadedSettings.EndCardPath;
|
||||
FfmpegPathBox.Text = _loadedSettings.FfmpegPath;
|
||||
FfprobePathBox.Text = _loadedSettings.FfprobePath;
|
||||
PreviewPlayerPathBox.Text = _loadedSettings.PreviewPlayerPath;
|
||||
OutputNameBox.Text = "amigadb_intro";
|
||||
TrimBox.Text = _loadedSettings.TrimStart;
|
||||
FadeBox.Text = _loadedSettings.FadeSeconds;
|
||||
@@ -47,7 +48,8 @@ public sealed partial class MainPage : Page
|
||||
private async void AddInputs_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FileOpenPicker picker = CreateFileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".avi");
|
||||
foreach (string extension in SupportedVideoFormats.Extensions)
|
||||
picker.FileTypeFilter.Add(extension);
|
||||
picker.SuggestedStartLocation = PickerLocationId.VideosLibrary;
|
||||
var files = await picker.PickMultipleFilesAsync();
|
||||
if (files is null) return;
|
||||
@@ -69,13 +71,21 @@ public sealed partial class MainPage : Page
|
||||
{
|
||||
if (!e.DataView.Contains(StandardDataFormats.StorageItems)) return;
|
||||
|
||||
DragOperationDeferral deferral = e.GetDeferral();
|
||||
try
|
||||
{
|
||||
IReadOnlyList<IStorageItem> items = await e.DataView.GetStorageItemsAsync();
|
||||
foreach (string path in items.OfType<StorageFile>()
|
||||
.Select(file => file.Path)
|
||||
.Where(path => string.Equals(Path.GetExtension(path), ".avi", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(SupportedVideoFormats.IsSupported)
|
||||
.OrderBy(NaturalKey))
|
||||
if (!_inputs.Contains(path, StringComparer.OrdinalIgnoreCase)) _inputs.Add(path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private async void BrowseEndCard_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -116,6 +126,15 @@ public sealed partial class MainPage : Page
|
||||
FfprobePathBox.Text = file.Path;
|
||||
}
|
||||
|
||||
private async void BrowsePreviewPlayer_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FileOpenPicker picker = CreateFileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
if (file is not null)
|
||||
PreviewPlayerPathBox.Text = file.Path;
|
||||
}
|
||||
|
||||
private async void OpenSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SettingsDialog.XamlRoot = XamlRoot;
|
||||
@@ -171,16 +190,16 @@ public sealed partial class MainPage : Page
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private void PreviewSource_Click(object sender, RoutedEventArgs e)
|
||||
private async void PreviewSource_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (InputList.SelectedItem is string path)
|
||||
OpenPreview(path);
|
||||
await OpenPreviewAsync(path);
|
||||
}
|
||||
|
||||
private void PreviewRendered_Click(object sender, RoutedEventArgs e)
|
||||
private async void PreviewRendered_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_lastRenderedFile) && File.Exists(_lastRenderedFile))
|
||||
OpenPreview(_lastRenderedFile);
|
||||
await OpenPreviewAsync(_lastRenderedFile);
|
||||
}
|
||||
|
||||
private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
@@ -241,13 +260,40 @@ public sealed partial class MainPage : Page
|
||||
StatusText.Text = value.Message;
|
||||
}
|
||||
|
||||
private void OpenPreview(string mediaPath)
|
||||
private async Task OpenPreviewAsync(string mediaPath)
|
||||
{
|
||||
if (!File.Exists(mediaPath))
|
||||
{
|
||||
await ShowMessageAsync("Preview unavailable", "The selected media file was not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
string playerPath = PreviewPlayerPathBox.Text.Trim();
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(playerPath))
|
||||
{
|
||||
if (!File.Exists(playerPath))
|
||||
throw new FileNotFoundException("Configured preview player was not found.", playerPath);
|
||||
|
||||
ProcessStartInfo customPlayer = new()
|
||||
{
|
||||
FileName = playerPath,
|
||||
UseShellExecute = false
|
||||
};
|
||||
customPlayer.ArgumentList.Add(mediaPath);
|
||||
Process.Start(customPlayer);
|
||||
return;
|
||||
}
|
||||
|
||||
Process.Start(new ProcessStartInfo(mediaPath) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await ShowMessageAsync("Preview failed", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string line)
|
||||
{
|
||||
@@ -285,7 +331,7 @@ public sealed partial class MainPage : Page
|
||||
EndCardBox.Text.Trim(),
|
||||
FfmpegPathBox.Text.Trim(),
|
||||
FfprobePathBox.Text.Trim(),
|
||||
_loadedSettings.PreviewPlayerPath,
|
||||
PreviewPlayerPathBox.Text.Trim(),
|
||||
SelectedTheme(),
|
||||
SelectedEncoder(),
|
||||
TrimBox.Text.Trim(),
|
||||
@@ -334,9 +380,8 @@ public sealed partial class MainPage : Page
|
||||
|
||||
private void ApplyTheme(string theme)
|
||||
{
|
||||
RequestedTheme = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase)
|
||||
? ElementTheme.Light
|
||||
: ElementTheme.Dark;
|
||||
bool isDark = !string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase);
|
||||
App.MainWindowInstance?.ApplyTheme(isDark);
|
||||
}
|
||||
|
||||
private FileOpenPicker CreateFileOpenPicker()
|
||||
@@ -365,13 +410,10 @@ public sealed partial class MainPage : Page
|
||||
|
||||
private async Task ShowMessageAsync(string title, string message)
|
||||
{
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
Title = title,
|
||||
Content = message,
|
||||
CloseButtonText = "OK",
|
||||
XamlRoot = XamlRoot
|
||||
};
|
||||
ContentDialog dialog = DialogHelper.CreateStyled(XamlRoot, ActualTheme);
|
||||
dialog.Title = title;
|
||||
dialog.Content = message;
|
||||
dialog.CloseButtonText = "OK";
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,22 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="AmiReel"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid>
|
||||
<Grid Background="{ThemeResource PageBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid x:Name="AppTitleBar" Height="44" Background="Transparent">
|
||||
<Grid x:Name="AppTitleBar" Grid.Row="0" Height="44" Background="{ThemeResource PanelBrush}">
|
||||
<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"
|
||||
FontFamily="Bahnschrift"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Graphics;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -21,6 +26,7 @@ public sealed partial class MainWindow : Window
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
App.MainWindowInstance = this;
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
SetTitleBar(AppTitleBar);
|
||||
@@ -29,26 +35,77 @@ public sealed partial class MainWindow : Window
|
||||
if (!string.IsNullOrWhiteSpace(executablePath) && File.Exists(executablePath))
|
||||
AppWindow.SetIcon(executablePath);
|
||||
|
||||
SetDefaultSizeAndPosition();
|
||||
|
||||
// Navigate the root frame to the main page on startup.
|
||||
RootFrame.Navigate(typeof(MainPage));
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
}
|
||||
|
||||
private void SetDefaultSizeAndPosition()
|
||||
{
|
||||
const int DefaultWidth = 1280;
|
||||
const int DefaultHeight = 860;
|
||||
const int MinWidth = 1100;
|
||||
const int MinHeight = 780;
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(this);
|
||||
double scale = GetDpiForWindow(hwnd) / 96.0;
|
||||
int width = (int)(DefaultWidth * scale);
|
||||
int height = (int)(DefaultHeight * scale);
|
||||
|
||||
AppWindow.Resize(new SizeInt32(width, height));
|
||||
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.PreferredMinimumWidth = (int)(MinWidth * scale);
|
||||
presenter.PreferredMinimumHeight = (int)(MinHeight * scale);
|
||||
}
|
||||
|
||||
DisplayArea? displayArea = DisplayArea.GetFromWindowId(Win32Interop.GetWindowIdFromWindow(hwnd), DisplayAreaFallback.Primary);
|
||||
if (displayArea is not null)
|
||||
{
|
||||
int centerX = displayArea.WorkArea.X + (displayArea.WorkArea.Width - width) / 2;
|
||||
int centerY = displayArea.WorkArea.Y + (displayArea.WorkArea.Height - height) / 2;
|
||||
AppWindow.Move(new PointInt32(centerX, centerY));
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetDpiForWindow(IntPtr hwnd);
|
||||
|
||||
public void ApplyTheme(bool isDark)
|
||||
{
|
||||
if (Content is FrameworkElement root)
|
||||
root.RequestedTheme = isDark ? ElementTheme.Dark : ElementTheme.Light;
|
||||
|
||||
AppWindowTitleBar titleBar = AppWindow.TitleBar;
|
||||
Color foreground = isDark ? Color.FromArgb(255, 0xF5, 0xF7, 0xFB) : Color.FromArgb(255, 0x12, 0x20, 0x33);
|
||||
Color hoverBackground = isDark ? Color.FromArgb(255, 0x2E, 0x44, 0x6C) : Color.FromArgb(255, 0xD7, 0xE4, 0xF4);
|
||||
|
||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonForegroundColor = foreground;
|
||||
titleBar.ButtonInactiveForegroundColor = foreground;
|
||||
titleBar.ButtonHoverBackgroundColor = hoverBackground;
|
||||
titleBar.ButtonHoverForegroundColor = foreground;
|
||||
titleBar.ButtonPressedBackgroundColor = hoverBackground;
|
||||
titleBar.ButtonPressedForegroundColor = foreground;
|
||||
}
|
||||
|
||||
private async void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
|
||||
{
|
||||
if (_exitConfirmed) return;
|
||||
args.Cancel = true;
|
||||
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
Title = "Exit AmiReel?",
|
||||
Content = "Are you sure you want to close the application?",
|
||||
PrimaryButtonText = "Exit",
|
||||
CloseButtonText = "Cancel",
|
||||
DefaultButton = ContentDialogButton.Close,
|
||||
XamlRoot = RootFrame.XamlRoot
|
||||
};
|
||||
FrameworkElement root = (FrameworkElement)Content;
|
||||
ContentDialog dialog = DialogHelper.CreateStyled(RootFrame.XamlRoot, root.RequestedTheme);
|
||||
dialog.Title = "Exit AmiReel?";
|
||||
dialog.Content = "Are you sure you want to close the application?";
|
||||
dialog.PrimaryButtonText = "Exit";
|
||||
dialog.CloseButtonText = "Cancel";
|
||||
dialog.DefaultButton = ContentDialogButton.Primary;
|
||||
|
||||
ContentDialogResult result = await dialog.ShowAsync();
|
||||
if (result != ContentDialogResult.Primary) return;
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
<uap:VisualElements
|
||||
DisplayName="AmiReel"
|
||||
Description="AmiReel"
|
||||
BackgroundColor="transparent"
|
||||
BackgroundColor="#0E1525"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#0E1525" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
@@ -77,12 +77,12 @@
|
||||
<Grid>
|
||||
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="98"/><RowDefinition Height="Auto"/></Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<Button Content="Add AVI files…" Click="AddInputs_Click" Margin="0,0,8,0"/>
|
||||
<Button Content="Add video 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"
|
||||
AllowDrop="True" DragEnter="InputList_DragEnter" DragOver="InputList_DragEnter" Drop="InputList_Drop"
|
||||
ToolTip="Drag and drop AVI files here to add them"/>
|
||||
ToolTip="Drag and drop video files here to add them"/>
|
||||
<Button x:Name="PreviewSourceButton" Grid.Row="2" Content="Preview selected" Click="PreviewSource_Click"
|
||||
HorizontalAlignment="Left" Margin="0,10,0,0" IsEnabled="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -82,13 +82,13 @@ public partial class MainWindow : Window
|
||||
{
|
||||
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
|
||||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
|
||||
foreach (string file in files.Where(f => string.Equals(Path.GetExtension(f), ".avi", StringComparison.OrdinalIgnoreCase)).OrderBy(NaturalKey))
|
||||
foreach (string file in files.Where(Models.SupportedVideoFormats.IsSupported).OrderBy(NaturalKey))
|
||||
if (!_inputs.Contains(file, StringComparer.OrdinalIgnoreCase)) _inputs.Add(file);
|
||||
}
|
||||
|
||||
private void AddInputs_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog dialog = new() { Filter = "AVI recordings (*.avi)|*.avi", Multiselect = true };
|
||||
OpenFileDialog dialog = new() { Filter = $"Video files|{Models.SupportedVideoFormats.PickerFilter}", 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);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace AmigaDB.VideoRenderer.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Video file extensions accepted as render sources. FFmpeg can demux far more than this,
|
||||
/// but this list covers what a screen/video capture workflow is realistically going to produce.
|
||||
/// </summary>
|
||||
public static class SupportedVideoFormats
|
||||
{
|
||||
public static readonly IReadOnlyList<string> Extensions =
|
||||
[
|
||||
".avi", ".mp4", ".m4v", ".mov", ".mkv", ".webm",
|
||||
".wmv", ".flv", ".mpg", ".mpeg", ".ts", ".3gp"
|
||||
];
|
||||
|
||||
public static bool IsSupported(string path) =>
|
||||
Extensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>Picker filter string, e.g. "*.avi;*.mp4;*.m4v;...".</summary>
|
||||
public static string PickerFilter => string.Join(';', Extensions.Select(e => "*" + e));
|
||||
}
|
||||
@@ -360,8 +360,8 @@ public sealed class RenderPipeline
|
||||
|
||||
private static void Validate(RenderSettings s)
|
||||
{
|
||||
if (s.InputFiles.Count == 0) throw new ArgumentException("Select at least one AVI input file.");
|
||||
if (s.InputFiles.Any(f => !File.Exists(f))) throw new FileNotFoundException("One or more AVI files no longer exist.");
|
||||
if (s.InputFiles.Count == 0) throw new ArgumentException("Select at least one video input file.");
|
||||
if (s.InputFiles.Any(f => !File.Exists(f))) throw new FileNotFoundException("One or more input files no longer exist.");
|
||||
if (!string.IsNullOrWhiteSpace(s.EndCardPath) && !File.Exists(s.EndCardPath))
|
||||
throw new FileNotFoundException("End-card image was not found.");
|
||||
if (string.IsNullOrWhiteSpace(s.OutputDirectory)) throw new ArgumentException("Select an output directory.");
|
||||
|
||||