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 +}