Aggiunta gestione modelli di denominazione file video

- Modificato `MainWindow.xaml` per includere il binding del modello di denominazione.
- Introdotta l'enumerazione `NamingPattern` in `VideoJob.cs`.
- Aggiunti campi e proprietà per gestire il modello di denominazione e il prefisso personalizzato.
- Aggiornate le impostazioni predefinite per `FrameSize` e aggiunte nuove impostazioni per `DefaultNamingPattern` e `DefaultCustomPrefix`.
- Aggiornato `VideoProcessingService.cs` per utilizzare il modello di denominazione e il prefisso personalizzato.
- Modificato `JobConfigWindow.xaml` per aggiungere controlli per le impostazioni di denominazione.
- Aggiunti controlli per le impostazioni di denominazione predefinite in `SettingsWindow.xaml`.
- Creata la classe `NamingHelper` per la generazione dei nomi dei file.
This commit is contained in:
Alberto Balbo
2025-09-07 22:42:36 +02:00
parent 91695f350c
commit bf436d0926
10 changed files with 402 additions and 42 deletions

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using System.ComponentModel;
using System.Reflection;
using Ganimede.Models;
namespace Ganimede.Helpers
{
public static class NamingHelper
{
public static string GenerateFileName(NamingPattern pattern, VideoJob job, int frameIndex, TimeSpan frameTime, string customPrefix = "")
{
var videoName = Path.GetFileNameWithoutExtension(job.VideoPath);
var progressiveNumber = frameIndex + 1; // Start from 1 instead of 0
return pattern switch
{
NamingPattern.VideoNameProgressive =>
$"{videoName}_{progressiveNumber:D6}.png",
NamingPattern.FrameProgressive =>
$"frame_{progressiveNumber:D6}.png",
NamingPattern.VideoNameTimestamp =>
$"{videoName}_{(int)frameTime.TotalMilliseconds:D6}ms.png",
NamingPattern.CustomProgressive =>
$"{(string.IsNullOrEmpty(customPrefix) ? "custom" : customPrefix)}_{progressiveNumber:D6}.png",
NamingPattern.TimestampOnly =>
$"{(int)frameTime.Hours:D2}h{frameTime.Minutes:D2}m{frameTime.Seconds:D2}s{frameTime.Milliseconds:D3}ms.png",
NamingPattern.VideoNameFrameProgressive =>
$"{videoName}_frame_{progressiveNumber:D6}.png",
_ => $"frame_{progressiveNumber:D6}.png"
};
}
public static string GetPatternDescription(NamingPattern pattern)
{
var field = pattern.GetType().GetField(pattern.ToString());
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
return attribute?.Description ?? pattern.ToString();
}
public static string GetPatternExample(NamingPattern pattern, string videoName = "VID20250725", string customPrefix = "custom")
{
var job = new VideoJob
{
VideoPath = $"{videoName}.mp4",
OutputFolder = "",
CustomPrefix = customPrefix
};
var frameTime = new TimeSpan(0, 12, 34, 567); // 12:34.567
var example = GenerateFileName(pattern, job, 0, frameTime, customPrefix); // frameIndex 0 will become 1
return example;
}
}
}

View File

@@ -141,10 +141,11 @@
<!-- Job specific settings display --> <!-- Job specific settings display -->
<TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="3" FontSize="8" Foreground="#666" Margin="0,2,0,0"> <TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="3" FontSize="8" Foreground="#666" Margin="0,2,0,0">
<TextBlock.Text> <TextBlock.Text>
<MultiBinding StringFormat="{}📁 {0} | 📐 {1} | 🔄 {2}"> <MultiBinding StringFormat="{}📁 {0} | 📐 {1} | 🔄 {2} | 🏷 {3}">
<Binding Path="OutputFolderDisplay"/> <Binding Path="OutputFolderDisplay"/>
<Binding Path="FrameSizeDisplay"/> <Binding Path="FrameSizeDisplay"/>
<Binding Path="OverwriteModeDisplay"/> <Binding Path="OverwriteModeDisplay"/>
<Binding Path="NamingPatternDisplay"/>
</MultiBinding> </MultiBinding>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>

View File

