Miglioramenti UI e funzionalità di configurazione

- Tradotta l'interfaccia utente in italiano.
- Migliorato lo stile visivo con nuovi colori e stili.
- Riorganizzata la struttura dell'interfaccia per maggiore chiarezza.
- Aggiunte risorse di colore e stili riutilizzabili.
- Introdotta barra di stato con riepilogo dei job.
- Ottimizzato il codice per leggibilità e manutenzione.
- Aggiunte nuove opzioni di configurazione (naming, output, frame).
- Migliorata la gestione degli errori e dei messaggi di stato.
- Aumentato il limite di caricamento delle anteprime a 60.
- Rimosso codice obsoleto e duplicato.
- Migliorata la compatibilità con WPF (binding e trigger).
- Riorganizzate e tradotte le finestre di dialogo.
- Migliorate le finestre di configurazione dei job e delle impostazioni.
- Semplificata la gestione di FFmpeg e rimosso codice legacy.
This commit is contained in:
Alberto Balbo
2025-09-22 20:41:22 +02:00
parent 8879a9375f
commit bd7e71d67c
6 changed files with 586 additions and 640 deletions

View File

@@ -5,127 +5,245 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Ganimede"
mc:Ignorable="d"
Title="Ganimede - Frame Extractor" Height="800" Width="1200"
Background="#222">
Title="Estrattore Frame Video" Height="800" Width="1250"
Background="#1E2228" WindowStartupLocation="CenterScreen">
<Window.Resources>
<local:StatusColorConverter x:Key="StatusColorConverter"/>
<!-- Color resources -->
<Color x:Key="AccentColor">#268BFF</Color>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
<SolidColorBrush x:Key="AccentBrushLight" Color="#39A3FF"/>
<SolidColorBrush x:Key="BaseBrush" Color="#1E2228"/>
<SolidColorBrush x:Key="PanelBrush" Color="#242A31"/>
<SolidColorBrush x:Key="PanelSubBrush" Color="#2C333B"/>
<SolidColorBrush x:Key="BorderBrushColor" Color="#38424D"/>
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#B5BDC7"/>
<!-- Button Style -->
<Style TargetType="Button" x:Key="ToolbarButton">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#2F3740"/>
<Setter Property="BorderBrush" Value="#3F4A55"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="14 8"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="8" SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#39444F"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#46525E"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.4"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Accent Button -->
<Style TargetType="Button" x:Key="AccentButton" BasedOn="{StaticResource ToolbarButton}">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="BorderBrush" Value="#1673D5"/>
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBrushLight}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Danger Button -->
<Style TargetType="Button" x:Key="DangerButton" BasedOn="{StaticResource ToolbarButton}">
<Setter Property="Background" Value="#D9534F"/>
<Setter Property="BorderBrush" Value="#B33E3B"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E36460"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Flat small badge button -->
<Style TargetType="Button" x:Key="SmallGhostButton" BasedOn="{StaticResource ToolbarButton}">
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="12 6"/>
<Setter Property="MinHeight" Value="36"/>
<Setter Property="Background" Value="#2F3740"/>
</Style>
<!-- ProgressBar -->
<Style TargetType="ProgressBar">
<Setter Property="Height" Value="6"/>
<Setter Property="Foreground" Value="{StaticResource AccentBrush}"/>
<Setter Property="Background" Value="#313941"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border Background="{TemplateBinding Background}" CornerRadius="3">
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" Fill="{TemplateBinding Foreground}" RadiusX="3" RadiusY="3"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ScrollViewer styling for thin scrollbar -->
<Style TargetType="ScrollBar">
<Setter Property="Width" Value="10"/>
<Setter Property="Background" Value="#20252B"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Top controls condensed -->
<DockPanel Grid.Row="0" LastChildFill="False" Margin="0,0,0,10">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" Margin="0,0,15,0">
<Button x:Name="BrowseVideoButton" Content="Aggiungi Video" Width="120" Height="35" Margin="0,0,8,0" Click="BrowseVideoButton_Click"/>
<Button x:Name="ImportFolderButton" Content="Importa Cartella" Width="130" Height="35" Margin="0,0,8,0" Click="ImportFolderButton_Click"/>
<Button x:Name="SelectOutputFolderButton" Content="Cartella Output" Width="130" Height="35" Margin="0,0,8,0" Click="SelectOutputFolderButton_Click"/>
<Button x:Name="SettingsButton" Content="Impostazioni" Width="110" Height="35" Margin="0,0,8,0" Click="SettingsButton_Click"/>
<!-- TOOLBAR -->
<Border Background="#242A31" Padding="16 12" BorderBrush="#303840" BorderThickness="0,0,0,1">
<DockPanel LastChildFill="False">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
<Button x:Name="BrowseVideoButton" Style="{StaticResource AccentButton}" Content=" Aggiungi Video" Click="BrowseVideoButton_Click" Margin="0,0,10,0"/>
<Button x:Name="ImportFolderButton" Style="{StaticResource ToolbarButton}" Content="📁 Importa Cartella" Click="ImportFolderButton_Click" Margin="0,0,10,0"/>
<Button x:Name="SelectOutputFolderButton" Style="{StaticResource ToolbarButton}" Content="🗂 Seleziona Cartella Output" Click="SelectOutputFolderButton_Click" Margin="0,0,10,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
<Button x:Name="ConfigureSelectedButton" Style="{StaticResource ToolbarButton}" Content="⚙ Configura Selezionati" Width="195" IsEnabled="False" Click="ConfigureSelectedButton_Click" Margin="0,0,10,0"/>
<Button x:Name="StartQueueButton" Style="{StaticResource AccentButton}" Content="▶ Avvia Coda" Width="150" Click="StartQueueButton_Click" Margin="0,0,10,0"/>
<Button x:Name="StopQueueButton" Style="{StaticResource DangerButton}" Content="⏹ Ferma" Width="110" IsEnabled="False" Click="StopQueueButton_Click" Margin="0,0,10,0"/>
<Button x:Name="ClearCompletedButton" Style="{StaticResource SmallGhostButton}" Content="🧹 Pulisci Completati" Click="ClearCompletedButton_Click" Margin="0,0,10,0"/>
<Button x:Name="ClearAllButton" Style="{StaticResource SmallGhostButton}" Content="🗑 Pulisci Tutto" Click="ClearAllButton_Click" Margin="0,0,10,0"/>
<Button x:Name="SettingsButton" Style="{StaticResource SmallGhostButton}" Content="⚙ Impostazioni" Click="SettingsButton_Click"/>
</StackPanel>
</DockPanel>
</Border>
<!-- CONTENUTO PRINCIPALE -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="370"/>
</Grid.ColumnDefinitions>
<!-- Coda -->
<Grid Margin="18 12 8 12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0 0 0 10" VerticalAlignment="Center">
<TextBlock Text="Coda Job" FontSize="18" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
<TextBlock x:Name="QueueCountText" Text="(0)" Foreground="{StaticResource TextSecondaryBrush}" Margin="8,4,0,0"/>
</StackPanel>
<Border Grid.Row="1" Background="{StaticResource PanelBrush}" BorderBrush="{StaticResource BorderBrushColor}" BorderThickness="1" CornerRadius="8" Padding="4">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="QueueItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{StaticResource PanelSubBrush}" Margin="6" Padding="10" CornerRadius="6" BorderBrush="#3A454F" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="JobCheckBox" Grid.Row="0" Grid.Column="0" Margin="0,0,10,0" VerticalAlignment="Center" Tag="{Binding}" Checked="JobCheckBox_CheckedChanged" Unchecked="JobCheckBox_CheckedChanged"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding VideoName}" Foreground="White" FontWeight="SemiBold" TextTrimming="CharacterEllipsis" Margin="0,0,8,0"/>
<TextBlock Text="{Binding Progress, StringFormat={}{0:0}%}" Foreground="{StaticResource TextSecondaryBrush}" FontSize="11"/>
</StackPanel>
<Button Grid.Row="0" Grid.Column="2" Content="✕" Width="30" Height="26" Style="{StaticResource SmallGhostButton}" Tag="{Binding}" Click="RemoveQueueItem_Click" ToolTip="Rimuovi"/>
<ProgressBar Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,8,0,0" Value="{Binding Progress}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Text="{Binding StatusMessage}" Foreground="{StaticResource TextSecondaryBrush}" FontSize="11" Margin="0,6,0,0" TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" FontSize="10" Foreground="#87939F" Margin="0,8,0,0" TextWrapping="Wrap">
<TextBlock.Text>
<MultiBinding StringFormat="{}📁 {0} 📐 {1} 🔄 {2} 🏷 {3} 🎯 {4}">
<Binding Path="OutputFolderDisplay"/>
<Binding Path="FrameSizeDisplay"/>
<Binding Path="OverwriteModeDisplay"/>
<Binding Path="NamingPatternDisplay"/>
<Binding Path="ExtractionModeDisplay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
<!-- Pannello destro -->
<StackPanel Grid.Column="1" Margin="8 12 18 12">
<Border Background="{StaticResource PanelBrush}" BorderBrush="{StaticResource BorderBrushColor}" BorderThickness="1" CornerRadius="8" Padding="14">
<StackPanel>
<TextBlock Text="Impostazioni Globali" FontSize="16" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
<TextBlock Text="Cartella Output" Foreground="{StaticResource TextSecondaryBrush}" FontSize="12" Margin="0,10,0,2"/>
<DockPanel LastChildFill="True">
<TextBox x:Name="GlobalOutputFolderTextBox" Height="34" Margin="0,0,10,0" IsReadOnly="True" Background="#2C333B" BorderBrush="#3A434C" Foreground="White" BorderThickness="1"/>
<Button Content="Sfoglia" Width="80" Style="{StaticResource SmallGhostButton}" Click="SelectOutputFolderButton_Click"/>
</DockPanel>
<TextBlock Text="Anteprime (Thumbnails)" Foreground="{StaticResource TextSecondaryBrush}" FontSize="12" Margin="0,14,0,4"/>
<Border Background="{StaticResource PanelSubBrush}" BorderBrush="#3A454F" BorderThickness="1" CornerRadius="6" Padding="6" Height="260">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ThumbnailsPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="4" BorderBrush="#3D4853" BorderThickness="1" CornerRadius="4">
<Image Source="{Binding}" Width="90" Height="52" Stretch="UniformToFill"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</StackPanel>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
<Button x:Name="StartQueueButton" Content="▶ Avvia" Width="90" Height="35" Margin="0,0,8,0"
Click="StartQueueButton_Click" Background="#4FC3F7" Foreground="White" FontSize="12"/>
<Button x:Name="StopQueueButton" Content="⏹ Stop" Width="90" Height="35"
Click="StopQueueButton_Click" Background="#DC3545" Foreground="White" FontSize="12" IsEnabled="False"/>
</StackPanel>
</DockPanel>
<!-- Secondary controls -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10" HorizontalAlignment="Center">
<Button x:Name="ConfigureSelectedButton" Content="⚙ Configura" Width="100" Height="30" Margin="0,0,8,0"
Click="ConfigureSelectedButton_Click" FontSize="11" IsEnabled="False"/>
<Button x:Name="ClearCompletedButton" Content="🗑 Pulisci Completati" Width="140" Height="30" Margin="0,0,8,0"
Click="ClearCompletedButton_Click" FontSize="11"/>
<Button x:Name="ClearAllButton" Content="🗑 Pulisci Tutto" Width="120" Height="30"
Click="ClearAllButton_Click" Background="#6C757D" Foreground="White"/>
</StackPanel>
<!-- Queue List FULL AREA -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10" HorizontalAlignment="Center">
<TextBlock Text="Coda Elaborazione" FontSize="18" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
<TextBlock x:Name="QueueCountText" Text="(0 elementi)" Foreground="#AAA" FontSize="12" Margin="10,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="QueueItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="#2C2C2C" Margin="0,0,0,8" Padding="10" CornerRadius="6" BorderBrush="#444" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="JobCheckBox" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,8,0"
Checked="JobCheckBox_CheckedChanged" Unchecked="JobCheckBox_CheckedChanged" Tag="{Binding}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding VideoName}" Foreground="White" FontWeight="SemiBold" TextTrimming="CharacterEllipsis"/>
<Button Grid.Row="0" Grid.Column="2" Content="×" Width="22" Height="22" FontSize="14"
Click="RemoveQueueItem_Click" Background="Transparent" Foreground="#FF6B6B" BorderThickness="0"
Tag="{Binding}" ToolTip="Rimuovi dalla coda"/>
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding StatusMessage}" Foreground="#AAA" FontSize="11" Margin="0,4,0,0"/>
<ProgressBar Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Height="5" Margin="0,6,0,0" Value="{Binding Progress}" Background="#555" Foreground="#4FC3F7"/>
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" Text="{Binding Status}" Foreground="{Binding Status, Converter={StaticResource StatusColorConverter}}" FontSize="10" Margin="0,4,0,0"/>
<TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="3" FontSize="9" Foreground="#666" Margin="0,4,0,0">
<TextBlock.Text>
<MultiBinding StringFormat="{}📁 {0} | 📐 {1} | 🔄 {2} | 🏷 {3} | 🎯 {4}">
<Binding Path="OutputFolderDisplay"/>
<Binding Path="FrameSizeDisplay"/>
<Binding Path="OverwriteModeDisplay"/>
<Binding Path="NamingPatternDisplay"/>
<Binding Path="ExtractionModeDisplay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<!-- Bottom status and thumbnails (thumbnails optional) -->
<DockPanel Grid.Row="3" Margin="0,10,0,0">
<StackPanel DockPanel.Dock="Left" Width="400">
<ProgressBar x:Name="ProgressBar" Height="20" Minimum="0" Maximum="100" Value="0" Background="#333" Foreground="#4FC3F7"/>
<TextBlock x:Name="StatusText" Foreground="#AAA" FontSize="13" Margin="0,6,0,0" TextWrapping="Wrap"/>
</StackPanel>
<ScrollViewer DockPanel.Dock="Right" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Height="80">
<ItemsControl x:Name="ThumbnailsPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="2" BorderBrush="#555" BorderThickness="1" CornerRadius="4">
<Image Source="{Binding}" Width="100" Height="56" Stretch="UniformToFill"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
<!-- BARRA STATO -->
<Border Grid.Row="2" Background="#242A31" BorderBrush="#303840" BorderThickness="1,1,1,0" Padding="16 6" CornerRadius="6 6 0 0" Margin="14 0 14 14">
<DockPanel>
<TextBlock x:Name="StatusText" Foreground="{StaticResource TextSecondaryBrush}" FontSize="13" VerticalAlignment="Center" Text="Pronto"/>
<TextBlock Text=" | " Foreground="#55606B" Margin="6,0"/>
<TextBlock Text="Job:" Foreground="#77818B" Margin="0,0,4,0"/>
<TextBlock x:Name="JobsSummaryText" Foreground="#4F5962" FontSize="11" VerticalAlignment="Center"/>
</DockPanel>
</Border>
</Grid>
</Window>

