diff --git a/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml b/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml index 229bc551..f9f38ba9 100644 --- a/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml @@ -24,7 +24,7 @@ Grid.Column="0" Margin="0,0,8,0" VerticalAlignment="Center" - Text="Frames" /> + Text="{x:Static lang:Resources.Label_Frames}" /> + Text="{x:Static lang:Resources.Label_Fps}" /> + Text="{x:Static lang:Resources.Label_MinCfg}" /> + Text="{x:Static lang:Resources.Label_MotionBucketId}" /> + Text="{x:Static lang:Resources.Label_AugmentationLevel}" /> + Text="{x:Static lang:Resources.Label_Fps}" /> + Text="{x:Static lang:Resources.Label_Lossless}" /> + Text="{x:Static lang:Resources.Label_VideoQuality}" /> + Text="{x:Static lang:Resources.Label_VideoOutputMethod}" /> + /// Looks up a localized string similar to Augmentation Level. + /// + public static string Label_AugmentationLevel { + get { + return ResourceManager.GetString("Label_AugmentationLevel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Auto Completion. /// @@ -1193,6 +1202,24 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Frames Per Second. + /// + public static string Label_Fps { + get { + return ResourceManager.GetString("Label_Fps", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Frames. + /// + public static string Label_Frames { + get { + return ResourceManager.GetString("Label_Frames", resourceCulture); + } + } + /// /// Looks up a localized string similar to General. /// @@ -1238,6 +1265,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Image to Video. + /// + public static string Label_ImageToVideo { + get { + return ResourceManager.GetString("Label_ImageToVideo", resourceCulture); + } + } + /// /// Looks up a localized string similar to Image Viewer. /// @@ -1436,6 +1472,24 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Lossless. + /// + public static string Label_Lossless { + get { + return ResourceManager.GetString("Label_Lossless", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Min CFG. + /// + public static string Label_MinCfg { + get { + return ResourceManager.GetString("Label_MinCfg", resourceCulture); + } + } + /// /// Looks up a localized string similar to Missing Image File. /// @@ -1490,6 +1544,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Motion Bucket ID. + /// + public static string Label_MotionBucketId { + get { + return ResourceManager.GetString("Label_MotionBucketId", resourceCulture); + } + } + /// /// Looks up a localized string similar to Networks (Lora / LyCORIS). /// @@ -2165,6 +2228,24 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Method. + /// + public static string Label_VideoOutputMethod { + get { + return ResourceManager.GetString("Label_VideoOutputMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Quality. + /// + public static string Label_VideoQuality { + get { + return ResourceManager.GetString("Label_VideoQuality", resourceCulture); + } + } + /// /// Looks up a localized string similar to Waiting to connect.... /// diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx index c0a18936..d9641154 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -900,4 +900,31 @@ CLIP Skip + + Image to Video + + + Frames Per Second + + + Min CFG + + + Lossless + + + Frames + + + Motion Bucket ID + + + Augmentation Level + + + Method + + + Quality + diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs index e3966c1f..a0959d5e 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs @@ -53,7 +53,8 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable private readonly ILiteDbContext liteDbContext; public override string Title => "Inference"; - public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.AppGeneric, IsFilled = true }; + public override IconSource IconSource => + new SymbolIconSource { Symbol = Symbol.AppGeneric, IsFilled = true }; public RefreshBadgeViewModel ConnectionBadge { get; } = new() @@ -110,6 +111,8 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable // "Send to Inference" EventManager.Instance.InferenceTextToImageRequested += OnInferenceTextToImageRequested; EventManager.Instance.InferenceUpscaleRequested += OnInferenceUpscaleRequested; + EventManager.Instance.InferenceImageToImageRequested += OnInferenceImageToImageRequested; + EventManager.Instance.InferenceImageToVideoRequested += OnInferenceImageToVideoRequested; MenuSaveAsCommand.WithConditionalNotificationErrorHandler(notificationService); MenuOpenProjectCommand.WithConditionalNotificationErrorHandler(notificationService); @@ -126,47 +129,43 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable IDisposable? onStartupComplete = null; - Dispatcher - .UIThread - .Post(() => + Dispatcher.UIThread.Post(() => + { + if (e.CurrentPackagePair?.BasePackage is ComfyUI package) { - if (e.CurrentPackagePair?.BasePackage is ComfyUI package) - { - IsWaitingForConnection = true; - onStartupComplete = Observable - .FromEventPattern(package, nameof(package.StartupComplete)) - .Take(1) - .Subscribe(_ => + IsWaitingForConnection = true; + onStartupComplete = Observable + .FromEventPattern(package, nameof(package.StartupComplete)) + .Take(1) + .Subscribe(_ => + { + Dispatcher.UIThread.Post(() => { - Dispatcher - .UIThread - .Post(() => - { - if (ConnectCommand.CanExecute(null)) - { - Logger.Trace("On package launch - starting connection"); - ConnectCommand.Execute(null); - } - IsWaitingForConnection = false; - }); + if (ConnectCommand.CanExecute(null)) + { + Logger.Trace("On package launch - starting connection"); + ConnectCommand.Execute(null); + } + IsWaitingForConnection = false; }); - } - else + }); + } + else + { + // Cancel any pending connection + if (ConnectCancelCommand.CanExecute(null)) { - // Cancel any pending connection - if (ConnectCancelCommand.CanExecute(null)) - { - ConnectCancelCommand.Execute(null); - } - onStartupComplete?.Dispose(); - onStartupComplete = null; - IsWaitingForConnection = false; - - // Disconnect - Logger.Trace("On package close - disconnecting"); - DisconnectCommand.Execute(null); + ConnectCancelCommand.Execute(null); } - }); + onStartupComplete?.Dispose(); + onStartupComplete = null; + IsWaitingForConnection = false; + + // Disconnect + Logger.Trace("On package close - disconnecting"); + DisconnectCommand.Execute(null); + } + }); } public override void OnLoaded() @@ -216,9 +215,14 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable ); // Set not open - await liteDbContext - .InferenceProjects - .UpdateAsync(project with { IsOpen = false, IsSelected = false, CurrentTabIndex = -1 }); + await liteDbContext.InferenceProjects.UpdateAsync( + project with + { + IsOpen = false, + IsSelected = false, + CurrentTabIndex = -1 + } + ); } } } @@ -249,6 +253,16 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable Dispatcher.UIThread.Post(() => AddUpscalerTabFromImage(e).SafeFireAndForget()); } + private void OnInferenceImageToImageRequested(object? sender, LocalImageFile e) + { + Dispatcher.UIThread.Post(() => AddImageToImageFromImage(e).SafeFireAndForget()); + } + + private void OnInferenceImageToVideoRequested(object? sender, LocalImageFile e) + { + Dispatcher.UIThread.Post(() => AddImageToVideoFromImage(e).SafeFireAndForget()); + } + /// /// Update the database with current tabs /// @@ -272,7 +286,11 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable entry.IsOpen = tab == SelectedTab; entry.CurrentTabIndex = i; - Logger.Trace("SyncTabStatesWithDatabase updated entry for tab '{Title}': {@Entry}", tab.TabTitle, entry); + Logger.Trace( + "SyncTabStatesWithDatabase updated entry for tab '{Title}': {@Entry}", + tab.TabTitle, + entry + ); await liteDbContext.InferenceProjects.UpsertAsync(entry); } } @@ -287,7 +305,9 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable return; } - var entry = await liteDbContext.InferenceProjects.FindOneAsync(p => p.FilePath == projectFile.ToString()); + var entry = await liteDbContext.InferenceProjects.FindOneAsync( + p => p.FilePath == projectFile.ToString() + ); // Create if not found entry ??= new InferenceProjectEntry { Id = Guid.NewGuid(), FilePath = projectFile.ToString() }; @@ -295,7 +315,11 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable entry.IsOpen = tab == SelectedTab; entry.CurrentTabIndex = Tabs.IndexOf(tab); - Logger.Trace("SyncTabStatesWithDatabase updated entry for tab '{Title}': {@Entry}", tab.TabTitle, entry); + Logger.Trace( + "SyncTabStatesWithDatabase updated entry for tab '{Title}': {@Entry}", + tab.TabTitle, + entry + ); await liteDbContext.InferenceProjects.UpsertAsync(entry); } @@ -408,7 +432,10 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable return; } - await notificationService.TryAsync(ClientManager.CloseAsync(), "Could not disconnect from ComfyUI backend"); + await notificationService.TryAsync( + ClientManager.CloseAsync(), + "Could not disconnect from ComfyUI backend" + ); } /// @@ -464,7 +491,11 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable await using var stream = await result.OpenWriteAsync(); stream.SetLength(0); // Overwrite fully - await JsonSerializer.SerializeAsync(stream, document, new JsonSerializerOptions { WriteIndented = true }); + await JsonSerializer.SerializeAsync( + stream, + document, + new JsonSerializerOptions { WriteIndented = true } + ); } catch (Exception e) { @@ -512,7 +543,11 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable await using var stream = projectFile.Info.OpenWrite(); stream.SetLength(0); // Overwrite fully - await JsonSerializer.SerializeAsync(stream, document, new JsonSerializerOptions { WriteIndented = true }); + await JsonSerializer.SerializeAsync( + stream, + document, + new JsonSerializerOptions { WriteIndented = true } + ); } catch (Exception e) { @@ -624,6 +659,38 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable await SyncTabStatesWithDatabase(); } + private async Task AddImageToImageFromImage(LocalImageFile imageFile) + { + var imgToImgVm = vmFactory.Get(); + imgToImgVm.SelectImageCardViewModel.ImageSource = new ImageSource(imageFile.AbsolutePath); + + if (imageFile.GenerationParameters != null) + { + imgToImgVm.LoadStateFromParameters(imageFile.GenerationParameters); + } + + Tabs.Add(imgToImgVm); + SelectedTab = imgToImgVm; + + await SyncTabStatesWithDatabase(); + } + + private async Task AddImageToVideoFromImage(LocalImageFile imageFile) + { + var imgToVidVm = vmFactory.Get(); + imgToVidVm.SelectImageCardViewModel.ImageSource = new ImageSource(imageFile.AbsolutePath); + + if (imageFile.GenerationParameters != null) + { + imgToVidVm.LoadStateFromParameters(imageFile.GenerationParameters); + } + + Tabs.Add(imgToVidVm); + SelectedTab = imgToVidVm; + + await SyncTabStatesWithDatabase(); + } + /// /// Menu "Open Project" command. /// diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs index 99bfca1b..f1186751 100644 --- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs @@ -315,6 +315,18 @@ public partial class OutputsPageViewModel : PageViewModelBase EventManager.Instance.OnInferenceUpscaleRequested(vm.ImageFile); } + public void SendToImageToImage(OutputImageViewModel vm) + { + navigationService.NavigateTo(); + EventManager.Instance.OnInferenceImageToImageRequested(vm.ImageFile); + } + + public void SendToImageToVideo(OutputImageViewModel vm) + { + navigationService.NavigateTo(); + EventManager.Instance.OnInferenceImageToVideoRequested(vm.ImageFile); + } + public void ClearSelection() { foreach (var output in Outputs) diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml index b9f29cfc..22fdbc37 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml @@ -219,19 +219,11 @@ IconSource="FullScreenMaximize" Text="{x:Static lang:Resources.Label_TextToImage}" /> - + diff --git a/StabilityMatrix.Core/Helper/EventManager.cs b/StabilityMatrix.Core/Helper/EventManager.cs index 72a2b3c3..22c09cc2 100644 --- a/StabilityMatrix.Core/Helper/EventManager.cs +++ b/StabilityMatrix.Core/Helper/EventManager.cs @@ -37,15 +37,14 @@ public class EventManager public event EventHandler? ImageFileAdded; public event EventHandler? InferenceTextToImageRequested; public event EventHandler? InferenceUpscaleRequested; + public event EventHandler? InferenceImageToImageRequested; + public event EventHandler? InferenceImageToVideoRequested; - public void OnGlobalProgressChanged(int progress) => - GlobalProgressChanged?.Invoke(this, progress); + public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress); - public void OnInstalledPackagesChanged() => - InstalledPackagesChanged?.Invoke(this, EventArgs.Empty); + public void OnInstalledPackagesChanged() => InstalledPackagesChanged?.Invoke(this, EventArgs.Empty); - public void OnOneClickInstallFinished(bool skipped) => - OneClickInstallFinished?.Invoke(this, skipped); + public void OnOneClickInstallFinished(bool skipped) => OneClickInstallFinished?.Invoke(this, skipped); public void OnTeachingTooltipNeeded() => TeachingTooltipNeeded?.Invoke(this, EventArgs.Empty); @@ -53,11 +52,9 @@ public class EventManager public void OnUpdateAvailable(UpdateInfo args) => UpdateAvailable?.Invoke(this, args); - public void OnPackageLaunchRequested(Guid packageId) => - PackageLaunchRequested?.Invoke(this, packageId); + public void OnPackageLaunchRequested(Guid packageId) => PackageLaunchRequested?.Invoke(this, packageId); - public void OnScrollToBottomRequested() => - ScrollToBottomRequested?.Invoke(this, EventArgs.Empty); + public void OnScrollToBottomRequested() => ScrollToBottomRequested?.Invoke(this, EventArgs.Empty); public void OnProgressChanged(ProgressItem progress) => ProgressChanged?.Invoke(this, progress); @@ -83,4 +80,10 @@ public class EventManager public void OnInferenceUpscaleRequested(LocalImageFile imageFile) => InferenceUpscaleRequested?.Invoke(this, imageFile); + + public void OnInferenceImageToImageRequested(LocalImageFile imageFile) => + InferenceImageToImageRequested?.Invoke(this, imageFile); + + public void OnInferenceImageToVideoRequested(LocalImageFile imageFile) => + InferenceImageToVideoRequested?.Invoke(this, imageFile); } diff --git a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs index 76b3ff78..54153635 100644 --- a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs +++ b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs @@ -83,7 +83,7 @@ public record LocalImageFile // TODO: Support other types const LocalImageFileType imageType = LocalImageFileType.Inference | LocalImageFileType.TextToImage; - if (filePath.Extension.Contains("webp")) + if (filePath.Extension.Equals(".webp", StringComparison.OrdinalIgnoreCase)) { var paramsJson = ImageMetadata.ReadTextChunkFromWebp( filePath,