@@ -19,6 +19,27 @@ namespace Ganimede.Models
Overwrite Overwrite
} }
public enum NamingPattern
{
[Description("VideoName + Progressive (VID20250725_000001)")]
VideoNameProgressive,
[Description("Frame + Progressive (frame_000001)")]
FrameProgressive,
[Description("VideoName + Timestamp (VID20250725_001234ms)")]
VideoNameTimestamp,
[Description("Custom Prefix + Progressive (custom_000001)")]
CustomProgressive,
[Description("Timestamp Only (00h12m34s567ms)")]
TimestampOnly,
[Description("VideoName + Frame + Progressive (VID20250725_frame_000001)")]
VideoNameFrameProgressive
}
public class VideoJob : INotifyPropertyChanged public class VideoJob : INotifyPropertyChanged
{ {
private JobStatus _status = JobStatus.Pending; private JobStatus _status = JobStatus.Pending;
@@ -28,6 +49,8 @@ namespace Ganimede.Models
private string _customFrameSize = string.Empty; private string _customFrameSize = string.Empty;
private OverwriteMode? _customOverwriteMode = null; private OverwriteMode? _customOverwriteMode = null;
private bool _customCreateSubfolder = true; private bool _customCreateSubfolder = true;
private NamingPattern? _customNamingPattern = null;
private string _customPrefix = string.Empty;
public required string VideoPath { get; set; } public required string VideoPath { get; set; }
public string VideoName => System.IO.Path.GetFileNameWithoutExtension(VideoPath); public string VideoName => System.IO.Path.GetFileNameWithoutExtension(VideoPath);
@@ -109,16 +132,43 @@ namespace Ganimede.Models
} }
} }
public NamingPattern? CustomNamingPattern
{
get => _customNamingPattern;
set
{
_customNamingPattern = value;
OnPropertyChanged(nameof(CustomNamingPattern));
OnPropertyChanged(nameof(NamingPatternDisplay));
}
}
public string CustomPrefix
{
get => _customPrefix;
set
{
_customPrefix = value;
OnPropertyChanged(nameof(CustomPrefix));
OnPropertyChanged(nameof(NamingPatternDisplay));
}
}
// Display properties for UI // Display properties for UI
public string OutputFolderDisplay => public string OutputFolderDisplay =>
string.IsNullOrEmpty(CustomOutputFolder) ? "Default" : System.IO.Path.GetFileName(CustomOutputFolder) + (CustomCreateSubfolder ? "/??" : ""); string.IsNullOrEmpty(CustomOutputFolder) ? "Default" : System.IO.Path.GetFileName(CustomOutputFolder) + (CustomCreateSubfolder ? "/??" : "");
public string FrameSizeDisplay => public string FrameSizeDisplay =>
string.IsNullOrEmpty(CustomFrameSize) ? "Default" : CustomFrameSize; string.IsNullOrEmpty(CustomFrameSize) ? "Default" :
(CustomFrameSize == "original" ? "Original" : CustomFrameSize);
public string OverwriteModeDisplay => public string OverwriteModeDisplay =>
CustomOverwriteMode?.ToString() ?? "Default"; CustomOverwriteMode?.ToString() ?? "Default";
public string NamingPatternDisplay =>
CustomNamingPattern?.ToString() ?? "Default" +
(!string.IsNullOrEmpty(CustomPrefix) ? $" ({CustomPrefix}_)" : "");
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) protected virtual void OnPropertyChanged(string propertyName)

View File