View File

@@ -19,9 +19,6 @@ using WpfButton = System.Windows.Controls.Button;
namespace Ganimede
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string? outputFolder;
@@ -40,28 +37,41 @@ namespace Ganimede
{
ThumbnailsPanel.ItemsSource = thumbnails;
QueueItemsControl.ItemsSource = _processingService.JobQueue;
// Load saved settings
outputFolder = Settings.Default.LastOutputFolder;
if (!string.IsNullOrEmpty(outputFolder))
StatusText.Text = $"Output folder: {outputFolder}";
{
StatusText.Text = "Pronto";
GlobalOutputFolderTextBox.Text = outputFolder;
}
// Subscribe to processing service events
_processingService.JobCompleted += OnJobCompleted;
_processingService.JobFailed += OnJobFailed;
_processingService.ProcessingStarted += OnProcessingStarted;
_processingService.ProcessingStopped += OnProcessingStopped;
_processingService.JobQueue.CollectionChanged += (s, e) => UpdateQueueCount();
Debug.WriteLine("[INIT] MainWindow initialized with queue support.");
UpdateQueueCount();
}
private void UpdateQueueCount()
{
Dispatcher.Invoke(() =>
{
var count = _processingService.JobQueue.Count;
QueueCountText.Text = $"({count} elementi)";
QueueCountText.Text = $"({_processingService.JobQueue.Count})";
UpdateJobsSummary();
});
}
private void UpdateJobsSummary()
{
var pending = _processingService.JobQueue.Count(j => j.Status == JobStatus.Pending);
var processing = _processingService.JobQueue.Count(j => j.Status == JobStatus.Processing);
var completed = _processingService.JobQueue.Count(j => j.Status == JobStatus.Completed);
var failed = _processingService.JobQueue.Count(j => j.Status == JobStatus.Failed);
Dispatcher.Invoke(() =>
{
JobsSummaryText.Text = $"In attesa: {pending} | In corso: {processing} | Completati: {completed} | Falliti: {failed}";
});
}
@@ -71,7 +81,8 @@ namespace Ganimede
{
StartQueueButton.IsEnabled = false;
StopQueueButton.IsEnabled = true;
StatusText.Text = "Elaborazione coda avviata...";
StatusText.Text = "Elaborazione coda...";
UpdateJobsSummary();
});
}
@@ -81,7 +92,8 @@ namespace Ganimede
{
StartQueueButton.IsEnabled = true;
StopQueueButton.IsEnabled = false;
StatusText.Text = "Elaborazione coda fermata.";
StatusText.Text = "Coda fermata";
UpdateJobsSummary();
});
}
@@ -90,8 +102,8 @@ namespace Ganimede
Dispatcher.Invoke(() =>
{
StatusText.Text = $"✓ Completato: {job.VideoName}";
// Load thumbnails for the completed job
LoadThumbnailsFromFolder(job.OutputFolder);
UpdateJobsSummary();
});
}
@@ -99,7 +111,8 @@ namespace Ganimede
{
Dispatcher.Invoke(() =>
{
StatusText.Text = $"✗ Fallito: {job.VideoName} - {job.StatusMessage}";
StatusText.Text = $"✗ Fallito: {job.VideoName}";
UpdateJobsSummary();
});
}
@@ -108,206 +121,125 @@ namespace Ganimede
try
{
if (!Directory.Exists(folder)) return;
var imageFiles = Directory.GetFiles(folder, "*.png")
.OrderBy(f => f)
.Take(20) // Load max 20 thumbnails for preview
.ToList();
var imageFiles = Directory.GetFiles(folder, "*.png").OrderBy(f => f).Take(60).ToList();
thumbnails.Clear();
foreach (var imagePath in imageFiles)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(imagePath);
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
thumbnails.Add(bitmap);
var bmp = new BitmapImage();
bmp.BeginInit();
bmp.UriSource = new Uri(imagePath);
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.EndInit();
thumbnails.Add(bmp);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[ERROR] Failed to load thumbnails: {ex.Message}");
Debug.WriteLine($"[ERROR] Impossibile caricare thumbnails: {ex.Message}");
}
}
private void ConfigureFFMpeg()
{
var ffmpegBin = Settings.Default.FFmpegBinFolder;
if (!string.IsNullOrEmpty(ffmpegBin) && ValidateFFMpegBinaries(ffmpegBin))
{
FFMpegCore.GlobalFFOptions.Configure(options => options.BinaryFolder = ffmpegBin);
Debug.WriteLine($"[CONFIG] FFMpeg bin folder set: {ffmpegBin}");
}
else if (TryUseSystemFFMpeg())
{
Debug.WriteLine("[CONFIG] Using system FFMpeg from PATH");
}
FFMpegCore.GlobalFFOptions.Configure(o => o.BinaryFolder = ffmpegBin);
else if (TryUseSystemFFMpeg()) { }
else if (TryFixMissingFFMpeg(ffmpegBin))
{
FFMpegCore.GlobalFFOptions.Configure(options => options.BinaryFolder = ffmpegBin);
Debug.WriteLine($"[CONFIG] FFMpeg fixed and configured: {ffmpegBin}");
}
else
{
Debug.WriteLine("[ERROR] FFMpeg configuration failed.");
}
FFMpegCore.GlobalFFOptions.Configure(o => o.BinaryFolder = ffmpegBin);
}
private bool ValidateFFMpegBinaries(string binFolder)
{
if (!Directory.Exists(binFolder))
return false;
var ffmpegPath = Path.Combine(binFolder, "ffmpeg.exe");
var ffprobePath = Path.Combine(binFolder, "ffprobe.exe");
return File.Exists(ffmpegPath) && File.Exists(ffprobePath);
}
private bool ValidateFFMpegBinaries(string binFolder) =>
Directory.Exists(binFolder) &&
File.Exists(Path.Combine(binFolder, "ffmpeg.exe")) &&
File.Exists(Path.Combine(binFolder, "ffprobe.exe"));
private bool TryUseSystemFFMpeg()
{
try
{
var processInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = "-version",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
using var process = Process.Start(processInfo);
return process != null && process.WaitForExit(5000) && process.ExitCode == 0;
}
catch
{
return false;
var psi = new ProcessStartInfo { FileName = "ffmpeg", Arguments = "-version", UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true };
using var p = Process.Start(psi);
return p != null && p.WaitForExit(4000) && p.ExitCode == 0;
}
catch { return false; }
}
private bool TryFixMissingFFMpeg(string binFolder)
{
if (string.IsNullOrEmpty(binFolder) || !Directory.Exists(binFolder))
return false;
if (string.IsNullOrEmpty(binFolder) || !Directory.Exists(binFolder)) return false;
var ffmpegPath = Path.Combine(binFolder, "ffmpeg.exe");
var ffprobePath = Path.Combine(binFolder, "ffprobe.exe");
if (!File.Exists(ffmpegPath) && File.Exists(ffprobePath))
{
try
{
Debug.WriteLine("[FIX] Attempting to copy ffprobe.exe as ffmpeg.exe");
File.Copy(ffprobePath, ffmpegPath, true);
return File.Exists(ffmpegPath);
}
catch (Exception ex)
{
Debug.WriteLine($"[ERROR] Failed to copy ffprobe as ffmpeg: {ex.Message}");
return false;
}
try { File.Copy(ffprobePath, ffmpegPath, true); return File.Exists(ffmpegPath); } catch { return false; }
}
return false;
}
private void BrowseVideoButton_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("[UI] BrowseVideoButton_Click invoked.");
var dialog = new WpfOpenFileDialog
{
Filter = "Video files (*.mp4;*.avi;*.mov;*.mkv;*.wmv)|*.mp4;*.avi;*.mov;*.mkv;*.wmv|All files (*.*)|*.*",
Multiselect = true
};
if (dialog.ShowDialog() == true)
{
AddVideosToQueue(dialog.FileNames);
}
var dialog = new WpfOpenFileDialog { Filter = "Video (*.mp4;*.avi;*.mov;*.mkv;*.wmv)|*.mp4;*.avi;*.mov;*.mkv;*.wmv|Tutti i file (*.*)|*.*", Multiselect = true };
if (dialog.ShowDialog() == true) AddVideosToQueue(dialog.FileNames);
}
private void ImportFolderButton_Click(object sender, RoutedEventArgs e)
{
using var dialog = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Seleziona la cartella contenente i video da importare",
ShowNewFolderButton = false
};
using var dialog = new System.Windows.Forms.FolderBrowserDialog { Description = "Seleziona la cartella con i video", ShowNewFolderButton = false };
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
var folder = dialog.SelectedPath;
try
{
var videoFiles = Directory.EnumerateFiles(folder, "*.*", SearchOption.TopDirectoryOnly)
.Where(IsVideoFile)
.ToArray();
if (videoFiles.Length == 0)
var files = Directory.EnumerateFiles(dialog.SelectedPath, "*.*", SearchOption.TopDirectoryOnly).Where(IsVideoFile).ToArray();
if (files.Length == 0)
{
WpfMessageBox.Show("Nessun file video valido trovato nella cartella.", "Importa Cartella", MessageBoxButton.OK, MessageBoxImage.Information);
WpfMessageBox.Show("Nessun file video valido trovato.", "Importa Cartella", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
AddVideosToQueue(videoFiles);
StatusText.Text = $"Importati {videoFiles.Length} video dalla cartella.";
AddVideosToQueue(files);
StatusText.Text = $"Importati {files.Length} video.";
}
catch (Exception ex)
{
WpfMessageBox.Show($"Errore durante l'importazione: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
WpfMessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
private void AddVideosToQueue(string[] videoPaths)
private void AddVideosToQueue(string[] paths)
{
if (string.IsNullOrEmpty(outputFolder))
{
WpfMessageBox.Show("Seleziona prima una cartella di output.", "Cartella Output Richiesta",
MessageBoxButton.OK, MessageBoxImage.Warning);
WpfMessageBox.Show("Seleziona prima una cartella di output.", "Cartella Output Richiesta", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var createSubfolder = Settings.Default.CreateSubfolder;
foreach (var videoPath in videoPaths)
{
_processingService.AddJob(videoPath, outputFolder, createSubfolder);
Debug.WriteLine($"[QUEUE] Added video to queue: {Path.GetFileName(videoPath)}");
}
StatusText.Text = $"Aggiunti {videoPaths.Length} video in coda (Pending)";
Settings.Default.LastVideoPath = videoPaths.FirstOrDefault();
var createSub = Settings.Default.CreateSubfolder;
foreach (var p in paths) _processingService.AddJob(p, outputFolder, createSub);
StatusText.Text = $"Aggiunti {paths.Length} video (In attesa)";
Settings.Default.LastVideoPath = paths.FirstOrDefault();
Settings.Default.Save();
UpdateQueueCount();
}
private void SelectOutputFolderButton_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("[UI] SelectOutputFolderButton_Click invoked.");
using var dialog = new System.Windows.Forms.FolderBrowserDialog();
if (!string.IsNullOrEmpty(outputFolder))
dialog.SelectedPath = outputFolder;
if (!string.IsNullOrEmpty(outputFolder)) dialog.SelectedPath = outputFolder;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
outputFolder = dialog.SelectedPath;
StatusText.Text = $"Output folder: {outputFolder}";
GlobalOutputFolderTextBox.Text = outputFolder;
StatusText.Text = "Cartella output aggiornata";
Settings.Default.LastOutputFolder = outputFolder;
Settings.Default.Save();
Debug.WriteLine($"[INFO] Output folder selected: {outputFolder}");
}
}
private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
var settingsWindow = new SettingsWindow
var win = new SettingsWindow { Owner = this };
if (win.ShowDialog() == true)
{
Owner = this
};
if (settingsWindow.ShowDialog() == true)
{
// Reconfigure FFMpeg if settings changed
ConfigureFFMpeg();
StatusText.Text = "Impostazioni aggiornate";
}
@@ -317,97 +249,59 @@ namespace Ganimede
{
if (_processingService.JobQueue.Count == 0)
{
WpfMessageBox.Show("Nessun video in coda. Aggiungi prima dei video.", "Coda Vuota",
MessageBoxButton.OK, MessageBoxImage.Information);
WpfMessageBox.Show("Nessun video in coda.", "Coda Vuota", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var pendingJobs = _processingService.JobQueue.Count(job => job.Status == JobStatus.Pending);
if (pendingJobs == 0)
if (_processingService.JobQueue.All(j => j.Status != JobStatus.Pending))
{
WpfMessageBox.Show("Nessun job in stato Pending.", "Nessun Job",
MessageBoxButton.OK, MessageBoxImage.Information);
WpfMessageBox.Show("Nessun job in stato In attesa.", "Nessun Job", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
Debug.WriteLine("[QUEUE] Starting queue processing manually");
await _processingService.StartProcessingAsync();
UpdateJobsSummary();
}
private void StopQueueButton_Click(object sender, RoutedEventArgs e)
{
_processingService.StopProcessing();
StatusText.Text = "Arresto elaborazione in corso...";
Debug.WriteLine("[QUEUE] Stop processing requested by user");
StatusText.Text = "Arresto in corso...";
UpdateJobsSummary();
}
private void JobCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
UpdateSelectedJobs();
}
private void JobCheckBox_CheckedChanged(object sender, RoutedEventArgs e) => UpdateSelectedJobs();
private void UpdateSelectedJobs()
{
_selectedJobs.Clear();
// Find all checked checkboxes in the ItemsControl
var checkBoxes = FindVisualChildren<System.Windows.Controls.CheckBox>(QueueItemsControl).Where(cb => cb.Name == "JobCheckBox");
foreach (var checkBox in checkBoxes)
{
if (checkBox.IsChecked == true && checkBox.Tag is VideoJob job)
{
_selectedJobs.Add(job);
}
}
// Update configure button state
var boxes = FindVisualChildren<System.Windows.Controls.CheckBox>(QueueItemsControl).Where(cb => cb.Name == "JobCheckBox");
foreach (var cb in boxes) if (cb.IsChecked == true && cb.Tag is VideoJob job) _selectedJobs.Add(job);
ConfigureSelectedButton.IsEnabled = _selectedJobs.Count > 0;
Debug.WriteLine($"[SELECTION] {_selectedJobs.Count} jobs selected");
}
private void ConfigureSelectedButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedJobs.Count == 0)
return;
var configWindow = new JobConfigWindow(_selectedJobs.ToList())
if (_selectedJobs.Count == 0) return;
var cfg = new JobConfigWindow(_selectedJobs.ToList()) { Owner = this };
if (cfg.ShowDialog() == true)
{
Owner = this
};
if (configWindow.ShowDialog() == true)
{
StatusText.Text = $"Configurazione applicata a {_selectedJobs.Count} job(s)";
// Reset job output folders for those without custom settings
StatusText.Text = $"Configurazione applicata a {_selectedJobs.Count} job";
foreach (var job in _selectedJobs.Where(j => string.IsNullOrEmpty(j.CustomOutputFolder)))
{
var createSubfolder = Settings.Default.CreateSubfolder;
if (job.ExtractionMode == ExtractionMode.SingleFrame)
{
job.OutputFolder = outputFolder; // single frame directly
}
else
{
job.OutputFolder = createSubfolder
? Path.Combine(outputFolder, job.VideoName)
: outputFolder;
}
var createSub = Settings.Default.CreateSubfolder;
job.OutputFolder = job.ExtractionMode == ExtractionMode.SingleFrame ? outputFolder : (createSub ? Path.Combine(outputFolder, job.VideoName) : outputFolder);
}
UpdateJobsSummary();
}
}
private void RemoveQueueItem_Click(object sender, RoutedEventArgs e)
{
if (sender is WpfButton button && button.Tag is VideoJob job)
if (sender is WpfButton btn && btn.Tag is VideoJob job)
{
_processingService.CancelJob(job);
if (job.Status == JobStatus.Cancelled || job.Status == JobStatus.Pending)
{
_processingService.JobQueue.Remove(job);
}
UpdateQueueCount();
}
}
@@ -415,83 +309,55 @@ namespace Ganimede
{
_processingService.RemoveCompletedJobs();
StatusText.Text = "Job completati rimossi";
UpdateQueueCount();
}
private void ClearAllButton_Click(object sender, RoutedEventArgs e)
{
var processingJobs = _processingService.JobQueue.Where(j => j.Status == JobStatus.Processing).ToList();
if (processingJobs.Count > 0)
var processing = _processingService.JobQueue.Any(j => j.Status == JobStatus.Processing);
if (processing)
{
var result = WpfMessageBox.Show(
$"Ci sono {processingJobs.Count} job in elaborazione.\n\n" +
"Sì: Ferma tutto e svuota la coda\n" +
"No: Rimuovi solo job completati/pending\n" +
"Annulla: Non fare nulla",
"Job in corso",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question);
switch (result)
var res = WpfMessageBox.Show("Ci sono job in elaborazione.\n\nSi: Ferma e svuota la coda\nNo: Rimuovi solo job non in elaborazione\nAnnulla: Annulla", "Conferma", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
if (res == MessageBoxResult.Cancel) return;
if (res == MessageBoxResult.Yes)
{
case MessageBoxResult.Yes:
_processingService.StopProcessing();
_processingService.JobQueue.Clear();
thumbnails.Clear();
StatusText.Text = "Tutti i job rimossi e elaborazione fermata";
break;
case MessageBoxResult.No:
for (int i = _processingService.JobQueue.Count - 1; i >= 0; i--)
{
if (_processingService.JobQueue[i].Status != JobStatus.Processing)
{
_processingService.JobQueue.RemoveAt(i);
}
}
StatusText.Text = "Coda ripulita (job in corso mantenuti)";
break;
case MessageBoxResult.Cancel:
return;
_processingService.StopProcessing();
_processingService.JobQueue.Clear();
thumbnails.Clear();
}
else if (res == MessageBoxResult.No)
{
for (int i = _processingService.JobQueue.Count - 1; i >= 0; i--)
if (_processingService.JobQueue[i].Status != JobStatus.Processing)
_processingService.JobQueue.RemoveAt(i);
}
}
else
{
var result = WpfMessageBox.Show("Sicuro di voler rimuovere tutti i job?",
"Pulisci Tutto", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
if (WpfMessageBox.Show("Rimuovere tutti i job?", "Conferma", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
_processingService.JobQueue.Clear();
thumbnails.Clear();
StatusText.Text = "Tutti i job rimossi";
}
}
StatusText.Text = "Coda aggiornata";
UpdateQueueCount();
}
private static bool IsVideoFile(string filePath)
private static bool IsVideoFile(string path)
{
var extension = Path.GetExtension(filePath).ToLowerInvariant();
return extension is ".mp4" or ".avi" or ".mov" or ".mkv" or ".wmv" or ".flv" or ".webm";
var ext = Path.GetExtension(path).ToLowerInvariant();
return ext is ".mp4" or ".avi" or ".mov" or ".mkv" or ".wmv" or ".flv" or ".webm";
}
// Helper method to find visual children
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject dep) where T : DependencyObject
{
if (depObj != null)
if (dep == null) yield break;
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(dep); i++)
{
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = System.Windows.Media.VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
var child = System.Windows.Media.VisualTreeHelper.GetChild(dep, i);
if (child is T t) yield return t;
foreach (var c in FindVisualChildren<T>(child)) yield return c;
}
}
}

View File

@@ -4,131 +4,183 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Configure Selected Jobs" Height="560" Width="600"
Background="#222" WindowStartupLocation="CenterOwner">
<Grid Margin="20">
Title="Configurazione Job" Height="640" Width="640"
Background="#1E2228" WindowStartupLocation="CenterOwner">
<Window.Resources>
<SolidColorBrush x:Key="PanelBrush" Color="#242A31"/>
<SolidColorBrush x:Key="PanelSubBrush" Color="#2C333B"/>
<SolidColorBrush x:Key="BorderBrushColor" Color="#38424D"/>
<SolidColorBrush x:Key="AccentBrush" Color="#268BFF"/>
<Style TargetType="GroupBox">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Border Background="{StaticResource PanelBrush}" BorderBrush="{StaticResource BorderBrushColor}" BorderThickness="1" CornerRadius="8" Padding="12" >
<DockPanel>
<Border Background="#2C333B" CornerRadius="4" Padding="6 4" Margin="0 0 0 10" DockPanel.Dock="Top">
<ContentPresenter ContentSource="Header"/>
</Border>
<ContentPresenter/>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="PrimaryButton">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#268BFF"/>
<Setter Property="BorderBrush" Value="#1776DF"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="14 8"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#3595FF"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#1570D4"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.45"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="FlatButton" BasedOn="{StaticResource PrimaryButton}">
<Setter Property="Background" Value="#2F3740"/>
<Setter Property="BorderBrush" Value="#404B55"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#38434D"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox">
<Setter Property="Background" Value="#2C333B"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#3A444E"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="6 4"/>
</Style>
<Style TargetType="ComboBox">
<Setter Property="Background" Value="#2C333B"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#3A444E"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="4 2"/>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0 2 0 6"/>
</Style>
<Style TargetType="RadioButton">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0 0 14 0"/>
</Style>
</Window.Resources>
<Grid Margin="18">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Title -->
<StackPanel Grid.Row="0" Margin="0,0,0,20">
<TextBlock Text="Configure Selected Jobs" FontSize="20" FontWeight="Bold" Foreground="White"/>
<TextBlock x:Name="SelectedJobsText" Text="2 jobs selected" FontSize="12" Foreground="#AAA" Margin="0,5,0,0"/>
<StackPanel Grid.Row="0" Margin="0 0 0 14">
<TextBlock Text="Configurazione Job" FontSize="22" FontWeight="SemiBold" Foreground="White"/>
<TextBlock x:Name="SelectedJobsText" Text="0 job selezionati" Foreground="#9BA5AF" FontSize="12" Margin="0 4 0 0"/>
</StackPanel>
<!-- Settings Content -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- Extraction Mode Settings -->
<GroupBox Header="Modalita Estrazione" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<TextBlock Text="Seleziona come estrarre i frame:" Foreground="#CCC" Margin="0,0,0,8"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<RadioButton x:Name="ExtractionFullRadio" Content="Completa" GroupName="ExtractionMode" IsChecked="True" Foreground="White" Margin="0,0,15,0"/>
<RadioButton x:Name="ExtractionSingleRadio" Content="Singolo Frame" GroupName="ExtractionMode" Foreground="White" Margin="0,0,15,0"/>
<RadioButton x:Name="ExtractionAutoRadio" Content="Auto" GroupName="ExtractionMode" Foreground="White"/>
<!-- Output -->
<GroupBox Header="Cartella di Output (Override facoltativo)" Margin="0,0,0,14">
<StackPanel>
<CheckBox x:Name="UseCustomOutputCheckBox" Content="Usa cartella di output personalizzata" Checked="UseCustomOutputCheckBox_CheckedChanged" Unchecked="UseCustomOutputCheckBox_CheckedChanged"/>
<DockPanel Margin="0 4 0 0" IsEnabled="{Binding IsChecked, ElementName=UseCustomOutputCheckBox}">
<TextBox x:Name="CustomOutputTextBox" Height="34" VerticalContentAlignment="Center" DockPanel.Dock="Left" MinWidth="320" Margin="0 0 10 0"/>
<Button x:Name="BrowseCustomOutputButton" Content="Sfoglia" Width="90" Height="34" Style="{StaticResource FlatButton}" Click="BrowseCustomOutputButton_Click"/>
</DockPanel>
<CheckBox x:Name="CreateSubfolderCheckBox" Content="Crea sottocartella per ogni video" IsChecked="True"/>
</StackPanel>
</GroupBox>
<!-- Extraction Mode -->
<GroupBox Header="Modalita di Estrazione" Margin="0,0,0,14">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<RadioButton x:Name="ExtractionFullRadio" Content="Completa" GroupName="ExtractionMode" IsChecked="True"/>
<RadioButton x:Name="ExtractionSingleRadio" Content="Singolo Frame" GroupName="ExtractionMode"/>
<RadioButton x:Name="ExtractionAutoRadio" Content="Auto" GroupName="ExtractionMode"/>
</StackPanel>
<TextBlock Text="Auto: il sistema analizza il video e decide se conviene estrarre tutti i frame o solo uno (tipico dei video social con immagine statica)." TextWrapping="Wrap" Foreground="#888" FontSize="11"/>
<TextBlock Text="Auto: analizza il video e decide se estrarre tutti i frame o solo uno." Foreground="#8D969F" FontSize="11" TextWrapping="Wrap"/>
</StackPanel>
</GroupBox>
<!-- Output Settings -->
<GroupBox Header="Output Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="UseCustomOutputCheckBox" Content="Use custom output folder for selected jobs"
Foreground="White" Margin="0,0,0,10" Checked="UseCustomOutputCheckBox_CheckedChanged" Unchecked="UseCustomOutputCheckBox_CheckedChanged"/>
<Grid IsEnabled="{Binding IsChecked, ElementName=UseCustomOutputCheckBox}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="CustomOutputTextBox" Grid.Column="0" Height="30" VerticalContentAlignment="Center"
Background="#333" Foreground="White" BorderBrush="#555" Margin="0,0,10,0"/>
<Button x:Name="BrowseCustomOutputButton" Grid.Column="1" Content="Browse" Width="80" Height="30"
Click="BrowseCustomOutputButton_Click"/>
</Grid>
<CheckBox x:Name="CreateSubfolderCheckBox" Content="Create subfolder for each video"
Foreground="White" IsChecked="True" Margin="0,10,0,0"/>
<!-- Naming Pattern -->
<GroupBox Header="Pattern Nome File (Override facoltativo)" Margin="0,0,0,14">
<StackPanel>
<CheckBox x:Name="UseCustomNamingCheckBox" Content="Usa pattern di naming personalizzato" Checked="UseCustomNamingCheckBox_CheckedChanged" Unchecked="UseCustomNamingCheckBox_CheckedChanged"/>
<ComboBox x:Name="CustomNamingComboBox" Height="32" Margin="0 6 0 0" SelectionChanged="CustomNamingComboBox_SelectionChanged" IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}">
<ComboBoxItem Content="NomeVideo + Progressivo" Tag="VideoNameProgressive"/>
<ComboBoxItem Content="Frame + Progressivo" Tag="FrameProgressive"/>
<ComboBoxItem Content="NomeVideo + Timestamp" Tag="VideoNameTimestamp"/>
<ComboBoxItem Content="Prefisso Custom + Progressivo" Tag="CustomProgressive"/>
<ComboBoxItem Content="Solo Timestamp" Tag="TimestampOnly"/>
<ComboBoxItem Content="NomeVideo + Frame + Progressivo" Tag="VideoNameFrameProgressive"/>
</ComboBox>
<StackPanel Orientation="Horizontal" Margin="0 6 0 0" IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}">
<TextBlock Text="Prefisso:" Foreground="#9BA5AF" VerticalAlignment="Center" Margin="0 0 8 0"/>
<TextBox x:Name="CustomNamingPrefixTextBox" Width="160" Text="custom" Height="30" VerticalContentAlignment="Center" TextChanged="CustomNamingPrefixTextBox_TextChanged"/>
</StackPanel>
<Border Background="#2F3740" CornerRadius="4" Padding="6" Margin="0 6 0 0" IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}">
<TextBlock x:Name="JobNamingPreviewText" Text="Video1_000001.png" FontFamily="Consolas" Foreground="#4FA6FF" FontSize="12"/>
</Border>
</StackPanel>
</GroupBox>
<!-- Frame Settings -->
<GroupBox Header="Frame Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="UseCustomFrameSizeCheckBox" Content="Use custom frame size for selected jobs"
Foreground="White" Margin="0,0,0,10" Checked="UseCustomFrameSizeCheckBox_CheckedChanged" Unchecked="UseCustomFrameSizeCheckBox_CheckedChanged"/>
<ComboBox x:Name="CustomFrameSizeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
IsEnabled="{Binding IsChecked, ElementName=UseCustomFrameSizeCheckBox}">
<ComboBoxItem Content="Original Size (Keep aspect ratio)" Tag="original" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="320x180 (Fast)" Tag="320,180" Foreground="Black" Background="White"/>
<ComboBoxItem Content="640x360 (Medium)" Tag="640,360" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1280x720 (High)" Tag="1280,720" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080" Foreground="Black" Background="White"/>
<!-- Overwrite -->
<GroupBox Header="Modalita Sovrascrittura (Override facoltativo)" Margin="0,0,0,14">
<StackPanel>
<CheckBox x:Name="UseCustomOverwriteCheckBox" Content="Usa comportamento di sovrascrittura personalizzato" Checked="UseCustomOverwriteCheckBox_CheckedChanged" Unchecked="UseCustomOverwriteCheckBox_CheckedChanged"/>
<ComboBox x:Name="CustomOverwriteComboBox" Height="32" Margin="0 6 0 0" IsEnabled="{Binding IsChecked, ElementName=UseCustomOverwriteCheckBox}">
<ComboBoxItem Content="Chiedi" Tag="Ask"/>
<ComboBoxItem Content="Salta" Tag="Skip"/>
<ComboBoxItem Content="Sovrascrivi" Tag="Overwrite"/>
</ComboBox>
</StackPanel>
</GroupBox>
<!-- Overwrite Settings -->
<GroupBox Header="File Overwrite Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="UseCustomOverwriteCheckBox" Content="Use custom overwrite behavior for selected jobs"
Foreground="White" Margin="0,0,0,10" Checked="UseCustomOverwriteCheckBox_CheckedChanged" Unchecked="UseCustomOverwriteCheckBox_CheckedChanged"/>
<ComboBox x:Name="CustomOverwriteComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
IsEnabled="{Binding IsChecked, ElementName=UseCustomOverwriteCheckBox}">
<ComboBoxItem Content="Ask each time" Tag="Ask" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Skip existing files" Tag="Skip" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Overwrite existing files" Tag="Overwrite" Foreground="Black" Background="White"/>
<!-- Frame size -->
<GroupBox Header="Ridimensionamento Frame (Override facoltativo)" Margin="0,0,0,14">
<StackPanel>
<CheckBox x:Name="UseCustomFrameSizeCheckBox" Content="Usa dimensione frame personalizzata" Checked="UseCustomFrameSizeCheckBox_CheckedChanged" Unchecked="UseCustomFrameSizeCheckBox_CheckedChanged"/>
<ComboBox x:Name="CustomFrameSizeComboBox" Height="32" Margin="0 6 0 0" IsEnabled="{Binding IsChecked, ElementName=UseCustomFrameSizeCheckBox}">
<ComboBoxItem Content="Risoluzione Originale" Tag="original"/>
<ComboBoxItem Content="320x180 (Veloce)" Tag="320,180"/>
<ComboBoxItem Content="640x360 (Media)" Tag="640,360"/>
<ComboBoxItem Content="1280x720 (HD)" Tag="1280,720"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080"/>
</ComboBox>
</StackPanel>
</GroupBox>
<!-- Naming Settings -->
<GroupBox Header="File Naming Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="UseCustomNamingCheckBox" Content="Use custom naming pattern for selected jobs"
Foreground="White" Margin="0,0,0,10" Checked="UseCustomNamingCheckBox_CheckedChanged" Unchecked="UseCustomNamingCheckBox_CheckedChanged"/>
<TextBlock Text="Naming Pattern:" Foreground="#CCC" Margin="0,0,0,5"
IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}"/>
<ComboBox x:Name="CustomNamingComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}" SelectionChanged="CustomNamingComboBox_SelectionChanged">
<ComboBoxItem Content="VideoName + Progressive (recommended)" Tag="VideoNameProgressive" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Frame + Progressive" Tag="FrameProgressive" Foreground="Black" Background="White"/>
<ComboBoxItem Content="VideoName + Timestamp" Tag="VideoNameTimestamp" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Custom Prefix + Progressive" Tag="CustomProgressive" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Timestamp Only" Tag="TimestampOnly" Foreground="Black" Background="White"/>
<ComboBoxItem Content="VideoName + Frame + Progressive" Tag="VideoNameFrameProgressive" Foreground="Black" Background="White"/>
</ComboBox>
<TextBlock Text="Custom Prefix:" Foreground="#CCC" Margin="0,10,0,5"
IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}"/>
<TextBox x:Name="CustomNamingPrefixTextBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
VerticalContentAlignment="Center" Text="custom"
IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}" TextChanged="CustomNamingPrefixTextBox_TextChanged"/>
<TextBlock Text="Preview:" Foreground="#777" FontSize="11" Margin="0,8,0,2"
IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}"/>
<TextBlock x:Name="JobNamingPreviewText" Text="Video1_000001.png" Foreground="#4FC3F7" FontSize="10"
FontFamily="Consolas" Background="#1A1A1A" Padding="3"
IsEnabled="{Binding IsChecked, ElementName=UseCustomNamingCheckBox}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
<!-- Buttons -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,20,0,0">
<Button x:Name="ApplyButton" Content="Apply to Selected" Width="120" Height="35" Margin="0,0,10,0"
Click="ApplyButton_Click" Background="#4FC3F7" Foreground="White" BorderThickness="0"/>
<Button x:Name="CancelButton" Content="Cancel" Width="80" Height="35"
Click="CancelButton_Click"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 16 0 0">
<Button x:Name="ApplyButton" Content="Applica" Width="140" Height="42" Style="{StaticResource PrimaryButton}" Click="ApplyButton_Click" Margin="0,0,12,0"/>
<Button x:Name="CancelButton" Content="Annulla" Width="110" Height="42" Style="{StaticResource FlatButton}" Click="CancelButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@@ -17,32 +17,21 @@ namespace Ganimede.Windows
{
InitializeComponent();
_selectedJobs = selectedJobs;
SelectedJobsText.Text = $"{selectedJobs.Count} job(s) selected";
SelectedJobsText.Text = $"{selectedJobs.Count} job selezionati";
LoadCurrentSettings();
}
private void SetExtractionModeRadio(ExtractionMode mode)
{
var fullObj = FindName("ExtractionFullRadio");
if (fullObj is System.Windows.Controls.RadioButton fullRb)
fullRb.IsChecked = mode == ExtractionMode.Full;
var singleObj = FindName("ExtractionSingleRadio");
if (singleObj is System.Windows.Controls.RadioButton singleRb)
singleRb.IsChecked = mode == ExtractionMode.SingleFrame;
var autoObj = FindName("ExtractionAutoRadio");
if (autoObj is System.Windows.Controls.RadioButton autoRb)
autoRb.IsChecked = mode == ExtractionMode.Auto;
if (FindName("ExtractionFullRadio") is System.Windows.Controls.RadioButton fullRb) fullRb.IsChecked = mode == ExtractionMode.Full;
if (FindName("ExtractionSingleRadio") is System.Windows.Controls.RadioButton singleRb) singleRb.IsChecked = mode == ExtractionMode.SingleFrame;
if (FindName("ExtractionAutoRadio") is System.Windows.Controls.RadioButton autoRb) autoRb.IsChecked = mode == ExtractionMode.Auto;
}
private ExtractionMode GetSelectedExtractionMode()
{
var singleObj = FindName("ExtractionSingleRadio");
if (singleObj is System.Windows.Controls.RadioButton singleRb && singleRb.IsChecked == true)
return ExtractionMode.SingleFrame;
var autoObj = FindName("ExtractionAutoRadio");
if (autoObj is System.Windows.Controls.RadioButton autoRb && autoRb.IsChecked == true)
return ExtractionMode.Auto;
if (FindName("ExtractionSingleRadio") is System.Windows.Controls.RadioButton singleRb && singleRb.IsChecked == true) return ExtractionMode.SingleFrame;
if (FindName("ExtractionAutoRadio") is System.Windows.Controls.RadioButton autoRb && autoRb.IsChecked == true) return ExtractionMode.Auto;
return ExtractionMode.Full;
}
@@ -52,8 +41,6 @@ namespace Ganimede.Windows
if (firstJob != null)
{
SetExtractionModeRadio(firstJob.ExtractionMode);
// Output settings
if (!string.IsNullOrEmpty(firstJob.CustomOutputFolder))
{
UseCustomOutputCheckBox.IsChecked = true;
@@ -61,7 +48,6 @@ namespace Ganimede.Windows
}
CreateSubfolderCheckBox.IsChecked = firstJob.CustomCreateSubfolder;
// Frame size settings
if (!string.IsNullOrEmpty(firstJob.CustomFrameSize))
{
UseCustomFrameSizeCheckBox.IsChecked = true;
@@ -75,7 +61,6 @@ namespace Ganimede.Windows
}
}
// Overwrite settings
if (firstJob.CustomOverwriteMode.HasValue)
{
UseCustomOverwriteCheckBox.IsChecked = true;
@@ -89,7 +74,6 @@ namespace Ganimede.Windows
}
}
// Naming settings
if (firstJob.CustomNamingPattern.HasValue)
{
UseCustomNamingCheckBox.IsChecked = true;
@@ -105,12 +89,9 @@ namespace Ganimede.Windows
}
}
if (CustomFrameSizeComboBox.SelectedItem == null)
CustomFrameSizeComboBox.SelectedIndex = 0;
if (CustomOverwriteComboBox.SelectedItem == null)
CustomOverwriteComboBox.SelectedIndex = 0;
if (CustomNamingComboBox.SelectedItem == null)
CustomNamingComboBox.SelectedIndex = 0;
if (CustomFrameSizeComboBox.SelectedItem == null) CustomFrameSizeComboBox.SelectedIndex = 0;
if (CustomOverwriteComboBox.SelectedItem == null) CustomOverwriteComboBox.SelectedIndex = 0;
if (CustomNamingComboBox.SelectedItem == null) CustomNamingComboBox.SelectedIndex = 0;
UpdateJobNamingPreview();
}
@@ -130,7 +111,7 @@ namespace Ganimede.Windows
}
else
{
JobNamingPreviewText.Text = "Video1_000001.png (using default)";
JobNamingPreviewText.Text = "Video1_000001.png (default)";
}
}
catch
@@ -148,19 +129,9 @@ namespace Ganimede.Windows
private void BrowseCustomOutputButton_Click(object sender, RoutedEventArgs e)
{
using var dialog = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Select custom output folder for selected jobs",
ShowNewFolderButton = true
};
if (!string.IsNullOrEmpty(CustomOutputTextBox.Text))
dialog.SelectedPath = CustomOutputTextBox.Text;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
CustomOutputTextBox.Text = dialog.SelectedPath;
}
using var dialog = new System.Windows.Forms.FolderBrowserDialog { Description = "Seleziona cartella di output personalizzata", ShowNewFolderButton = true };
if (!string.IsNullOrEmpty(CustomOutputTextBox.Text)) dialog.SelectedPath = CustomOutputTextBox.Text;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) CustomOutputTextBox.Text = dialog.SelectedPath;
}
private void ApplyButton_Click(object sender, RoutedEventArgs e)
@@ -176,50 +147,28 @@ namespace Ganimede.Windows
{
job.CustomOutputFolder = CustomOutputTextBox.Text;
job.CustomCreateSubfolder = CreateSubfolderCheckBox.IsChecked ?? true;
// Do NOT create per-video folder if SingleFrame
if (job.CustomCreateSubfolder && job.ExtractionMode != ExtractionMode.SingleFrame)
{
job.OutputFolder = System.IO.Path.Combine(job.CustomOutputFolder, job.VideoName);
}
else
{
job.OutputFolder = job.CustomOutputFolder; // single frame goes directly here
}
job.OutputFolder = job.CustomOutputFolder;
}
else
{
job.CustomOutputFolder = string.Empty;
// OutputFolder will be recalculated in MainWindow if needed
}
if (UseCustomFrameSizeCheckBox.IsChecked == true && CustomFrameSizeComboBox.SelectedItem is ComboBoxItem frameSizeItem)
{
job.CustomFrameSize = frameSizeItem.Tag?.ToString() ?? string.Empty;
}
else
{
job.CustomFrameSize = string.Empty;
}
else job.CustomFrameSize = string.Empty;
if (UseCustomOverwriteCheckBox.IsChecked == true && CustomOverwriteComboBox.SelectedItem is ComboBoxItem overwriteItem)
{
if (Enum.TryParse<OverwriteMode>(overwriteItem.Tag?.ToString(), out var overwriteMode))
{
job.CustomOverwriteMode = overwriteMode;
}
}
else
{
job.CustomOverwriteMode = null;
}
if (UseCustomOverwriteCheckBox.IsChecked == true && CustomOverwriteComboBox.SelectedItem is ComboBoxItem overwriteItem && Enum.TryParse<OverwriteMode>(overwriteItem.Tag?.ToString(), out var overwriteMode))
job.CustomOverwriteMode = overwriteMode;
else job.CustomOverwriteMode = null;
if (UseCustomNamingCheckBox.IsChecked == true && CustomNamingComboBox.SelectedItem is ComboBoxItem namingItem)
if (UseCustomNamingCheckBox.IsChecked == true && CustomNamingComboBox.SelectedItem is ComboBoxItem namingItem && Enum.TryParse<NamingPattern>(namingItem.Tag?.ToString(), out var namingPattern))
{
if (Enum.TryParse<NamingPattern>(namingItem.Tag?.ToString(), out var namingPattern))
{
job.CustomNamingPattern = namingPattern;
job.CustomPrefix = string.IsNullOrWhiteSpace(CustomNamingPrefixTextBox.Text) ? "custom" : CustomNamingPrefixTextBox.Text;
}
job.CustomNamingPattern = namingPattern;
job.CustomPrefix = string.IsNullOrWhiteSpace(CustomNamingPrefixTextBox.Text) ? "custom" : CustomNamingPrefixTextBox.Text;
}
else
{
@@ -227,13 +176,12 @@ namespace Ganimede.Windows
job.CustomPrefix = string.Empty;
}
}
DialogResult = true;
Close();
}
catch (Exception ex)
{
WpfMessageBox.Show($"Error applying settings: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
WpfMessageBox.Show($"Errore durante l'applicazione delle impostazioni: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

View File

@@ -4,9 +4,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Settings" Height="560" Width="600"
Background="#222" WindowStartupLocation="CenterOwner">
<Grid Margin="20">
Title="Impostazioni" Height="600" Width="640"
Background="#1E2228" WindowStartupLocation="CenterOwner">
<Grid Margin="22">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
@@ -14,113 +14,102 @@
</Grid.RowDefinitions>
<!-- Title -->
<TextBlock Grid.Row="0" Text="Settings" FontSize="24" FontWeight="Bold" Foreground="White" Margin="0,0,0,20"/>
<TextBlock Grid.Row="0" Text="Impostazioni" FontSize="26" FontWeight="Bold" Foreground="White" Margin="0,0,0,18"/>
<!-- Settings Content -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- FFmpeg Settings -->
<GroupBox Header="FFmpeg Configuration" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<TextBlock Text="FFmpeg Binary Folder:" Foreground="#CCC" Margin="0,0,0,5"/>
<GroupBox Header="Configurazione FFmpeg" Foreground="White" BorderBrush="#444" Margin="0,0,0,18">
<StackPanel Margin="12">
<TextBlock Text="Cartella Binari FFmpeg:" Foreground="#CCC" Margin="0,0,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="FFmpegPathTextBox" Grid.Column="0" Height="30" VerticalContentAlignment="Center"
<TextBox x:Name="FFmpegPathTextBox" Grid.Column="0" Height="34" VerticalContentAlignment="Center"
Background="#333" Foreground="White" BorderBrush="#555" Margin="0,0,10,0"/>
<Button x:Name="BrowseFFmpegButton" Grid.Column="1" Content="Browse" Width="80" Height="30"
<Button x:Name="BrowseFFmpegButton" Grid.Column="1" Content="Sfoglia" Width="90" Height="34"
Click="BrowseFFmpegButton_Click"/>
</Grid>
<TextBlock x:Name="FFmpegStatusText" Foreground="#AAA" FontSize="12" Margin="0,5,0,0"/>
<TextBlock x:Name="FFmpegStatusText" Foreground="#AAA" FontSize="12" Margin="0,6,0,0"/>
</StackPanel>
</GroupBox>
<!-- Output Settings -->
<GroupBox Header="Output Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="CreateSubfolderCheckBox" Content="Create subfolder for each video"
<GroupBox Header="Output" Foreground="White" BorderBrush="#444" Margin="0,0,0,18">
<StackPanel Margin="12">
<CheckBox x:Name="CreateSubfolderCheckBox" Content="Crea sottocartella per ogni video"
Foreground="White" IsChecked="True" Margin="0,0,0,10"/>
<TextBlock Text="Default Output Folder:" Foreground="#CCC" Margin="0,0,0,5"/>
<TextBlock Text="Cartella Output Predefinita:" Foreground="#CCC" Margin="0,0,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="DefaultOutputTextBox" Grid.Column="0" Height="30" VerticalContentAlignment="Center"
<TextBox x:Name="DefaultOutputTextBox" Grid.Column="0" Height="34" VerticalContentAlignment="Center"
Background="#333" Foreground="White" BorderBrush="#555" Margin="0,0,10,0"/>
<Button x:Name="BrowseOutputButton" Grid.Column="1" Content="Browse" Width="80" Height="30"
<Button x:Name="BrowseOutputButton" Grid.Column="1" Content="Sfoglia" Width="90" Height="34"
Click="BrowseOutputButton_Click"/>
</Grid>
</StackPanel>
</GroupBox>
<!-- Performance Settings -->
<GroupBox Header="Default Processing Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<TextBlock Text="Default Frame Size:" Foreground="#CCC" Margin="0,0,0,5"/>
<ComboBox x:Name="FrameSizeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555">
<ComboBoxItem Content="Original Size (Keep aspect ratio)" Tag="original" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="320x180 (Fast)" Tag="320,180" Foreground="Black" Background="White"/>
<ComboBoxItem Content="640x360 (Medium)" Tag="640,360" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1280x720 (High)" Tag="1280,720" Foreground="Black" Background="White"/>
<GroupBox Header="Impostazioni di Elaborazione" Foreground="White" BorderBrush="#444" Margin="0,0,0,18">
<StackPanel Margin="12">
<TextBlock Text="Dimensione Frame Predefinita:" Foreground="#CCC" Margin="0,0,0,6"/>
<ComboBox x:Name="FrameSizeComboBox" Height="32" Background="#333" Foreground="White" BorderBrush="#555">
<ComboBoxItem Content="Dimensione Originale" Tag="original" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="320x180 (Veloce)" Tag="320,180" Foreground="Black" Background="White"/>
<ComboBoxItem Content="640x360 (Media)" Tag="640,360" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1280x720 (Alta)" Tag="1280,720" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080" Foreground="Black" Background="White"/>
</ComboBox>
<TextBlock Text="Default File Overwrite Behavior:" Foreground="#CCC" Margin="15,15,0,5"/>
<ComboBox x:Name="OverwriteModeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555">
<ComboBoxItem Content="Ask each time" Tag="Ask" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Skip existing files" Tag="Skip" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Overwrite existing files" Tag="Overwrite" Foreground="Black" Background="White"/>
<TextBlock Text="Comportamento Sovrascrittura Predefinito:" Foreground="#CCC" Margin="16,16,0,6"/>
<ComboBox x:Name="OverwriteModeComboBox" Height="32" Background="#333" Foreground="White" BorderBrush="#555">
<ComboBoxItem Content="Chiedi" Tag="Ask" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Salta" Tag="Skip" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Sovrascrivi" Tag="Overwrite" Foreground="Black" Background="White"/>
</ComboBox>
<!-- New default extraction mode -->
<TextBlock Text="Default Extraction Mode:" Foreground="#CCC" Margin="15,15,0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
<RadioButton x:Name="DefaultModeFullRadio" Content="Full" GroupName="DefExtraction" Foreground="White" Margin="0,0,15,0" IsChecked="True"/>
<RadioButton x:Name="DefaultModeSingleRadio" Content="Single Frame" GroupName="DefExtraction" Foreground="White" Margin="0,0,15,0"/>
<TextBlock Text="Modalita di Estrazione Predefinita:" Foreground="#CCC" Margin="16,16,0,6"/>
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="DefaultModeFullRadio" Content="Completa" GroupName="DefExtraction" Foreground="White" Margin="0,0,18,0" IsChecked="True"/>
<RadioButton x:Name="DefaultModeSingleRadio" Content="Singolo Frame" GroupName="DefExtraction" Foreground="White" Margin="0,0,18,0"/>
<RadioButton x:Name="DefaultModeAutoRadio" Content="Auto" GroupName="DefExtraction" Foreground="White"/>
</StackPanel>
<TextBlock Text="Auto: analyze video to decide if extracting all frames or only one." Foreground="#777" FontSize="10" TextWrapping="Wrap" Margin="0,4,0,0"/>
<TextBlock Text="Auto: analizza il video e decide cosa estrarre." Foreground="#777" FontSize="10" TextWrapping="Wrap" Margin="0,4,0,0"/>
<TextBlock Text="Single Frame Output (default behaviour = direct in output folder):" Foreground="#CCC" Margin="15,15,0,5"/>
<CheckBox x:Name="SingleFrameUseSubfolderCheckBox" Content="Create per-video subfolder for single frame jobs (uncheck = save directly)" Foreground="White" IsChecked="False" ToolTip="Unchecked: single frame saved directly in main output folder. Checked: create video-named subfolder."/>
<TextBlock Text="Singolo Frame: salvataggio" Foreground="#CCC" Margin="16,16,0,6"/>
<CheckBox x:Name="SingleFrameUseSubfolderCheckBox" Content="Crea sottocartella anche per job a singolo frame" Foreground="White" IsChecked="False"/>
</StackPanel>
</GroupBox>
<!-- File Naming Settings -->
<GroupBox Header="File Naming Settings" Foreground="White" BorderBrush="#444" Margin="0,0,0,20">
<StackPanel Margin="10">
<TextBlock Text="Default Naming Pattern:" Foreground="#CCC" Margin="0,0,0,5"/>
<ComboBox x:Name="NamingPatternComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
<GroupBox Header="Naming File" Foreground="White" BorderBrush="#444" Margin="0,0,0,18">
<StackPanel Margin="12">
<TextBlock Text="Pattern di Naming Predefinito:" Foreground="#CCC" Margin="0,0,0,6"/>
<ComboBox x:Name="NamingPatternComboBox" Height="32" Background="#333" Foreground="White" BorderBrush="#555"
SelectionChanged="NamingPatternComboBox_SelectionChanged">
<ComboBoxItem Content="VideoName + Progressive (recommended)" Tag="VideoNameProgressive" IsSelected="True"/>
<ComboBoxItem Content="Frame + Progressive" Tag="FrameProgressive"/>
<ComboBoxItem Content="VideoName + Timestamp" Tag="VideoNameTimestamp"/>
<ComboBoxItem Content="Custom Prefix + Progressive" Tag="CustomProgressive"/>
<ComboBoxItem Content="Timestamp Only" Tag="TimestampOnly"/>
<ComboBoxItem Content="VideoName + Frame + Progressive" Tag="VideoNameFrameProgressive"/>
<ComboBoxItem Content="NomeVideo + Progressivo" Tag="VideoNameProgressive" IsSelected="True"/>
<ComboBoxItem Content="Frame + Progressivo" Tag="FrameProgressive"/>
<ComboBoxItem Content="NomeVideo + Timestamp" Tag="VideoNameTimestamp"/>
<ComboBoxItem Content="Prefisso Custom + Progressivo" Tag="CustomProgressive"/>
<ComboBoxItem Content="Solo Timestamp" Tag="TimestampOnly"/>
<ComboBoxItem Content="NomeVideo + Frame + Progressivo" Tag="VideoNameFrameProgressive"/>
</ComboBox>
<TextBlock Text="Default Custom Prefix:" Foreground="#CCC" Margin="0,10,0,5"/>
<TextBox x:Name="CustomPrefixTextBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
<TextBlock Text="Prefisso Custom Predefinito:" Foreground="#CCC" Margin="0,12,0,6"/>
<TextBox x:Name="CustomPrefixTextBox" Height="32" Background="#333" Foreground="White" BorderBrush="#555"
VerticalContentAlignment="Center" Text="custom" TextChanged="CustomPrefixTextBox_TextChanged"/>
<TextBlock Text="Preview:" Foreground="#CCC" Margin="0,10,0,2" FontSize="11"/>
<TextBlock x:Name="NamingPreviewText" Text="VID20250725_000001.png" Foreground="#4FC3F7" FontSize="11"
FontFamily="Consolas" Background="#1A1A1A" Padding="5" Margin="0,0,0,5"/>
<TextBlock Text="Available patterns:" Foreground="#777" FontSize="10" Margin="0,5,0,2"/>
<StackPanel Margin="0,0,0,0">
<TextBlock Text="VideoName + Progressive: Best for organization" Foreground="#777" FontSize="10"/>
<TextBlock Text="Frame + Progressive: Classic naming (frame_000001)" Foreground="#777" FontSize="10"/>
<TextBlock Text="VideoName + Timestamp: Based on video time" Foreground="#777" FontSize="10"/>
<TextBlock Text="Custom Prefix: Use your own prefix" Foreground="#777" FontSize="10"/>
<TextBlock Text="Timestamp Only: Human readable time" Foreground="#777" FontSize="10"/>
<TextBlock Text="VideoName + Frame: Combined format" Foreground="#777" FontSize="10"/>
</StackPanel>
<TextBlock Text="Anteprima:" Foreground="#CCC" Margin="0,12,0,4" FontSize="11"/>
<TextBlock x:Name="NamingPreviewText" Text="Video1_000001.png" Foreground="#4FC3F7" FontSize="11"
FontFamily="Consolas" Background="#1A1A1A" Padding="6" Margin="0,0,0,6"/>
</StackPanel>
</GroupBox>
</StackPanel>
@@ -128,9 +117,9 @@
<!-- Buttons -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,20,0,0">
<Button x:Name="SaveButton" Content="Save" Width="80" Height="35" Margin="0,0,10,0"
<Button x:Name="SaveButton" Content="Salva" Width="95" Height="40" Margin="0,0,12,0"
Click="SaveButton_Click" Background="#4FC3F7" Foreground="White" BorderThickness="0"/>
<Button x:Name="CancelButton" Content="Cancel" Width="80" Height="35"
<Button x:Name="CancelButton" Content="Annulla" Width="100" Height="40"
Click="CancelButton_Click"/>
</StackPanel>
</Grid>

View File

@@ -24,9 +24,8 @@ namespace Ganimede.Windows
DefaultOutputTextBox.Text = Settings.Default.LastOutputFolder;
CreateSubfolderCheckBox.IsChecked = Settings.Default.CreateSubfolder;
var singleFrameChk = GetCheckBox("SingleFrameUseSubfolderCheckBox");
if (singleFrameChk != null)
singleFrameChk.IsChecked = Settings.Default.SingleFrameUseSubfolder;
if (singleFrameChk != null) singleFrameChk.IsChecked = Settings.Default.SingleFrameUseSubfolder;
var frameSize = Settings.Default.FrameSize;
foreach (System.Windows.Controls.ComboBoxItem item in FrameSizeComboBox.Items)
{
@@ -47,15 +46,14 @@ namespace Ganimede.Windows
}
}
// Default extraction mode
switch (Settings.Default.DefaultExtractionMode)
{
case "SingleFrame":
if (GetDefaultModeRadio("DefaultModeSingleRadio") is { } r1) r1.IsChecked = true; break;
GetDefaultModeRadio("DefaultModeSingleRadio")!.IsChecked = true; break;
case "Auto":
if (GetDefaultModeRadio("DefaultModeAutoRadio") is { } r2) r2.IsChecked = true; break;
GetDefaultModeRadio("DefaultModeAutoRadio")!.IsChecked = true; break;
default:
if (GetDefaultModeRadio("DefaultModeFullRadio") is { } r3) r3.IsChecked = true; break;
GetDefaultModeRadio("DefaultModeFullRadio")!.IsChecked = true; break;
}
UpdateFFmpegStatus();
@@ -68,53 +66,38 @@ namespace Ganimede.Windows
return "Full";
}
private bool GetSingleFrameUseSubfolder()
{
return GetCheckBox("SingleFrameUseSubfolderCheckBox")?.IsChecked == true;
}
private bool GetSingleFrameUseSubfolder() => GetCheckBox("SingleFrameUseSubfolderCheckBox")?.IsChecked == true;
private void UpdateFFmpegStatus()
{
var path = FFmpegPathTextBox.Text;
if (string.IsNullOrEmpty(path))
{
FFmpegStatusText.Text = "No path specified";
FFmpegStatusText.Text = "Nessun percorso specificato";
FFmpegStatusText.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Orange);
}
else if (ValidateFFMpegPath(path))
{
FFmpegStatusText.Text = "? Valid FFmpeg installation found";
FFmpegStatusText.Text = "? FFmpeg valido";
FFmpegStatusText.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.LightGreen);
}
else
{
FFmpegStatusText.Text = "? FFmpeg binaries not found in specified path";
FFmpegStatusText.Text = "? Binari FFmpeg non trovati";
FFmpegStatusText.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
}
}
private bool ValidateFFMpegPath(string path)
{
if (!Directory.Exists(path))
return false;
var ffmpegPath = Path.Combine(path, "ffmpeg.exe");
var ffprobePath = Path.Combine(path, "ffprobe.exe");
return File.Exists(ffmpegPath) && File.Exists(ffprobePath);
if (!Directory.Exists(path)) return false;
return File.Exists(Path.Combine(path, "ffmpeg.exe")) && File.Exists(Path.Combine(path, "ffprobe.exe"));
}
private void BrowseFFmpegButton_Click(object sender, RoutedEventArgs e)
{
using var dialog = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Select FFmpeg binary folder",
ShowNewFolderButton = false
};
if (!string.IsNullOrEmpty(FFmpegPathTextBox.Text))
dialog.SelectedPath = FFmpegPathTextBox.Text;
using var dialog = new System.Windows.Forms.FolderBrowserDialog { Description = "Seleziona cartella binari FFmpeg", ShowNewFolderButton = false };
if (!string.IsNullOrEmpty(FFmpegPathTextBox.Text)) dialog.SelectedPath = FFmpegPathTextBox.Text;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
FFmpegPathTextBox.Text = dialog.SelectedPath;
@@ -124,19 +107,9 @@ namespace Ganimede.Windows
private void BrowseOutputButton_Click(object sender, RoutedEventArgs e)
{
using var dialog = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Select default output folder",
ShowNewFolderButton = true
};
if (!string.IsNullOrEmpty(DefaultOutputTextBox.Text))
dialog.SelectedPath = DefaultOutputTextBox.Text;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
DefaultOutputTextBox.Text = dialog.SelectedPath;
}
using var dialog = new System.Windows.Forms.FolderBrowserDialog { Description = "Seleziona cartella output predefinita", ShowNewFolderButton = true };
if (!string.IsNullOrEmpty(DefaultOutputTextBox.Text)) dialog.SelectedPath = DefaultOutputTextBox.Text;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) DefaultOutputTextBox.Text = dialog.SelectedPath;
}
private void NamingPatternComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { }
@@ -156,13 +129,13 @@ namespace Ganimede.Windows
Settings.Default.DefaultExtractionMode = GetSelectedDefaultExtractionMode();
Settings.Default.SingleFrameUseSubfolder = GetSingleFrameUseSubfolder();
Settings.Default.Save();
Debug.WriteLine("[SETTINGS] Settings saved successfully");
Debug.WriteLine("[SETTINGS] Salvate");
DialogResult = true;
Close();
}
catch (Exception ex)
{
WpfMessageBox.Show($"Error saving settings: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
WpfMessageBox.Show($"Errore durante il salvataggio: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
Debug.WriteLine($"[ERROR] Failed to save settings: {ex.Message}");
}
}