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,