@@ -73,7 +73,7 @@ namespace Ganimede.Properties {
[global::System.Configuration.UserScopedSettingAttribute()] [global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("320,180")] [global::System.Configuration.DefaultSettingValueAttribute("original")]
public string FrameSize { public string FrameSize {
get { get {
return ((string)(this["FrameSize"])); return ((string)(this["FrameSize"]));
@@ -94,5 +94,29 @@ namespace Ganimede.Properties {
this["DefaultOverwriteMode"] = value; this["DefaultOverwriteMode"] = value;
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("VideoNameProgressive")]
public string DefaultNamingPattern {
get {
return ((string)(this["DefaultNamingPattern"]));
}
set {
this["DefaultNamingPattern"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("custom")]
public string DefaultCustomPrefix {
get {
return ((string)(this["DefaultCustomPrefix"]));
}
set {
this["DefaultCustomPrefix"] = value;
}
}
} }
} }

View File

@@ -15,10 +15,16 @@
<Value Profile="(Default)">True</Value> <Value Profile="(Default)">True</Value>
</Setting> </Setting>
<Setting Name="FrameSize" Type="System.String" Scope="User"> <Setting Name="FrameSize" Type="System.String" Scope="User">
<Value Profile="(Default)">320,180</Value> <Value Profile="(Default)">original</Value>
</Setting> </Setting>
<Setting Name="DefaultOverwriteMode" Type="System.String" Scope="User"> <Setting Name="DefaultOverwriteMode" Type="System.String" Scope="User">
<Value Profile="(Default)">Ask</Value> <Value Profile="(Default)">Ask</Value>
</Setting> </Setting>
<Setting Name="DefaultNamingPattern" Type="System.String" Scope="User">
<Value Profile="(Default)">VideoNameProgressive</Value>
</Setting>
<Setting Name="DefaultCustomPrefix" Type="System.String" Scope="User">
<Value Profile="(Default)">custom</Value>
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@@ -7,6 +7,7 @@ using FFMpegCore;
using System.IO; using System.IO;
using Ganimede.Models; using Ganimede.Models;
using Ganimede.Properties; using Ganimede.Properties;
using Ganimede.Helpers;
namespace Ganimede.Services namespace Ganimede.Services
{ {
@@ -144,9 +145,11 @@ namespace Ganimede.Services
var frameSize = GetFrameSize(job); var frameSize = GetFrameSize(job);
var overwriteMode = GetOverwriteMode(job); var overwriteMode = GetOverwriteMode(job);
var namingPattern = GetNamingPattern(job);
var customPrefix = GetCustomPrefix(job);
// Check for existing files if needed // Check for existing files if needed (using naming pattern)
var existingFiles = Directory.GetFiles(job.OutputFolder, "frame_*.png"); var existingFiles = Directory.GetFiles(job.OutputFolder, "*.png");
if (existingFiles.Length > 0 && overwriteMode == OverwriteMode.Ask) if (existingFiles.Length > 0 && overwriteMode == OverwriteMode.Ask)
{ {
var dialogResult = System.Windows.Application.Current.Dispatcher.Invoke(() => var dialogResult = System.Windows.Application.Current.Dispatcher.Invoke(() =>
@@ -176,7 +179,9 @@ namespace Ganimede.Services
return; return;
} }
string framePath = Path.Combine(job.OutputFolder, $"frame_{i:D6}.png"); var frameTime = TimeSpan.FromSeconds((double)i / frameRate);
var fileName = NamingHelper.GenerateFileName(namingPattern, job, i, frameTime, customPrefix);
string framePath = Path.Combine(job.OutputFolder, fileName);
// Check if file exists and handle according to overwrite mode // Check if file exists and handle according to overwrite mode
if (File.Exists(framePath) && overwriteMode == OverwriteMode.Skip) if (File.Exists(framePath) && overwriteMode == OverwriteMode.Skip)
@@ -228,14 +233,14 @@ namespace Ganimede.Services
// Use job-specific frame size if set, otherwise use default setting // Use job-specific frame size if set, otherwise use default setting
var frameSize = !string.IsNullOrEmpty(job.CustomFrameSize) ? job.CustomFrameSize : Settings.Default.FrameSize; var frameSize = !string.IsNullOrEmpty(job.CustomFrameSize) ? job.CustomFrameSize : Settings.Default.FrameSize;
if (string.IsNullOrEmpty(frameSize)) if (string.IsNullOrEmpty(frameSize) || frameSize == "original")
return (320, 180); return (-1, -1); // Special value indicating original size
var parts = frameSize.Split(','); var parts = frameSize.Split(',');
if (parts.Length == 2 && int.TryParse(parts[0], out int width) && int.TryParse(parts[1], out int height)) if (parts.Length == 2 && int.TryParse(parts[0], out int width) && int.TryParse(parts[1], out int height))
return (width, height); return (width, height);
return (320, 180); return (-1, -1); // Default to original size if parsing fails
} }
private OverwriteMode GetOverwriteMode(VideoJob job) private OverwriteMode GetOverwriteMode(VideoJob job)
@@ -251,27 +256,77 @@ namespace Ganimede.Services
return OverwriteMode.Ask; return OverwriteMode.Ask;
} }
private NamingPattern GetNamingPattern(VideoJob job)
{
// Use job-specific naming pattern if set, otherwise use default setting
if (job.CustomNamingPattern.HasValue)
return job.CustomNamingPattern.Value;
var defaultPattern = Settings.Default.DefaultNamingPattern;
if (Enum.TryParse<NamingPattern>(defaultPattern, out var pattern))
return pattern;
return NamingPattern.VideoNameProgressive;
}
private string GetCustomPrefix(VideoJob job)
{
// Use job-specific custom prefix if set, otherwise use default setting
if (!string.IsNullOrEmpty(job.CustomPrefix))
return job.CustomPrefix;
return Settings.Default.DefaultCustomPrefix ?? "custom";
}
private async Task ExtractFrameAsync(VideoJob job, int frameIndex, int frameRate, (int width, int height) frameSize, string framePath) private async Task ExtractFrameAsync(VideoJob job, int frameIndex, int frameRate, (int width, int height) frameSize, string framePath)
{ {
var frameTime = TimeSpan.FromSeconds((double)frameIndex / frameRate); var frameTime = TimeSpan.FromSeconds((double)frameIndex / frameRate);
try try
{ {
// Try with PNG codec first // Check if we should use original size
await FFMpegArguments if (frameSize.width == -1 && frameSize.height == -1)
.FromFileInput(job.VideoPath) {
.OutputToFile(framePath, true, options => options // Extract frame with original video size (no resize)
.Seek(frameTime) try
.WithFrameOutputCount(1) {
.WithVideoCodec("png") await FFMpegArguments
.Resize(frameSize.width, frameSize.height)) .FromFileInput(job.VideoPath)
.ProcessAsynchronously(); .OutputToFile(framePath, true, options => options
} .Seek(frameTime)
catch .WithFrameOutputCount(1)
{ .WithVideoCodec("png"))
// Fallback without codec specification .ProcessAsynchronously();
return;
}
catch
{
// Fallback without codec specification
await FFMpegArguments
.FromFileInput(job.VideoPath)
.OutputToFile(framePath, true, options => options
.Seek(frameTime)
.WithFrameOutputCount(1))
.ProcessAsynchronously();
return;
}
}
// Extract frame with specified resize
try try
{ {
await FFMpegArguments
.FromFileInput(job.VideoPath)
.OutputToFile(framePath, true, options => options
.Seek(frameTime)
.WithFrameOutputCount(1)
.WithVideoCodec("png")
.Resize(frameSize.width, frameSize.height))
.ProcessAsynchronously();
}
catch
{
// Fallback without codec specification
await FFMpegArguments await FFMpegArguments
.FromFileInput(job.VideoPath) .FromFileInput(job.VideoPath)
.OutputToFile(framePath, true, options => options .OutputToFile(framePath, true, options => options
@@ -280,11 +335,11 @@ namespace Ganimede.Services
.Resize(frameSize.width, frameSize.height)) .Resize(frameSize.width, frameSize.height))
.ProcessAsynchronously(); .ProcessAsynchronously();
} }
catch (Exception ex) }
{ catch (Exception ex)
Debug.WriteLine($"[ERROR] Failed to extract frame {frameIndex} from {job.VideoName}: {ex.Message}"); {
// Continue processing other frames even if this one fails Debug.WriteLine($"[ERROR] Failed to extract frame {frameIndex} from {job.VideoName}: {ex.Message}");
} // Continue processing other frames even if this one fails
} }
} }
} }

View File

@@ -52,10 +52,11 @@
<ComboBox x:Name="CustomFrameSizeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555" <ComboBox x:Name="CustomFrameSizeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
IsEnabled="{Binding IsChecked, ElementName=UseCustomFrameSizeCheckBox}"> IsEnabled="{Binding IsChecked, ElementName=UseCustomFrameSizeCheckBox}">
<ComboBoxItem Content="320x180 (Fast)" Tag="320,180"/> <ComboBoxItem Content="Original Size (Keep aspect ratio)" Tag="original" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="640x360 (Medium)" Tag="640,360"/> <ComboBoxItem Content="320x180 (Fast)" Tag="320,180" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1280x720 (High)" Tag="1280,720"/> <ComboBoxItem Content="640x360 (Medium)" Tag="640,360" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080"/> <ComboBoxItem Content="1280x720 (High)" Tag="1280,720" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080" Foreground="Black" Background="White"/>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
@@ -68,12 +69,44 @@
<ComboBox x:Name="CustomOverwriteComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555" <ComboBox x:Name="CustomOverwriteComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"
IsEnabled="{Binding IsChecked, ElementName=UseCustomOverwriteCheckBox}"> IsEnabled="{Binding IsChecked, ElementName=UseCustomOverwriteCheckBox}">
<ComboBoxItem Content="Ask each time" Tag="Ask"/> <ComboBoxItem Content="Ask each time" Tag="Ask" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Skip existing files" Tag="Skip"/> <ComboBoxItem Content="Skip existing files" Tag="Skip" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Overwrite existing files" Tag="Overwrite"/> <ComboBoxItem Content="Overwrite existing files" Tag="Overwrite" Foreground="Black" Background="White"/>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
</GroupBox> </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> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using Ganimede.Models; using Ganimede.Models;
using Ganimede.Helpers;
using WpfMessageBox = System.Windows.MessageBox; using WpfMessageBox = System.Windows.MessageBox;
namespace Ganimede.Windows namespace Ganimede.Windows
@@ -62,6 +63,21 @@ namespace Ganimede.Windows
} }
} }
} }
// Naming settings
if (firstJob.CustomNamingPattern.HasValue)
{
UseCustomNamingCheckBox.IsChecked = true;
foreach (ComboBoxItem item in CustomNamingComboBox.Items)
{
if (item.Tag?.ToString() == firstJob.CustomNamingPattern.Value.ToString())
{
CustomNamingComboBox.SelectedItem = item;
break;
}
}
CustomNamingPrefixTextBox.Text = firstJob.CustomPrefix ?? "custom";
}
} }
// Set default selections if nothing is selected // Set default selections if nothing is selected
@@ -70,6 +86,35 @@ namespace Ganimede.Windows
if (CustomOverwriteComboBox.SelectedItem == null) if (CustomOverwriteComboBox.SelectedItem == null)
CustomOverwriteComboBox.SelectedIndex = 0; CustomOverwriteComboBox.SelectedIndex = 0;
if (CustomNamingComboBox.SelectedItem == null)
CustomNamingComboBox.SelectedIndex = 0;
UpdateJobNamingPreview();
}
private void UpdateJobNamingPreview()
{
try
{
if (UseCustomNamingCheckBox.IsChecked == true &&
CustomNamingComboBox.SelectedItem is ComboBoxItem selectedItem &&
Enum.TryParse<NamingPattern>(selectedItem.Tag?.ToString(), out var pattern))
{
var firstVideoName = _selectedJobs.FirstOrDefault()?.VideoName ?? "Video1";
var customPrefix = string.IsNullOrWhiteSpace(CustomNamingPrefixTextBox.Text) ? "custom" : CustomNamingPrefixTextBox.Text;
var example = NamingHelper.GetPatternExample(pattern, firstVideoName, customPrefix);
JobNamingPreviewText.Text = example;
}
else
{
JobNamingPreviewText.Text = "Video1_000001.png (using default)";
}
}
catch
{
JobNamingPreviewText.Text = "Video1_000001.png";
}
} }
private void UseCustomOutputCheckBox_CheckedChanged(object sender, RoutedEventArgs e) private void UseCustomOutputCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
@@ -87,6 +132,21 @@ namespace Ganimede.Windows
// Enable/disable controls handled by binding // Enable/disable controls handled by binding
} }
private void UseCustomNamingCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
UpdateJobNamingPreview();
}
private void CustomNamingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateJobNamingPreview();
}
private void CustomNamingPrefixTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateJobNamingPreview();
}
private void BrowseCustomOutputButton_Click(object sender, RoutedEventArgs e) private void BrowseCustomOutputButton_Click(object sender, RoutedEventArgs e)
{ {
using var dialog = new System.Windows.Forms.FolderBrowserDialog using var dialog = new System.Windows.Forms.FolderBrowserDialog
@@ -149,6 +209,21 @@ namespace Ganimede.Windows
{ {
job.CustomOverwriteMode = null; job.CustomOverwriteMode = null;
} }
// Apply naming settings
if (UseCustomNamingCheckBox.IsChecked == true && CustomNamingComboBox.SelectedItem is ComboBoxItem namingItem)
{
if (Enum.TryParse<NamingPattern>(namingItem.Tag?.ToString(), out var namingPattern))
{
job.CustomNamingPattern = namingPattern;
job.CustomPrefix = string.IsNullOrWhiteSpace(CustomNamingPrefixTextBox.Text) ? "custom" : CustomNamingPrefixTextBox.Text;
}
}
else
{
job.CustomNamingPattern = null;
job.CustomPrefix = string.Empty;
}
} }
DialogResult = true; DialogResult = true;

