diff --git a/Ganimede/App.config b/Ganimede/App.config
new file mode 100644
index 0000000..97b23e5
--- /dev/null
+++ b/Ganimede/App.config
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\alber\source\repos\Ganimede\Ganimede\FFMpeg
+
+
+
+
\ No newline at end of file
diff --git a/Ganimede/App.xaml.cs b/Ganimede/App.xaml.cs
index 84b63a8..77738f7 100644
--- a/Ganimede/App.xaml.cs
+++ b/Ganimede/App.xaml.cs
@@ -1,14 +1,11 @@
-using System.Configuration;
-using System.Data;
-using System.Windows;
+using System.Windows;
namespace Ganimede
{
///
/// Interaction logic for App.xaml
///
- public partial class App : Application
+ public partial class App : System.Windows.Application
{
}
-
}
diff --git a/Ganimede/Ganimede.csproj b/Ganimede/Ganimede.csproj
index e3e33e3..f2919d7 100644
--- a/Ganimede/Ganimede.csproj
+++ b/Ganimede/Ganimede.csproj
@@ -6,6 +6,30 @@
enable
enable
true
+ true
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
diff --git a/Ganimede/MainWindow.xaml b/Ganimede/MainWindow.xaml
index 5cb4ea1..02e701b 100644
--- a/Ganimede/MainWindow.xaml
+++ b/Ganimede/MainWindow.xaml
@@ -5,8 +5,44 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Ganimede"
mc:Ignorable="d"
- Title="MainWindow" Height="450" Width="800">
-
+ Title="Frame Extractor" Height="600" Width="900"
+ Background="#222">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ganimede/MainWindow.xaml.cs b/Ganimede/MainWindow.xaml.cs
index 3187273..760f14f 100644
--- a/Ganimede/MainWindow.xaml.cs
+++ b/Ganimede/MainWindow.xaml.cs
@@ -1,13 +1,14 @@
-using System.Text;
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
+using Microsoft.Win32;
+using FFMpegCore;
+using System.Diagnostics;
+using Ganimede.Properties;
namespace Ganimede
{
@@ -16,9 +17,173 @@ namespace Ganimede
///
public partial class MainWindow : Window
{
+ private string? videoPath;
+ private string? outputFolder;
+ private ObservableCollection thumbnails = new();
+
public MainWindow()
{
InitializeComponent();
+ ThumbnailsPanel.ItemsSource = thumbnails;
+ Debug.WriteLine("[INIT] MainWindow initialized.");
+
+ // Carica i percorsi salvati
+ outputFolder = Settings.Default.LastOutputFolder;
+ videoPath = Settings.Default.LastVideoPath;
+ if (!string.IsNullOrEmpty(outputFolder))
+ StatusText.Text = $"Last output folder: {outputFolder}";
+ if (!string.IsNullOrEmpty(videoPath))
+ StatusText.Text += $"\nLast video: {System.IO.Path.GetFileName(videoPath)}";
+
+ // Configura FFMpegCore con percorso binari
+ var ffmpegBin = Settings.Default.FFmpegBinFolder;
+ if (!string.IsNullOrEmpty(ffmpegBin) && Directory.Exists(ffmpegBin))
+ {
+ FFMpegCore.GlobalFFOptions.Configure(options => options.BinaryFolder = ffmpegBin);
+ Debug.WriteLine($"[CONFIG] FFMpeg bin folder set: {ffmpegBin}");
+ }
+ else
+ {
+ StatusText.Text += "\n[WARNING] ffmpeg/ffprobe path not set. Configure in settings.";
+ Debug.WriteLine("[WARNING] ffmpeg/ffprobe path not set or invalid.");
+ }
+ }
+
+ private void BrowseVideoButton_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("[UI] BrowseVideoButton_Click invoked.");
+ var dialog = new Microsoft.Win32.OpenFileDialog { Filter = "Video files (*.mp4;*.avi;*.mov)|*.mp4;*.avi;*.mov|All files (*.*)|*.*" };
+ if (dialog.ShowDialog() == true)
+ {
+ videoPath = dialog.FileName;
+ StatusText.Text = $"Selected video: {Path.GetFileName(videoPath)}";
+ Settings.Default.LastVideoPath = videoPath;
+ Settings.Default.Save();
+ Debug.WriteLine($"[INFO] Video selected: {videoPath}");
+ }
+ else
+ {
+ Debug.WriteLine("[INFO] Video selection cancelled.");
+ }
+ }
+
+ private void SelectOutputFolderButton_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("[UI] SelectOutputFolderButton_Click invoked.");
+ using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
+ {
+ if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ outputFolder = dialog.SelectedPath;
+ StatusText.Text = $"Selected output folder: {outputFolder}";
+ Settings.Default.LastOutputFolder = outputFolder;
+ Settings.Default.Save();
+ Debug.WriteLine($"[INFO] Output folder selected: {outputFolder}");
+ }
+ else
+ {
+ Debug.WriteLine("[INFO] Output folder selection cancelled.");
+ }
+ }
+ }
+
+ private async void ExtractFramesButton_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("[UI] ExtractFramesButton_Click invoked.");
+ if (string.IsNullOrEmpty(videoPath) || string.IsNullOrEmpty(outputFolder))
+ {
+ StatusText.Text = "Please select a video and output folder.";
+ Debug.WriteLine("[ERROR] Video path or output folder not set.");
+ return;
+ }
+ ExtractFramesButton.IsEnabled = false;
+ ProgressBar.Value = 0;
+ thumbnails.Clear();
+ StatusText.Text = "Analyzing video...";
+ Debug.WriteLine($"[PROCESS] Starting analysis for video: {videoPath}");
+ try
+ {
+ var mediaInfo = await FFProbe.AnalyseAsync(videoPath);
+ Debug.WriteLine($"[INFO] Video duration: {mediaInfo.Duration}, FrameRate: 24");
+ int frameRate = 24;
+ int frameCount = (int)mediaInfo.Duration.TotalSeconds * frameRate;
+ Debug.WriteLine($"[INFO] Total frames to extract: {frameCount}");
+ for (int i = 0; i < frameCount; i++)
+ {
+ var frameTime = TimeSpan.FromSeconds((double)i / frameRate);
+ string framePath = Path.Combine(outputFolder, $"frame_{i}.png");
+ Debug.WriteLine($"[PROCESS] Extracting frame {i + 1}/{frameCount} at {frameTime}");
+ await FFMpegArguments
+ .FromFileInput(videoPath)
+ .OutputToFile(framePath, false, options => options
+ .Seek(frameTime)
+ .WithFrameOutputCount(1)
+ .ForceFormat("png")
+ .Resize(320, 180))
+ .ProcessAsynchronously();
+ if (File.Exists(framePath))
+ {
+ var bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.UriSource = new Uri(framePath);
+ bitmap.CacheOption = BitmapCacheOption.OnLoad;
+ bitmap.EndInit();
+ thumbnails.Add(bitmap);
+ Debug.WriteLine($"[INFO] Frame saved and loaded: {framePath}");
+ }
+ else
+ {
+ Debug.WriteLine($"[ERROR] Frame file not found: {framePath}");
+ }
+ ProgressBar.Value = (i + 1) * 100 / frameCount;
+ StatusText.Text = $"Extracting frames {i + 1}/{frameCount} ({ProgressBar.Value}%) - Processing frame {i + 1}.";
+ }
+ StatusText.Text = "Extraction complete!";
+ Debug.WriteLine("[SUCCESS] Extraction complete.");
+ }
+ catch (Exception ex)
+ {
+ StatusText.Text = $"Error: {ex.Message}";
+ Debug.WriteLine($"[EXCEPTION] {ex.GetType()}: {ex.Message}\n{ex.StackTrace}");
+ }
+ ExtractFramesButton.IsEnabled = true;
+ }
+
+ private void DragDropArea_Drop(object sender, System.Windows.DragEventArgs e)
+ {
+ Debug.WriteLine("[UI] DragDropArea_Drop invoked.");
+ if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop))
+ {
+ var files = (string[])e.Data.GetData(System.Windows.DataFormats.FileDrop);
+ if (files.Length > 0)
+ {
+ videoPath = files[0];
+ StatusText.Text = $"Selected video: {Path.GetFileName(videoPath)}";
+ Settings.Default.LastVideoPath = videoPath;
+ Settings.Default.Save();
+ Debug.WriteLine($"[INFO] Video selected via drag & drop: {videoPath}");
+ }
+ else
+ {
+ Debug.WriteLine("[WARN] Drag & drop did not contain files.");
+ }
+ }
+ else
+ {
+ Debug.WriteLine("[WARN] Drag & drop did not contain file drop format.");
+ }
+ }
+
+ private void DragDropArea_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
+ {
+ DragDropArea.BorderBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(79, 195, 247));
+ Debug.WriteLine("[UI] DragDropArea_MouseEnter");
+ }
+
+ private void DragDropArea_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
+ {
+ DragDropArea.BorderBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(68, 68, 68));
+ Debug.WriteLine("[UI] DragDropArea_MouseLeave");
}
}
}
\ No newline at end of file
diff --git a/Ganimede/Models/.keep b/Ganimede/Models/.keep
new file mode 100644
index 0000000..24a1d50
--- /dev/null
+++ b/Ganimede/Models/.keep
@@ -0,0 +1,4 @@
+namespace Ganimede.Models
+{
+ // Modelli futuri (es. VideoInfo, FrameInfo)
+}
diff --git a/Ganimede/Properties/Settings.Designer.cs b/Ganimede/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..eaa7c6e
--- /dev/null
+++ b/Ganimede/Properties/Settings.Designer.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+//
+// Il codice è stato generato da uno strumento.
+// Versione runtime:4.0.30319.42000
+//
+// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se
+// il codice viene rigenerato.
+//
+//------------------------------------------------------------------------------
+
+namespace Ganimede.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string LastOutputFolder {
+ get {
+ return ((string)(this["LastOutputFolder"]));
+ }
+ set {
+ this["LastOutputFolder"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string LastVideoPath {
+ get {
+ return ((string)(this["LastVideoPath"]));
+ }
+ set {
+ this["LastVideoPath"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("C:\\Users\\alber\\source\\repos\\Ganimede\\Ganimede\\FFMpeg")]
+ public string FFmpegBinFolder {
+ get {
+ return ((string)(this["FFmpegBinFolder"]));
+ }
+ set {
+ this["FFmpegBinFolder"] = value;
+ }
+ }
+ }
+}
diff --git a/Ganimede/Properties/Settings.settings b/Ganimede/Properties/Settings.settings
new file mode 100644
index 0000000..9620fbb
--- /dev/null
+++ b/Ganimede/Properties/Settings.settings
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\alber\source\repos\Ganimede\Ganimede\FFMpeg
+
+
+
\ No newline at end of file
diff --git a/Ganimede/ViewModels/.keep b/Ganimede/ViewModels/.keep
new file mode 100644
index 0000000..5a7e5de
--- /dev/null
+++ b/Ganimede/ViewModels/.keep
@@ -0,0 +1,4 @@
+namespace Ganimede.ViewModels
+{
+ // ViewModel principale e futuri ViewModel
+}
diff --git a/Ganimede/Views/.keep b/Ganimede/Views/.keep
new file mode 100644
index 0000000..b7fc37d
--- /dev/null
+++ b/Ganimede/Views/.keep
@@ -0,0 +1,4 @@
+namespace Ganimede.Views
+{
+ // Views aggiuntive se necessario
+}