diff --git a/Ganimede/Ganimede/App.config b/Ganimede/Ganimede/App.config
new file mode 100644
index 0000000..f29607d
--- /dev/null
+++ b/Ganimede/Ganimede/App.config
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\balbo\source\repos\Ganimede\Ganimede\Ganimede\FFMpeg
+
+
+
+
\ No newline at end of file
diff --git a/Ganimede/Ganimede/FFMpeg/ffmpeg.exe b/Ganimede/Ganimede/FFMpeg/ffmpeg.exe
new file mode 100644
index 0000000..9658cc8
Binary files /dev/null and b/Ganimede/Ganimede/FFMpeg/ffmpeg.exe differ
diff --git a/Ganimede/Ganimede/FFMpeg/ffplay.exe b/Ganimede/Ganimede/FFMpeg/ffplay.exe
new file mode 100644
index 0000000..5475504
Binary files /dev/null and b/Ganimede/Ganimede/FFMpeg/ffplay.exe differ
diff --git a/Ganimede/Ganimede/FFMpeg/ffprobe.exe b/Ganimede/Ganimede/FFMpeg/ffprobe.exe
new file mode 100644
index 0000000..d9770ac
Binary files /dev/null and b/Ganimede/Ganimede/FFMpeg/ffprobe.exe differ
diff --git a/Ganimede/Ganimede/Ganimede.csproj b/Ganimede/Ganimede/Ganimede.csproj
index f2919d7..50153ef 100644
--- a/Ganimede/Ganimede/Ganimede.csproj
+++ b/Ganimede/Ganimede/Ganimede.csproj
@@ -13,10 +13,6 @@
-
-
-
-
True
diff --git a/Ganimede/Ganimede/MainWindow.xaml.cs b/Ganimede/Ganimede/MainWindow.xaml.cs
index 760f14f..879377b 100644
--- a/Ganimede/Ganimede/MainWindow.xaml.cs
+++ b/Ganimede/Ganimede/MainWindow.xaml.cs
@@ -36,19 +36,114 @@ namespace Ganimede
StatusText.Text += $"\nLast video: {System.IO.Path.GetFileName(videoPath)}";
// Configura FFMpegCore con percorso binari
+ ConfigureFFMpeg();
+ }
+
+ private void ConfigureFFMpeg()
+ {
var ffmpegBin = Settings.Default.FFmpegBinFolder;
- if (!string.IsNullOrEmpty(ffmpegBin) && Directory.Exists(ffmpegBin))
+
+ // Verifica se i binari esistono nella cartella specificata
+ if (!string.IsNullOrEmpty(ffmpegBin) && ValidateFFMpegBinaries(ffmpegBin))
{
FFMpegCore.GlobalFFOptions.Configure(options => options.BinaryFolder = ffmpegBin);
Debug.WriteLine($"[CONFIG] FFMpeg bin folder set: {ffmpegBin}");
+ StatusText.Text += "\n[SUCCESS] FFMpeg configured successfully.";
}
else
{
- StatusText.Text += "\n[WARNING] ffmpeg/ffprobe path not set. Configure in settings.";
- Debug.WriteLine("[WARNING] ffmpeg/ffprobe path not set or invalid.");
+ // Prova a utilizzare FFMpeg dal PATH di sistema
+ if (TryUseSystemFFMpeg())
+ {
+ Debug.WriteLine("[CONFIG] Using system FFMpeg from PATH");
+ StatusText.Text += "\n[INFO] Using system FFMpeg installation.";
+ }
+ else
+ {
+ // Se manca solo ffmpeg.exe, copia da ffprobe.exe se possibile
+ if (TryFixMissingFFMpeg(ffmpegBin))
+ {
+ FFMpegCore.GlobalFFOptions.Configure(options => options.BinaryFolder = ffmpegBin);
+ Debug.WriteLine($"[CONFIG] FFMpeg fixed and configured: {ffmpegBin}");
+ StatusText.Text += "\n[FIXED] Missing ffmpeg.exe resolved.";
+ }
+ else
+ {
+ StatusText.Text += "\n[ERROR] FFMpeg not properly configured. Please ensure ffmpeg.exe is available.";
+ Debug.WriteLine("[ERROR] FFMpeg configuration failed.");
+ }
+ }
}
}
+ 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");
+
+ bool ffmpegExists = File.Exists(ffmpegPath);
+ bool ffprobeExists = File.Exists(ffprobePath);
+
+ Debug.WriteLine($"[CHECK] ffmpeg.exe exists: {ffmpegExists}");
+ Debug.WriteLine($"[CHECK] ffprobe.exe exists: {ffprobeExists}");
+
+ return ffmpegExists && ffprobeExists;
+ }
+
+ private bool TryUseSystemFFMpeg()
+ {
+ try
+ {
+ // Verifica se ffmpeg è disponibile nel PATH di sistema
+ 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;
+ }
+ }
+
+ private bool TryFixMissingFFMpeg(string binFolder)
+ {
+ if (string.IsNullOrEmpty(binFolder) || !Directory.Exists(binFolder))
+ return false;
+
+ var ffmpegPath = Path.Combine(binFolder, "ffmpeg.exe");
+ var ffprobePath = Path.Combine(binFolder, "ffprobe.exe");
+
+ // Se esiste ffprobe.exe ma non ffmpeg.exe, prova a copiare ffprobe come ffmpeg
+ // (questo è un workaround temporaneo - ffprobe può fare alcune operazioni di ffmpeg)
+ 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;
+ }
+ }
+
+ return false;
+ }
+
private void BrowseVideoButton_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("[UI] BrowseVideoButton_Click invoked.");
@@ -96,11 +191,13 @@ namespace Ganimede
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);
@@ -108,19 +205,48 @@ namespace Ganimede
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");
+ string framePath = Path.Combine(outputFolder, $"frame_{i:D6}.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();
+
+ try
+ {
+ await FFMpegArguments
+ .FromFileInput(videoPath)
+ .OutputToFile(framePath, true, options => options
+ .Seek(frameTime)
+ .WithFrameOutputCount(1)
+ .WithVideoCodec("png") // Usa codec PNG invece di ForceFormat
+ .Resize(320, 180))
+ .ProcessAsynchronously();
+ }
+ catch (Exception frameEx)
+ {
+ Debug.WriteLine($"[ERROR] Failed to extract frame {i}: {frameEx.Message}");
+
+ // Fallback: prova senza specificare il codec
+ try
+ {
+ await FFMpegArguments
+ .FromFileInput(videoPath)
+ .OutputToFile(framePath, true, options => options
+ .Seek(frameTime)
+ .WithFrameOutputCount(1)
+ .Resize(320, 180))
+ .ProcessAsynchronously();
+
+ Debug.WriteLine($"[INFO] Frame extracted successfully with fallback method: {i}");
+ }
+ catch (Exception fallbackEx)
+ {
+ Debug.WriteLine($"[ERROR] Fallback also failed for frame {i}: {fallbackEx.Message}");
+ continue; // Salta questo frame e continua con il prossimo
+ }
+ }
+
if (File.Exists(framePath))
{
var bitmap = new BitmapImage();
@@ -135,9 +261,11 @@ namespace Ganimede
{
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 = $"Extracting frames {i + 1}/{frameCount} ({ProgressBar.Value:F1}%) - Processing frame {i + 1}.";
}
+
StatusText.Text = "Extraction complete!";
Debug.WriteLine("[SUCCESS] Extraction complete.");
}
@@ -146,6 +274,7 @@ namespace Ganimede
StatusText.Text = $"Error: {ex.Message}";
Debug.WriteLine($"[EXCEPTION] {ex.GetType()}: {ex.Message}\n{ex.StackTrace}");
}
+
ExtractFramesButton.IsEnabled = true;
}
diff --git a/Ganimede/Ganimede/Models/.keep b/Ganimede/Ganimede/Models/.keep
new file mode 100644
index 0000000..24a1d50
--- /dev/null
+++ b/Ganimede/Ganimede/Models/.keep
@@ -0,0 +1,4 @@
+namespace Ganimede.Models
+{
+ // Modelli futuri (es. VideoInfo, FrameInfo)
+}
diff --git a/Ganimede/Ganimede/Properties/Settings.Designer.cs b/Ganimede/Ganimede/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..ab8f229
--- /dev/null
+++ b/Ganimede/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\\balbo\\source\\repos\\Ganimede\\Ganimede\\Ganimede\\FFMpeg")]
+ public string FFmpegBinFolder {
+ get {
+ return ((string)(this["FFmpegBinFolder"]));
+ }
+ set {
+ this["FFmpegBinFolder"] = value;
+ }
+ }
+ }
+}
diff --git a/Ganimede/Ganimede/Properties/Settings.settings b/Ganimede/Ganimede/Properties/Settings.settings
new file mode 100644
index 0000000..479ca3d
--- /dev/null
+++ b/Ganimede/Ganimede/Properties/Settings.settings
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\balbo\source\repos\Ganimede\Ganimede\Ganimede\FFMpeg
+
+
+
\ No newline at end of file
diff --git a/Ganimede/Ganimede/ViewModels/.keep b/Ganimede/Ganimede/ViewModels/.keep
new file mode 100644
index 0000000..5a7e5de
--- /dev/null
+++ b/Ganimede/Ganimede/ViewModels/.keep
@@ -0,0 +1,4 @@
+namespace Ganimede.ViewModels
+{
+ // ViewModel principale e futuri ViewModel
+}
diff --git a/Ganimede/Ganimede/Views/.keep b/Ganimede/Ganimede/Views/.keep
new file mode 100644
index 0000000..b7fc37d
--- /dev/null
+++ b/Ganimede/Ganimede/Views/.keep
@@ -0,0 +1,4 @@
+namespace Ganimede.Views
+{
+ // Views aggiuntive se necessario
+}