View File

@@ -62,20 +62,55 @@
<StackPanel Margin="10"> <StackPanel Margin="10">
<TextBlock Text="Default Frame Size:" Foreground="#CCC" Margin="0,0,0,5"/> <TextBlock Text="Default Frame Size:" Foreground="#CCC" Margin="0,0,0,5"/>
<ComboBox x:Name="FrameSizeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"> <ComboBox x:Name="FrameSizeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555">
<ComboBoxItem Content="320x180 (Fast)" Tag="320,180" IsSelected="True"/> <ComboBoxItem Content="Original Size (Keep aspect ratio)" Tag="original" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="640x360 (Medium)" Tag="640,360"/> <ComboBoxItem Content="320x180 (Fast)" Tag="320,180" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1280x720 (High)" Tag="1280,720"/> <ComboBoxItem Content="640x360 (Medium)" Tag="640,360" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080"/> <ComboBoxItem Content="1280x720 (High)" Tag="1280,720" Foreground="Black" Background="White"/>
<ComboBoxItem Content="1920x1080 (Full HD)" Tag="1920,1080" Foreground="Black" Background="White"/>
</ComboBox> </ComboBox>
<TextBlock Text="Default File Overwrite Behavior:" Foreground="#CCC" Margin="0,15,0,5"/> <TextBlock Text="Default File Overwrite Behavior:" Foreground="#CCC" Margin="0,15,0,5"/>
<ComboBox x:Name="OverwriteModeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555"> <ComboBox x:Name="OverwriteModeComboBox" Height="30" Background="#333" Foreground="White" BorderBrush="#555">
<ComboBoxItem Content="Ask each time" Tag="Ask" IsSelected="True"/> <ComboBoxItem Content="Ask each time" Tag="Ask" IsSelected="True" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Skip existing files" Tag="Skip"/> <ComboBoxItem Content="Skip existing files" Tag="Skip" Foreground="Black" Background="White"/>
<ComboBoxItem Content="Overwrite existing files" Tag="Overwrite"/> <ComboBoxItem Content="Overwrite existing files" Tag="Overwrite" Foreground="Black" Background="White"/>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
</GroupBox> </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"
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"/>
</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"
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>
</StackPanel>
</GroupBox>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@@ -42,6 +42,10 @@ namespace Ganimede.Windows
} }
} }
// TODO: Load naming pattern settings when controls are generated
// var namingPattern = Settings.Default.DefaultNamingPattern;
// CustomPrefixTextBox.Text = Settings.Default.DefaultCustomPrefix;
UpdateFFmpegStatus(); UpdateFFmpegStatus();
} }
@@ -111,6 +115,17 @@ namespace Ganimede.Windows
} }
} }
// TODO: Implement naming pattern event handlers when controls are generated
private void NamingPatternComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
// UpdateNamingPreview();
}
private void CustomPrefixTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
// UpdateNamingPreview();
}
private void SaveButton_Click(object sender, RoutedEventArgs e) private void SaveButton_Click(object sender, RoutedEventArgs e)
{ {
try try
@@ -125,6 +140,10 @@ namespace Ganimede.Windows
var selectedOverwriteItem = OverwriteModeComboBox.SelectedItem as System.Windows.Controls.ComboBoxItem; var selectedOverwriteItem = OverwriteModeComboBox.SelectedItem as System.Windows.Controls.ComboBoxItem;
Settings.Default.DefaultOverwriteMode = selectedOverwriteItem?.Tag?.ToString() ?? "Ask"; Settings.Default.DefaultOverwriteMode = selectedOverwriteItem?.Tag?.ToString() ?? "Ask";
// TODO: Save naming settings when controls are available
// Settings.Default.DefaultNamingPattern = ...
// Settings.Default.DefaultCustomPrefix = ...
Settings.Default.Save(); Settings.Default.Save();