From b4fd64e7fc9592b60ba9dd2c6fa3887a1986080e Mon Sep 17 00:00:00 2001
From: jt
Date: Sat, 18 Nov 2023 00:14:22 -0800
Subject: [PATCH 001/276] macos fixes & remove mac label for volta
---
.../StabilityMatrix.Avalonia.Diagnostics.csproj | 1 +
StabilityMatrix.Avalonia/App.axaml | 1 +
StabilityMatrix.Avalonia/Helpers/UriHandler.cs | 2 +-
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj | 9 +++++++++
StabilityMatrix.Core/Models/Packages/BasePackage.cs | 5 +++++
StabilityMatrix.Core/Models/Packages/VoltaML.cs | 2 +-
StabilityMatrix.Core/Processes/ProcessRunner.cs | 2 +-
StabilityMatrix.Core/StabilityMatrix.Core.csproj | 1 +
StabilityMatrix.Tests/StabilityMatrix.Tests.csproj | 1 +
StabilityMatrix.UITests/StabilityMatrix.UITests.csproj | 1 +
10 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
index eb84e3f36..84e8c3f97 100644
--- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
+++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
@@ -21,6 +21,7 @@
+
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index c2821641a..2e08109c8 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -4,6 +4,7 @@
xmlns:local="using:StabilityMatrix.Avalonia"
xmlns:idcr="using:Dock.Avalonia.Controls.Recycling"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
+ Name="Stability Matrix"
RequestedThemeVariant="Dark">
diff --git a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
index e500a1e49..a6f04b3db 100644
--- a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
+++ b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
@@ -64,7 +64,7 @@ public void RegisterUriScheme()
{
RegisterUriSchemeWin();
}
- else
+ else if (Compat.IsLinux)
{
RegisterUriSchemeUnix();
}
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
index fb16d35f8..d3111ecd6 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
@@ -1,5 +1,6 @@
+ Stability Matrix
WinExe
net8.0
win-x64;linux-x64;osx-x64;osx-arm64
@@ -14,6 +15,13 @@
true
+
+
+ StabilityMatrix.URL
+ stabilitymatrix;stabilitymatrix://
+
+
+
@@ -37,6 +45,7 @@
+
diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs
index 29ff06b98..f704b0f07 100644
--- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs
@@ -133,6 +133,11 @@ public virtual TorchVersion GetRecommendedTorchVersion()
return TorchVersion.DirectMl;
}
+ if (Compat.IsMacOS && Compat.IsArm && AvailableTorchVersions.Contains(TorchVersion.Mps))
+ {
+ return TorchVersion.Mps;
+ }
+
return TorchVersion.Cpu;
}
diff --git a/StabilityMatrix.Core/Models/Packages/VoltaML.cs b/StabilityMatrix.Core/Models/Packages/VoltaML.cs
index eeef1ce3a..933e4fb30 100644
--- a/StabilityMatrix.Core/Models/Packages/VoltaML.cs
+++ b/StabilityMatrix.Core/Models/Packages/VoltaML.cs
@@ -62,7 +62,7 @@ IPrerequisiteHelper prerequisiteHelper
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override IEnumerable AvailableTorchVersions =>
- new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Mps };
+ new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl };
public override IEnumerable AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };
diff --git a/StabilityMatrix.Core/Processes/ProcessRunner.cs b/StabilityMatrix.Core/Processes/ProcessRunner.cs
index 3a20cc60b..8adde5ebf 100644
--- a/StabilityMatrix.Core/Processes/ProcessRunner.cs
+++ b/StabilityMatrix.Core/Processes/ProcessRunner.cs
@@ -92,7 +92,7 @@ public static async Task OpenFileBrowser(string filePath)
else if (Compat.IsMacOS)
{
using var process = new Process();
- process.StartInfo.FileName = "explorer";
+ process.StartInfo.FileName = "open";
process.StartInfo.Arguments = $"-R {Quote(filePath)}";
process.Start();
await process.WaitForExitAsync().ConfigureAwait(false);
diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
index 254f8efe3..f62ba22af 100644
--- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj
+++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
@@ -23,6 +23,7 @@
+
diff --git a/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj b/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
index 874234751..a0acdc339 100644
--- a/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
+++ b/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj b/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj
index 8062d3bd4..961598f07 100644
--- a/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj
+++ b/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj
@@ -11,6 +11,7 @@
+
From e389085ec11104f51ac5b10c4e7edab3dc3f9f42 Mon Sep 17 00:00:00 2001
From: JT
Date: Tue, 28 Nov 2023 23:03:51 -0800
Subject: [PATCH 002/276] Added image to video in inference (mostly)
---
StabilityMatrix.Avalonia/App.axaml | 3 +-
.../Controls/ModelCard.axaml | 2 +
.../VideoGenerationSettingsCard.axaml | 122 +++++++++
.../VideoGenerationSettingsCard.axaml.cs | 7 +
.../Controls/VideoOutputSettingsCard.axaml | 86 +++++++
.../Controls/VideoOutputSettingsCard.axaml.cs | 7 +
.../DesignData/DesignData.cs | 14 +
.../Models/Inference/VideoOutputMethod.cs | 8 +
.../Models/InferenceProjectDocument.cs | 1 +
.../Models/InferenceProjectType.cs | 4 +-
.../StabilityMatrix.Avalonia.csproj | 12 +
.../Base/InferenceGenerationViewModelBase.cs | 37 ++-
.../InferenceImageToVideoViewModel.cs | 243 ++++++++++++++++++
.../Inference/ModelCardViewModel.cs | 5 +-
.../Video/ImgToVidModelCardViewModel.cs | 37 +++
.../Video/SvdImgToVidConditioningViewModel.cs | 90 +++++++
.../Video/VideoOutputSettingsCardViewModel.cs | 80 ++++++
.../Inference/InferenceImageToVideoView.axaml | 221 ++++++++++++++++
.../InferenceImageToVideoView.axaml.cs | 13 +
.../Views/InferencePage.axaml | 10 +
.../Views/InferencePage.axaml.cs | 7 +
.../Api/Comfy/NodeTypes/NodeConnections.cs | 2 +
.../Api/Comfy/Nodes/ComfyNodeBuilder.cs | 41 +++
23 files changed, 1038 insertions(+), 14 deletions(-)
create mode 100644 StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml
create mode 100644 StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml.cs
create mode 100644 StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml
create mode 100644 StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml.cs
create mode 100644 StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs
create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
create mode 100644 StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
create mode 100644 StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml.cs
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index 93486dab1..4e02cb075 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -74,7 +74,8 @@
-
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/ModelCard.axaml b/StabilityMatrix.Avalonia/Controls/ModelCard.axaml
index e0fc6a1c0..ce990ac96 100644
--- a/StabilityMatrix.Avalonia/Controls/ModelCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/ModelCard.axaml
@@ -12,6 +12,7 @@
+
@@ -135,6 +136,7 @@
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index d27b15009..3515089c5 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -418,19 +418,34 @@ ImageGenerationEventArgs args
);
}
- var bytesWithMetadata = PngDataHelper.AddMetadata(imageArray, parameters, project);
-
- // Write using generated name
- var filePath = await WriteOutputImageAsync(
- new MemoryStream(bytesWithMetadata),
- args,
- i + 1,
- images.Count
- );
+ if (comfyImage.FileName.EndsWith(".png"))
+ {
+ var bytesWithMetadata = PngDataHelper.AddMetadata(imageArray, parameters, project);
+
+ // Write using generated name
+ var filePath = await WriteOutputImageAsync(
+ new MemoryStream(bytesWithMetadata),
+ args,
+ i + 1,
+ images.Count
+ );
- outputImages.Add(new ImageSource(filePath));
+ outputImages.Add(new ImageSource(filePath));
+ EventManager.Instance.OnImageFileAdded(filePath);
+ }
+ else
+ {
+ // Write using generated name
+ var filePath = await WriteOutputImageAsync(
+ new MemoryStream(imageArray),
+ args,
+ i + 1,
+ images.Count
+ );
- EventManager.Instance.OnImageFileAdded(filePath);
+ outputImages.Add(new ImageSource(filePath));
+ EventManager.Instance.OnImageFileAdded(filePath);
+ }
}
// Download all images to make grid, if multiple
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
new file mode 100644
index 000000000..3a6c5f5d5
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Drawing;
+using System.Linq;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using NLog;
+using StabilityMatrix.Avalonia.Extensions;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
+using StabilityMatrix.Avalonia.ViewModels.Inference.Video;
+using StabilityMatrix.Avalonia.Views.Inference;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Services;
+
+#pragma warning disable CS0657 // Not a valid attribute location for this declaration
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference;
+
+[View(typeof(InferenceImageToVideoView), persistent: true)]
+[ManagedService]
+[Transient]
+public class InferenceImageToVideoViewModel
+ : InferenceGenerationViewModelBase,
+ IParametersLoadableState
+{
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ private readonly INotificationService notificationService;
+ private readonly IModelIndexService modelIndexService;
+
+ [JsonIgnore]
+ public StackCardViewModel StackCardViewModel { get; }
+
+ [JsonPropertyName("Model")]
+ public ImgToVidModelCardViewModel ModelCardViewModel { get; }
+
+ [JsonPropertyName("Sampler")]
+ public SamplerCardViewModel SamplerCardViewModel { get; }
+
+ [JsonPropertyName("BatchSize")]
+ public BatchSizeCardViewModel BatchSizeCardViewModel { get; }
+
+ [JsonPropertyName("Seed")]
+ public SeedCardViewModel SeedCardViewModel { get; }
+
+ [JsonPropertyName("ImageLoader")]
+ public SelectImageCardViewModel SelectImageCardViewModel { get; }
+
+ [JsonPropertyName("Conditioning")]
+ public SvdImgToVidConditioningViewModel SvdImgToVidConditioningViewModel { get; }
+
+ [JsonPropertyName("VideoOutput")]
+ public VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel { get; }
+
+ public InferenceImageToVideoViewModel(
+ INotificationService notificationService,
+ IInferenceClientManager inferenceClientManager,
+ ISettingsManager settingsManager,
+ ServiceManager vmFactory,
+ IModelIndexService modelIndexService
+ )
+ : base(vmFactory, inferenceClientManager, notificationService, settingsManager)
+ {
+ this.notificationService = notificationService;
+ this.modelIndexService = modelIndexService;
+
+ // Get sub view models from service manager
+
+ SeedCardViewModel = vmFactory.Get();
+ SeedCardViewModel.GenerateNewSeed();
+
+ ModelCardViewModel = vmFactory.Get();
+
+ SamplerCardViewModel = vmFactory.Get(samplerCard =>
+ {
+ samplerCard.IsDimensionsEnabled = true;
+ samplerCard.IsCfgScaleEnabled = true;
+ samplerCard.IsSamplerSelectionEnabled = true;
+ samplerCard.IsSchedulerSelectionEnabled = true;
+ });
+
+ BatchSizeCardViewModel = vmFactory.Get();
+
+ SelectImageCardViewModel = vmFactory.Get();
+ SvdImgToVidConditioningViewModel = vmFactory.Get();
+ VideoOutputSettingsCardViewModel = vmFactory.Get();
+
+ StackCardViewModel = vmFactory.Get();
+ StackCardViewModel.AddCards(
+ ModelCardViewModel,
+ SvdImgToVidConditioningViewModel,
+ SamplerCardViewModel,
+ SeedCardViewModel,
+ VideoOutputSettingsCardViewModel,
+ BatchSizeCardViewModel
+ );
+ }
+
+ ///
+ protected override void BuildPrompt(BuildPromptEventArgs args)
+ {
+ base.BuildPrompt(args);
+
+ var builder = args.Builder;
+
+ builder.Connections.Seed = args.SeedOverride switch
+ {
+ { } seed => Convert.ToUInt64(seed),
+ _ => Convert.ToUInt64(SeedCardViewModel.Seed)
+ };
+
+ // Load models
+ ModelCardViewModel.ApplyStep(args);
+
+ // Setup latent from image
+ var imageLoad = builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LoadImage
+ {
+ Name = builder.Nodes.GetUniqueName("ControlNet_LoadImage"),
+ Image =
+ SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException()
+ }
+ );
+ builder.Connections.Primary = imageLoad.Output1;
+ builder.Connections.PrimarySize =
+ SelectImageCardViewModel.CurrentBitmapSize
+ ?? new Size(SamplerCardViewModel.Width, SamplerCardViewModel.Height);
+
+ // Setup img2vid stuff
+ // Set width & height from SamplerCard
+ SvdImgToVidConditioningViewModel.Width = SamplerCardViewModel.Width;
+ SvdImgToVidConditioningViewModel.Height = SamplerCardViewModel.Height;
+ SvdImgToVidConditioningViewModel.ApplyStep(args);
+
+ // Setup Sampler and Refiner if enabled
+ SamplerCardViewModel.ApplyStep(args);
+
+ // Animated webp output
+ VideoOutputSettingsCardViewModel.ApplyStep(args);
+ }
+
+ ///
+ protected override IEnumerable GetInputImages()
+ {
+ if (SelectImageCardViewModel.ImageSource is { } image)
+ {
+ yield return image;
+ }
+ }
+
+ ///
+ protected override async Task GenerateImageImpl(
+ GenerateOverrides overrides,
+ CancellationToken cancellationToken
+ )
+ {
+ if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)
+ {
+ return;
+ }
+
+ // If enabled, randomize the seed
+ var seedCard = StackCardViewModel.GetCard();
+ if (overrides is not { UseCurrentSeed: true } && seedCard.IsRandomizeEnabled)
+ {
+ seedCard.GenerateNewSeed();
+ }
+
+ var batches = BatchSizeCardViewModel.BatchCount;
+
+ var batchArgs = new List();
+
+ for (var i = 0; i < batches; i++)
+ {
+ var seed = seedCard.Seed + i;
+
+ var buildPromptArgs = new BuildPromptEventArgs
+ {
+ Overrides = overrides,
+ SeedOverride = seed
+ };
+ BuildPrompt(buildPromptArgs);
+
+ var generationArgs = new ImageGenerationEventArgs
+ {
+ Client = ClientManager.Client,
+ Nodes = buildPromptArgs.Builder.ToNodeDictionary(),
+ OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),
+ Parameters = SaveStateToParameters(new GenerationParameters()),
+ Project = InferenceProjectDocument.FromLoadable(this),
+ // Only clear output images on the first batch
+ ClearOutputImages = i == 0
+ };
+
+ batchArgs.Add(generationArgs);
+ }
+
+ // Run batches
+ foreach (var args in batchArgs)
+ {
+ await RunGeneration(args, cancellationToken);
+ }
+ }
+
+ ///
+ public void LoadStateFromParameters(GenerationParameters parameters)
+ {
+ SamplerCardViewModel.LoadStateFromParameters(parameters);
+ ModelCardViewModel.LoadStateFromParameters(parameters);
+
+ SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);
+ }
+
+ ///
+ public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
+ {
+ parameters = SamplerCardViewModel.SaveStateToParameters(parameters);
+ parameters = ModelCardViewModel.SaveStateToParameters(parameters);
+
+ parameters.Seed = (ulong)SeedCardViewModel.Seed;
+
+ return parameters;
+ }
+
+ // Migration for v2 deserialization
+ public override void LoadStateFromJsonObject(JsonObject state, int version)
+ {
+ if (version > 2)
+ {
+ LoadStateFromJsonObject(state);
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
index aa748d051..87dc4b5ee 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
@@ -38,6 +38,9 @@ public partial class ModelCardViewModel
[ObservableProperty]
private bool isVaeSelectionEnabled;
+ [ObservableProperty]
+ private bool disableSettings;
+
public string? SelectedModelName => SelectedModel?.RelativePath;
public string? SelectedVaeName => SelectedVae?.RelativePath;
@@ -50,7 +53,7 @@ public ModelCardViewModel(IInferenceClientManager clientManager)
}
///
- public void ApplyStep(ModuleApplyStepEventArgs e)
+ public virtual void ApplyStep(ModuleApplyStepEventArgs e)
{
// Load base checkpoint
var baseLoader = e.Nodes.AddTypedNode(
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
new file mode 100644
index 000000000..c5513e6e3
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
@@ -0,0 +1,37 @@
+using System.ComponentModel.DataAnnotations;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
+
+[View(typeof(ModelCard))]
+[ManagedService]
+[Transient]
+public class ImgToVidModelCardViewModel : ModelCardViewModel
+{
+ public ImgToVidModelCardViewModel(IInferenceClientManager clientManager)
+ : base(clientManager)
+ {
+ DisableSettings = true;
+ }
+
+ public override void ApplyStep(ModuleApplyStepEventArgs e)
+ {
+ var imgToVidLoader = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.ImageOnlyCheckpointLoader
+ {
+ Name = "ImageOnlyCheckpointLoader",
+ CkptName =
+ SelectedModel?.RelativePath
+ ?? throw new ValidationException("Model not selected")
+ }
+ );
+
+ e.Builder.Connections.BaseModel = imgToVidLoader.Output1;
+ e.Builder.Connections.BaseClipVision = imgToVidLoader.Output2;
+ e.Builder.Connections.BaseVAE = imgToVidLoader.Output3;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
new file mode 100644
index 000000000..4932ad554
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
@@ -0,0 +1,90 @@
+using System.ComponentModel.DataAnnotations;
+using CommunityToolkit.Mvvm.ComponentModel;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
+
+[View(typeof(VideoGenerationSettingsCard))]
+[ManagedService]
+[Transient]
+public partial class SvdImgToVidConditioningViewModel
+ : LoadableViewModelBase,
+ IParametersLoadableState,
+ IComfyStep
+{
+ [ObservableProperty]
+ private int width = 1024;
+
+ [ObservableProperty]
+ private int height = 576;
+
+ [ObservableProperty]
+ private int numFrames = 14;
+
+ [ObservableProperty]
+ private int motionBucketId = 127;
+
+ [ObservableProperty]
+ private int fps = 6;
+
+ [ObservableProperty]
+ private double augmentationLevel;
+
+ [ObservableProperty]
+ private double minCfg = 1.0d;
+
+ public void LoadStateFromParameters(GenerationParameters parameters)
+ {
+ // TODO
+ }
+
+ public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
+ {
+ // TODO
+ return parameters;
+ }
+
+ public void ApplyStep(ModuleApplyStepEventArgs e)
+ {
+ // do VideoLinearCFGGuidance stuff first
+ var cfgGuidanceNode = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.VideoLinearCFGGuidance
+ {
+ Name = e.Nodes.GetUniqueName("LinearCfgGuidance"),
+ Model =
+ e.Builder.Connections.BaseModel
+ ?? throw new ValidationException("Model not selected"),
+ MinCfg = MinCfg
+ }
+ );
+
+ e.Builder.Connections.BaseModel = cfgGuidanceNode.Output;
+
+ // then do the SVD stuff
+ var svdImgToVidConditioningNode = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.SVD_img2vid_Conditioning
+ {
+ ClipVision = e.Builder.Connections.BaseClipVision!,
+ InitImage = e.Builder.GetPrimaryAsImage(),
+ Vae = e.Builder.Connections.BaseVAE!,
+ Name = e.Nodes.GetUniqueName("SvdImgToVidConditioning"),
+ Width = Width,
+ Height = Height,
+ VideoFrames = NumFrames,
+ MotionBucketId = MotionBucketId,
+ Fps = Fps,
+ AugmentationLevel = AugmentationLevel
+ }
+ );
+
+ e.Builder.Connections.BaseConditioning = svdImgToVidConditioningNode.Output1;
+ e.Builder.Connections.BaseNegativeConditioning = svdImgToVidConditioningNode.Output2;
+ e.Builder.Connections.Primary = svdImgToVidConditioningNode.Output3;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
new file mode 100644
index 000000000..3fd770798
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using CommunityToolkit.Mvvm.ComponentModel;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
+
+[View(typeof(VideoOutputSettingsCard))]
+[ManagedService]
+[Transient]
+public partial class VideoOutputSettingsCardViewModel
+ : LoadableViewModelBase,
+ IParametersLoadableState,
+ IComfyStep
+{
+ [ObservableProperty]
+ private double fps = 6;
+
+ [ObservableProperty]
+ private bool lossless = true;
+
+ [ObservableProperty]
+ private int quality = 85;
+
+ [ObservableProperty]
+ private VideoOutputMethod selectedMethod = VideoOutputMethod.Default;
+
+ [ObservableProperty]
+ private List availableMethods = Enum.GetValues().ToList();
+
+ public void LoadStateFromParameters(GenerationParameters parameters)
+ {
+ // TODO
+ }
+
+ public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
+ {
+ // TODO
+ return parameters;
+ }
+
+ public void ApplyStep(ModuleApplyStepEventArgs e)
+ {
+ if (e.Builder.Connections.Primary is null)
+ throw new ArgumentException("No Primary");
+
+ var image = e.Builder.Connections.Primary.Match(
+ _ =>
+ e.Builder.GetPrimaryAsImage(
+ e.Builder.Connections.PrimaryVAE
+ ?? e.Builder.Connections.RefinerVAE
+ ?? e.Builder.Connections.BaseVAE
+ ?? throw new ArgumentException("No Primary, Refiner, or Base VAE")
+ ),
+ image => image
+ );
+
+ var outputStep = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.SaveAnimatedWEBP
+ {
+ Name = e.Nodes.GetUniqueName("SaveAnimatedWEBP"),
+ Images = image,
+ FilenamePrefix = "InferenceVideo",
+ Fps = Fps,
+ Lossless = Lossless,
+ Quality = Quality,
+ Method = SelectedMethod.ToString().ToLowerInvariant()
+ }
+ );
+
+ e.Builder.Connections.OutputNodes.Add(outputStep);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
new file mode 100644
index 000000000..02a2fd505
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml.cs b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml.cs
new file mode 100644
index 000000000..585ae911f
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml.cs
@@ -0,0 +1,13 @@
+using StabilityMatrix.Avalonia.Controls.Dock;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.Views.Inference;
+
+[Transient]
+public partial class InferenceImageToVideoView : DockUserControlBase
+{
+ public InferenceImageToVideoView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/InferencePage.axaml b/StabilityMatrix.Avalonia/Views/InferencePage.axaml
index 746927056..e11be41d4 100644
--- a/StabilityMatrix.Avalonia/Views/InferencePage.axaml
+++ b/StabilityMatrix.Avalonia/Views/InferencePage.axaml
@@ -68,6 +68,16 @@
Symbol="ResizeImage"/>
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs b/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs
index 05e4eb76f..d3188e30e 100644
--- a/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs
+++ b/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs
@@ -60,4 +60,11 @@ private void AddTabMenu_Upscale_OnClick(object? sender, RoutedEventArgs e)
{
(DataContext as InferenceViewModel)!.AddTabCommand.Execute(InferenceProjectType.Upscale);
}
+
+ private void AddTabMenu_ImgToVideo_OnClick(object? sender, RoutedEventArgs e)
+ {
+ (DataContext as InferenceViewModel)!.AddTabCommand.Execute(
+ InferenceProjectType.ImageToVideo
+ );
+ }
}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/NodeConnections.cs b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/NodeConnections.cs
index ce6521fe4..259c99c46 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/NodeConnections.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/NodeConnections.cs
@@ -17,3 +17,5 @@ public class ConditioningNodeConnection : NodeConnectionBase { }
public class ClipNodeConnection : NodeConnectionBase { }
public class ControlNetNodeConnection : NodeConnectionBase { }
+
+public class ClipVisionNodeConnection : NodeConnectionBase { }
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
index dce175e36..eb6bce79d 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
@@ -290,6 +290,12 @@ public record CheckpointLoaderSimple
public required string CkptName { get; init; }
}
+ public record ImageOnlyCheckpointLoader
+ : ComfyTypedNodeBase
+ {
+ public required string CkptName { get; init; }
+ }
+
public record FreeU : ComfyTypedNodeBase
{
public required ModelNodeConnection Model { get; init; }
@@ -357,6 +363,40 @@ public record ControlNetApplyAdvanced
public required double EndPercent { get; init; }
}
+ public record SVD_img2vid_Conditioning
+ : ComfyTypedNodeBase<
+ ConditioningNodeConnection,
+ ConditioningNodeConnection,
+ LatentNodeConnection
+ >
+ {
+ public required ClipVisionNodeConnection ClipVision { get; init; }
+ public required ImageNodeConnection InitImage { get; init; }
+ public required VAENodeConnection Vae { get; init; }
+ public required int Width { get; init; }
+ public required int Height { get; init; }
+ public required int VideoFrames { get; init; }
+ public required int MotionBucketId { get; init; }
+ public required int Fps { get; set; }
+ public required double AugmentationLevel { get; init; }
+ }
+
+ public record VideoLinearCFGGuidance : ComfyTypedNodeBase
+ {
+ public required ModelNodeConnection Model { get; init; }
+ public required double MinCfg { get; init; }
+ }
+
+ public record SaveAnimatedWEBP : ComfyTypedNodeBase
+ {
+ public required ImageNodeConnection Images { get; init; }
+ public required string FilenamePrefix { get; init; }
+ public required double Fps { get; init; }
+ public required bool Lossless { get; init; }
+ public required int Quality { get; init; }
+ public required string Method { get; init; }
+ }
+
public ImageNodeConnection Lambda_LatentToImage(
LatentNodeConnection latent,
VAENodeConnection vae
@@ -823,6 +863,7 @@ public class NodeBuilderConnections
public ModelNodeConnection? BaseModel { get; set; }
public VAENodeConnection? BaseVAE { get; set; }
public ClipNodeConnection? BaseClip { get; set; }
+ public ClipVisionNodeConnection? BaseClipVision { get; set; }
public ConditioningNodeConnection? BaseConditioning { get; set; }
public ConditioningNodeConnection? BaseNegativeConditioning { get; set; }
From 6154d835eed8240d1a053028d615e3517b380f7b Mon Sep 17 00:00:00 2001
From: JT
Date: Tue, 28 Nov 2023 23:15:57 -0800
Subject: [PATCH 003/276] add header to VideoOutputSettingsCard to indicate
it's for output settings
---
.../Controls/VideoOutputSettingsCard.axaml | 106 ++++++++++--------
1 file changed, 59 insertions(+), 47 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml b/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml
index b9e9ac0b1..909626830 100644
--- a/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml
@@ -8,7 +8,8 @@
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages">
-
+
@@ -19,14 +20,23 @@
-
+
-
-
-
-
-
-
-
-
-
+ SpinButtonPlacementMode="Inline" />
+
+
+
+
+
+
+
+
+
From c5bda38264cd5fc9e0eb3e80c2fc39d840ab33b2 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 12:18:21 -0500
Subject: [PATCH 004/276] Add CLIPSetLastLayer Node
---
.../Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
index 25d3f9cf9..f308e9939 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
@@ -119,6 +119,14 @@ public record EmptyLatentImage : ComfyTypedNodeBase
public required int Width { get; init; }
}
+ public record CLIPSetLastLayer : ComfyTypedNodeBase
+ {
+ public required ClipNodeConnection Clip { get; init; }
+
+ [Range(-24, -1)]
+ public int StopAtClipLayer { get; init; } = -1;
+ }
+
public static NamedComfyNode LatentFromBatch(
string name,
LatentNodeConnection samples,
From 097845a70984eb7ec5eb9f15da8ffa006461a603 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 16:14:12 -0500
Subject: [PATCH 005/276] Add NullableExtensions.Unwrap
---
.../Extensions/NullableExtensions.cs | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
create mode 100644 StabilityMatrix.Core/Extensions/NullableExtensions.cs
diff --git a/StabilityMatrix.Core/Extensions/NullableExtensions.cs b/StabilityMatrix.Core/Extensions/NullableExtensions.cs
new file mode 100644
index 000000000..aa644fb9d
--- /dev/null
+++ b/StabilityMatrix.Core/Extensions/NullableExtensions.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace StabilityMatrix.Core.Extensions;
+
+public static class NullableExtensions
+{
+ ///
+ /// Unwraps a nullable object, throwing an exception if it is null.
+ ///
+ ///
+ /// Thrown if () is null.
+ ///
+ [DebuggerStepThrough]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Unwrap([NotNull] this T? obj, [CallerArgumentExpression("obj")] string? paramName = null)
+ where T : class
+ {
+ if (obj is null)
+ {
+ throw new ArgumentNullException(paramName, $"Unwrap of a null value ({typeof(T)}) {paramName}.");
+ }
+ return obj;
+ }
+}
From 74aac6272b9cdb10ea4759262c29527c81f9725f Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 16:53:14 -0500
Subject: [PATCH 006/276] Add NullableExtensions.Unwrap struct overload
---
.../Extensions/NullableExtensions.cs | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/StabilityMatrix.Core/Extensions/NullableExtensions.cs b/StabilityMatrix.Core/Extensions/NullableExtensions.cs
index aa644fb9d..64264ef69 100644
--- a/StabilityMatrix.Core/Extensions/NullableExtensions.cs
+++ b/StabilityMatrix.Core/Extensions/NullableExtensions.cs
@@ -25,4 +25,23 @@ public static T Unwrap([NotNull] this T? obj, [CallerArgumentExpression("obj"
}
return obj;
}
+
+ ///
+ /// Unwraps a nullable struct object, throwing an exception if it is null.
+ ///
+ ///
+ /// Thrown if () is null.
+ ///
+ [DebuggerStepThrough]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Unwrap([NotNull] this T? obj, [CallerArgumentExpression("obj")] string? paramName = null)
+ where T : struct
+ {
+ if (obj is null)
+ {
+ throw new ArgumentNullException(paramName, $"Unwrap of a null value ({typeof(T)}) {paramName}.");
+ }
+ return obj.Value;
+ }
}
From 5871241d6feedc05466857498d1d463737a3dbc6 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 16:54:39 -0500
Subject: [PATCH 007/276] Add Clip skip and model connection refactors
---
.../Extensions/ComfyNodeBuilderExtensions.cs | 67 +++++-----
.../Inference/ModuleApplyStepEventArgs.cs | 15 +--
.../Inference/ModelCardViewModel.cs | 79 +++++++----
.../Inference/Modules/FreeUModule.cs | 31 ++---
.../Inference/Modules/HiresFixModule.cs | 9 +-
.../Inference/PromptCardViewModel.cs | 125 ++++--------------
.../Inference/SamplerCardViewModel.cs | 64 ++++-----
.../NodeTypes/ConditioningConnections.cs | 12 ++
.../Api/Comfy/NodeTypes/ModelConnections.cs | 15 +++
.../Api/Comfy/Nodes/ComfyNodeBuilder.cs | 35 ++---
10 files changed, 208 insertions(+), 244 deletions(-)
create mode 100644 StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ConditioningConnections.cs
create mode 100644 StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs
diff --git a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
index 38c28fb32..a4a88705f 100644
--- a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
+++ b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
@@ -20,15 +20,17 @@ public static void SetupEmptyLatentSource(
int? batchIndex = null
)
{
- var emptyLatent = builder.Nodes.AddTypedNode(
- new ComfyNodeBuilder.EmptyLatentImage
- {
- Name = "EmptyLatentImage",
- BatchSize = batchSize,
- Height = height,
- Width = width
- }
- );
+ var emptyLatent = builder
+ .Nodes
+ .AddTypedNode(
+ new ComfyNodeBuilder.EmptyLatentImage
+ {
+ Name = "EmptyLatentImage",
+ BatchSize = batchSize,
+ Height = height,
+ Width = width
+ }
+ );
builder.Connections.Primary = emptyLatent.Output;
builder.Connections.PrimarySize = new Size(width, height);
@@ -36,7 +38,8 @@ public static void SetupEmptyLatentSource(
// If batch index is selected, add a LatentFromBatch
if (batchIndex is not null)
{
- builder.Connections.Primary = builder.Nodes
+ builder.Connections.Primary = builder
+ .Nodes
.AddNamedNode(
ComfyNodeBuilder.LatentFromBatch(
"LatentFromBatch",
@@ -64,9 +67,9 @@ public static void SetupImagePrimarySource(
var sourceImageRelativePath = Path.Combine("Inference", image.GetHashGuidFileNameCached());
// Load source
- var loadImage = builder.Nodes.AddTypedNode(
- new ComfyNodeBuilder.LoadImage { Name = "LoadImage", Image = sourceImageRelativePath }
- );
+ var loadImage = builder
+ .Nodes
+ .AddTypedNode(new ComfyNodeBuilder.LoadImage { Name = "LoadImage", Image = sourceImageRelativePath });
builder.Connections.Primary = loadImage.Output1;
builder.Connections.PrimarySize = imageSize;
@@ -74,7 +77,8 @@ public static void SetupImagePrimarySource(
// If batch index is selected, add a LatentFromBatch
if (batchIndex is not null)
{
- builder.Connections.Primary = builder.Nodes
+ builder.Connections.Primary = builder
+ .Nodes
.AddNamedNode(
ComfyNodeBuilder.LatentFromBatch(
"LatentFromBatch",
@@ -93,24 +97,25 @@ public static string SetupOutputImage(this ComfyNodeBuilder builder)
if (builder.Connections.Primary is null)
throw new ArgumentException("No Primary");
- var image = builder.Connections.Primary.Match(
- _ =>
- builder.GetPrimaryAsImage(
- builder.Connections.PrimaryVAE
- ?? builder.Connections.RefinerVAE
- ?? builder.Connections.BaseVAE
- ?? throw new ArgumentException("No Primary, Refiner, or Base VAE")
- ),
- image => image
- );
+ var image = builder
+ .Connections
+ .Primary
+ .Match(
+ _ =>
+ builder.GetPrimaryAsImage(
+ builder.Connections.PrimaryVAE
+ ?? builder.Connections.Refiner.VAE
+ ?? builder.Connections.Base.VAE
+ ?? throw new ArgumentException("No Primary, Refiner, or Base VAE")
+ ),
+ image => image
+ );
- var previewImage = builder.Nodes.AddTypedNode(
- new ComfyNodeBuilder.PreviewImage
- {
- Name = builder.Nodes.GetUniqueName("SaveImage"),
- Images = image
- }
- );
+ var previewImage = builder
+ .Nodes
+ .AddTypedNode(
+ new ComfyNodeBuilder.PreviewImage { Name = builder.Nodes.GetUniqueName("SaveImage"), Images = image }
+ );
builder.Connections.OutputNodes.Add(previewImage);
diff --git a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
index f90baa4bf..8119abc16 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.Models.Inference;
@@ -29,26 +29,19 @@ public class ModuleApplyStepEventArgs : EventArgs
///
/// Generation overrides (like hires fix generate, current seed generate, etc.)
///
- public IReadOnlyDictionary IsEnabledOverrides { get; init; } =
- new Dictionary();
+ public IReadOnlyDictionary IsEnabledOverrides { get; init; } = new Dictionary();
public class ModuleApplyStepTemporaryArgs
{
///
/// Temporary conditioning apply step, used by samplers to apply control net.
///
- public (
- ConditioningNodeConnection Positive,
- ConditioningNodeConnection Negative
- )? Conditioning { get; set; }
+ public ConditioningConnections? Conditioning { get; set; }
///
/// Temporary refiner conditioning apply step, used by samplers to apply control net.
///
- public (
- ConditioningNodeConnection Positive,
- ConditioningNodeConnection Negative
- )? RefinerConditioning { get; set; }
+ public ConditioningConnections? RefinerConditioning { get; set; }
///
/// Temporary model apply step, used by samplers to apply control net.
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
index 5336a3e1e..38e13bb28 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
@@ -9,9 +9,10 @@
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
-using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
@@ -38,6 +39,14 @@ public partial class ModelCardViewModel(IInferenceClientManager clientManager)
[ObservableProperty]
private bool isVaeSelectionEnabled;
+ [ObservableProperty]
+ private bool isClipSkipEnabled;
+
+ [NotifyDataErrorInfo]
+ [ObservableProperty]
+ [Range(1, 24)]
+ private int clipSkip = 1;
+
public IInferenceClientManager ClientManager { get; } = clientManager;
///
@@ -47,50 +56,68 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
var baseLoader = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.CheckpointLoaderSimple
{
- Name = "CheckpointLoader",
- CkptName =
- SelectedModel?.RelativePath
- ?? throw new ValidationException("Model not selected")
+ Name = "CheckpointLoader_Base",
+ CkptName = SelectedModel?.RelativePath ?? throw new ValidationException("Model not selected")
}
);
- e.Builder.Connections.BaseModel = baseLoader.Output1;
- e.Builder.Connections.BaseClip = baseLoader.Output2;
- e.Builder.Connections.BaseVAE = baseLoader.Output3;
+ e.Builder.Connections.Base.Model = baseLoader.Output1;
+ e.Builder.Connections.Base.Clip = baseLoader.Output2;
+ e.Builder.Connections.Base.VAE = baseLoader.Output3;
- // Load refiner
+ // Load refiner if enabled
if (IsRefinerSelectionEnabled && SelectedRefiner is { IsNone: false })
{
var refinerLoader = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.CheckpointLoaderSimple
{
- Name = "Refiner_CheckpointLoader",
+ Name = "CheckpointLoader_Refiner",
CkptName =
SelectedRefiner?.RelativePath
?? throw new ValidationException("Refiner Model enabled but not selected")
}
);
- e.Builder.Connections.RefinerModel = refinerLoader.Output1;
- e.Builder.Connections.RefinerClip = refinerLoader.Output2;
- e.Builder.Connections.RefinerVAE = refinerLoader.Output3;
+ e.Builder.Connections.Refiner.Model = refinerLoader.Output1;
+ e.Builder.Connections.Refiner.Clip = refinerLoader.Output2;
+ e.Builder.Connections.Refiner.VAE = refinerLoader.Output3;
}
- // Load custom VAE
+ // Load VAE override if enabled
if (IsVaeSelectionEnabled && SelectedVae is { IsNone: false, IsDefault: false })
{
var vaeLoader = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.VAELoader
{
Name = "VAELoader",
- VaeName =
- SelectedVae?.RelativePath
- ?? throw new ValidationException("VAE enabled but not selected")
+ VaeName = SelectedVae?.RelativePath ?? throw new ValidationException("VAE enabled but not selected")
}
);
e.Builder.Connections.PrimaryVAE = vaeLoader.Output;
}
+
+ // Clip skip all models if enabled
+ if (IsClipSkipEnabled)
+ {
+ foreach (var (modelName, model) in e.Builder.Connections.Models)
+ {
+ if (model.Clip is not { } modelClip)
+ continue;
+
+ var clipSetLastLayer = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.CLIPSetLastLayer
+ {
+ Name = $"CLIP_Skip_{modelName}",
+ Clip = modelClip,
+ // Need to convert to negative indexing from (1 to 24) to (-1 to -24)
+ StopAtClipLayer = -ClipSkip
+ }
+ );
+
+ model.Clip = clipSetLastLayer.Output;
+ }
+ }
}
///
@@ -102,8 +129,10 @@ public override JsonObject SaveStateToJsonObject()
SelectedModelName = SelectedModel?.RelativePath,
SelectedVaeName = SelectedVae?.RelativePath,
SelectedRefinerName = SelectedRefiner?.RelativePath,
+ ClipSkip = ClipSkip,
IsVaeSelectionEnabled = IsVaeSelectionEnabled,
- IsRefinerSelectionEnabled = IsRefinerSelectionEnabled
+ IsRefinerSelectionEnabled = IsRefinerSelectionEnabled,
+ IsClipSkipEnabled = IsClipSkipEnabled
}
);
}
@@ -125,8 +154,11 @@ public override void LoadStateFromJsonObject(JsonObject state)
? HybridModelFile.None
: ClientManager.Models.FirstOrDefault(x => x.RelativePath == model.SelectedRefinerName);
+ ClipSkip = model.ClipSkip;
+
IsVaeSelectionEnabled = model.IsVaeSelectionEnabled;
IsRefinerSelectionEnabled = model.IsRefinerSelectionEnabled;
+ IsClipSkipEnabled = model.IsClipSkipEnabled;
}
///
@@ -145,19 +177,14 @@ public void LoadStateFromParameters(GenerationParameters parameters)
model = currentModels.FirstOrDefault(
m =>
m.Local?.ConnectedModelInfo?.Hashes.SHA256 is { } sha256
- && sha256.StartsWith(
- parameters.ModelHash,
- StringComparison.InvariantCultureIgnoreCase
- )
+ && sha256.StartsWith(parameters.ModelHash, StringComparison.InvariantCultureIgnoreCase)
);
}
else
{
// Name matches
model = currentModels.FirstOrDefault(m => m.RelativePath.EndsWith(paramsModelName));
- model ??= currentModels.FirstOrDefault(
- m => m.ShortDisplayName.StartsWith(paramsModelName)
- );
+ model ??= currentModels.FirstOrDefault(m => m.ShortDisplayName.StartsWith(paramsModelName));
}
if (model is not null)
@@ -181,8 +208,10 @@ internal class ModelCardModel
public string? SelectedModelName { get; init; }
public string? SelectedRefinerName { get; init; }
public string? SelectedVaeName { get; init; }
+ public int ClipSkip { get; init; }
public bool IsVaeSelectionEnabled { get; init; }
public bool IsRefinerSelectionEnabled { get; init; }
+ public bool IsClipSkipEnabled { get; init; }
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs
index 4ac393b6d..120933100 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs
@@ -1,7 +1,9 @@
-using StabilityMatrix.Avalonia.Models.Inference;
+using System.Linq;
+using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
@@ -25,34 +27,17 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
{
var card = GetCard();
- // Currently applies to both base and refiner model
+ // Currently applies to all models
// TODO: Add option to apply to either base or refiner
- if (e.Builder.Connections.BaseModel is not null)
+ foreach (var modelConnections in e.Builder.Connections.Models.Values.Where(m => m.Model is not null))
{
- e.Builder.Connections.BaseModel = e.Nodes
+ modelConnections.Model = e.Nodes
.AddTypedNode(
new ComfyNodeBuilder.FreeU
{
- Name = e.Nodes.GetUniqueName("FreeU"),
- Model = e.Builder.Connections.BaseModel,
- B1 = card.B1,
- B2 = card.B2,
- S1 = card.S1,
- S2 = card.S2
- }
- )
- .Output;
- }
-
- if (e.Builder.Connections.RefinerModel is not null)
- {
- e.Builder.Connections.RefinerModel = e.Nodes
- .AddTypedNode(
- new ComfyNodeBuilder.FreeU
- {
- Name = e.Nodes.GetUniqueName("Refiner_FreeU"),
- Model = e.Builder.Connections.RefinerModel,
+ Name = e.Nodes.GetUniqueName($"FreeU_{modelConnections.Name}"),
+ Model = modelConnections.Model!,
B1 = card.B1,
B2 = card.B2,
S1 = card.S1,
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
index 4fd98fea1..588cd263e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
@@ -1,10 +1,7 @@
using System;
-using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
-using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -73,7 +70,7 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
{
builder.Connections.Primary = builder.Group_Upscale(
builder.Nodes.GetUniqueName("HiresFix"),
- builder.Connections.Primary ?? throw new ArgumentException("No Primary"),
+ builder.Connections.Primary.Unwrap(),
builder.Connections.GetDefaultVAE(),
selectedUpscaler,
hiresSize.Width,
@@ -99,8 +96,8 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
samplerCard.SelectedScheduler?.Name
?? e.Builder.Connections.PrimaryScheduler?.Name
?? throw new ArgumentException("No PrimaryScheduler"),
- Positive = builder.Connections.GetRefinerOrBaseConditioning(),
- Negative = builder.Connections.GetRefinerOrBaseNegativeConditioning(),
+ Positive = builder.Connections.GetRefinerOrBaseConditioning().Positive,
+ Negative = builder.Connections.GetRefinerOrBaseConditioning().Negative,
LatentImage = builder.GetPrimaryAsLatent(),
Denoise = samplerCard.DenoiseStrength
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
index bad20dba0..b6973534f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
@@ -18,6 +18,7 @@
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
using StabilityMatrix.Core.Services;
@@ -26,10 +27,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(PromptCard))]
[ManagedService]
[Transient]
-public partial class PromptCardViewModel
- : LoadableViewModelBase,
- IParametersLoadableState,
- IComfyStep
+public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoadableState, IComfyStep
{
private readonly IModelIndexService modelIndexService;
@@ -74,13 +72,11 @@ SharedState sharedState
/// Applies the prompt step.
/// Requires:
///
- ///
- ///
+ /// - - Model, Clip
///
/// Provides:
///
- ///
- ///
+ /// - - Conditioning
///
///
public void ApplyStep(ModuleApplyStepEventArgs e)
@@ -91,90 +87,44 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
var negativePrompt = GetNegativePrompt();
negativePrompt.Process();
- // If need to load loras, add a group
- if (positivePrompt.ExtraNetworks.Count > 0)
+ foreach (var modelConnections in e.Builder.Connections.Models.Values)
{
- var loras = positivePrompt.GetExtraNetworksAsLocalModels(modelIndexService).ToList();
- // Add group to load loras onto model and clip in series
- var lorasGroup = e.Builder.Group_LoraLoadMany(
- "Loras",
- e.Builder.Connections.BaseModel ?? throw new ArgumentException("BaseModel is null"),
- e.Builder.Connections.BaseClip ?? throw new ArgumentException("BaseClip is null"),
- loras
- );
-
- // Set last outputs as base model and clip
- e.Builder.Connections.BaseModel = lorasGroup.Output1;
- e.Builder.Connections.BaseClip = lorasGroup.Output2;
+ if (modelConnections.Model is not { } model || modelConnections.Clip is not { } clip)
+ continue;
- // Refiner loras
- if (e.Builder.Connections.RefinerModel is not null)
+ // If need to load loras, add a group
+ if (positivePrompt.ExtraNetworks.Count > 0)
{
- // Add group to load loras onto refiner model and clip in series
- var lorasGroupRefiner = e.Builder.Group_LoraLoadMany(
- "Refiner_Loras",
- e.Builder.Connections.RefinerModel
- ?? throw new ArgumentException("RefinerModel is null"),
- e.Builder.Connections.RefinerClip
- ?? throw new ArgumentException("RefinerClip is null"),
- loras
- );
+ var loras = positivePrompt.GetExtraNetworksAsLocalModels(modelIndexService).ToList();
- // Set last outputs as refiner model and clip
- e.Builder.Connections.RefinerModel = lorasGroupRefiner.Output1;
- e.Builder.Connections.RefinerClip = lorasGroupRefiner.Output2;
- }
- }
+ // Add group to load loras onto model and clip in series
+ var lorasGroup = e.Builder.Group_LoraLoadMany($"Loras_{modelConnections.Name}", model, clip, loras);
- // Clips
- var positiveClip = e.Builder.Nodes.AddTypedNode(
- new ComfyNodeBuilder.CLIPTextEncode
- {
- Name = "PositiveCLIP",
- Clip = e.Builder.Connections.BaseClip!,
- Text = positivePrompt.ProcessedText
- }
- );
- var negativeClip = e.Builder.Nodes.AddTypedNode(
- new ComfyNodeBuilder.CLIPTextEncode
- {
- Name = "NegativeCLIP",
- Clip = e.Builder.Connections.BaseClip!,
- Text = negativePrompt.ProcessedText
+ // Set last outputs as model and clip
+ modelConnections.Model = lorasGroup.Output1;
+ modelConnections.Clip = lorasGroup.Output2;
}
- );
- // Set base conditioning from Clips
- e.Builder.Connections.BaseConditioning = positiveClip.Output;
- e.Builder.Connections.BaseNegativeConditioning = negativeClip.Output;
-
- // Refiner Clips
- if (e.Builder.Connections.RefinerModel is not null)
- {
- var positiveClipRefiner = e.Builder.Nodes.AddTypedNode(
+ // Clips
+ var positiveClip = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.CLIPTextEncode
{
- Name = "Refiner_PositiveCLIP",
- Clip =
- e.Builder.Connections.RefinerClip
- ?? throw new ArgumentException("RefinerClip is null"),
+ Name = $"PositiveCLIP_{modelConnections.Name}",
+ Clip = e.Builder.Connections.Base.Clip!,
Text = positivePrompt.ProcessedText
}
);
- var negativeClipRefiner = e.Builder.Nodes.AddTypedNode(
+ var negativeClip = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.CLIPTextEncode
{
- Name = "Refiner_NegativeCLIP",
- Clip =
- e.Builder.Connections.RefinerClip
- ?? throw new ArgumentException("RefinerClip is null"),
+ Name = $"NegativeCLIP_{modelConnections.Name}",
+ Clip = e.Builder.Connections.Base.Clip!,
Text = negativePrompt.ProcessedText
}
);
- // Set refiner conditioning from Clips
- e.Builder.Connections.RefinerConditioning = positiveClipRefiner.Output;
- e.Builder.Connections.RefinerNegativeConditioning = negativeClipRefiner.Output;
+ // Set conditioning from Clips
+ modelConnections.Conditioning = (positiveClip.Output, negativeClip.Output);
}
}
@@ -283,11 +233,7 @@ a red cat # also comments
```
""";
- var dialog = DialogHelper.CreateMarkdownDialog(
- md,
- "Prompt Syntax",
- TextEditorPreset.Prompt
- );
+ var dialog = DialogHelper.CreateMarkdownDialog(md, "Prompt Syntax", TextEditorPreset.Prompt);
dialog.MinDialogWidth = 800;
dialog.MaxDialogHeight = 1000;
dialog.MaxDialogWidth = 1000;
@@ -305,9 +251,7 @@ private async Task DebugShowTokens()
}
catch (PromptError e)
{
- await DialogHelper
- .CreatePromptErrorDialog(e, prompt.RawText, modelIndexService)
- .ShowAsync();
+ await DialogHelper.CreatePromptErrorDialog(e, prompt.RawText, modelIndexService).ShowAsync();
return;
}
@@ -327,10 +271,7 @@ await DialogHelper
builder.AppendLine($"## Networks ({networks.Count}):");
builder.AppendLine("```csharp");
builder.AppendLine(
- JsonSerializer.Serialize(
- networks,
- new JsonSerializerOptions() { WriteIndented = true, }
- )
+ JsonSerializer.Serialize(networks, new JsonSerializerOptions() { WriteIndented = true, })
);
builder.AppendLine("```");
}
@@ -378,11 +319,7 @@ private void EditorCut(TextEditor? textEditor)
public override JsonObject SaveStateToJsonObject()
{
return SerializeModel(
- new PromptCardModel
- {
- Prompt = PromptDocument.Text,
- NegativePrompt = NegativePromptDocument.Text
- }
+ new PromptCardModel { Prompt = PromptDocument.Text, NegativePrompt = NegativePromptDocument.Text }
);
}
@@ -405,10 +342,6 @@ public void LoadStateFromParameters(GenerationParameters parameters)
///
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
- return parameters with
- {
- PositivePrompt = PromptDocument.Text,
- NegativePrompt = NegativePromptDocument.Text
- };
+ return parameters with { PositivePrompt = PromptDocument.Text, NegativePrompt = NegativePromptDocument.Text };
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
index 32640d8e1..a129696f0 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
@@ -12,6 +12,7 @@
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.Comfy;
@@ -116,14 +117,8 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
}
// Provide temp values
- e.Temp.Conditioning = (
- e.Builder.Connections.BaseConditioning!,
- e.Builder.Connections.BaseNegativeConditioning!
- );
- e.Temp.RefinerConditioning = (
- e.Builder.Connections.RefinerConditioning!,
- e.Builder.Connections.RefinerNegativeConditioning!
- );
+ e.Temp.Conditioning = e.Builder.Connections.Base.Conditioning;
+ e.Temp.RefinerConditioning = e.Builder.Connections.Refiner.Conditioning;
// Apply steps from our addons
ApplyAddonSteps(e);
@@ -147,15 +142,17 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
var primaryLatent = e.Builder.GetPrimaryAsLatent();
// Set primary sampler and scheduler
- e.Builder.Connections.PrimarySampler = SelectedSampler ?? throw new ValidationException("Sampler not selected");
- e.Builder.Connections.PrimaryScheduler =
- SelectedScheduler ?? throw new ValidationException("Scheduler not selected");
+ var primarySampler = SelectedSampler ?? throw new ValidationException("Sampler not selected");
+ e.Builder.Connections.PrimarySampler = primarySampler;
+
+ var primaryScheduler = SelectedScheduler ?? throw new ValidationException("Scheduler not selected");
+ e.Builder.Connections.PrimaryScheduler = primaryScheduler;
// Use custom sampler if SDTurbo scheduler is selected
if (e.Builder.Connections.PrimaryScheduler == ComfyScheduler.SDTurbo)
{
// Error if using refiner
- if (e.Builder.Connections.RefinerModel is not null)
+ if (e.Builder.Connections.Refiner.Model is not null)
{
throw new ValidationException("SDTurbo Scheduler cannot be used with Refiner Model");
}
@@ -172,7 +169,7 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.SDTurboScheduler
{
Name = "SDTurboScheduler",
- Model = e.Builder.Connections.BaseModel ?? throw new ArgumentException("No BaseModel"),
+ Model = e.Builder.Connections.Base.Model.Unwrap(),
Steps = Steps
}
);
@@ -181,7 +178,7 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.SamplerCustom
{
Name = "Sampler",
- Model = e.Builder.Connections.BaseModel ?? throw new ArgumentException("No BaseModel"),
+ Model = e.Builder.Connections.Base.Model,
AddNoise = true,
NoiseSeed = e.Builder.Connections.Seed,
Cfg = CfgScale,
@@ -199,21 +196,23 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
}
// Use KSampler if no refiner, otherwise need KSamplerAdvanced
- if (e.Builder.Connections.RefinerModel is null)
+ if (e.Builder.Connections.Refiner.Model is null)
{
+ var baseConditioning = e.Builder.Connections.Base.Conditioning.Unwrap();
+
// No refiner
var sampler = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.KSampler
{
Name = "Sampler",
- Model = e.Builder.Connections.BaseModel ?? throw new ArgumentException("No BaseModel"),
+ Model = e.Builder.Connections.Base.Model.Unwrap(),
Seed = e.Builder.Connections.Seed,
- SamplerName = e.Builder.Connections.PrimarySampler?.Name!,
- Scheduler = e.Builder.Connections.PrimaryScheduler?.Name!,
+ SamplerName = primarySampler.Name,
+ Scheduler = primaryScheduler.Name,
Steps = Steps,
Cfg = CfgScale,
- Positive = e.Temp.Conditioning?.Positive!,
- Negative = e.Temp.Conditioning?.Negative!,
+ Positive = baseConditioning.Positive,
+ Negative = baseConditioning.Negative,
LatentImage = primaryLatent,
Denoise = DenoiseStrength,
}
@@ -223,20 +222,23 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
}
else
{
+ var baseConditioning = e.Builder.Connections.Base.Conditioning.Unwrap();
+ var refinerConditioning = e.Builder.Connections.Refiner.Conditioning.Unwrap();
+
// Advanced base sampler for refiner
var sampler = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.KSamplerAdvanced
{
Name = "Sampler",
- Model = e.Builder.Connections.BaseModel ?? throw new ArgumentException("No BaseModel"),
+ Model = e.Builder.Connections.Base.Model.Unwrap(),
AddNoise = true,
NoiseSeed = e.Builder.Connections.Seed,
Steps = TotalSteps,
Cfg = CfgScale,
- SamplerName = e.Builder.Connections.PrimarySampler?.Name!,
- Scheduler = e.Builder.Connections.PrimaryScheduler?.Name!,
- Positive = e.Temp.Conditioning?.Positive!,
- Negative = e.Temp.Conditioning?.Negative!,
+ SamplerName = primarySampler.Name,
+ Scheduler = primaryScheduler.Name,
+ Positive = baseConditioning.Positive,
+ Negative = baseConditioning.Negative,
LatentImage = primaryLatent,
StartAtStep = 0,
EndAtStep = Steps,
@@ -248,16 +250,16 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
var refinerSampler = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.KSamplerAdvanced
{
- Name = "Refiner_Sampler",
- Model = e.Builder.Connections.RefinerModel ?? throw new ArgumentException("No RefinerModel"),
+ Name = "Sampler_Refiner",
+ Model = e.Builder.Connections.Refiner.Model,
AddNoise = false,
NoiseSeed = e.Builder.Connections.Seed,
Steps = TotalSteps,
Cfg = CfgScale,
- SamplerName = e.Builder.Connections.PrimarySampler?.Name!,
- Scheduler = e.Builder.Connections.PrimaryScheduler?.Name!,
- Positive = e.Temp.RefinerConditioning?.Positive!,
- Negative = e.Temp.RefinerConditioning?.Negative!,
+ SamplerName = primarySampler.Name,
+ Scheduler = primaryScheduler.Name,
+ Positive = refinerConditioning.Positive,
+ Negative = refinerConditioning.Negative,
// Connect to previous sampler
LatentImage = sampler.Output,
StartAtStep = Steps,
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ConditioningConnections.cs b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ConditioningConnections.cs
new file mode 100644
index 000000000..02a3c2af7
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ConditioningConnections.cs
@@ -0,0 +1,12 @@
+namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+
+///
+/// Combination of the positive and negative conditioning connections.
+///
+public record ConditioningConnections(ConditioningNodeConnection Positive, ConditioningNodeConnection Negative)
+{
+ // Implicit from tuple
+ public static implicit operator ConditioningConnections(
+ (ConditioningNodeConnection Positive, ConditioningNodeConnection Negative) value
+ ) => new(value.Positive, value.Negative);
+}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs
new file mode 100644
index 000000000..abb665966
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs
@@ -0,0 +1,15 @@
+namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+
+///
+/// Connections from a loaded model
+///
+public record ModelConnections(string Name)
+{
+ public ModelNodeConnection? Model { get; set; }
+
+ public VAENodeConnection? VAE { get; set; }
+
+ public ClipNodeConnection? Clip { get; set; }
+
+ public ConditioningConnections? Conditioning { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
index f308e9939..73e94197b 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
@@ -733,18 +733,16 @@ public class NodeBuilderConnections
public int BatchSize { get; set; } = 1;
public int? BatchIndex { get; set; }
- public ModelNodeConnection? BaseModel { get; set; }
- public VAENodeConnection? BaseVAE { get; set; }
- public ClipNodeConnection? BaseClip { get; set; }
+ public Dictionary Models { get; } =
+ new() { ["Base"] = new ModelConnections("Base"), ["Refiner"] = new ModelConnections("Refiner") };
- public ConditioningNodeConnection? BaseConditioning { get; set; }
- public ConditioningNodeConnection? BaseNegativeConditioning { get; set; }
+ ///
+ /// ModelConnections from with set
+ ///
+ public IEnumerable LoadedModels => Models.Values.Where(m => m.Model is not null);
- public ModelNodeConnection? RefinerModel { get; set; }
- public VAENodeConnection? RefinerVAE { get; set; }
- public ClipNodeConnection? RefinerClip { get; set; }
- public ConditioningNodeConnection? RefinerConditioning { get; set; }
- public ConditioningNodeConnection? RefinerNegativeConditioning { get; set; }
+ public ModelConnections Base => Models["Base"];
+ public ModelConnections Refiner => Models["Refiner"];
public PrimaryNodeConnection? Primary { get; set; }
public VAENodeConnection? PrimaryVAE { get; set; }
@@ -759,24 +757,19 @@ public class NodeBuilderConnections
public ModelNodeConnection GetRefinerOrBaseModel()
{
- return RefinerModel ?? BaseModel ?? throw new NullReferenceException("No Model");
- }
-
- public ConditioningNodeConnection GetRefinerOrBaseConditioning()
- {
- return RefinerConditioning ?? BaseConditioning ?? throw new NullReferenceException("No Conditioning");
+ return Refiner.Model ?? Base.Model ?? throw new NullReferenceException("No Refiner or Base Model");
}
- public ConditioningNodeConnection GetRefinerOrBaseNegativeConditioning()
+ public ConditioningConnections GetRefinerOrBaseConditioning()
{
- return RefinerNegativeConditioning
- ?? BaseNegativeConditioning
- ?? throw new NullReferenceException("No Negative Conditioning");
+ return Refiner.Conditioning
+ ?? Base.Conditioning
+ ?? throw new NullReferenceException("No Refiner or Base Conditioning");
}
public VAENodeConnection GetDefaultVAE()
{
- return PrimaryVAE ?? RefinerVAE ?? BaseVAE ?? throw new NullReferenceException("No VAE");
+ return PrimaryVAE ?? Refiner.VAE ?? Base.VAE ?? throw new NullReferenceException("No VAE");
}
}
From 5eaaedf4dd23fac184feca37a171db728255531f Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 16:55:06 -0500
Subject: [PATCH 008/276] Add clip skip selection to ModelCard control
---
.../Controls/Inference/ModelCard.axaml | 37 ++++++++++++++++---
1 file changed, 32 insertions(+), 5 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
index e0fc6a1c0..7bf995a87 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
@@ -7,6 +7,7 @@
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
x:DataType="inference:ModelCardViewModel">
@@ -21,7 +22,7 @@
-
+
-
-
+ -->
+
-
+
@@ -190,6 +192,31 @@
IsVisible="{Binding IsVaeSelectionEnabled}"
ItemsSource="{Binding ClientManager.VaeModels}"
SelectedItem="{Binding SelectedVae}" />
+
+
+
+
+
From d82f3d4843b3ec4200fa95893c946f71ea8f1d17 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 16:57:23 -0500
Subject: [PATCH 009/276] Fix Value access
---
.../Inference/Modules/ControlNetModule.cs | 26 ++++++-------------
1 file changed, 8 insertions(+), 18 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
index 4f4e71915..26bf78970 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -42,9 +40,8 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
{
Name = e.Nodes.GetUniqueName("ControlNet_LoadImage"),
Image =
- card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(
- "Inference"
- ) ?? throw new ValidationException("No ImageSource")
+ card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException("No ImageSource")
}
);
@@ -52,9 +49,7 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.ControlNetLoader
{
Name = e.Nodes.GetUniqueName("ControlNetLoader"),
- ControlNetName =
- card.SelectedModel?.FileName
- ?? throw new ValidationException("No SelectedModel"),
+ ControlNetName = card.SelectedModel?.FileName ?? throw new ValidationException("No SelectedModel"),
}
);
@@ -64,10 +59,8 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
Name = e.Nodes.GetUniqueName("ControlNetApply"),
Image = imageLoad.Output1,
ControlNet = controlNetLoader.Output,
- Positive =
- e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
- Negative =
- e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
+ Positive = e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
+ Negative = e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
Strength = card.Strength,
StartPercent = card.StartPercent,
EndPercent = card.EndPercent,
@@ -85,18 +78,15 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
Name = e.Nodes.GetUniqueName("Refiner_ControlNetApply"),
Image = imageLoad.Output1,
ControlNet = controlNetLoader.Output,
- Positive = e.Temp.RefinerConditioning.Value.Positive,
- Negative = e.Temp.RefinerConditioning.Value.Negative,
+ Positive = e.Temp.RefinerConditioning.Positive,
+ Negative = e.Temp.RefinerConditioning.Negative,
Strength = card.Strength,
StartPercent = card.StartPercent,
EndPercent = card.EndPercent,
}
);
- e.Temp.RefinerConditioning = (
- controlNetRefinerApply.Output1,
- controlNetRefinerApply.Output2
- );
+ e.Temp.RefinerConditioning = (controlNetRefinerApply.Output1, controlNetRefinerApply.Output2);
}
}
}
From 1c37e9b4e400e0639be005dc30f5d89647ccd04c Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 16:58:28 -0500
Subject: [PATCH 010/276] Version bump
---
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
index 446a24766..2113c78c6 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
@@ -9,7 +9,7 @@
app.manifest
true
./Assets/Icon.ico
- 2.7.0-pre.999
+ 2.8.0-dev.999
$(Version)
true
true
From ed4f78a57ece8bcbc9c548f118b3b1e4bdf4ada4 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 17:02:21 -0500
Subject: [PATCH 011/276] Add clip skip default value
---
.../ViewModels/Inference/ModelCardViewModel.cs | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
index 38e13bb28..b8a1d94bb 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
@@ -9,9 +9,7 @@
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
-using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
-using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
@@ -208,7 +206,7 @@ internal class ModelCardModel
public string? SelectedModelName { get; init; }
public string? SelectedRefinerName { get; init; }
public string? SelectedVaeName { get; init; }
- public int ClipSkip { get; init; }
+ public int ClipSkip { get; init; } = 1;
public bool IsVaeSelectionEnabled { get; init; }
public bool IsRefinerSelectionEnabled { get; init; }
From a6c87a2be3f28c9143617266ace9b51841846006 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 21:11:56 -0500
Subject: [PATCH 012/276] Add BetterComboBox
---
StabilityMatrix.Avalonia/App.axaml | 1 +
.../Controls/BetterComboBox.cs | 41 ++++
.../ControlThemes/BetterComboBoxStyles.axaml | 202 ++++++++++++++++++
3 files changed, 244 insertions(+)
create mode 100644 StabilityMatrix.Avalonia/Controls/BetterComboBox.cs
create mode 100644 StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index f54dc933c..afeea8697 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -24,6 +24,7 @@
+
diff --git a/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs b/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs
new file mode 100644
index 000000000..7209879cc
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs
@@ -0,0 +1,41 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+public class BetterComboBox : ComboBox
+{
+ // protected override Type StyleKeyOverride { get; } = typeof(CheckBox);
+
+ public static readonly DirectProperty SelectionBoxItemTemplateProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(SelectionBoxItemTemplate),
+ v => v.SelectionBoxItemTemplate,
+ (x, v) => x.SelectionBoxItemTemplate = v
+ );
+
+ private IDataTemplate? _selectionBoxItemTemplate;
+
+ public IDataTemplate? SelectionBoxItemTemplate
+ {
+ get => _selectionBoxItemTemplate;
+ private set => SetAndRaise(SelectionBoxItemTemplateProperty, ref _selectionBoxItemTemplate, value);
+ }
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ if (e.NameScope.Find("ContentPresenter") is { } contentPresenter)
+ {
+ if (SelectionBoxItemTemplate is { } template)
+ {
+ contentPresenter.ContentTemplate = template;
+ }
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
new file mode 100644
index 000000000..887299ca6
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 1afe3e6347a48a5d7a22dd4507ccfadfaf99db13 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sat, 16 Dec 2023 21:12:59 -0500
Subject: [PATCH 013/276] Add improved model card combo box styles
---
.../Controls/Inference/ModelCard.axaml | 117 ++----------------
.../DesignData/DesignData.cs | 38 ++++++
.../Models/Database/LocalModelFile.cs | 17 ++-
3 files changed, 67 insertions(+), 105 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
index 7bf995a87..4e5a3ed24 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
@@ -30,112 +30,21 @@
VerticalAlignment="Center"
Text="{x:Static lang:Resources.Label_Model}"
TextAlignment="Left" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ SelectedItem="{Binding SelectedModel}"/>
+
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index 3515089c5..e49fe3033 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -23,6 +23,7 @@
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
+using StabilityMatrix.Core.Animation;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
@@ -41,9 +42,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base;
/// This includes a progress reporter, image output view model, and generation virtual methods.
///
[SuppressMessage("ReSharper", "VirtualMemberNeverOverridden.Global")]
-public abstract partial class InferenceGenerationViewModelBase
- : InferenceTabViewModelBase,
- IImageGalleryComponent
+public abstract partial class InferenceGenerationViewModelBase : InferenceTabViewModelBase, IImageGalleryComponent
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -92,20 +91,14 @@ protected Task WriteOutputImageAsync(
ImageGenerationEventArgs args,
int batchNum = 0,
int batchTotal = 0,
- bool isGrid = false
+ bool isGrid = false,
+ string fileExtension = "png"
)
{
var defaultOutputDir = settingsManager.ImagesInferenceDirectory;
defaultOutputDir.Create();
- return WriteOutputImageAsync(
- imageStream,
- defaultOutputDir,
- args,
- batchNum,
- batchTotal,
- isGrid
- );
+ return WriteOutputImageAsync(imageStream, defaultOutputDir, args, batchNum, batchTotal, isGrid, fileExtension);
}
///
@@ -117,7 +110,8 @@ protected async Task WriteOutputImageAsync(
ImageGenerationEventArgs args,
int batchNum = 0,
int batchTotal = 0,
- bool isGrid = false
+ bool isGrid = false,
+ string fileExtension = "png"
)
{
var formatTemplateStr = settingsManager.Settings.InferenceOutputImageFileNameFormat;
@@ -136,10 +130,7 @@ protected async Task WriteOutputImageAsync(
)
{
// Fallback to default
- Logger.Warn(
- "Failed to parse format template: {FormatTemplate}, using default",
- formatTemplateStr
- );
+ Logger.Warn("Failed to parse format template: {FormatTemplate}, using default", formatTemplateStr);
format = FileNameFormat.Parse(FileNameFormat.DefaultTemplate, formatProvider);
}
@@ -155,7 +146,7 @@ protected async Task WriteOutputImageAsync(
}
var fileName = format.GetFileName();
- var file = outputDir.JoinFile($"{fileName}.png");
+ var file = outputDir.JoinFile($"{fileName}.{fileExtension}");
// Until the file is free, keep adding _{i} to the end
for (var i = 0; i < 100; i++)
@@ -163,14 +154,14 @@ protected async Task WriteOutputImageAsync(
if (!file.Exists)
break;
- file = outputDir.JoinFile($"{fileName}_{i + 1}.png");
+ file = outputDir.JoinFile($"{fileName}_{i + 1}.{fileExtension}");
}
// If that fails, append an 7-char uuid
if (file.Exists)
{
var uuid = Guid.NewGuid().ToString("N")[..7];
- file = outputDir.JoinFile($"{fileName}_{uuid}.png");
+ file = outputDir.JoinFile($"{fileName}_{uuid}.{fileExtension}");
}
await using var fileStream = file.Info.OpenWrite();
@@ -200,11 +191,7 @@ protected async Task UploadInputImages(ComfyClient client)
{
var uploadName = await image.GetHashGuidFileNameAsync();
- Logger.Debug(
- "Uploading image {FileName} as {UploadName}",
- localFile.Name,
- uploadName
- );
+ Logger.Debug("Uploading image {FileName} as {UploadName}", localFile.Name, uploadName);
// For pngs, strip metadata since Pillow can't handle some valid files?
if (localFile.Info.Extension.Equals(".png", StringComparison.OrdinalIgnoreCase))
@@ -228,10 +215,7 @@ protected async Task UploadInputImages(ComfyClient client)
/// Runs a generation task
///
/// Thrown if args.Parameters or args.Project are null
- protected async Task RunGeneration(
- ImageGenerationEventArgs args,
- CancellationToken cancellationToken
- )
+ protected async Task RunGeneration(ImageGenerationEventArgs args, CancellationToken cancellationToken)
{
var client = args.Client;
var nodes = args.Nodes;
@@ -311,32 +295,18 @@ CancellationToken cancellationToken
{
Logger.Warn(e, "Comfy node exception while queuing prompt");
await DialogHelper
- .CreateJsonDialog(
- e.JsonData,
- "Comfy Error",
- "Node execution encountered an error"
- )
+ .CreateJsonDialog(e.JsonData, "Comfy Error", "Node execution encountered an error")
.ShowAsync();
return;
}
// Get output images
- var imageOutputs = await client.GetImagesForExecutedPromptAsync(
- promptTask.Id,
- cancellationToken
- );
-
- if (
- !imageOutputs.TryGetValue(args.OutputNodeNames[0], out var images)
- || images is not { Count: > 0 }
- )
+ var imageOutputs = await client.GetImagesForExecutedPromptAsync(promptTask.Id, cancellationToken);
+
+ if (!imageOutputs.TryGetValue(args.OutputNodeNames[0], out var images) || images is not { Count: > 0 })
{
// No images match
- notificationService.Show(
- "No output",
- "Did not receive any output images",
- NotificationType.Warning
- );
+ notificationService.Show("No output", "Did not receive any output images", NotificationType.Warning);
return;
}
@@ -369,10 +339,7 @@ await DialogHelper
///
/// Handles image output metadata for generation runs
///
- private async Task ProcessOutputImages(
- IReadOnlyCollection images,
- ImageGenerationEventArgs args
- )
+ private async Task ProcessOutputImages(IReadOnlyCollection images, ImageGenerationEventArgs args)
{
var client = args.Client;
@@ -395,10 +362,7 @@ ImageGenerationEventArgs args
var project = args.Project!;
// Lock seed
- project.TryUpdateModel(
- "Seed",
- model => model with { IsRandomizeEnabled = false }
- );
+ project.TryUpdateModel("Seed", model => model with { IsRandomizeEnabled = false });
// Seed and batch override for batches
if (images.Count > 1 && project.ProjectType is InferenceProjectType.TextToImage)
@@ -433,6 +397,29 @@ ImageGenerationEventArgs args
outputImages.Add(new ImageSource(filePath));
EventManager.Instance.OnImageFileAdded(filePath);
}
+ else if (comfyImage.FileName.EndsWith(".webp"))
+ {
+ // Write using generated name
+ var webpFilePath = await WriteOutputImageAsync(
+ new MemoryStream(imageArray),
+ args,
+ i + 1,
+ images.Count,
+ fileExtension: Path.GetExtension(comfyImage.FileName).Replace(".", "")
+ );
+
+ // convert to gif
+ await GifConverter.ConvertWebpToGif(webpFilePath);
+ var gifFilePath = webpFilePath.ToString().Replace(".webp", ".gif");
+ if (File.Exists(gifFilePath))
+ {
+ // delete webp
+ File.Delete(webpFilePath);
+ }
+
+ outputImages.Add(new ImageSource(gifFilePath));
+ EventManager.Instance.OnImageFileAdded(gifFilePath);
+ }
else
{
// Write using generated name
@@ -440,7 +427,8 @@ ImageGenerationEventArgs args
new MemoryStream(imageArray),
args,
i + 1,
- images.Count
+ images.Count,
+ fileExtension: Path.GetExtension(comfyImage.FileName).Replace(".", "")
);
outputImages.Add(new ImageSource(filePath));
@@ -456,25 +444,14 @@ ImageGenerationEventArgs args
var project = args.Project!;
// Lock seed
- project.TryUpdateModel(
- "Seed",
- model => model with { IsRandomizeEnabled = false }
- );
+ project.TryUpdateModel("Seed", model => model with { IsRandomizeEnabled = false });
var grid = ImageProcessor.CreateImageGrid(loadedImages);
var gridBytes = grid.Encode().ToArray();
- var gridBytesWithMetadata = PngDataHelper.AddMetadata(
- gridBytes,
- args.Parameters!,
- args.Project!
- );
+ var gridBytesWithMetadata = PngDataHelper.AddMetadata(gridBytes, args.Parameters!, args.Project!);
// Save to disk
- var gridPath = await WriteOutputImageAsync(
- new MemoryStream(gridBytesWithMetadata),
- args,
- isGrid: true
- );
+ var gridPath = await WriteOutputImageAsync(new MemoryStream(gridBytesWithMetadata), args, isGrid: true);
// Insert to start of images
var gridImage = new ImageSource(gridPath);
@@ -497,10 +474,7 @@ ImageGenerationEventArgs args
///
/// Implementation for Generate Image
///
- protected virtual Task GenerateImageImpl(
- GenerateOverrides overrides,
- CancellationToken cancellationToken
- )
+ protected virtual Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
@@ -511,10 +485,7 @@ CancellationToken cancellationToken
/// Optional overrides (side buttons)
/// Cancellation token
[RelayCommand(IncludeCancelCommand = true, FlowExceptionsToTaskScheduler = true)]
- private async Task GenerateImage(
- GenerateFlags options = default,
- CancellationToken cancellationToken = default
- )
+ private async Task GenerateImage(GenerateFlags options = default, CancellationToken cancellationToken = default)
{
var overrides = GenerateOverrides.FromFlags(options);
@@ -555,21 +526,19 @@ protected virtual void OnPreviewImageReceived(object? sender, ComfyWebSocketImag
/// Handles the progress update received event from the websocket.
/// Updates the progress view model.
///
- protected virtual void OnProgressUpdateReceived(
- object? sender,
- ComfyProgressUpdateEventArgs args
- )
+ protected virtual void OnProgressUpdateReceived(object? sender, ComfyProgressUpdateEventArgs args)
{
- Dispatcher.UIThread.Post(() =>
- {
- OutputProgress.Value = args.Value;
- OutputProgress.Maximum = args.Maximum;
- OutputProgress.IsIndeterminate = false;
+ Dispatcher
+ .UIThread
+ .Post(() =>
+ {
+ OutputProgress.Value = args.Value;
+ OutputProgress.Maximum = args.Maximum;
+ OutputProgress.IsIndeterminate = false;
- OutputProgress.Text =
- $"({args.Value} / {args.Maximum})"
- + (args.RunningNode != null ? $" {args.RunningNode}" : "");
- });
+ OutputProgress.Text =
+ $"({args.Value} / {args.Maximum})" + (args.RunningNode != null ? $" {args.RunningNode}" : "");
+ });
}
private void AttachRunningNodeChangedHandler(ComfyTask comfyTask)
@@ -594,13 +563,15 @@ protected virtual void OnRunningNodeChanged(object? sender, string? nodeName)
return;
}
- Dispatcher.UIThread.Post(() =>
- {
- OutputProgress.IsIndeterminate = true;
- OutputProgress.Value = 100;
- OutputProgress.Maximum = 100;
- OutputProgress.Text = nodeName;
- });
+ Dispatcher
+ .UIThread
+ .Post(() =>
+ {
+ OutputProgress.IsIndeterminate = true;
+ OutputProgress.Value = 100;
+ OutputProgress.Maximum = 100;
+ OutputProgress.Text = nodeName;
+ });
}
public class ImageGenerationEventArgs : EventArgs
@@ -628,11 +599,7 @@ public static implicit operator ModuleApplyStepEventArgs(BuildPromptEventArgs ar
overrides[typeof(HiresFixModule)] = args.Overrides.IsHiresFixEnabled.Value;
}
- return new ModuleApplyStepEventArgs
- {
- Builder = args.Builder,
- IsEnabledOverrides = overrides
- };
+ return new ModuleApplyStepEventArgs { Builder = args.Builder, IsEnabledOverrides = overrides };
}
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
index 965b787fc..fdd9a520a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
@@ -28,24 +28,16 @@ public abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
- private static readonly Type[] SerializerIgnoredTypes =
- {
- typeof(ICommand),
- typeof(IRelayCommand)
- };
+ private static readonly Type[] SerializerIgnoredTypes = { typeof(ICommand), typeof(IRelayCommand) };
private static readonly string[] SerializerIgnoredNames = { nameof(HasErrors) };
- private static readonly JsonSerializerOptions SerializerOptions =
- new() { IgnoreReadOnlyProperties = true };
+ private static readonly JsonSerializerOptions SerializerOptions = new() { IgnoreReadOnlyProperties = true };
private static bool ShouldIgnoreProperty(PropertyInfo property)
{
// Skip if read-only and not IJsonLoadableState
- if (
- property.SetMethod is null
- && !typeof(IJsonLoadableState).IsAssignableFrom(property.PropertyType)
- )
+ if (property.SetMethod is null && !typeof(IJsonLoadableState).IsAssignableFrom(property.PropertyType))
{
Logger.ConditionalTrace("Skipping {Property} - read-only", property.Name);
return true;
@@ -107,11 +99,7 @@ public virtual void LoadStateFromJsonObject(JsonObject state)
{
// Get all of our properties using reflection
var properties = GetType().GetProperties();
- Logger.ConditionalTrace(
- "Serializing {Type} with {Count} properties",
- GetType(),
- properties.Length
- );
+ Logger.ConditionalTrace("Serializing {Type} with {Count} properties", GetType(), properties.Length);
foreach (var property in properties)
{
@@ -119,9 +107,7 @@ public virtual void LoadStateFromJsonObject(JsonObject state)
// If JsonPropertyName provided, use that as the key
if (
- property
- .GetCustomAttributes(typeof(JsonPropertyNameAttribute), true)
- .FirstOrDefault()
+ property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), true).FirstOrDefault()
is JsonPropertyNameAttribute jsonPropertyName
)
{
@@ -168,10 +154,7 @@ is JsonPropertyNameAttribute jsonPropertyName
if (property.GetValue(this) is not IJsonLoadableState propertyValue)
{
// If null, it must have a default constructor
- if (
- property.PropertyType.GetConstructor(Type.EmptyTypes)
- is not { } constructorInfo
- )
+ if (property.PropertyType.GetConstructor(Type.EmptyTypes) is not { } constructorInfo)
{
throw new InvalidOperationException(
$"Property {property.Name} is IJsonLoadableState but current object is null and has no default constructor"
@@ -188,11 +171,7 @@ is not { } constructorInfo
}
else
{
- Logger.ConditionalTrace(
- "Loading {Property} ({Type})",
- property.Name,
- property.PropertyType
- );
+ Logger.ConditionalTrace("Loading {Property} ({Type})", property.Name, property.PropertyType);
var propertyValue = value.Deserialize(property.PropertyType, SerializerOptions);
property.SetValue(this, propertyValue);
@@ -216,11 +195,7 @@ public virtual JsonObject SaveStateToJsonObject()
{
// Get all of our properties using reflection.
var properties = GetType().GetProperties();
- Logger.ConditionalTrace(
- "Serializing {Type} with {Count} properties",
- GetType(),
- properties.Length
- );
+ Logger.ConditionalTrace("Serializing {Type} with {Count} properties", GetType(), properties.Length);
// Create a JSON object to store the state.
var state = new JsonObject();
@@ -237,9 +212,7 @@ public virtual JsonObject SaveStateToJsonObject()
// If JsonPropertyName provided, use that as the key.
if (
- property
- .GetCustomAttributes(typeof(JsonPropertyNameAttribute), true)
- .FirstOrDefault()
+ property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), true).FirstOrDefault()
is JsonPropertyNameAttribute jsonPropertyName
)
{
@@ -270,11 +243,7 @@ is JsonPropertyNameAttribute jsonPropertyName
}
else
{
- Logger.ConditionalTrace(
- "Serializing {Property} ({Type})",
- property.Name,
- property.PropertyType
- );
+ Logger.ConditionalTrace("Serializing {Property} ({Type})", property.Name, property.PropertyType);
var value = property.GetValue(this);
if (value is not null)
{
@@ -297,8 +266,7 @@ public virtual void LoadStateFromJsonObject(JsonObject state, int version)
protected static JsonObject SerializeModel(T model)
{
var node = JsonSerializer.SerializeToNode(model);
- return node?.AsObject()
- ?? throw new NullReferenceException("Failed to serialize state to JSON object.");
+ return node?.AsObject() ?? throw new NullReferenceException("Failed to serialize state to JSON object.");
}
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
index 34253d7be..f68934004 100644
--- a/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
@@ -7,9 +7,9 @@
using Avalonia.Threading;
using AvaloniaEdit.Document;
using CommunityToolkit.Mvvm.ComponentModel;
+using NLog;
using Nito.AsyncEx;
using Nito.AsyncEx.Synchronous;
-using NLog;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Processes;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
index 5cab55c20..8a5dae665 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
@@ -126,10 +126,7 @@ private async Task InstallUpdate()
ShowProgressBar = true;
IsProgressIndeterminate = true;
- UpdateText = string.Format(
- Resources.TextTemplate_UpdatingPackage,
- Resources.Label_StabilityMatrix
- );
+ UpdateText = string.Format(Resources.TextTemplate_UpdatingPackage, Resources.Label_StabilityMatrix);
try
{
@@ -173,10 +170,7 @@ await updateHelper.DownloadUpdate(
UpdateText = "Getting a few things ready...";
await using (new MinimumDelay(500, 1000))
{
- Process.Start(
- UpdateHelper.ExecutablePath,
- $"--wait-for-exit-pid {Environment.ProcessId}"
- );
+ Process.Start(UpdateHelper.ExecutablePath, $"--wait-for-exit-pid {Environment.ProcessId}");
}
UpdateText = "Update complete. Restarting Stability Matrix in 3 seconds...";
@@ -189,7 +183,7 @@ await updateHelper.DownloadUpdate(
App.Shutdown();
}
-
+
internal async Task GetReleaseNotes(string changelogUrl)
{
using var client = httpClientFactory.CreateClient();
@@ -262,15 +256,10 @@ out var version
// Join all blocks until and excluding the current version
// If we're on a pre-release, include the current release
- var currentVersionBlock = results.FindIndex(
- x => x.Version == currentVersion.WithoutMetadata()
- );
+ var currentVersionBlock = results.FindIndex(x => x.Version == currentVersion.WithoutMetadata());
// For mismatching build metadata, add one
- if (
- currentVersionBlock != -1
- && results[currentVersionBlock].Version?.Metadata != currentVersion.Metadata
- )
+ if (currentVersionBlock != -1 && results[currentVersionBlock].Version?.Metadata != currentVersion.Metadata)
{
currentVersionBlock++;
}
@@ -278,9 +267,7 @@ out var version
// Support for previous pre-release without changelogs
if (currentVersionBlock == -1)
{
- currentVersionBlock = results.FindIndex(
- x => x.Version == currentVersion.WithoutPrereleaseOrMetadata()
- );
+ currentVersionBlock = results.FindIndex(x => x.Version == currentVersion.WithoutPrereleaseOrMetadata());
// Add 1 if found to include the current release
if (currentVersionBlock != -1)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index 3a6c5f5d5..131836913 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -7,18 +7,20 @@
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
using NLog;
-using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
-using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
using StabilityMatrix.Avalonia.ViewModels.Inference.Video;
using StabilityMatrix.Avalonia.Views.Inference;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Services;
#pragma warning disable CS0657 // Not a valid attribute location for this declaration
@@ -28,9 +30,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceImageToVideoView), persistent: true)]
[ManagedService]
[Transient]
-public class InferenceImageToVideoViewModel
- : InferenceGenerationViewModelBase,
- IParametersLoadableState
+public partial class InferenceImageToVideoViewModel : InferenceGenerationViewModelBase, IParametersLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -61,6 +61,10 @@ public class InferenceImageToVideoViewModel
[JsonPropertyName("VideoOutput")]
public VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel { get; }
+ [ObservableProperty]
+ [JsonIgnore]
+ private string outputUri;
+
public InferenceImageToVideoViewModel(
INotificationService notificationService,
IInferenceClientManager inferenceClientManager,
@@ -86,6 +90,10 @@ IModelIndexService modelIndexService
samplerCard.IsCfgScaleEnabled = true;
samplerCard.IsSamplerSelectionEnabled = true;
samplerCard.IsSchedulerSelectionEnabled = true;
+ samplerCard.CfgScale = 2.5d;
+ samplerCard.SelectedSampler = ComfySampler.Euler;
+ samplerCard.SelectedScheduler = ComfyScheduler.Karras;
+ samplerCard.IsDenoiseStrengthEnabled = true;
});
BatchSizeCardViewModel = vmFactory.Get();
@@ -105,6 +113,19 @@ IModelIndexService modelIndexService
);
}
+ public override void OnLoaded()
+ {
+ EventManager.Instance.ImageFileAdded += OnImageFileAdded;
+ }
+
+ private void OnImageFileAdded(object? sender, FilePath e)
+ {
+ if (!e.Extension.Contains("gif"))
+ return;
+
+ OutputUri = e;
+ }
+
///
protected override void BuildPrompt(BuildPromptEventArgs args)
{
@@ -122,19 +143,19 @@ protected override void BuildPrompt(BuildPromptEventArgs args)
ModelCardViewModel.ApplyStep(args);
// Setup latent from image
- var imageLoad = builder.Nodes.AddTypedNode(
- new ComfyNodeBuilder.LoadImage
- {
- Name = builder.Nodes.GetUniqueName("ControlNet_LoadImage"),
- Image =
- SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
- ?? throw new ValidationException()
- }
- );
+ var imageLoad = builder
+ .Nodes
+ .AddTypedNode(
+ new ComfyNodeBuilder.LoadImage
+ {
+ Name = builder.Nodes.GetUniqueName("ControlNet_LoadImage"),
+ Image =
+ SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException()
+ }
+ );
builder.Connections.Primary = imageLoad.Output1;
- builder.Connections.PrimarySize =
- SelectImageCardViewModel.CurrentBitmapSize
- ?? new Size(SamplerCardViewModel.Width, SamplerCardViewModel.Height);
+ builder.Connections.PrimarySize = SelectImageCardViewModel.CurrentBitmapSize;
// Setup img2vid stuff
// Set width & height from SamplerCard
@@ -159,10 +180,7 @@ protected override IEnumerable GetInputImages()
}
///
- protected override async Task GenerateImageImpl(
- GenerateOverrides overrides,
- CancellationToken cancellationToken
- )
+ protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
{
if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)
{
@@ -184,11 +202,7 @@ CancellationToken cancellationToken
{
var seed = seedCard.Seed + i;
- var buildPromptArgs = new BuildPromptEventArgs
- {
- Overrides = overrides,
- SeedOverride = seed
- };
+ var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed };
BuildPrompt(buildPromptArgs);
var generationArgs = new ImageGenerationEventArgs
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
index 4531d1b08..2ca1dbd81 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
@@ -107,9 +107,7 @@ protected override void BuildPrompt(BuildPromptEventArgs args)
// If upscale is enabled, add another upscale group
if (IsUpscaleEnabled)
{
- var upscaleSize = builder.Connections.PrimarySize.WithScale(
- UpscalerCardViewModel.Scale
- );
+ var upscaleSize = builder.Connections.PrimarySize.WithScale(UpscalerCardViewModel.Scale);
// Build group
builder.Connections.Primary = builder
@@ -144,10 +142,7 @@ protected override void BuildPrompt(BuildPromptEventArgs args)
}
///
- protected override async Task GenerateImageImpl(
- GenerateOverrides overrides,
- CancellationToken cancellationToken
- )
+ protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
{
if (!ClientManager.IsConnected)
{
@@ -174,10 +169,7 @@ CancellationToken cancellationToken
Client = ClientManager.Client,
Nodes = buildPromptArgs.Builder.ToNodeDictionary(),
OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),
- Parameters = new GenerationParameters
- {
- ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name,
- },
+ Parameters = new GenerationParameters { ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name, },
Project = InferenceProjectDocument.FromLoadable(this)
};
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
index 31ce98432..764f7a07e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
@@ -26,9 +26,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceTextToImageView), IsPersistent = true)]
[ManagedService]
[Transient]
-public class InferenceTextToImageViewModel
- : InferenceGenerationViewModelBase,
- IParametersLoadableState
+public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, IParametersLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -162,22 +160,19 @@ protected override void BuildPrompt(BuildPromptEventArgs args)
///
protected override IEnumerable GetInputImages()
{
- var samplerImages = SamplerCardViewModel.ModulesCardViewModel.Cards
+ var samplerImages = SamplerCardViewModel
+ .ModulesCardViewModel
+ .Cards
.OfType()
.SelectMany(m => m.GetInputImages());
- var moduleImages = ModulesCardViewModel.Cards
- .OfType()
- .SelectMany(m => m.GetInputImages());
+ var moduleImages = ModulesCardViewModel.Cards.OfType().SelectMany(m => m.GetInputImages());
return samplerImages.Concat(moduleImages);
}
///
- protected override async Task GenerateImageImpl(
- GenerateOverrides overrides,
- CancellationToken cancellationToken
- )
+ protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
{
// Validate the prompts
if (!await PromptCardViewModel.ValidatePrompts())
@@ -205,11 +200,7 @@ CancellationToken cancellationToken
{
var seed = seedCard.Seed + i;
- var buildPromptArgs = new BuildPromptEventArgs
- {
- Overrides = overrides,
- SeedOverride = seed
- };
+ var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed };
BuildPrompt(buildPromptArgs);
var generationArgs = new ImageGenerationEventArgs
@@ -270,16 +261,12 @@ public override void LoadStateFromJsonObject(JsonObject state, int version)
if (state.TryGetPropertyValue("HiresSampler", out var hiresSamplerState))
{
- module
- .GetCard()
- .LoadStateFromJsonObject(hiresSamplerState!.AsObject());
+ module.GetCard().LoadStateFromJsonObject(hiresSamplerState!.AsObject());
}
if (state.TryGetPropertyValue("HiresUpscaler", out var hiresUpscalerState))
{
- module
- .GetCard()
- .LoadStateFromJsonObject(hiresUpscalerState!.AsObject());
+ module.GetCard().LoadStateFromJsonObject(hiresUpscalerState!.AsObject());
}
});
@@ -289,17 +276,17 @@ public override void LoadStateFromJsonObject(JsonObject state, int version)
if (state.TryGetPropertyValue("Upscaler", out var upscalerState))
{
- module
- .GetCard()
- .LoadStateFromJsonObject(upscalerState!.AsObject());
+ module.GetCard().LoadStateFromJsonObject(upscalerState!.AsObject());
}
});
// Add FreeU to sampler
- SamplerCardViewModel.ModulesCardViewModel.AddModule(module =>
- {
- module.IsEnabled = state.GetPropertyValueOrDefault("IsFreeUEnabled");
- });
+ SamplerCardViewModel
+ .ModulesCardViewModel
+ .AddModule(module =>
+ {
+ module.IsEnabled = state.GetPropertyValueOrDefault("IsFreeUEnabled");
+ });
}
base.LoadStateFromJsonObject(state);
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
index 51a994986..2a4f652fe 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
@@ -10,8 +10,8 @@
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models;
-using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
@@ -42,6 +42,7 @@ public partial class ModelCardViewModel(IInferenceClientManager clientManager)
private bool disableSettings;
public IInferenceClientManager ClientManager { get; } = clientManager;
+
///
public virtual void ApplyStep(ModuleApplyStepEventArgs e)
{
@@ -50,9 +51,7 @@ public virtual void ApplyStep(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.CheckpointLoaderSimple
{
Name = "CheckpointLoader",
- CkptName =
- SelectedModel?.RelativePath
- ?? throw new ValidationException("Model not selected")
+ CkptName = SelectedModel?.RelativePath ?? throw new ValidationException("Model not selected")
}
);
@@ -85,9 +84,7 @@ public virtual void ApplyStep(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.VAELoader
{
Name = "VAELoader",
- VaeName =
- SelectedVae?.RelativePath
- ?? throw new ValidationException("VAE enabled but not selected")
+ VaeName = SelectedVae?.RelativePath ?? throw new ValidationException("VAE enabled but not selected")
}
);
@@ -147,19 +144,14 @@ public void LoadStateFromParameters(GenerationParameters parameters)
model = currentModels.FirstOrDefault(
m =>
m.Local?.ConnectedModelInfo?.Hashes.SHA256 is { } sha256
- && sha256.StartsWith(
- parameters.ModelHash,
- StringComparison.InvariantCultureIgnoreCase
- )
+ && sha256.StartsWith(parameters.ModelHash, StringComparison.InvariantCultureIgnoreCase)
);
}
else
{
// Name matches
model = currentModels.FirstOrDefault(m => m.RelativePath.EndsWith(paramsModelName));
- model ??= currentModels.FirstOrDefault(
- m => m.ShortDisplayName.StartsWith(paramsModelName)
- );
+ model ??= currentModels.FirstOrDefault(m => m.ShortDisplayName.StartsWith(paramsModelName));
}
if (model is not null)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
index 4f4e71915..2433debc1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
@@ -42,9 +42,8 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
{
Name = e.Nodes.GetUniqueName("ControlNet_LoadImage"),
Image =
- card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(
- "Inference"
- ) ?? throw new ValidationException("No ImageSource")
+ card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException("No ImageSource")
}
);
@@ -52,9 +51,7 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.ControlNetLoader
{
Name = e.Nodes.GetUniqueName("ControlNetLoader"),
- ControlNetName =
- card.SelectedModel?.FileName
- ?? throw new ValidationException("No SelectedModel"),
+ ControlNetName = card.SelectedModel?.FileName ?? throw new ValidationException("No SelectedModel"),
}
);
@@ -64,10 +61,8 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
Name = e.Nodes.GetUniqueName("ControlNetApply"),
Image = imageLoad.Output1,
ControlNet = controlNetLoader.Output,
- Positive =
- e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
- Negative =
- e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
+ Positive = e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
+ Negative = e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
Strength = card.Strength,
StartPercent = card.StartPercent,
EndPercent = card.EndPercent,
@@ -93,10 +88,7 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
}
);
- e.Temp.RefinerConditioning = (
- controlNetRefinerApply.Output1,
- controlNetRefinerApply.Output2
- );
+ e.Temp.RefinerConditioning = (controlNetRefinerApply.Output1, controlNetRefinerApply.Output2);
}
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
index c1c66a722..912a34f1f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
@@ -54,11 +54,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
private readonly ILogger logger;
public override string Title => Resources.Label_OutputsPageTitle;
- public override IconSource IconSource =>
- new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
+ public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
- public SourceCache OutputsCache { get; } =
- new(file => file.AbsolutePath);
+ public SourceCache OutputsCache { get; } = new(file => file.AbsolutePath);
public IObservableCollection Outputs { get; set; } =
new ObservableCollectionExtended();
@@ -88,8 +86,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private bool isConsolidating;
- public bool CanShowOutputTypes =>
- SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
+ public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
public string NumImagesSelected =>
NumItemsSelected == 1
@@ -163,10 +160,7 @@ public override void OnLoaded()
GetOutputs(path);
}
- partial void OnSelectedCategoryChanged(
- PackageOutputCategory? oldValue,
- PackageOutputCategory? newValue
- )
+ partial void OnSelectedCategoryChanged(PackageOutputCategory? oldValue, PackageOutputCategory? newValue)
{
if (oldValue == newValue || newValue == null)
return;
@@ -217,13 +211,11 @@ public async Task ShowImageDialog(OutputImageViewModel item)
var vm = new ImageViewerViewModel { ImageSource = image, LocalImageFile = item.ImageFile };
using var onNext = Observable
- .FromEventPattern(
- vm,
- nameof(ImageViewerViewModel.NavigationRequested)
- )
+ .FromEventPattern(vm, nameof(ImageViewerViewModel.NavigationRequested))
.Subscribe(ctx =>
{
- Dispatcher.UIThread
+ Dispatcher
+ .UIThread
.InvokeAsync(async () =>
{
var sender = (ImageViewerViewModel)ctx.Sender!;
@@ -232,9 +224,7 @@ public async Task ShowImageDialog(OutputImageViewModel item)
if (newIndex >= 0 && newIndex < Outputs.Count)
{
var newImage = Outputs[newIndex];
- var newImageSource = new ImageSource(
- new FilePath(newImage.ImageFile.AbsolutePath)
- );
+ var newImageSource = new ImageSource(new FilePath(newImage.ImageFile.AbsolutePath));
// Preload
await newImageSource.GetBitmapAsync();
@@ -386,14 +376,16 @@ public async Task DeleteAllSelected()
public async Task ConsolidateImages()
{
var stackPanel = new StackPanel();
- stackPanel.Children.Add(
- new TextBlock
- {
- Text = Resources.Label_ConsolidateExplanation,
- TextWrapping = TextWrapping.Wrap,
- Margin = new Thickness(0, 8, 0, 16)
- }
- );
+ stackPanel
+ .Children
+ .Add(
+ new TextBlock
+ {
+ Text = Resources.Label_ConsolidateExplanation,
+ TextWrapping = TextWrapping.Wrap,
+ Margin = new Thickness(0, 8, 0, 16)
+ }
+ );
foreach (var category in Categories)
{
if (category.Name == "Shared Output Folder")
@@ -401,15 +393,17 @@ public async Task ConsolidateImages()
continue;
}
- stackPanel.Children.Add(
- new CheckBox
- {
- Content = $"{category.Name} ({category.Path})",
- IsChecked = true,
- Margin = new Thickness(0, 8, 0, 0),
- Tag = category.Path
- }
- );
+ stackPanel
+ .Children
+ .Add(
+ new CheckBox
+ {
+ Content = $"{category.Name} ({category.Path})",
+ IsChecked = true,
+ Margin = new Thickness(0, 8, 0, 0),
+ Tag = category.Path
+ }
+ );
}
var confirmationDialog = new BetterContentDialog
@@ -430,25 +424,14 @@ public async Task ConsolidateImages()
Directory.CreateDirectory(settingsManager.ConsolidatedImagesDirectory);
- foreach (
- var category in stackPanel.Children.OfType().Where(c => c.IsChecked == true)
- )
+ foreach (var category in stackPanel.Children.OfType().Where(c => c.IsChecked == true))
{
- if (
- string.IsNullOrWhiteSpace(category.Tag?.ToString())
- || !Directory.Exists(category.Tag?.ToString())
- )
+ if (string.IsNullOrWhiteSpace(category.Tag?.ToString()) || !Directory.Exists(category.Tag?.ToString()))
continue;
var directory = category.Tag.ToString();
- foreach (
- var path in Directory.EnumerateFiles(
- directory,
- "*.png",
- SearchOption.AllDirectories
- )
- )
+ foreach (var path in Directory.EnumerateFiles(directory, "*.png", SearchOption.AllDirectories))
{
try
{
@@ -499,10 +482,7 @@ private void GetOutputs(string directory)
if (
!Directory.Exists(directory)
- && (
- SelectedCategory.Path != settingsManager.ImagesDirectory
- || SelectedOutputType != SharedOutputType.All
- )
+ && (SelectedCategory.Path != settingsManager.ImagesDirectory || SelectedOutputType != SharedOutputType.All)
)
{
Directory.CreateDirectory(directory);
@@ -534,23 +514,18 @@ private void RefreshCategories()
var previouslySelectedCategory = SelectedCategory;
- var packageCategories = settingsManager.Settings.InstalledPackages
+ var packageCategories = settingsManager
+ .Settings
+ .InstalledPackages
.Where(x => !x.UseSharedOutputFolder)
.Select(packageFactory.GetPackagePair)
.WhereNotNull()
- .Where(
- p =>
- p.BasePackage.SharedOutputFolders != null
- && p.BasePackage.SharedOutputFolders.Any()
- )
+ .Where(p => p.BasePackage.SharedOutputFolders != null && p.BasePackage.SharedOutputFolders.Any())
.Select(
pair =>
new PackageOutputCategory
{
- Path = Path.Combine(
- pair.InstalledPackage.FullPath!,
- pair.BasePackage.OutputFolderName
- ),
+ Path = Path.Combine(pair.InstalledPackage.FullPath!, pair.BasePackage.OutputFolderName),
Name = pair.InstalledPackage.DisplayName ?? ""
}
)
@@ -558,25 +533,16 @@ private void RefreshCategories()
packageCategories.Insert(
0,
- new PackageOutputCategory
- {
- Path = settingsManager.ImagesDirectory,
- Name = "Shared Output Folder"
- }
+ new PackageOutputCategory { Path = settingsManager.ImagesDirectory, Name = "Shared Output Folder" }
);
packageCategories.Insert(
1,
- new PackageOutputCategory
- {
- Path = settingsManager.ImagesInferenceDirectory,
- Name = "Inference"
- }
+ new PackageOutputCategory { Path = settingsManager.ImagesInferenceDirectory, Name = "Inference" }
);
Categories = new ObservableCollection(packageCategories);
SelectedCategory =
- Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name)
- ?? Categories.First();
+ Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name) ?? Categories.First();
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
index f88d17f71..6f3c3c4c5 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
@@ -169,7 +169,7 @@ private async Task ImportTagCsv()
var files = await storage.OpenFilePickerAsync(
new FilePickerOpenOptions
{
- FileTypeFilter = new List { new("CSV") { Patterns = ["*.csv"] } }
+ FileTypeFilter = new List { new("CSV") { Patterns = ["*.csv"] } }
}
);
diff --git a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
index 02a2fd505..92a5866c2 100644
--- a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
+++ b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
@@ -10,6 +10,7 @@
xmlns:vmInference="using:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:dock="clr-namespace:StabilityMatrix.Avalonia.Controls.Dock"
xmlns:modelsInference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference"
+ xmlns:gif="clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif"
d:DataContext="{x:Static mocks:DesignData.InferenceImageToVideoViewModel}"
d:DesignHeight="800"
d:DesignWidth="1000"
@@ -93,9 +94,9 @@
-
+ SourceUri="{Binding OutputUri}" />
+
diff --git a/StabilityMatrix.sln b/StabilityMatrix.sln
index 77cf2c6d7..ff5b52a41 100644
--- a/StabilityMatrix.sln
+++ b/StabilityMatrix.sln
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StabilityMatrix.Avalonia.Di
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StabilityMatrix.UITests", "StabilityMatrix.UITests\StabilityMatrix.UITests.csproj", "{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Gif", "Avalonia.Gif\Avalonia.Gif.csproj", "{72A73F1E-024B-4A25-AD34-626198D9527F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@ Global
{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72A73F1E-024B-4A25-AD34-626198D9527F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72A73F1E-024B-4A25-AD34-626198D9527F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72A73F1E-024B-4A25-AD34-626198D9527F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72A73F1E-024B-4A25-AD34-626198D9527F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
From a2d8040310eb27678d318678360ed74b2eb15c6c Mon Sep 17 00:00:00 2001
From: JT
Date: Sun, 17 Dec 2023 01:09:02 -0800
Subject: [PATCH 016/276] faster gif conversion
---
.../Base/InferenceGenerationViewModelBase.cs | 19 +++++---
.../InferenceImageToVideoViewModel.cs | 14 ++++++
.../Inference/InferenceImageToVideoView.axaml | 13 +++--
.../Animation/GifConverter.cs | 48 ++++++++++++++++---
.../Models/Database/LocalImageFile.cs | 20 ++------
.../StabilityMatrix.Core.csproj | 3 ++
6 files changed, 86 insertions(+), 31 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index e49fe3033..51ea578b3 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -409,13 +409,20 @@ private async Task ProcessOutputImages(IReadOnlyCollection images, I
);
// convert to gif
- await GifConverter.ConvertWebpToGif(webpFilePath);
+ var inputStream = File.OpenRead(webpFilePath);
var gifFilePath = webpFilePath.ToString().Replace(".webp", ".gif");
- if (File.Exists(gifFilePath))
- {
- // delete webp
- File.Delete(webpFilePath);
- }
+ var outputStream = File.OpenWrite(gifFilePath);
+
+ await GifConverter.ConvertAnimatedWebpToGifAsync(inputStream, outputStream);
+ await inputStream.DisposeAsync();
+ await outputStream.FlushAsync();
+ await outputStream.DisposeAsync();
+
+ // if (File.Exists(gifFilePath))
+ // {
+ // // delete webp
+ // File.Delete(webpFilePath);
+ // }
outputImages.Add(new ImageSource(gifFilePath));
EventManager.Instance.OnImageFileAdded(gifFilePath);
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index 131836913..b1e6d8384 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -65,6 +65,10 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
[JsonIgnore]
private string outputUri;
+ [ObservableProperty]
+ [JsonIgnore]
+ private bool isGenerating;
+
public InferenceImageToVideoViewModel(
INotificationService notificationService,
IInferenceClientManager inferenceClientManager,
@@ -94,6 +98,7 @@ IModelIndexService modelIndexService
samplerCard.SelectedSampler = ComfySampler.Euler;
samplerCard.SelectedScheduler = ComfyScheduler.Karras;
samplerCard.IsDenoiseStrengthEnabled = true;
+ samplerCard.DenoiseStrength = 1.0f;
});
BatchSizeCardViewModel = vmFactory.Get();
@@ -118,6 +123,11 @@ public override void OnLoaded()
EventManager.Instance.ImageFileAdded += OnImageFileAdded;
}
+ public override void OnUnloaded()
+ {
+ EventManager.Instance.ImageFileAdded -= OnImageFileAdded;
+ }
+
private void OnImageFileAdded(object? sender, FilePath e)
{
if (!e.Extension.Contains("gif"))
@@ -219,11 +229,15 @@ protected override async Task GenerateImageImpl(GenerateOverrides overrides, Can
batchArgs.Add(generationArgs);
}
+ IsGenerating = true;
+
// Run batches
foreach (var args in batchArgs)
{
await RunGeneration(args, cancellationToken);
}
+
+ IsGenerating = false;
}
///
diff --git a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
index 92a5866c2..c3497945f 100644
--- a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
+++ b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
@@ -94,10 +94,17 @@
-
-
+ IsVisible="{Binding ElementName=Dock, Path=DataContext.IsGenerating}"
+ DataContext="{Binding ImageGalleryCardViewModel}"/>
+
+
+
+
EnumerateAnimatedWebp(Stream webpSource)
{
- using var webp = new MagickImageCollection(filePath, MagickFormat.WebP);
- var path = filePath.ToString().Replace(".webp", ".gif");
- await webp.WriteAsync(path, MagickFormat.Gif).ConfigureAwait(false);
+ using var webp = new SKManagedStream(webpSource);
+ using var codec = SKCodec.Create(webp);
+
+ var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);
+
+ for (var i = 0; i < codec.FrameCount; i++)
+ {
+ using var tempSurface = new SKBitmap(info);
+
+ codec.GetFrameInfo(i, out var frameInfo);
+
+ var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);
+
+ tempSurface.TryAllocPixels(decodeInfo);
+
+ var result = codec.GetPixels(decodeInfo, tempSurface.GetPixels(), new SKCodecOptions(i));
+
+ if (result != SKCodecResult.Success)
+ throw new InvalidDataException($"Could not decode frame {i} of {codec.FrameCount}.");
+
+ using var peekPixels = tempSurface.PeekPixels();
+
+ yield return peekPixels.GetReadableBitmapData(WorkingColorSpace.Default);
+ }
+ }
+
+ public static Task ConvertAnimatedWebpToGifAsync(Stream webpSource, Stream gifOutput)
+ {
+ var gifBitmaps = EnumerateAnimatedWebp(webpSource);
+
+ return GifEncoder.EncodeAnimationAsync(
+ new AnimatedGifConfiguration(gifBitmaps, TimeSpan.FromMilliseconds(150))
+ {
+ Quantizer = OptimizedPaletteQuantizer.Wu(alphaThreshold: 0),
+ AllowDeltaFrames = true
+ },
+ gifOutput
+ );
}
}
diff --git a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
index 1abde8dd0..3f327f285 100644
--- a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
@@ -48,19 +48,9 @@ public record LocalImageFile
///
public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath);
- public (
- string? Parameters,
- string? ParametersJson,
- string? SMProject,
- string? ComfyNodes
- ) ReadMetadata()
+ public (string? Parameters, string? ParametersJson, string? SMProject, string? ComfyNodes) ReadMetadata()
{
- using var stream = new FileStream(
- AbsolutePath,
- FileMode.Open,
- FileAccess.Read,
- FileShare.Read
- );
+ using var stream = new FileStream(AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read);
using var reader = new BinaryReader(stream);
var parameters = ImageMetadata.ReadTextChunk(reader, "parameters");
@@ -79,8 +69,7 @@ public record LocalImageFile
public static LocalImageFile FromPath(FilePath filePath)
{
// TODO: Support other types
- const LocalImageFileType imageType =
- LocalImageFileType.Inference | LocalImageFileType.TextToImage;
+ const LocalImageFileType imageType = LocalImageFileType.Inference | LocalImageFileType.TextToImage;
// Get metadata
using var stream = filePath.Info.OpenRead();
@@ -115,6 +104,5 @@ public static LocalImageFile FromPath(FilePath filePath)
};
}
- public static readonly HashSet SupportedImageExtensions =
- new() { ".png", ".jpg", ".jpeg", ".webp" };
+ public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif"];
}
diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
index ab540ac68..d64996a35 100644
--- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj
+++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
@@ -29,6 +29,8 @@
+
+
@@ -51,6 +53,7 @@
+
From 280fc8c89f857610bfe69ec0001b9d3a2994a03c Mon Sep 17 00:00:00 2001
From: Ionite
Date: Sun, 17 Dec 2023 18:02:58 -0500
Subject: [PATCH 017/276] Add animated webp rendering
---
Avalonia.Gif/Avalonia.Gif.csproj | 1 +
Avalonia.Gif/GifImage.cs | 9 +-
Avalonia.Gif/GifInstance.cs | 2 +-
Avalonia.Gif/IGifInstance.cs | 15 ++
Avalonia.Gif/WebpInstance.cs | 180 ++++++++++++++++++
.../Base/InferenceGenerationViewModelBase.cs | 8 +-
.../InferenceImageToVideoViewModel.cs | 2 +-
.../Models/Database/LocalImageFile.cs | 2 +-
8 files changed, 208 insertions(+), 11 deletions(-)
create mode 100644 Avalonia.Gif/IGifInstance.cs
create mode 100644 Avalonia.Gif/WebpInstance.cs
diff --git a/Avalonia.Gif/Avalonia.Gif.csproj b/Avalonia.Gif/Avalonia.Gif.csproj
index 2d72f153d..49c6015e0 100644
--- a/Avalonia.Gif/Avalonia.Gif.csproj
+++ b/Avalonia.Gif/Avalonia.Gif.csproj
@@ -11,6 +11,7 @@
+
diff --git a/Avalonia.Gif/GifImage.cs b/Avalonia.Gif/GifImage.cs
index d53cbd194..0ce8dca84 100644
--- a/Avalonia.Gif/GifImage.cs
+++ b/Avalonia.Gif/GifImage.cs
@@ -32,7 +32,7 @@ public class GifImage : Control
IterationCount
>("IterationCount", IterationCount.Infinite);
- private GifInstance? _gifInstance;
+ private IGifInstance? _gifInstance;
public static readonly StyledProperty StretchDirectionProperty = AvaloniaProperty.Register<
GifImage,
@@ -166,7 +166,7 @@ private class CustomVisualHandler : CompositionCustomVisualHandler
{
private TimeSpan _animationElapsed;
private TimeSpan? _lastServerTime;
- private GifInstance? _currentInstance;
+ private IGifInstance? _currentInstance;
private bool _running;
public static readonly object StopMessage = new(),
@@ -184,7 +184,7 @@ public override void OnMessage(object message)
{
_running = false;
}
- else if (message is GifInstance instance)
+ else if (message is IGifInstance instance)
{
_currentInstance?.Dispose();
_currentInstance = instance;
@@ -288,7 +288,8 @@ e.NewValue is null
private void UpdateGifInstance(object source)
{
_gifInstance?.Dispose();
- _gifInstance = new GifInstance(source);
+ _gifInstance = new WebpInstance(source);
+ // _gifInstance = new GifInstance(source);
_gifInstance.IterationCount = IterationCount;
_customVisual?.SendHandlerMessage(_gifInstance);
}
diff --git a/Avalonia.Gif/GifInstance.cs b/Avalonia.Gif/GifInstance.cs
index 30e002d13..b97badaa5 100644
--- a/Avalonia.Gif/GifInstance.cs
+++ b/Avalonia.Gif/GifInstance.cs
@@ -11,7 +11,7 @@
namespace Avalonia.Gif
{
- public class GifInstance : IDisposable
+ public class GifInstance : IGifInstance
{
public IterationCount IterationCount { get; set; }
public bool AutoStart { get; private set; } = true;
diff --git a/Avalonia.Gif/IGifInstance.cs b/Avalonia.Gif/IGifInstance.cs
new file mode 100644
index 000000000..667f91636
--- /dev/null
+++ b/Avalonia.Gif/IGifInstance.cs
@@ -0,0 +1,15 @@
+using Avalonia.Animation;
+using Avalonia.Media.Imaging;
+
+namespace Avalonia.Gif;
+
+public interface IGifInstance : IDisposable
+{
+ IterationCount IterationCount { get; set; }
+ bool AutoStart { get; }
+ CancellationTokenSource CurrentCts { get; }
+ int GifFrameCount { get; }
+ PixelSize GifPixelSize { get; }
+ bool IsDisposed { get; }
+ WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed);
+}
diff --git a/Avalonia.Gif/WebpInstance.cs b/Avalonia.Gif/WebpInstance.cs
new file mode 100644
index 000000000..b92569349
--- /dev/null
+++ b/Avalonia.Gif/WebpInstance.cs
@@ -0,0 +1,180 @@
+using Avalonia.Animation;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Gif;
+
+public class WebpInstance : IGifInstance
+{
+ public IterationCount IterationCount { get; set; }
+ public bool AutoStart { get; private set; } = true;
+
+ private readonly WriteableBitmap? _targetBitmap;
+ private TimeSpan _totalTime;
+ private readonly List _frameTimes;
+ private uint _iterationCount;
+ private int _currentFrameIndex;
+
+ private SKCodec? _codec;
+
+ public CancellationTokenSource CurrentCts { get; }
+
+ internal WebpInstance(object newValue)
+ : this(
+ newValue switch
+ {
+ Stream s => s,
+ Uri u => GetStreamFromUri(u),
+ string str => GetStreamFromString(str),
+ _ => throw new InvalidDataException("Unsupported source object")
+ }
+ ) { }
+
+ public WebpInstance(string uri)
+ : this(GetStreamFromString(uri)) { }
+
+ public WebpInstance(Uri uri)
+ : this(GetStreamFromUri(uri)) { }
+
+ public WebpInstance(Stream currentStream)
+ {
+ if (!currentStream.CanSeek)
+ throw new InvalidDataException("The provided stream is not seekable.");
+
+ if (!currentStream.CanRead)
+ throw new InvalidOperationException("Can't read the stream provided.");
+
+ currentStream.Seek(0, SeekOrigin.Begin);
+
+ CurrentCts = new CancellationTokenSource();
+
+ var managedStream = new SKManagedStream(currentStream);
+ _codec = SKCodec.Create(managedStream);
+
+ var pixSize = new PixelSize(_codec.Info.Width, _codec.Info.Height);
+
+ _targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
+ GifPixelSize = pixSize;
+
+ _totalTime = TimeSpan.Zero;
+
+ _frameTimes = _codec
+ .FrameInfo
+ .Select(frame =>
+ {
+ _totalTime = _totalTime.Add(TimeSpan.FromMilliseconds(frame.Duration));
+ return _totalTime;
+ })
+ .ToList();
+
+ RenderFrame(_codec, _targetBitmap, 0);
+ }
+
+ private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index)
+ {
+ codec.GetFrameInfo(index, out var frameInfo);
+
+ var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);
+ var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);
+
+ using var frameBuffer = targetBitmap.Lock();
+
+ var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index));
+
+ if (result != SKCodecResult.Success)
+ throw new InvalidDataException($"Could not decode frame {index} of {codec.FrameCount}.");
+ }
+
+ private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index, int priorIndex)
+ {
+ codec.GetFrameInfo(index, out var frameInfo);
+
+ var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);
+ var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);
+
+ using var frameBuffer = targetBitmap.Lock();
+
+ var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index, priorIndex));
+
+ if (result != SKCodecResult.Success)
+ throw new InvalidDataException($"Could not decode frame {index} of {codec.FrameCount}.");
+ }
+
+ private static Stream GetStreamFromString(string str)
+ {
+ if (!Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var res))
+ {
+ throw new InvalidCastException("The string provided can't be converted to URI.");
+ }
+
+ return GetStreamFromUri(res);
+ }
+
+ private static Stream GetStreamFromUri(Uri uri)
+ {
+ var uriString = uri.OriginalString.Trim();
+
+ if (!uriString.StartsWith("resm") && !uriString.StartsWith("avares"))
+ {
+ return new FileStream(uriString, FileMode.Open, FileAccess.Read);
+ }
+
+ return AssetLoader.Open(uri);
+ }
+
+ public int GifFrameCount => _frameTimes.Count;
+
+ public PixelSize GifPixelSize { get; }
+
+ public void Dispose()
+ {
+ IsDisposed = true;
+ CurrentCts.Cancel();
+ _targetBitmap?.Dispose();
+ _codec?.Dispose();
+ }
+
+ public bool IsDisposed { get; private set; }
+
+ public WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed)
+ {
+ if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value)
+ {
+ return null;
+ }
+
+ if (CurrentCts.IsCancellationRequested || _targetBitmap is null)
+ {
+ return null;
+ }
+
+ var elapsedTicks = stopwatchElapsed.Ticks;
+ var timeModulus = TimeSpan.FromTicks(elapsedTicks % _totalTime.Ticks);
+ var targetFrame = _frameTimes.FirstOrDefault(x => timeModulus < x);
+ var currentFrame = _frameTimes.IndexOf(targetFrame);
+ if (currentFrame == -1)
+ currentFrame = 0;
+
+ if (_currentFrameIndex == currentFrame)
+ return _targetBitmap;
+
+ _iterationCount = (uint)(elapsedTicks / _totalTime.Ticks);
+
+ return ProcessFrameIndex(currentFrame);
+ }
+
+ internal WriteableBitmap ProcessFrameIndex(int frameIndex)
+ {
+ if (_codec is null)
+ throw new InvalidOperationException("The codec is null.");
+
+ if (_targetBitmap is null)
+ throw new InvalidOperationException("The target bitmap is null.");
+
+ RenderFrame(_codec, _targetBitmap, frameIndex, _currentFrameIndex);
+ _currentFrameIndex = frameIndex;
+
+ return _targetBitmap;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index 51ea578b3..99183592b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -409,14 +409,14 @@ private async Task ProcessOutputImages(IReadOnlyCollection images, I
);
// convert to gif
- var inputStream = File.OpenRead(webpFilePath);
+ /*var inputStream = File.OpenRead(webpFilePath);
var gifFilePath = webpFilePath.ToString().Replace(".webp", ".gif");
var outputStream = File.OpenWrite(gifFilePath);
await GifConverter.ConvertAnimatedWebpToGifAsync(inputStream, outputStream);
await inputStream.DisposeAsync();
await outputStream.FlushAsync();
- await outputStream.DisposeAsync();
+ await outputStream.DisposeAsync();*/
// if (File.Exists(gifFilePath))
// {
@@ -424,8 +424,8 @@ private async Task ProcessOutputImages(IReadOnlyCollection images, I
// File.Delete(webpFilePath);
// }
- outputImages.Add(new ImageSource(gifFilePath));
- EventManager.Instance.OnImageFileAdded(gifFilePath);
+ outputImages.Add(new ImageSource(webpFilePath));
+ EventManager.Instance.OnImageFileAdded(webpFilePath);
}
else
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index b1e6d8384..4be708a77 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -130,7 +130,7 @@ public override void OnUnloaded()
private void OnImageFileAdded(object? sender, FilePath e)
{
- if (!e.Extension.Contains("gif"))
+ if (!e.Extension.Equals(".webp", StringComparison.OrdinalIgnoreCase))
return;
OutputUri = e;
diff --git a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
index 3f327f285..a050b6c62 100644
--- a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
@@ -104,5 +104,5 @@ public static LocalImageFile FromPath(FilePath filePath)
};
}
- public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif"];
+ public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
}
From 0f52bcb3a8971582499a61996b9779813871a1cb Mon Sep 17 00:00:00 2001
From: Ionite
Date: Mon, 18 Dec 2023 15:40:53 -0500
Subject: [PATCH 018/276] Fix combobox styles
---
.../ControlThemes/BetterComboBoxStyles.axaml | 82 +++++++++++++++----
.../Styles/FAComboBoxStyles.axaml | 50 ++++++-----
2 files changed, 91 insertions(+), 41 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
index 887299ca6..e2e71476c 100644
--- a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
@@ -9,8 +9,8 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
-
-
-
-
-
-
-
-
diff --git a/StabilityMatrix.Avalonia/Styles/FAComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/FAComboBoxStyles.axaml
index d0c2007ed..d0c54b78c 100644
--- a/StabilityMatrix.Avalonia/Styles/FAComboBoxStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/FAComboBoxStyles.axaml
@@ -2,33 +2,36 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
+ xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
+ xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
+ xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
-
-
+
+
+
+
-
+
-
-
+ ColumnDefinitions="Auto,*"
+ ColumnSpacing="6"
+ RowSpacing="0">
-
+
-
-
+
+
-
+
-
+
-
+
From f6a1a241eb29c6cda399f195661e920d41704dc9 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Tue, 19 Dec 2023 15:51:06 -0500
Subject: [PATCH 019/276] Improved Model card spacing a bit
---
.../Controls/Inference/ModelCard.axaml | 20 ++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
index 4e5a3ed24..725f8b69f 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
@@ -8,6 +8,7 @@
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
+ xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
x:DataType="inference:ModelCardViewModel">
@@ -22,11 +23,14 @@
-
+
@@ -34,7 +38,7 @@
+ Margin="0,0,0,0">
@@ -60,7 +64,6 @@
-
+
From ee66f0dc492bb3242ef9ad7e15b55ebd30234cf4 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Tue, 19 Dec 2023 15:51:35 -0500
Subject: [PATCH 020/276] Made default markdown dialog size larger
---
StabilityMatrix.Avalonia/DialogHelper.cs | 47 ++++++++----------------
1 file changed, 15 insertions(+), 32 deletions(-)
diff --git a/StabilityMatrix.Avalonia/DialogHelper.cs b/StabilityMatrix.Avalonia/DialogHelper.cs
index 0cdfeabb7..7ad27bcba 100644
--- a/StabilityMatrix.Avalonia/DialogHelper.cs
+++ b/StabilityMatrix.Avalonia/DialogHelper.cs
@@ -23,15 +23,15 @@
using Refit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Helpers;
+using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Services;
using TextMateSharp.Grammars;
using Process = FuzzySharp.Process;
-using StabilityMatrix.Avalonia.Languages;
-using StabilityMatrix.Avalonia.Models;
-using StabilityMatrix.Core.Helper;
namespace StabilityMatrix.Avalonia;
@@ -48,11 +48,7 @@ public static BetterContentDialog CreateTextEntryDialog(
IReadOnlyList textFields
)
{
- return CreateTextEntryDialog(
- title,
- new MarkdownScrollViewer { Markdown = description },
- textFields
- );
+ return CreateTextEntryDialog(title, new MarkdownScrollViewer { Markdown = description }, textFields);
}
///
@@ -80,11 +76,7 @@ IReadOnlyList textFields
var grid = new Grid
{
- RowDefinitions =
- {
- new RowDefinition(GridLength.Star),
- new RowDefinition(GridLength.Auto)
- },
+ RowDefinitions = { new RowDefinition(GridLength.Star), new RowDefinition(GridLength.Auto) },
Children = { markdown, image }
};
@@ -109,18 +101,14 @@ IReadOnlyList textFields
var grid = new Grid
{
- RowDefinitions =
- {
- new RowDefinition(GridLength.Auto),
- new RowDefinition(GridLength.Star)
- },
+ RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Star) },
Children = { content, stackPanel }
};
grid.Loaded += (_, _) =>
{
// Focus first TextBox
- var firstTextBox = stackPanel.Children
- .OfType()
+ var firstTextBox = stackPanel
+ .Children.OfType()
.FirstOrDefault()
.FindDescendantOfType();
firstTextBox!.Focus();
@@ -254,16 +242,16 @@ public static BetterContentDialog CreateMarkdownDialog(
Content = viewer,
CloseButtonText = Resources.Action_Close,
IsPrimaryButtonEnabled = false,
+ MinDialogWidth = 800,
+ MaxDialogHeight = 1000,
+ MaxDialogWidth = 1000
};
}
///
/// Create a dialog for displaying an ApiException
///
- public static BetterContentDialog CreateApiExceptionDialog(
- ApiException exception,
- string? title = null
- )
+ public static BetterContentDialog CreateApiExceptionDialog(ApiException exception, string? title = null)
{
Dispatcher.UIThread.VerifyAccess();
@@ -275,9 +263,7 @@ public static BetterContentDialog CreateApiExceptionDialog(
Options = { ShowColumnRulers = false, AllowScrollBelowDocument = false }
};
var registryOptions = new RegistryOptions(ThemeName.DarkPlus);
- textEditor
- .InstallTextMate(registryOptions)
- .SetGrammar(registryOptions.GetScopeByLanguageId("json"));
+ textEditor.InstallTextMate(registryOptions).SetGrammar(registryOptions.GetScopeByLanguageId("json"));
var mainGrid = new StackPanel
{
@@ -354,9 +340,7 @@ public static BetterContentDialog CreateJsonDialog(
Options = { ShowColumnRulers = false, AllowScrollBelowDocument = false }
};
var registryOptions = new RegistryOptions(ThemeName.DarkPlus);
- textEditor
- .InstallTextMate(registryOptions)
- .SetGrammar(registryOptions.GetScopeByLanguageId("json"));
+ textEditor.InstallTextMate(registryOptions).SetGrammar(registryOptions.GetScopeByLanguageId("json"));
var mainGrid = new StackPanel
{
@@ -437,8 +421,7 @@ public static BetterContentDialog CreatePromptErrorDialog(
{
Dispatcher.UIThread.VerifyAccess();
- var title =
- exception is PromptSyntaxError ? "Prompt Syntax Error" : "Prompt Validation Error";
+ var title = exception is PromptSyntaxError ? "Prompt Syntax Error" : "Prompt Validation Error";
// Get the index of the error
var errorIndex = exception.TextOffset;
From d533be332995933007f1ec9e268634077d3c40a1 Mon Sep 17 00:00:00 2001
From: JT
Date: Sat, 23 Dec 2023 17:53:37 -0800
Subject: [PATCH 021/276] handle window size/position outside of shutdown, load
pngs faster, start of saving some img2vid stuff, webp metadata reading, and
different settings write method (that still doesn't entirely work and
occasionally adds extra right curly brackets to the end of the file)
---
StabilityMatrix.Avalonia/App.axaml.cs | 224 ++++++++----------
.../FallbackRamCachedWebImageLoader.cs | 18 ++
.../Video/SvdImgToVidConditioningViewModel.cs | 11 +-
.../ViewModels/MainWindowViewModel.cs | 11 +-
.../Settings/MainSettingsViewModel.cs | 5 +
.../Views/MainWindow.axaml.cs | 124 +++++++---
StabilityMatrix.Core/Helper/ImageMetadata.cs | 122 +++++++++-
StabilityMatrix.Core/Helper/MyTiffFile.cs | 6 +
StabilityMatrix.Core/Helper/Utilities.cs | 23 +-
.../Services/SettingsManager.cs | 14 +-
.../StabilityMatrix.Core.csproj | 1 +
11 files changed, 383 insertions(+), 176 deletions(-)
create mode 100644 StabilityMatrix.Core/Helper/MyTiffFile.cs
diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs
index 742b10a35..08e8ba515 100644
--- a/StabilityMatrix.Avalonia/App.axaml.cs
+++ b/StabilityMatrix.Avalonia/App.axaml.cs
@@ -80,7 +80,8 @@ public sealed class App : Application
public static TopLevel TopLevel => TopLevel.GetTopLevel(VisualRoot)!;
- internal static bool IsHeadlessMode => TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or "STUB";
+ internal static bool IsHeadlessMode =>
+ TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or "STUB";
[NotNull]
public static IStorageProvider? StorageProvider { get; internal set; }
@@ -117,8 +118,7 @@ public override void OnFrameworkInitializationCompleted()
{
// Remove DataAnnotations validation plugin since we're using INotifyDataErrorInfo from MvvmToolkit
var dataValidationPluginsToRemove = BindingPlugins
- .DataValidators
- .OfType()
+ .DataValidators.OfType()
.ToArray();
foreach (var plugin in dataValidationPluginsToRemove)
@@ -161,22 +161,19 @@ public override void OnFrameworkInitializationCompleted()
DesktopLifetime.MainWindow = setupWindow;
- setupWindow
- .ShowAsyncCts
- .Token
- .Register(() =>
+ setupWindow.ShowAsyncCts.Token.Register(() =>
+ {
+ if (setupWindow.Result == ContentDialogResult.Primary)
+ {
+ settingsManager.SetEulaAccepted();
+ ShowMainWindow();
+ DesktopLifetime.MainWindow.Show();
+ }
+ else
{
- if (setupWindow.Result == ContentDialogResult.Primary)
- {
- settingsManager.SetEulaAccepted();
- ShowMainWindow();
- DesktopLifetime.MainWindow.Show();
- }
- else
- {
- Shutdown();
- }
- });
+ Shutdown();
+ }
+ });
}
else
{
@@ -297,7 +294,9 @@ internal static void ConfigureDialogViewModels(IServiceCollection services, Type
var serviceManager = new ServiceManager();
var serviceManagedTypes = exportedTypes
- .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(ManagedServiceAttribute), true) })
+ .Select(
+ t => new { t, attributes = t.GetCustomAttributes(typeof(ManagedServiceAttribute), true) }
+ )
.Where(t1 => t1.attributes is { Length: > 0 })
.Select(t1 => t1.t)
.ToList();
@@ -322,8 +321,7 @@ internal static IServiceCollection ConfigureServices()
services.AddMessagePipeNamedPipeInterprocess("StabilityMatrix");
var exportedTypes = AppDomain
- .CurrentDomain
- .GetAssemblies()
+ .CurrentDomain.GetAssemblies()
.Where(a => a.FullName?.StartsWith("StabilityMatrix") == true)
.SelectMany(a => a.GetExportedTypes())
.ToArray();
@@ -332,7 +330,8 @@ internal static IServiceCollection ConfigureServices()
.Select(t => new { t, attributes = t.GetCustomAttributes(typeof(TransientAttribute), false) })
.Where(
t1 =>
- t1.attributes is { Length: > 0 } && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
+ t1.attributes is { Length: > 0 }
+ && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
)
.Select(t1 => new { Type = t1.t, Attribute = (TransientAttribute)t1.attributes[0] });
@@ -352,9 +351,12 @@ internal static IServiceCollection ConfigureServices()
.Select(t => new { t, attributes = t.GetCustomAttributes(typeof(SingletonAttribute), false) })
.Where(
t1 =>
- t1.attributes is { Length: > 0 } && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
+ t1.attributes is { Length: > 0 }
+ && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
)
- .Select(t1 => new { Type = t1.t, Attributes = t1.attributes.Cast().ToArray() });
+ .Select(
+ t1 => new { Type = t1.t, Attributes = t1.attributes.Cast().ToArray() }
+ );
foreach (var typePair in singletonTypes)
{
@@ -386,7 +388,9 @@ internal static IServiceCollection ConfigureServices()
// Rich presence
services.AddSingleton();
- services.AddSingleton(provider => provider.GetRequiredService());
+ services.AddSingleton(
+ provider => provider.GetRequiredService()
+ );
Config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
@@ -557,7 +561,11 @@ internal static IServiceCollection ConfigureServices()
#if DEBUG
builder.AddNLog(
ConfigureLogging(),
- new NLogProviderOptions { IgnoreEmptyEventId = false, CaptureEventId = EventIdCaptureType.Legacy }
+ new NLogProviderOptions
+ {
+ IgnoreEmptyEventId = false,
+ CaptureEventId = EventIdCaptureType.Legacy
+ }
);
#else
builder.AddNLog(ConfigureLogging());
@@ -591,8 +599,6 @@ private static void OnMainWindowClosing(object? sender, WindowClosingEventArgs e
if (e.Cancel)
return;
- var mainWindow = (MainWindow)sender!;
-
// Show confirmation if package running
var launchPageViewModel = Services.GetRequiredService();
launchPageViewModel.OnMainWindowClosing(e);
@@ -602,66 +608,38 @@ private static void OnMainWindowClosing(object? sender, WindowClosingEventArgs e
// Check if we need to dispose IAsyncDisposables
if (
- !isAsyncDisposeComplete
- && Services.GetServices().ToList() is { Count: > 0 } asyncDisposables
+ isAsyncDisposeComplete
+ || Services.GetServices().ToList() is not { Count: > 0 } asyncDisposables
)
- {
- // Cancel shutdown for now
- e.Cancel = true;
- isAsyncDisposeComplete = true;
+ return;
- Debug.WriteLine("OnShutdownRequested Canceled: Disposing IAsyncDisposables");
+ // Cancel shutdown for now
+ e.Cancel = true;
+ isAsyncDisposeComplete = true;
- Task.Run(async () =>
+ Debug.WriteLine("OnShutdownRequested Canceled: Disposing IAsyncDisposables");
+
+ Task.Run(async () =>
+ {
+ foreach (var disposable in asyncDisposables)
{
- foreach (var disposable in asyncDisposables)
+ Debug.WriteLine($"Disposing IAsyncDisposable ({disposable.GetType().Name})");
+ try
{
- Debug.WriteLine($"Disposing IAsyncDisposable ({disposable.GetType().Name})");
- try
- {
- await disposable.DisposeAsync().ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Debug.Fail(ex.ToString());
- }
+ await disposable.DisposeAsync().ConfigureAwait(false);
}
- })
- .ContinueWith(_ =>
- {
- // Shutdown again
- Dispatcher.UIThread.Invoke(() => Shutdown());
- })
- .SafeFireAndForget();
-
- return;
- }
-
- OnMainWindowClosingTerminal(mainWindow);
- }
-
- ///
- /// Called at the end of before the main window is closed.
- ///
- private static void OnMainWindowClosingTerminal(Window sender)
- {
- var settingsManager = Services.GetRequiredService();
-
- // Save window position
- var validWindowPosition = sender.Screens.All.Any(screen => screen.Bounds.Contains(sender.Position));
-
- settingsManager.Transaction(
- s =>
+ catch (Exception ex)
+ {
+ Debug.Fail(ex.ToString());
+ }
+ }
+ })
+ .ContinueWith(_ =>
{
- s.WindowSettings = new WindowSettings(
- sender.Width,
- sender.Height,
- validWindowPosition ? sender.Position.X : 0,
- validWindowPosition ? sender.Position.Y : 0
- );
- },
- ignoreMissingLibraryDir: true
- );
+ // Shutdown again
+ Dispatcher.UIThread.Invoke(() => Shutdown());
+ })
+ .SafeFireAndForget();
}
private static void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs args)
@@ -714,10 +692,12 @@ private static LoggingConfiguration ConfigureLogging()
.WriteTo(
new FileTarget
{
- Layout = "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}",
+ Layout =
+ "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}",
ArchiveOldFileOnStartup = true,
FileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.log",
- ArchiveFileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log",
+ ArchiveFileName =
+ "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log",
ArchiveNumbering = ArchiveNumberingMode.Rolling,
MaxArchiveFiles = 2
}
@@ -730,7 +710,9 @@ private static LoggingConfiguration ConfigureLogging()
builder.ForLogger("Microsoft.Extensions.Http.*").WriteToNil(NLog.LogLevel.Warn);
// Disable console trace logging by default
- builder.ForLogger("StabilityMatrix.Avalonia.ViewModels.ConsoleViewModel").WriteToNil(NLog.LogLevel.Debug);
+ builder
+ .ForLogger("StabilityMatrix.Avalonia.ViewModels.ConsoleViewModel")
+ .WriteToNil(NLog.LogLevel.Debug);
// Disable LoadableViewModelBase trace logging by default
builder
@@ -751,20 +733,18 @@ private static LoggingConfiguration ConfigureLogging()
// Sentry
if (SentrySdk.IsEnabled)
{
- LogManager
- .Configuration
- .AddSentry(o =>
- {
- o.InitializeSdk = false;
- o.Layout = "${message}";
- o.ShutdownTimeoutSeconds = 5;
- o.IncludeEventDataOnBreadcrumbs = true;
- o.BreadcrumbLayout = "${logger}: ${message}";
- // Debug and higher are stored as breadcrumbs (default is Info)
- o.MinimumBreadcrumbLevel = NLog.LogLevel.Debug;
- // Error and higher is sent as event (default is Error)
- o.MinimumEventLevel = NLog.LogLevel.Error;
- });
+ LogManager.Configuration.AddSentry(o =>
+ {
+ o.InitializeSdk = false;
+ o.Layout = "${message}";
+ o.ShutdownTimeoutSeconds = 5;
+ o.IncludeEventDataOnBreadcrumbs = true;
+ o.BreadcrumbLayout = "${logger}: ${message}";
+ // Debug and higher are stored as breadcrumbs (default is Info)
+ o.MinimumBreadcrumbLevel = NLog.LogLevel.Debug;
+ // Error and higher is sent as event (default is Error)
+ o.MinimumEventLevel = NLog.LogLevel.Error;
+ });
}
LogManager.ReconfigExistingLoggers();
@@ -803,34 +783,36 @@ internal static void DebugSaveScreenshot(int dpi = 96)
results.Add(ms);
}
- Dispatcher
- .UIThread
- .InvokeAsync(async () =>
- {
- var dest = await StorageProvider.SaveFilePickerAsync(
- new FilePickerSaveOptions() { SuggestedFileName = "screenshot.png", ShowOverwritePrompt = true }
- );
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ var dest = await StorageProvider.SaveFilePickerAsync(
+ new FilePickerSaveOptions()
+ {
+ SuggestedFileName = "screenshot.png",
+ ShowOverwritePrompt = true
+ }
+ );
- if (dest?.TryGetLocalPath() is { } localPath)
+ if (dest?.TryGetLocalPath() is { } localPath)
+ {
+ var localFile = new FilePath(localPath);
+ foreach (var (i, stream) in results.Enumerate())
{
- var localFile = new FilePath(localPath);
- foreach (var (i, stream) in results.Enumerate())
+ var name = localFile.NameWithoutExtension;
+ if (results.Count > 1)
{
- var name = localFile.NameWithoutExtension;
- if (results.Count > 1)
- {
- name += $"_{i + 1}";
- }
-
- localFile = localFile.Directory!.JoinFile(name + ".png");
- localFile.Create();
-
- await using var fileStream = localFile.Info.OpenWrite();
- stream.Seek(0, SeekOrigin.Begin);
- await stream.CopyToAsync(fileStream);
+ name += $"_{i + 1}";
}
+
+ localFile = localFile.Directory!.JoinFile(name + ".png");
+ localFile.Create();
+
+ await using var fileStream = localFile.Info.OpenWrite();
+ stream.Seek(0, SeekOrigin.Begin);
+ await stream.CopyToAsync(fileStream);
}
- });
+ }
+ });
}
[Conditional("DEBUG")]
diff --git a/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs b/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
index f8ae8235f..7dc265552 100644
--- a/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
+++ b/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
@@ -1,12 +1,15 @@
using System;
using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using AsyncImageLoader.Loaders;
using Avalonia.Media.Imaging;
using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper;
namespace StabilityMatrix.Avalonia;
@@ -42,6 +45,11 @@ protected void OnLoadFailed(string url, Exception exception) =>
{
try
{
+ if (url.EndsWith("png", StringComparison.OrdinalIgnoreCase))
+ {
+ return await LoadPngAsync(url);
+ }
+
return new Bitmap(url);
}
catch (Exception e)
@@ -98,4 +106,14 @@ public void RemoveAllNamesFromCache(string fileName)
}
}
}
+
+ private async Task LoadPngAsync(string url)
+ {
+ using var fileStream = new BinaryReader(File.OpenRead(url));
+ var imageBytes = ImageMetadata.BuildImageWithoutMetadata(fileStream).ToArray();
+ using var memoryStream = new MemoryStream();
+ await memoryStream.WriteAsync(imageBytes, 0, imageBytes.Length);
+ memoryStream.Position = 0;
+ return new Bitmap(memoryStream);
+ }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
index 4932ad554..ae4cbcc59 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
@@ -41,13 +41,15 @@ public partial class SvdImgToVidConditioningViewModel
public void LoadStateFromParameters(GenerationParameters parameters)
{
- // TODO
+ Width = parameters.Width;
+ Height = parameters.Height;
+ // TODO: add more metadata
}
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
- // TODO
- return parameters;
+ // TODO: add more metadata
+ return parameters with { Width = Width, Height = Height, };
}
public void ApplyStep(ModuleApplyStepEventArgs e)
@@ -58,8 +60,7 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
{
Name = e.Nodes.GetUniqueName("LinearCfgGuidance"),
Model =
- e.Builder.Connections.BaseModel
- ?? throw new ValidationException("Model not selected"),
+ e.Builder.Connections.BaseModel ?? throw new ValidationException("Model not selected"),
MinCfg = MinCfg
}
);
diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
index 842df99e3..c546d1ee7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Reactive.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
@@ -10,6 +11,7 @@
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using KGySoft.CoreLibraries;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
@@ -112,10 +114,7 @@ protected override async Task OnInitialLoadedAsync()
var startupTime = CodeTimer.FormatTime(Program.StartupTimer.Elapsed);
Logger.Info($"App started ({startupTime})");
- if (
- Program.Args.DebugOneClickInstall
- || settingsManager.Settings.InstalledPackages.Count == 0
- )
+ if (Program.Args.DebugOneClickInstall || settingsManager.Settings.InstalledPackages.Count == 0)
{
var viewModel = dialogFactory.Get();
var dialog = new BetterContentDialog
@@ -148,8 +147,8 @@ var page in Pages
.Where(p => p.GetType().GetCustomAttributes(typeof(PreloadAttribute), true).Any())
)
{
- Dispatcher.UIThread
- .InvokeAsync(
+ Dispatcher
+ .UIThread.InvokeAsync(
async () =>
{
var stopwatch = Stopwatch.StartNew();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index d1e259036..36d975932 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -24,6 +24,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData.Binding;
+using ExifLibrary;
using FluentAvalonia.UI.Controls;
using NLog;
using SkiaSharp;
@@ -708,6 +709,10 @@ private async Task DebugImageMetadata()
if (files.Count == 0)
return;
+ var data = await ImageMetadata.ReadTextChunkFromWebp(files[0].TryGetLocalPath(), ExifTag.Model);
+
+ return;
+
var metadata = ImageMetadata.ParseFile(files[0].TryGetLocalPath()!);
var textualTags = metadata.GetTextualData()?.ToArray();
diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
index c12634269..bd62a3028 100644
--- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
+++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
@@ -4,6 +4,9 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Threading;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
@@ -34,8 +37,11 @@
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models.Settings;
using StabilityMatrix.Core.Models.Update;
using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Services;
+using TeachingTip = FluentAvalonia.UI.Controls.TeachingTip;
#if DEBUG
using StabilityMatrix.Avalonia.Diagnostics.Views;
#endif
@@ -48,6 +54,7 @@ public partial class MainWindow : AppWindowBase
{
private readonly INotificationService notificationService;
private readonly INavigationService navigationService;
+ private readonly ISettingsManager settingsManager;
private FlyoutBase? progressFlyout;
@@ -61,11 +68,13 @@ public MainWindow()
public MainWindow(
INotificationService notificationService,
- INavigationService navigationService
+ INavigationService navigationService,
+ ISettingsManager settingsManager
)
{
this.notificationService = notificationService;
this.navigationService = navigationService;
+ this.settingsManager = settingsManager;
InitializeComponent();
@@ -82,6 +91,47 @@ INavigationService navigationService
EventManager.Instance.ToggleProgressFlyout += (_, _) => progressFlyout?.Hide();
EventManager.Instance.CultureChanged += (_, _) => SetDefaultFonts();
EventManager.Instance.UpdateAvailable += OnUpdateAvailable;
+
+ Observable
+ .FromEventPattern(this, nameof(SizeChanged))
+ .Where(x => x.EventArgs.PreviousSize != x.EventArgs.NewSize)
+ .Throttle(TimeSpan.FromMilliseconds(100))
+ .Select(x => x.EventArgs.NewSize)
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe(newSize =>
+ {
+ var validWindowPosition = Screens.All.Any(screen => screen.Bounds.Contains(Position));
+
+ settingsManager.Transaction(
+ s =>
+ {
+ s.WindowSettings = new WindowSettings(
+ newSize.Width,
+ newSize.Height,
+ validWindowPosition ? Position.X : 0,
+ validWindowPosition ? Position.Y : 0
+ );
+ },
+ ignoreMissingLibraryDir: true
+ );
+ });
+
+ Observable
+ .FromEventPattern(this, nameof(PositionChanged))
+ .Where(x => Screens.All.Any(screen => screen.Bounds.Contains(x.EventArgs.Point)))
+ .Throttle(TimeSpan.FromMilliseconds(100))
+ .Select(x => x.EventArgs.Point)
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe(position =>
+ {
+ settingsManager.Transaction(
+ s =>
+ {
+ s.WindowSettings = new WindowSettings(Width, Height, position.X, position.Y);
+ },
+ ignoreMissingLibraryDir: true
+ );
+ });
}
///
@@ -132,15 +182,16 @@ protected override void OnLoaded(RoutedEventArgs e)
return;
// Navigate to first page
- Dispatcher
- .UIThread
- .Post(
- () =>
- navigationService.NavigateTo(
- vm.Pages[0],
- new BetterSlideNavigationTransition { Effect = SlideNavigationTransitionEffect.FromBottom }
- )
- );
+ Dispatcher.UIThread.Post(
+ () =>
+ navigationService.NavigateTo(
+ vm.Pages[0],
+ new BetterSlideNavigationTransition
+ {
+ Effect = SlideNavigationTransitionEffect.FromBottom
+ }
+ )
+ );
// Check show update teaching tip
if (vm.UpdateViewModel.IsUpdateAvailable)
@@ -165,27 +216,24 @@ private void NavigationService_OnTypedNavigation(object? sender, TypedNavigation
var mainViewModel = (MainWindowViewModel)DataContext!;
mainViewModel.SelectedCategory = mainViewModel
- .Pages
- .Concat(mainViewModel.FooterPages)
+ .Pages.Concat(mainViewModel.FooterPages)
.FirstOrDefault(x => x.GetType() == e.ViewModelType);
}
private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo)
{
- Dispatcher
- .UIThread
- .Post(() =>
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo))
{
- if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo))
- {
- var target = this.FindControl("FooterUpdateItem")!;
- var tip = this.FindControl("UpdateAvailableTeachingTip")!;
+ var target = this.FindControl("FooterUpdateItem")!;
+ var tip = this.FindControl("UpdateAvailableTeachingTip")!;
- tip.Target = target;
- tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}";
- tip.IsOpen = true;
- }
- });
+ tip.Target = target;
+ tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}";
+ tip.IsOpen = true;
+ }
+ });
}
public void SetDefaultFonts()
@@ -282,18 +330,16 @@ private void OnActualThemeVariantChanged(object? sender, EventArgs e)
private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e)
{
- Dispatcher
- .UIThread
- .Post(() =>
- {
- var fileName = Path.GetFileName(e.Url);
- var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName;
- notificationService.ShowPersistent(
- "Failed to load image",
- $"Could not load '{displayName}'\n({e.Exception.Message})",
- NotificationType.Warning
- );
- });
+ Dispatcher.UIThread.Post(() =>
+ {
+ var fileName = Path.GetFileName(e.Url);
+ var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName;
+ notificationService.ShowPersistent(
+ "Failed to load image",
+ $"Could not load '{displayName}'\n({e.Exception.Message})",
+ NotificationType.Warning
+ );
+ });
}
private void TryEnableMicaEffect()
@@ -319,7 +365,11 @@ private void TryEnableMicaEffect()
else if (ActualThemeVariant == ThemeVariant.Light)
{
// Similar effect here
- var color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out var value)
+ var color = this.TryFindResource(
+ "SolidBackgroundFillColorBase",
+ ThemeVariant.Light,
+ out var value
+ )
? (Color2)(Color)value!
: new Color2(243, 243, 243);
diff --git a/StabilityMatrix.Core/Helper/ImageMetadata.cs b/StabilityMatrix.Core/Helper/ImageMetadata.cs
index a10dae53b..aca320e79 100644
--- a/StabilityMatrix.Core/Helper/ImageMetadata.cs
+++ b/StabilityMatrix.Core/Helper/ImageMetadata.cs
@@ -1,7 +1,10 @@
-using System.Text;
+using System.Diagnostics;
+using System.Text;
using System.Text.Json;
+using ExifLibrary;
using MetadataExtractor;
using MetadataExtractor.Formats.Png;
+using Microsoft.VisualBasic;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
@@ -13,10 +16,13 @@ public class ImageMetadata
{
private IReadOnlyList? Directories { get; set; }
- private static readonly byte[] PngHeader = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+ private static readonly byte[] PngHeader = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
private static readonly byte[] Idat = "IDAT"u8.ToArray();
private static readonly byte[] Text = "tEXt"u8.ToArray();
+ private static readonly byte[] Riff = "RIFF"u8.ToArray();
+ private static readonly byte[] Webp = "WEBP"u8.ToArray();
+
public static ImageMetadata ParseFile(FilePath path)
{
return new ImageMetadata { Directories = ImageMetadataReader.ReadMetadata(path) };
@@ -179,4 +185,116 @@ public static string ReadTextChunk(BinaryReader byteStream, string key)
return string.Empty;
}
+
+ public static IEnumerable BuildImageWithoutMetadata(BinaryReader byteStream)
+ {
+ var bytes = new List();
+ byteStream.BaseStream.Position = 0;
+
+ // Read first 8 bytes and make sure they match the png header
+ if (!byteStream.ReadBytes(8).SequenceEqual(PngHeader))
+ {
+ return Array.Empty();
+ }
+ bytes.AddRange(PngHeader);
+
+ var ihdrStuff = byteStream.ReadBytes(25);
+ bytes.AddRange(ihdrStuff);
+
+ while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)
+ {
+ var chunkSizeBytes = byteStream.ReadBytes(4);
+ var chunkSize = BitConverter.ToInt32(chunkSizeBytes.Reverse().ToArray());
+ var chunkTypeBytes = byteStream.ReadBytes(4);
+ var chunkType = Encoding.UTF8.GetString(chunkTypeBytes);
+
+ if (chunkType != Encoding.UTF8.GetString(Idat))
+ {
+ // skip chunk data
+ byteStream.BaseStream.Position += chunkSize;
+ // skip crc
+ byteStream.BaseStream.Position += 4;
+ continue;
+ }
+
+ bytes.AddRange(chunkSizeBytes);
+ bytes.AddRange(chunkTypeBytes);
+ var idatBytes = byteStream.ReadBytes(chunkSize);
+ bytes.AddRange(idatBytes);
+ var crcBytes = byteStream.ReadBytes(4);
+ bytes.AddRange(crcBytes);
+ }
+
+ // Add IEND chunk
+ bytes.AddRange([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]);
+
+ return bytes;
+ }
+
+ public static async Task ReadTextChunkFromWebp(FilePath filePath, ExifTag exifTag)
+ {
+ var sw = Stopwatch.StartNew();
+ try
+ {
+ await using var memoryStream = Utilities.GetMemoryStreamFromFile(filePath);
+ if (memoryStream is null)
+ return string.Empty;
+
+ var exifChunks = GetExifChunks(memoryStream);
+ if (exifChunks.Length == 0)
+ return string.Empty;
+
+ // write exifChunks to new memoryStream but skip first 6 bytes
+ using var newMemoryStream = new MemoryStream(exifChunks[6..]);
+ newMemoryStream.Seek(0, SeekOrigin.Begin);
+
+ var img = new MyTiffFile(newMemoryStream, Encoding.UTF8);
+ return img.Properties[exifTag]?.Value?.ToString() ?? string.Empty;
+ }
+ finally
+ {
+ sw.Stop();
+ Console.WriteLine($"ReadTextChunkFromWebp took {sw.ElapsedMilliseconds}ms");
+ }
+ }
+
+ private static byte[] GetExifChunks(MemoryStream memoryStream)
+ {
+ using var byteStream = new BinaryReader(memoryStream);
+ byteStream.BaseStream.Position = 0;
+
+ // Read first 8 bytes and make sure they match the RIFF header
+ if (!byteStream.ReadBytes(4).SequenceEqual(Riff))
+ {
+ return Array.Empty();
+ }
+
+ // skip 4 bytes then read next 4 for webp header
+ byteStream.BaseStream.Position += 4;
+ if (!byteStream.ReadBytes(4).SequenceEqual(Webp))
+ {
+ return Array.Empty();
+ }
+
+ while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)
+ {
+ var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));
+ var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).ToArray());
+
+ if (chunkType != "EXIF")
+ {
+ // skip chunk data
+ byteStream.BaseStream.Position += chunkSize;
+ continue;
+ }
+
+ var exifStart = byteStream.BaseStream.Position;
+ var exifBytes = byteStream.ReadBytes(chunkSize);
+ var exif = Encoding.UTF8.GetString(exifBytes);
+ Debug.WriteLine($"Found exif chunk of size {chunkSize}");
+ return exifBytes;
+ }
+
+ return Array.Empty();
+ }
}
diff --git a/StabilityMatrix.Core/Helper/MyTiffFile.cs b/StabilityMatrix.Core/Helper/MyTiffFile.cs
new file mode 100644
index 000000000..dd65110bf
--- /dev/null
+++ b/StabilityMatrix.Core/Helper/MyTiffFile.cs
@@ -0,0 +1,6 @@
+using System.Text;
+using ExifLibrary;
+
+namespace StabilityMatrix.Core.Helper;
+
+public class MyTiffFile(MemoryStream stream, Encoding encoding) : TIFFFile(stream, encoding);
diff --git a/StabilityMatrix.Core/Helper/Utilities.cs b/StabilityMatrix.Core/Helper/Utilities.cs
index 07afca73f..5e8bfc2e7 100644
--- a/StabilityMatrix.Core/Helper/Utilities.cs
+++ b/StabilityMatrix.Core/Helper/Utilities.cs
@@ -13,8 +13,12 @@ public static string GetAppVersion()
: $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
- public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive,
- bool includeReparsePoints = false)
+ public static void CopyDirectory(
+ string sourceDir,
+ string destinationDir,
+ bool recursive,
+ bool includeReparsePoints = false
+ )
{
// Get information about the source directory
var dir = new DirectoryInfo(sourceDir);
@@ -35,11 +39,13 @@ public static void CopyDirectory(string sourceDir, string destinationDir, bool r
foreach (var file in dir.GetFiles())
{
var targetFilePath = Path.Combine(destinationDir, file.Name);
- if (file.FullName == targetFilePath) continue;
+ if (file.FullName == targetFilePath)
+ continue;
file.CopyTo(targetFilePath, true);
}
- if (!recursive) return;
+ if (!recursive)
+ return;
// If recursive and copying subdirectories, recursively call this method
foreach (var subDir in dirs)
@@ -48,4 +54,13 @@ public static void CopyDirectory(string sourceDir, string destinationDir, bool r
CopyDirectory(subDir.FullName, newDestinationDir, true);
}
}
+
+ public static MemoryStream? GetMemoryStreamFromFile(string filePath)
+ {
+ var fileBytes = File.ReadAllBytes(filePath);
+ var stream = new MemoryStream(fileBytes);
+ stream.Position = 0;
+
+ return stream;
+ }
}
diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs
index 0c0c56fa4..2b0849c38 100644
--- a/StabilityMatrix.Core/Services/SettingsManager.cs
+++ b/StabilityMatrix.Core/Services/SettingsManager.cs
@@ -627,6 +627,7 @@ protected virtual void LoadSettings()
if (fileStream.Length == 0)
{
Logger.Warn("Settings file is empty, using default settings");
+ isLoaded = true;
return;
}
@@ -674,7 +675,18 @@ protected virtual void SaveSettings()
SettingsSerializerContext.Default.Settings
);
- File.WriteAllBytes(SettingsPath, jsonBytes);
+ if (jsonBytes.Length == 0)
+ {
+ Logger.Error("JsonSerializer returned empty bytes for some reason");
+ return;
+ }
+
+ using var fs = File.Open(SettingsPath, FileMode.Open);
+ if (fs.CanWrite)
+ {
+ fs.Write(jsonBytes, 0, jsonBytes.Length);
+ }
+ fs.Close();
}
finally
{
diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
index d64996a35..e2304c75f 100644
--- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj
+++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
@@ -27,6 +27,7 @@
+
From 1b06732c92c64d478376030b6fa725664401cf59 Mon Sep 17 00:00:00 2001
From: JT
Date: Sat, 23 Dec 2023 18:04:20 -0800
Subject: [PATCH 022/276] Fix merge/build errors
---
.../ViewModels/Inference/ModelCardViewModel.cs | 7 +++++--
.../Inference/Video/ImgToVidModelCardViewModel.cs | 8 +++-----
.../Video/SvdImgToVidConditioningViewModel.cs | 13 ++++++++-----
.../Video/VideoOutputSettingsCardViewModel.cs | 4 ++--
4 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
index d68302c0c..1fed69268 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
@@ -10,8 +10,8 @@
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models;
-using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
@@ -41,6 +41,7 @@ public partial class ModelCardViewModel(IInferenceClientManager clientManager)
[ObservableProperty]
private bool disableSettings;
+ [ObservableProperty]
private bool isClipSkipEnabled;
[NotifyDataErrorInfo]
@@ -91,7 +92,9 @@ public virtual void ApplyStep(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.VAELoader
{
Name = "VAELoader",
- VaeName = SelectedVae?.RelativePath ?? throw new ValidationException("VAE enabled but not selected")
+ VaeName =
+ SelectedVae?.RelativePath
+ ?? throw new ValidationException("VAE enabled but not selected")
}
);
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
index c5513e6e3..2bbf8021f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
@@ -24,14 +24,12 @@ public override void ApplyStep(ModuleApplyStepEventArgs e)
new ComfyNodeBuilder.ImageOnlyCheckpointLoader
{
Name = "ImageOnlyCheckpointLoader",
- CkptName =
- SelectedModel?.RelativePath
- ?? throw new ValidationException("Model not selected")
+ CkptName = SelectedModel?.RelativePath ?? throw new ValidationException("Model not selected")
}
);
- e.Builder.Connections.BaseModel = imgToVidLoader.Output1;
+ e.Builder.Connections.Base.Model = imgToVidLoader.Output1;
e.Builder.Connections.BaseClipVision = imgToVidLoader.Output2;
- e.Builder.Connections.BaseVAE = imgToVidLoader.Output3;
+ e.Builder.Connections.Base.VAE = imgToVidLoader.Output3;
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
index ae4cbcc59..9ff667e63 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
@@ -7,6 +7,7 @@
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
@@ -60,12 +61,12 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
{
Name = e.Nodes.GetUniqueName("LinearCfgGuidance"),
Model =
- e.Builder.Connections.BaseModel ?? throw new ValidationException("Model not selected"),
+ e.Builder.Connections.Base.Model ?? throw new ValidationException("Model not selected"),
MinCfg = MinCfg
}
);
- e.Builder.Connections.BaseModel = cfgGuidanceNode.Output;
+ e.Builder.Connections.Base.Model = cfgGuidanceNode.Output;
// then do the SVD stuff
var svdImgToVidConditioningNode = e.Nodes.AddTypedNode(
@@ -73,7 +74,7 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
{
ClipVision = e.Builder.Connections.BaseClipVision!,
InitImage = e.Builder.GetPrimaryAsImage(),
- Vae = e.Builder.Connections.BaseVAE!,
+ Vae = e.Builder.Connections.Base.VAE!,
Name = e.Nodes.GetUniqueName("SvdImgToVidConditioning"),
Width = Width,
Height = Height,
@@ -84,8 +85,10 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
}
);
- e.Builder.Connections.BaseConditioning = svdImgToVidConditioningNode.Output1;
- e.Builder.Connections.BaseNegativeConditioning = svdImgToVidConditioningNode.Output2;
+ e.Builder.Connections.Base.Conditioning = new ConditioningConnections(
+ svdImgToVidConditioningNode.Output1,
+ svdImgToVidConditioningNode.Output2
+ );
e.Builder.Connections.Primary = svdImgToVidConditioningNode.Output3;
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
index 3fd770798..c0cdea9b0 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
@@ -55,8 +55,8 @@ public void ApplyStep(ModuleApplyStepEventArgs e)
_ =>
e.Builder.GetPrimaryAsImage(
e.Builder.Connections.PrimaryVAE
- ?? e.Builder.Connections.RefinerVAE
- ?? e.Builder.Connections.BaseVAE
+ ?? e.Builder.Connections.Refiner.VAE
+ ?? e.Builder.Connections.Base.VAE
?? throw new ArgumentException("No Primary, Refiner, or Base VAE")
),
image => image
From 9e28cb6965ecf872e9764cc5c354b4845abe35fb Mon Sep 17 00:00:00 2001
From: JT
Date: Mon, 25 Dec 2023 02:04:17 -0800
Subject: [PATCH 023/276] save metadata
---
.../FallbackRamCachedWebImageLoader.cs | 2 +
.../Base/InferenceGenerationViewModelBase.cs | 16 ++
.../CheckpointBrowserCardViewModel.cs | 62 ++++----
.../Settings/MainSettingsViewModel.cs | 2 +-
StabilityMatrix.Core/Helper/ImageMetadata.cs | 142 +++++++++++++++++-
.../Models/Database/LocalModelFile.cs | 6 +-
.../Services/MetadataImportService.cs | 3 +-
.../Services/SettingsManager.cs | 2 +
8 files changed, 194 insertions(+), 41 deletions(-)
diff --git a/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs b/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
index 9c001a929..50dbe733a 100644
--- a/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
+++ b/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using AsyncImageLoader.Loaders;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index f756933f0..17970867b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -420,6 +420,22 @@ ImageGenerationEventArgs args
outputImages.Add(new ImageSource(filePath));
EventManager.Instance.OnImageFileAdded(filePath);
}
+ else if (comfyImage.FileName.EndsWith(".webp"))
+ {
+ var bytesWithMetadata = ImageMetadata.AddMetadataToWebp(imageArray, parameters);
+
+ // Write using generated name
+ var filePath = await WriteOutputImageAsync(
+ new MemoryStream(bytesWithMetadata.ToArray()),
+ args,
+ i + 1,
+ images.Count,
+ fileExtension: Path.GetExtension(comfyImage.FileName).Replace(".", "")
+ );
+
+ outputImages.Add(new ImageSource(filePath));
+ EventManager.Instance.OnImageFileAdded(filePath);
+ }
else
{
// Write using generated name
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
index 4dc4acbc2..afdeb0855 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
@@ -21,6 +21,7 @@
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
+using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
@@ -120,30 +121,24 @@ private void CheckIfInstalled()
var latestVersionInstalled =
latestVersion.Files != null
- && latestVersion
- .Files
- .Any(
- file =>
- file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }
- && installedModels.Contains(file.Hashes.BLAKE3)
- );
+ && latestVersion.Files.Any(
+ file =>
+ file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }
+ && installedModels.Contains(file.Hashes.BLAKE3)
+ );
// check if any of the ModelVersion.Files.Hashes.BLAKE3 hashes are in the installedModels list
var anyVersionInstalled =
latestVersionInstalled
- || CivitModel
- .ModelVersions
- .Any(
- version =>
- version.Files != null
- && version
- .Files
- .Any(
- file =>
- file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }
- && installedModels.Contains(file.Hashes.BLAKE3)
- )
- );
+ || CivitModel.ModelVersions.Any(
+ version =>
+ version.Files != null
+ && version.Files.Any(
+ file =>
+ file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }
+ && installedModels.Contains(file.Hashes.BLAKE3)
+ )
+ );
UpdateCardText = latestVersionInstalled
? "Installed"
@@ -154,7 +149,6 @@ private void CheckIfInstalled()
ShowUpdateCard = anyVersionInstalled;
}
- // Choose and load image based on nsfw setting
private void UpdateImage()
{
var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;
@@ -162,21 +156,17 @@ private void UpdateImage()
var images = version?.Images;
// Try to find a valid image
- var image = images?.FirstOrDefault(image => nsfwEnabled || image.Nsfw == "None");
+ var image = images
+ ?.Where(img => LocalModelFile.SupportedImageExtensions.Any(img.Url.Contains))
+ .FirstOrDefault(image => nsfwEnabled || image.Nsfw == "None");
if (image != null)
{
- // var imageStream = await downloadService.GetImageStreamFromUrl(image.Url);
- // Dispatcher.UIThread.Post(() => { CardImage = new Bitmap(imageStream); });
CardImage = new Uri(image.Url);
return;
}
// If no valid image found, use no image
CardImage = Assets.NoImage;
-
- // var assetStream = AssetLoader.Open(new Uri("avares://StabilityMatrix.Avalonia/Assets/noimage.png"));
- // Otherwise Default image
- // Dispatcher.UIThread.Post(() => { CardImage = new Bitmap(assetStream); });
}
[RelayCommand]
@@ -270,8 +260,7 @@ private static string PruneDescription(CivitModel model)
{
var prunedDescription =
model
- .Description
- ?.Replace("
", $"{Environment.NewLine}{Environment.NewLine}")
+ .Description?.Replace("
", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("
", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("
", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("", $"{Environment.NewLine}{Environment.NewLine}")
@@ -318,9 +307,9 @@ DirectoryPath downloadDirectory
var imageExtension = Path.GetExtension(image.Url).TrimStart('.');
if (imageExtension is "jpg" or "jpeg" or "png")
{
- var imageDownloadPath = modelFilePath
- .Directory!
- .JoinFile($"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}");
+ var imageDownloadPath = modelFilePath.Directory!.JoinFile(
+ $"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}"
+ );
var imageTask = downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);
await notificationService.TryAsync(imageTask, "Could not download preview image");
@@ -358,7 +347,8 @@ private async Task DoImport(
}
// Get latest version file
- var modelFile = selectedFile ?? modelVersion.Files?.FirstOrDefault(x => x.Type == CivitFileType.Model);
+ var modelFile =
+ selectedFile ?? modelVersion.Files?.FirstOrDefault(x => x.Type == CivitFileType.Model);
if (modelFile is null)
{
notificationService.Show(
@@ -374,7 +364,9 @@ private async Task DoImport(
var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);
- var downloadDirectory = rootModelsDirectory.JoinDir(model.Type.ConvertTo().GetStringValue());
+ var downloadDirectory = rootModelsDirectory.JoinDir(
+ model.Type.ConvertTo().GetStringValue()
+ );
// Folders might be missing if user didn't install any packages yet
downloadDirectory.Create();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index 36d975932..aadc47dab 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -709,7 +709,7 @@ private async Task DebugImageMetadata()
if (files.Count == 0)
return;
- var data = await ImageMetadata.ReadTextChunkFromWebp(files[0].TryGetLocalPath(), ExifTag.Model);
+ var data = ImageMetadata.ReadTextChunkFromWebp(files[0].TryGetLocalPath(), ExifTag.ImageDescription);
return;
diff --git a/StabilityMatrix.Core/Helper/ImageMetadata.cs b/StabilityMatrix.Core/Helper/ImageMetadata.cs
index 439242bff..df9d1a620 100644
--- a/StabilityMatrix.Core/Helper/ImageMetadata.cs
+++ b/StabilityMatrix.Core/Helper/ImageMetadata.cs
@@ -1,7 +1,10 @@
-using System.Text;
+using System.Diagnostics;
+using System.Text;
using System.Text.Json;
+using ExifLibrary;
using MetadataExtractor;
using MetadataExtractor.Formats.Png;
+using Microsoft.VisualBasic;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
@@ -13,10 +16,13 @@ public class ImageMetadata
{
private IReadOnlyList? Directories { get; set; }
- private static readonly byte[] PngHeader = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+ private static readonly byte[] PngHeader = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
private static readonly byte[] Idat = "IDAT"u8.ToArray();
private static readonly byte[] Text = "tEXt"u8.ToArray();
+ private static readonly byte[] Riff = "RIFF"u8.ToArray();
+ private static readonly byte[] Webp = "WEBP"u8.ToArray();
+
public static ImageMetadata ParseFile(FilePath path)
{
return new ImageMetadata { Directories = ImageMetadataReader.ReadMetadata(path) };
@@ -227,4 +233,136 @@ public static string ReadTextChunk(BinaryReader byteStream, string key)
memoryStream.Position = 0;
return memoryStream;
}
+
+ public static string ReadTextChunkFromWebp(FilePath filePath, ExifTag exifTag)
+ {
+ var sw = Stopwatch.StartNew();
+ try
+ {
+ var exifChunks = GetExifChunks(filePath);
+ if (exifChunks.Length == 0)
+ return string.Empty;
+
+ // write exifChunks to new memoryStream but skip first 6 bytes
+ using var newMemoryStream = new MemoryStream(exifChunks[6..]);
+ newMemoryStream.Seek(0, SeekOrigin.Begin);
+
+ var img = new MyTiffFile(newMemoryStream, Encoding.UTF8);
+ return img.Properties[exifTag]?.Value?.ToString() ?? string.Empty;
+ }
+ finally
+ {
+ sw.Stop();
+ Console.WriteLine($"ReadTextChunkFromWebp took {sw.ElapsedMilliseconds}ms");
+ }
+ }
+
+ public static IEnumerable AddMetadataToWebp(
+ byte[] inputImage,
+ GenerationParameters generationParameters
+ )
+ {
+ using var byteStream = new BinaryReader(new MemoryStream(inputImage));
+ byteStream.BaseStream.Position = 0;
+
+ // Read first 8 bytes and make sure they match the RIFF header
+ if (!byteStream.ReadBytes(4).SequenceEqual(Riff))
+ {
+ return Array.Empty();
+ }
+
+ // skip 4 bytes then read next 4 for webp header
+ byteStream.BaseStream.Position += 4;
+ if (!byteStream.ReadBytes(4).SequenceEqual(Webp))
+ {
+ return Array.Empty();
+ }
+
+ while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)
+ {
+ var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));
+ var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).ToArray());
+
+ if (chunkType != "EXIF")
+ {
+ // skip chunk data
+ byteStream.BaseStream.Position += chunkSize;
+ continue;
+ }
+
+ var exifStart = byteStream.BaseStream.Position - 8;
+ var exifBytes = byteStream.ReadBytes(chunkSize);
+ Debug.WriteLine($"Found exif chunk of size {chunkSize}");
+
+ using var stream = new MemoryStream(exifBytes[6..]);
+ var img = new MyTiffFile(stream, Encoding.UTF8);
+ img.Properties.Set(ExifTag.ImageDescription, JsonSerializer.Serialize(generationParameters));
+
+ using var newStream = new MemoryStream();
+ img.Save(newStream);
+ newStream.Seek(0, SeekOrigin.Begin);
+ var newExifBytes = exifBytes[..6].Concat(newStream.ToArray());
+ var newExifSize = newExifBytes.Count();
+ var newChunkSize = BitConverter.GetBytes(newExifSize);
+ var newChunk = "EXIF"u8.ToArray().Concat(newChunkSize).Concat(newExifBytes).ToArray();
+
+ var inputEndIndex = (int)exifStart;
+ var newImage = inputImage[..inputEndIndex].Concat(newChunk).ToArray();
+
+ // webp or tiff or something requires even number of bytes
+ if (newImage.Length % 2 != 0)
+ {
+ newImage = newImage.Concat(new byte[] { 0x00 }).ToArray();
+ }
+
+ var newImageSize = BitConverter.GetBytes(newImage.Length - 8);
+ newImage[4] = newImageSize[0];
+ newImage[5] = newImageSize[1];
+ newImage[6] = newImageSize[2];
+ newImage[7] = newImageSize[3];
+ return newImage;
+ }
+
+ return Array.Empty();
+ }
+
+ private static byte[] GetExifChunks(FilePath imagePath)
+ {
+ using var byteStream = new BinaryReader(File.OpenRead(imagePath));
+ byteStream.BaseStream.Position = 0;
+
+ // Read first 8 bytes and make sure they match the RIFF header
+ if (!byteStream.ReadBytes(4).SequenceEqual(Riff))
+ {
+ return Array.Empty();
+ }
+
+ // skip 4 bytes then read next 4 for webp header
+ byteStream.BaseStream.Position += 4;
+ if (!byteStream.ReadBytes(4).SequenceEqual(Webp))
+ {
+ return Array.Empty();
+ }
+
+ while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)
+ {
+ var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));
+ var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).ToArray());
+
+ if (chunkType != "EXIF")
+ {
+ // skip chunk data
+ byteStream.BaseStream.Position += chunkSize;
+ continue;
+ }
+
+ var exifStart = byteStream.BaseStream.Position;
+ var exifBytes = byteStream.ReadBytes(chunkSize);
+ var exif = Encoding.UTF8.GetString(exifBytes);
+ Debug.WriteLine($"Found exif chunk of size {chunkSize}");
+ return exifBytes;
+ }
+
+ return Array.Empty();
+ }
}
diff --git a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
index ab3b78980..462b9def3 100644
--- a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
@@ -63,7 +63,9 @@ public string GetFullPath(string rootModelDirectory)
if (PreviewImageFullPath != null)
return PreviewImageFullPath;
- return PreviewImageRelativePath == null ? null : Path.Combine(rootModelDirectory, PreviewImageRelativePath);
+ return PreviewImageRelativePath == null
+ ? null
+ : Path.Combine(rootModelDirectory, PreviewImageRelativePath);
}
[BsonIgnore]
@@ -113,6 +115,6 @@ public override int GetHashCode()
".pth",
".bin"
];
- public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif"];
+ public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".webp"];
public static readonly HashSet SupportedMetadataExtensions = [".json"];
}
diff --git a/StabilityMatrix.Core/Services/MetadataImportService.cs b/StabilityMatrix.Core/Services/MetadataImportService.cs
index 5a88fb832..ca1803b4f 100644
--- a/StabilityMatrix.Core/Services/MetadataImportService.cs
+++ b/StabilityMatrix.Core/Services/MetadataImportService.cs
@@ -1,4 +1,5 @@
-using System.Text.Json;
+using System.Diagnostics;
+using System.Text.Json;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs
index 2b0849c38..61e04aa74 100644
--- a/StabilityMatrix.Core/Services/SettingsManager.cs
+++ b/StabilityMatrix.Core/Services/SettingsManager.cs
@@ -685,6 +685,8 @@ protected virtual void SaveSettings()
if (fs.CanWrite)
{
fs.Write(jsonBytes, 0, jsonBytes.Length);
+ fs.Flush();
+ fs.SetLength(jsonBytes.Length);
}
fs.Close();
}
From 96f722a9da2413517aa9a587a437f5f9beb07ab8 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Tue, 26 Dec 2023 17:06:28 +0800
Subject: [PATCH 024/276] Add WebpAnimation support in ImageBoxView
---
.../Controls/AdvancedImageBoxView.axaml | 49 ++++++++++++-------
.../Controls/AdvancedImageBoxView.axaml.cs | 13 +++--
.../Controls/DataTemplateSelector.cs | 46 +++++++++++++++++
.../Models/ITemplateKey.cs | 11 +++++
.../Models/ImageSource.cs | 19 ++++++-
.../Models/ImageSourceTemplateType.cs | 8 +++
6 files changed, 122 insertions(+), 24 deletions(-)
create mode 100644 StabilityMatrix.Avalonia/Controls/DataTemplateSelector.cs
create mode 100644 StabilityMatrix.Avalonia/Models/ITemplateKey.cs
create mode 100644 StabilityMatrix.Avalonia/Models/ImageSourceTemplateType.cs
diff --git a/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml b/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
index eb681d53c..698daab86 100644
--- a/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
+++ b/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
@@ -8,30 +8,43 @@
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:gif="clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif"
d:DataContext="{x:Static mocks:DesignData.SampleImageSource}"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="models:ImageSource"
mc:Ignorable="d">
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
("CopyMenuItem")!;
- copyMenuItem.Command = new AsyncRelayCommand(FlyoutCopy);
+
+ if (this.FindControl("CopyMenuItem") is { } copyMenuItem)
+ {
+ copyMenuItem.Command = new AsyncRelayCommand(FlyoutCopy);
+ }
}
-
+
private static async Task FlyoutCopy(Bitmap? image)
{
- if (image is null || !Compat.IsWindows) return;
+ if (image is null || !Compat.IsWindows)
+ return;
await Task.Run(() =>
{
diff --git a/StabilityMatrix.Avalonia/Controls/DataTemplateSelector.cs b/StabilityMatrix.Avalonia/Controls/DataTemplateSelector.cs
new file mode 100644
index 000000000..4e9ac24fb
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/DataTemplateSelector.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using Avalonia.Metadata;
+using JetBrains.Annotations;
+using StabilityMatrix.Avalonia.Models;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+///
+/// Selector for objects implementing
+///
+[PublicAPI]
+public class DataTemplateSelector : IDataTemplate
+ where TKey : notnull
+{
+ ///
+ /// Key that is used when no other key matches
+ ///
+ public TKey? DefaultKey { get; set; }
+
+ [Content]
+ public Dictionary Templates { get; } = new();
+
+ public bool Match(object? data) => data is ITemplateKey;
+
+ ///
+ public Control Build(object? data)
+ {
+ if (data is not ITemplateKey key)
+ throw new ArgumentException(null, nameof(data));
+
+ if (Templates.TryGetValue(key.TemplateKey, out var template))
+ {
+ return template.Build(data)!;
+ }
+
+ if (DefaultKey is not null && Templates.TryGetValue(DefaultKey, out var defaultTemplate))
+ {
+ return defaultTemplate.Build(data)!;
+ }
+
+ throw new ArgumentException(null, nameof(data));
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Models/ITemplateKey.cs b/StabilityMatrix.Avalonia/Models/ITemplateKey.cs
new file mode 100644
index 000000000..34c998d77
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/ITemplateKey.cs
@@ -0,0 +1,11 @@
+using StabilityMatrix.Avalonia.Controls;
+
+namespace StabilityMatrix.Avalonia.Models;
+
+///
+/// Implements a template key for
+///
+public interface ITemplateKey
+{
+ T TemplateKey { get; }
+}
diff --git a/StabilityMatrix.Avalonia/Models/ImageSource.cs b/StabilityMatrix.Avalonia/Models/ImageSource.cs
index 4a8fffb73..f20d316af 100644
--- a/StabilityMatrix.Avalonia/Models/ImageSource.cs
+++ b/StabilityMatrix.Avalonia/Models/ImageSource.cs
@@ -12,7 +12,7 @@
namespace StabilityMatrix.Avalonia.Models;
-public record ImageSource : IDisposable
+public record ImageSource : IDisposable, ITemplateKey
{
private Hash? contentHashBlake3;
@@ -55,6 +55,23 @@ public ImageSource(Bitmap bitmap)
Bitmap = bitmap;
}
+ ///
+ public ImageSourceTemplateType TemplateKey
+ {
+ get
+ {
+ var ext = LocalFile?.Extension ?? Path.GetExtension(RemoteUrl?.ToString());
+
+ if (ext is not null && ext.Equals(".webp", StringComparison.OrdinalIgnoreCase))
+ {
+ // TODO: Check if webp is animated
+ return ImageSourceTemplateType.WebpAnimation;
+ }
+
+ return ImageSourceTemplateType.Image;
+ }
+ }
+
[JsonIgnore]
public Task BitmapAsync => GetBitmapAsync();
diff --git a/StabilityMatrix.Avalonia/Models/ImageSourceTemplateType.cs b/StabilityMatrix.Avalonia/Models/ImageSourceTemplateType.cs
new file mode 100644
index 000000000..14ab15d4f
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/ImageSourceTemplateType.cs
@@ -0,0 +1,8 @@
+namespace StabilityMatrix.Avalonia.Models;
+
+public enum ImageSourceTemplateType
+{
+ Default,
+ Image,
+ WebpAnimation
+}
From 25a1a40f6174906c39dd5a860c9edd6108e78825 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Tue, 26 Dec 2023 17:16:23 +0800
Subject: [PATCH 025/276] Use ImageGalleryCard for ImageToVideo
---
.../InferenceImageToVideoViewModel.cs | 59 +++++--------------
.../Inference/InferenceImageToVideoView.axaml | 7 ---
2 files changed, 16 insertions(+), 50 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index 4be708a77..c53af66d2 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -30,7 +30,9 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceImageToVideoView), persistent: true)]
[ManagedService]
[Transient]
-public partial class InferenceImageToVideoViewModel : InferenceGenerationViewModelBase, IParametersLoadableState
+public partial class InferenceImageToVideoViewModel
+ : InferenceGenerationViewModelBase,
+ IParametersLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -61,14 +63,6 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
[JsonPropertyName("VideoOutput")]
public VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel { get; }
- [ObservableProperty]
- [JsonIgnore]
- private string outputUri;
-
- [ObservableProperty]
- [JsonIgnore]
- private bool isGenerating;
-
public InferenceImageToVideoViewModel(
INotificationService notificationService,
IInferenceClientManager inferenceClientManager,
@@ -118,24 +112,6 @@ IModelIndexService modelIndexService
);
}
- public override void OnLoaded()
- {
- EventManager.Instance.ImageFileAdded += OnImageFileAdded;
- }
-
- public override void OnUnloaded()
- {
- EventManager.Instance.ImageFileAdded -= OnImageFileAdded;
- }
-
- private void OnImageFileAdded(object? sender, FilePath e)
- {
- if (!e.Extension.Equals(".webp", StringComparison.OrdinalIgnoreCase))
- return;
-
- OutputUri = e;
- }
-
///
protected override void BuildPrompt(BuildPromptEventArgs args)
{
@@ -153,17 +129,15 @@ protected override void BuildPrompt(BuildPromptEventArgs args)
ModelCardViewModel.ApplyStep(args);
// Setup latent from image
- var imageLoad = builder
- .Nodes
- .AddTypedNode(
- new ComfyNodeBuilder.LoadImage
- {
- Name = builder.Nodes.GetUniqueName("ControlNet_LoadImage"),
- Image =
- SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
- ?? throw new ValidationException()
- }
- );
+ var imageLoad = builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LoadImage
+ {
+ Name = builder.Nodes.GetUniqueName("ControlNet_LoadImage"),
+ Image =
+ SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException()
+ }
+ );
builder.Connections.Primary = imageLoad.Output1;
builder.Connections.PrimarySize = SelectImageCardViewModel.CurrentBitmapSize;
@@ -190,7 +164,10 @@ protected override IEnumerable GetInputImages()
}
///
- protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
+ protected override async Task GenerateImageImpl(
+ GenerateOverrides overrides,
+ CancellationToken cancellationToken
+ )
{
if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)
{
@@ -229,15 +206,11 @@ protected override async Task GenerateImageImpl(GenerateOverrides overrides, Can
batchArgs.Add(generationArgs);
}
- IsGenerating = true;
-
// Run batches
foreach (var args in batchArgs)
{
await RunGeneration(args, cancellationToken);
}
-
- IsGenerating = false;
}
///
diff --git a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
index c3497945f..0dcb6e0bd 100644
--- a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
+++ b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
@@ -96,14 +96,7 @@
DataContext="{Binding ElementName=Dock, Path=DataContext}">
-
-
-
Date: Tue, 26 Dec 2023 02:43:07 -0800
Subject: [PATCH 026/276] save/load img2vid and show webps in dialog thing
---
.../Models/Inference/VideoOutputMethod.cs | 5 +-
.../Base/InferenceGenerationViewModelBase.cs | 19 +++++-
.../InferenceImageToVideoViewModel.cs | 7 ++-
.../Video/SvdImgToVidConditioningViewModel.cs | 16 ++++-
.../Video/VideoOutputSettingsCardViewModel.cs | 20 ++++++-
.../Settings/MainSettingsViewModel.cs | 5 +-
.../Views/Dialogs/ImageViewerDialog.axaml | 59 +++++++++++++------
StabilityMatrix.Core/Helper/ImageMetadata.cs | 48 ++++++++-------
.../Models/Database/LocalImageFile.cs | 44 +++++++++++++-
.../Models/GenerationParameters.cs | 9 +++
10 files changed, 177 insertions(+), 55 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs b/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs
index b2ffcf998..134a3c0b0 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs
@@ -1,5 +1,8 @@
-namespace StabilityMatrix.Avalonia.Models.Inference;
+using System.Text.Json.Serialization;
+namespace StabilityMatrix.Avalonia.Models.Inference;
+
+[JsonConverter(typeof(JsonStringEnumConverter))]
public enum VideoOutputMethod
{
Fastest,
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index 17970867b..7a50ee01e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -5,6 +5,8 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Management;
+using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
@@ -12,6 +14,8 @@
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
+using ExifLibrary;
+using MetadataExtractor.Formats.Exif;
using NLog;
using Refit;
using SkiaSharp;
@@ -422,7 +426,20 @@ ImageGenerationEventArgs args
}
else if (comfyImage.FileName.EndsWith(".webp"))
{
- var bytesWithMetadata = ImageMetadata.AddMetadataToWebp(imageArray, parameters);
+ var opts = new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ Converters = { new JsonStringEnumConverter() }
+ };
+ var paramsJson = JsonSerializer.Serialize(parameters, opts);
+ var smProject = JsonSerializer.Serialize(project, opts);
+ var metadata = new Dictionary
+ {
+ { ExifTag.ImageDescription, paramsJson },
+ { ExifTag.Software, smProject }
+ };
+
+ var bytesWithMetadata = ImageMetadata.AddMetadataToWebp(imageArray, metadata);
// Write using generated name
var filePath = await WriteOutputImageAsync(
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index c53af66d2..b64a48015 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Drawing;
using System.Linq;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
@@ -23,8 +22,6 @@
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Services;
-#pragma warning disable CS0657 // Not a valid attribute location for this declaration
-
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceImageToVideoView), persistent: true)]
@@ -218,6 +215,8 @@ public void LoadStateFromParameters(GenerationParameters parameters)
{
SamplerCardViewModel.LoadStateFromParameters(parameters);
ModelCardViewModel.LoadStateFromParameters(parameters);
+ SvdImgToVidConditioningViewModel.LoadStateFromParameters(parameters);
+ VideoOutputSettingsCardViewModel.LoadStateFromParameters(parameters);
SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);
}
@@ -227,6 +226,8 @@ public GenerationParameters SaveStateToParameters(GenerationParameters parameter
{
parameters = SamplerCardViewModel.SaveStateToParameters(parameters);
parameters = ModelCardViewModel.SaveStateToParameters(parameters);
+ parameters = SvdImgToVidConditioningViewModel.SaveStateToParameters(parameters);
+ parameters = VideoOutputSettingsCardViewModel.SaveStateToParameters(parameters);
parameters.Seed = (ulong)SeedCardViewModel.Seed;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
index 9ff667e63..1bb70b269 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
@@ -44,13 +44,23 @@ public void LoadStateFromParameters(GenerationParameters parameters)
{
Width = parameters.Width;
Height = parameters.Height;
- // TODO: add more metadata
+ NumFrames = parameters.FrameCount;
+ MotionBucketId = parameters.MotionBucketId;
+ Fps = parameters.Fps;
+ AugmentationLevel = parameters.AugmentationLevel;
+ MinCfg = parameters.MinCfg;
}
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
- // TODO: add more metadata
- return parameters with { Width = Width, Height = Height, };
+ return parameters with
+ {
+ FrameCount = NumFrames,
+ MotionBucketId = MotionBucketId,
+ Fps = Fps,
+ AugmentationLevel = AugmentationLevel,
+ MinCfg = MinCfg,
+ };
}
public void ApplyStep(ModuleApplyStepEventArgs e)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
index c0cdea9b0..d44f469ae 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
@@ -37,13 +37,27 @@ public partial class VideoOutputSettingsCardViewModel
public void LoadStateFromParameters(GenerationParameters parameters)
{
- // TODO
+ Fps = parameters.OutputFps;
+ Lossless = parameters.Lossless;
+ Quality = parameters.VideoQuality;
+
+ if (string.IsNullOrWhiteSpace(parameters.VideoOutputMethod))
+ return;
+
+ SelectedMethod = Enum.TryParse(parameters.VideoOutputMethod, true, out var method)
+ ? method
+ : VideoOutputMethod.Default;
}
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
- // TODO
- return parameters;
+ return parameters with
+ {
+ OutputFps = Fps,
+ Lossless = Lossless,
+ VideoQuality = Quality,
+ VideoOutputMethod = SelectedMethod.ToString(),
+ };
}
public void ApplyStep(ModuleApplyStepEventArgs e)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index aadc47dab..bf241a123 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -26,6 +26,7 @@
using DynamicData.Binding;
using ExifLibrary;
using FluentAvalonia.UI.Controls;
+using MetadataExtractor.Formats.Exif;
using NLog;
using SkiaSharp;
using StabilityMatrix.Avalonia.Animations;
@@ -709,10 +710,6 @@ private async Task DebugImageMetadata()
if (files.Count == 0)
return;
- var data = ImageMetadata.ReadTextChunkFromWebp(files[0].TryGetLocalPath(), ExifTag.ImageDescription);
-
- return;
-
var metadata = ImageMetadata.ParseFile(files[0].TryGetLocalPath()!);
var textualTags = metadata.GetTextualData()?.ToArray();
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
index 39152677d..d39924d25 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
@@ -10,6 +10,9 @@
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:vmDialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
+ xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
+ xmlns:gif="clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif"
+ xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
d:DataContext="{x:Static mocks:DesignData.ImageViewerViewModel}"
@@ -46,24 +49,46 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RowDefinitions="*,Auto">
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /// Reads an EXIF tag from a webp file and returns the value as string
+ ///
+ /// The webp file to read EXIF data from
+ /// Use constants for the tag you'd like to search for
+ ///
+ public static string ReadTextChunkFromWebp(FilePath filePath, int exifTag)
{
- var sw = Stopwatch.StartNew();
- try
- {
- var exifChunks = GetExifChunks(filePath);
- if (exifChunks.Length == 0)
- return string.Empty;
-
- // write exifChunks to new memoryStream but skip first 6 bytes
- using var newMemoryStream = new MemoryStream(exifChunks[6..]);
- newMemoryStream.Seek(0, SeekOrigin.Begin);
-
- var img = new MyTiffFile(newMemoryStream, Encoding.UTF8);
- return img.Properties[exifTag]?.Value?.ToString() ?? string.Empty;
- }
- finally
- {
- sw.Stop();
- Console.WriteLine($"ReadTextChunkFromWebp took {sw.ElapsedMilliseconds}ms");
- }
+ var exifDirs = WebPMetadataReader.ReadMetadata(filePath).OfType().FirstOrDefault();
+ return exifDirs is null ? string.Empty : exifDirs.GetString(exifTag) ?? string.Empty;
}
public static IEnumerable AddMetadataToWebp(
byte[] inputImage,
- GenerationParameters generationParameters
+ Dictionary exifTagData
)
{
using var byteStream = new BinaryReader(new MemoryStream(inputImage));
@@ -296,7 +295,11 @@ GenerationParameters generationParameters
using var stream = new MemoryStream(exifBytes[6..]);
var img = new MyTiffFile(stream, Encoding.UTF8);
- img.Properties.Set(ExifTag.ImageDescription, JsonSerializer.Serialize(generationParameters));
+
+ foreach (var (key, value) in exifTagData)
+ {
+ img.Properties.Set(key, value);
+ }
using var newStream = new MemoryStream();
img.Save(newStream);
@@ -315,6 +318,7 @@ GenerationParameters generationParameters
newImage = newImage.Concat(new byte[] { 0x00 }).ToArray();
}
+ // no clue why the minus 8 is needed but it is
var newImageSize = BitConverter.GetBytes(newImage.Length - 8);
newImage[4] = newImageSize[0];
newImage[5] = newImageSize[1];
diff --git a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
index a050b6c62..76b3ff78c 100644
--- a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
@@ -1,4 +1,5 @@
using DynamicData.Tests;
+using MetadataExtractor.Formats.Exif;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.FileInterfaces;
using JsonSerializer = System.Text.Json.JsonSerializer;
@@ -50,6 +51,17 @@ public record LocalImageFile
public (string? Parameters, string? ParametersJson, string? SMProject, string? ComfyNodes) ReadMetadata()
{
+ if (AbsolutePath.EndsWith("webp"))
+ {
+ var paramsJson = ImageMetadata.ReadTextChunkFromWebp(
+ AbsolutePath,
+ ExifDirectoryBase.TagImageDescription
+ );
+ var smProj = ImageMetadata.ReadTextChunkFromWebp(AbsolutePath, ExifDirectoryBase.TagSoftware);
+
+ return (null, paramsJson, smProj, null);
+ }
+
using var stream = new FileStream(AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read);
using var reader = new BinaryReader(stream);
@@ -71,6 +83,29 @@ public static LocalImageFile FromPath(FilePath filePath)
// TODO: Support other types
const LocalImageFileType imageType = LocalImageFileType.Inference | LocalImageFileType.TextToImage;
+ if (filePath.Extension.Contains("webp"))
+ {
+ var paramsJson = ImageMetadata.ReadTextChunkFromWebp(
+ filePath,
+ ExifDirectoryBase.TagImageDescription
+ );
+ var parameters = string.IsNullOrWhiteSpace(paramsJson)
+ ? null
+ : JsonSerializer.Deserialize(paramsJson);
+
+ filePath.Info.Refresh();
+
+ return new LocalImageFile
+ {
+ AbsolutePath = filePath,
+ ImageType = imageType,
+ CreatedAt = filePath.Info.CreationTimeUtc,
+ LastModifiedAt = filePath.Info.LastWriteTimeUtc,
+ GenerationParameters = parameters,
+ ImageSize = new Size(parameters?.Width ?? 0, parameters?.Height ?? 0)
+ };
+ }
+
// Get metadata
using var stream = filePath.Info.OpenRead();
using var reader = new BinaryReader(stream);
@@ -104,5 +139,12 @@ public static LocalImageFile FromPath(FilePath filePath)
};
}
- public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
+ public static readonly HashSet SupportedImageExtensions =
+ [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".webp"
+ ];
}
diff --git a/StabilityMatrix.Core/Models/GenerationParameters.cs b/StabilityMatrix.Core/Models/GenerationParameters.cs
index c0b5aa9b5..46f7459a6 100644
--- a/StabilityMatrix.Core/Models/GenerationParameters.cs
+++ b/StabilityMatrix.Core/Models/GenerationParameters.cs
@@ -20,6 +20,15 @@ public partial record GenerationParameters
public int Width { get; set; }
public string? ModelHash { get; set; }
public string? ModelName { get; set; }
+ public int FrameCount { get; set; }
+ public int MotionBucketId { get; set; }
+ public int VideoQuality { get; set; }
+ public bool Lossless { get; set; }
+ public int Fps { get; set; }
+ public double OutputFps { get; set; }
+ public double MinCfg { get; set; }
+ public double AugmentationLevel { get; set; }
+ public string? VideoOutputMethod { get; set; }
public static bool TryParse(
string? text,
From b13bea5b6fad141c7b15f72099ce1951dd9ceb1d Mon Sep 17 00:00:00 2001
From: JT
Date: Tue, 26 Dec 2023 02:51:51 -0800
Subject: [PATCH 027/276] add webps to outputs page
---
.../ViewModels/OutputsPageViewModel.cs | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
index b49ff6d12..99bfca1b9 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
@@ -93,6 +93,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
? Resources.Label_OneImageSelected
: string.Format(Resources.Label_NumImagesSelected, NumItemsSelected);
+ private string[] allowedExtensions = [".png", ".webp"];
+
public OutputsPageViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory,
@@ -434,11 +436,14 @@ public async Task ConsolidateImages()
var directory = category.Tag.ToString();
- foreach (var path in Directory.EnumerateFiles(directory, "*.png", SearchOption.AllDirectories))
+ foreach (var path in Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories))
{
try
{
var file = new FilePath(path);
+ if (!allowedExtensions.Contains(file.Extension))
+ continue;
+
var newPath = settingsManager.ConsolidatedImagesDirectory + file.Name;
if (file.FullPath == newPath)
continue;
@@ -496,7 +501,8 @@ private void GetOutputs(string directory)
}
var files = Directory
- .EnumerateFiles(directory, "*.png", SearchOption.AllDirectories)
+ .EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
+ .Where(path => allowedExtensions.Contains(new FilePath(path).Extension))
.Select(file => LocalImageFile.FromPath(file))
.ToList();
From b4e4886249b8ebf886078cac16978427b4751b7c Mon Sep 17 00:00:00 2001
From: Ionite
Date: Tue, 26 Dec 2023 05:58:04 -0500
Subject: [PATCH 028/276] Update CHANGELOG.md
---
CHANGELOG.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 820add423..d08547fd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## v2.8.0-dev.1
+### Added
+#### Inference
+- Added image and model details in model selection boxes
+- Added CLIP Skip setting, toggleable from the model settings button
+
## v2.7.5
### Fixed
- Fixed Python Packages manager crash when pip list returns warnings in json
From 7ef86731a69109d5004928c81243ffb6562aae0b Mon Sep 17 00:00:00 2001
From: JT
Date: Tue, 26 Dec 2023 03:23:16 -0800
Subject: [PATCH 029/276] moved strings to resources & added send to
img2img/vid options in output browser
---
.../VideoGenerationSettingsCard.axaml | 10 +-
.../Controls/VideoOutputSettingsCard.axaml | 8 +-
.../FallbackRamCachedWebImageLoader.cs | 2 -
.../Languages/Resources.Designer.cs | 81 +++++++++
.../Languages/Resources.resx | 27 +++
.../ViewModels/InferenceViewModel.cs | 159 +++++++++++++-----
.../ViewModels/OutputsPageViewModel.cs | 12 ++
.../Views/OutputsPage.axaml | 16 +-
StabilityMatrix.Core/Helper/EventManager.cs | 23 +--
.../Models/Database/LocalImageFile.cs | 2 +-
10 files changed, 263 insertions(+), 77 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml b/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml
index 229bc551a..f9f38ba9b 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 @@ public static string Label_Folder {
}
}
+ ///
+ /// 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 @@ public static string Label_ImageToImage {
}
}
+ ///
+ /// 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 @@ public static string Label_LocalModel {
}
}
+ ///
+ /// 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 @@ public static string Label_ModelType {
}
}
+ ///
+ /// 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 @@ public static string Label_VersionType {
}
}
+ ///
+ /// 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 c0a189367..d96411540 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 e3966c1f8..a0959d5ec 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 @@ SharedState sharedState
// "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 @@ private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusC
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 @@ protected override async Task OnInitialLoadedAsync()
);
// 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 @@ private void OnInferenceUpscaleRequested(object? sender, LocalImageFile e)
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 @@ private async Task SyncTabStatesWithDatabase()
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 @@ private async Task SyncTabStateWithDatabase(InferenceTabViewModelBase tab)
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 @@ private async Task SyncTabStateWithDatabase(InferenceTabViewModelBase tab)
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 @@ private async Task Disconnect()
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 @@ private async Task MenuSaveAs()
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 @@ private async Task MenuSave()
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 @@ private async Task AddUpscalerTabFromImage(LocalImageFile imageFile)
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 99bfca1b9..f11867512 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
@@ -315,6 +315,18 @@ public void SendToUpscale(OutputImageViewModel vm)
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 b9f29cfcb..22fdbc372 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 72a2b3c37..22c09cc24 100644
--- a/StabilityMatrix.Core/Helper/EventManager.cs
+++ b/StabilityMatrix.Core/Helper/EventManager.cs
@@ -37,15 +37,14 @@ private 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 void OnOneClickInstallFinished(bool skipped) =>
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 void OnInferenceTextToImageRequested(LocalImageFile imageFile) =>
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 76b3ff78c..541536351 100644
--- a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs
@@ -83,7 +83,7 @@ public static LocalImageFile FromPath(FilePath filePath)
// 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,
From 8cd3ec1b981d46a8f2829c71b80cf56699f78683 Mon Sep 17 00:00:00 2001
From: JT
Date: Tue, 26 Dec 2023 18:24:05 -0800
Subject: [PATCH 030/276] chagenlog & preview fix
---
CHANGELOG.md | 10 ++++++++++
.../Controls/AdvancedImageBoxView.axaml | 2 +-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c61ffeeec..6280e8c6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## v2.8.0-dev.2
+### Added
+#### Inference
+- Added Image to Video project type
+#### Output Browser
+- Added support for webp files
+- Added "Send to Image to Image" and "Send to Image to Video" options to the context menu
+### Changed
+- Changed how settings file is written to disk to reduce potential data loss risk
+
## v2.8.0-dev.1
### Added
#### Inference
diff --git a/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml b/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
index 698daab86..73b43f25a 100644
--- a/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
+++ b/StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
@@ -21,7 +21,7 @@
+ SourceUri="{Binding LocalFile.FullPath}"/>
From 9275a7ad326a909b022b041aae8c621336661baf Mon Sep 17 00:00:00 2001
From: JT
Date: Tue, 26 Dec 2023 18:31:48 -0800
Subject: [PATCH 031/276] remove unused imageMagick lib
---
StabilityMatrix.Core/StabilityMatrix.Core.csproj | 1 -
1 file changed, 1 deletion(-)
diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
index e2304c75f..12b0de97d 100644
--- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj
+++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
@@ -34,7 +34,6 @@
-
From 7a939db678aaaaf02e0ee0631ffe4e454696c4dd Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 11:24:12 +0800
Subject: [PATCH 032/276] Fix pip install error on macos
---
StabilityMatrix.Core/Python/PyRunner.cs | 19 +++++--------------
1 file changed, 5 insertions(+), 14 deletions(-)
diff --git a/StabilityMatrix.Core/Python/PyRunner.cs b/StabilityMatrix.Core/Python/PyRunner.cs
index b053a2813..6e384449c 100644
--- a/StabilityMatrix.Core/Python/PyRunner.cs
+++ b/StabilityMatrix.Core/Python/PyRunner.cs
@@ -12,13 +12,7 @@
namespace StabilityMatrix.Core.Python;
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Global")]
-public record struct PyVersionInfo(
- int Major,
- int Minor,
- int Micro,
- string ReleaseLevel,
- int Serial
-);
+public record struct PyVersionInfo(int Major, int Minor, int Micro, string ReleaseLevel, int Serial);
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[Singleton(typeof(IPyRunner))]
@@ -32,8 +26,7 @@ public class PyRunner : IPyRunner
// This is same for all platforms
public const string PythonDirName = "Python310";
- public static string PythonDir =>
- Path.Combine(GlobalConfig.LibraryDir, "Assets", PythonDirName);
+ public static string PythonDir => Path.Combine(GlobalConfig.LibraryDir, "Assets", PythonDirName);
///
/// Path to the Python Linked library relative from the Python directory.
@@ -61,8 +54,7 @@ public class PyRunner : IPyRunner
public static string GetPipPath => Path.Combine(PythonDir, "get-pip.pyc");
- public static string VenvPath =>
- Path.Combine(PythonDir, "Scripts", "virtualenv" + Compat.ExeExtension);
+ public static string VenvPath => Path.Combine(PythonDir, "Scripts", "virtualenv" + Compat.ExeExtension);
public static bool PipInstalled => File.Exists(PipExePath);
public static bool VenvInstalled => File.Exists(VenvPath);
@@ -107,8 +99,7 @@ public async Task Initialize()
await RunInThreadWithLock(() =>
{
var sys =
- Py.Import("sys") as PyModule
- ?? throw new NullReferenceException("sys module not found");
+ Py.Import("sys") as PyModule ?? throw new NullReferenceException("sys module not found");
sys.Set("stdout", StdOutStream);
sys.Set("stderr", StdErrStream);
})
@@ -141,7 +132,7 @@ public async Task InstallPackage(string package)
throw new FileNotFoundException("pip not found", PipExePath);
}
var result = await ProcessRunner
- .GetProcessResultAsync(PipExePath, $"install {package}")
+ .GetProcessResultAsync(PythonExePath, $"-m pip install {package}")
.ConfigureAwait(false);
result.EnsureSuccessExitCode();
}
From 868edbffd1dce0f341389dd02b000cb191077366 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 11:35:11 +0800
Subject: [PATCH 033/276] Allow multiple arguments in pip install args
---
.../ViewModels/Dialogs/PythonPackagesViewModel.cs | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs
index b7d00f8ea..6f00313b5 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs
@@ -140,10 +140,7 @@ private Task ModifySelectedPackage(PythonPackagesItemViewModel? item)
? UpgradePackageVersion(
item.Package.Name,
item.SelectedVersion,
- PythonPackagesItemViewModel.GetKnownIndexUrl(
- item.Package.Name,
- item.SelectedVersion
- ),
+ PythonPackagesItemViewModel.GetKnownIndexUrl(item.Package.Name, item.SelectedVersion),
isDowngrade: item.CanDowngrade
)
: Task.CompletedTask;
@@ -167,9 +164,7 @@ private async Task UpgradePackageVersion(
Resources.Label_ConfirmQuestion
);
- dialog.PrimaryButtonText = isDowngrade
- ? Resources.Action_Downgrade
- : Resources.Action_Upgrade;
+ dialog.PrimaryButtonText = isDowngrade ? Resources.Action_Downgrade : Resources.Action_Upgrade;
dialog.IsPrimaryButtonEnabled = true;
dialog.DefaultButton = ContentDialogButton.Primary;
dialog.CloseButtonText = Resources.Action_Cancel;
@@ -225,7 +220,7 @@ private async Task InstallPackage()
var dialog = DialogHelper.CreateTextEntryDialog("Install Package", "", fields);
var result = await dialog.ShowAsync();
- if (result != ContentDialogResult.Primary || fields[0].Text is not { } packageName)
+ if (result != ContentDialogResult.Primary || fields[0].Text is not { } packageArgs)
{
return;
}
@@ -236,14 +231,14 @@ private async Task InstallPackage()
{
VenvDirectory = VenvPath,
WorkingDirectory = VenvPath.Parent,
- Args = new[] { "install", packageName }
+ Args = new ProcessArgs(packageArgs).Prepend("install")
}
};
var runner = new PackageModificationRunner
{
ShowDialogOnStart = true,
- ModificationCompleteMessage = $"Installed Python Package '{packageName}'"
+ ModificationCompleteMessage = $"Installed Python Package '{packageArgs}'"
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);
From b0a4f2e82ee1e7ff7806c3ae9ccf51d805037a66 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 11:38:18 +0800
Subject: [PATCH 034/276] Update CHANGELOG.md
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48a70fca0..2f95aaa67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## v2.7.7
+### Changed
+- Python Packages install dialog now allows entering multiple arguments or option flags
+
## v2.7.6
### Added
- Added SDXL Turbo and Stable Video Diffusion to the Hugging Face tab
From f94bc50a2ca4062dcebec9af4bfdedd001fac76c Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 11:40:35 +0800
Subject: [PATCH 035/276] Skip URI registration on macos
---
StabilityMatrix.Avalonia/Helpers/UriHandler.cs | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
index 75dd4a5fd..3b16d078d 100644
--- a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
+++ b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
@@ -58,17 +58,19 @@ public void SendAndExit(Uri uri)
public void RegisterUriScheme()
{
+ // Not supported on macos
+
if (Compat.IsWindows)
{
RegisterUriSchemeWin();
}
- else
+ else if (Compat.IsLinux)
{
- // Try to register on unix but ignore errors
+ // Try to register on linux but ignore errors
// Library does not support some distros
try
{
- RegisterUriSchemeUnix();
+ RegisterUriSchemeLinux();
}
catch (Exception e)
{
@@ -98,9 +100,14 @@ private void RegisterUriSchemeWin()
}
}
- private void RegisterUriSchemeUnix()
+ [SupportedOSPlatform("linux")]
+ private void RegisterUriSchemeLinux()
{
- var service = URISchemeServiceFactory.GetURISchemeSerivce(Scheme, Description, Compat.AppCurrentPath.FullPath);
+ var service = URISchemeServiceFactory.GetURISchemeSerivce(
+ Scheme,
+ Description,
+ Compat.AppCurrentPath.FullPath
+ );
service.Set();
}
}
From b60ccf9f36f788d6ce35beacc44f65b818eab80f Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:05:44 +0800
Subject: [PATCH 036/276] Formatting
---
.../Models/Packages/ComfyUI.cs | 47 +++++++++++++++----
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index 81a415c4a..5fbef8ac1 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -142,7 +142,14 @@ IPrerequisiteHelper prerequisiteHelper
public override string MainBranch => "master";
public override IEnumerable AvailableTorchVersions =>
- new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm, TorchVersion.Mps };
+ new[]
+ {
+ TorchVersion.Cpu,
+ TorchVersion.Cuda,
+ TorchVersion.DirectMl,
+ TorchVersion.Rocm,
+ TorchVersion.Mps
+ };
public override async Task InstallPackage(
string installLocation,
@@ -161,7 +168,9 @@ public override async Task InstallPackage(
await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
- progress?.Report(new ProgressReport(-1f, "Installing Package Requirements...", isIndeterminate: true));
+ progress?.Report(
+ new ProgressReport(-1f, "Installing Package Requirements...", isIndeterminate: true)
+ );
var pipArgs = new PipInstallArgs();
@@ -181,7 +190,12 @@ public override async Task InstallPackage(
TorchVersion.Cpu => "cpu",
TorchVersion.Cuda => "cu121",
TorchVersion.Rocm => "rocm5.6",
- _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
+ _
+ => throw new ArgumentOutOfRangeException(
+ nameof(torchVersion),
+ torchVersion,
+ null
+ )
}
)
};
@@ -239,16 +253,23 @@ void HandleConsoleOutput(ProcessOutput s)
}
}
- public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) =>
+ public override Task SetupModelFolders(
+ DirectoryPath installDirectory,
+ SharedFolderMethod sharedFolderMethod
+ ) =>
sharedFolderMethod switch
{
- SharedFolderMethod.Symlink => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink),
+ SharedFolderMethod.Symlink
+ => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink),
SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory),
SharedFolderMethod.None => Task.CompletedTask,
_ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null)
};
- public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod)
+ public override Task RemoveModelFolderLinks(
+ DirectoryPath installDirectory,
+ SharedFolderMethod sharedFolderMethod
+ )
{
return sharedFolderMethod switch
{
@@ -286,7 +307,9 @@ private async Task SetupModelFoldersConfig(DirectoryPath installDirectory)
throw new Exception("Invalid extra_model_paths.yaml");
}
// check if we have a child called "stability_matrix"
- var stabilityMatrixNode = mappingNode.Children.FirstOrDefault(c => c.Key.ToString() == "stability_matrix");
+ var stabilityMatrixNode = mappingNode.Children.FirstOrDefault(
+ c => c.Key.ToString() == "stability_matrix"
+ );
if (stabilityMatrixNode.Key != null)
{
@@ -337,7 +360,11 @@ private async Task SetupModelFoldersConfig(DirectoryPath installDirectory)
{ "hypernetworks", Path.Combine(modelsDir, "Hypernetwork") },
{
"controlnet",
- string.Join('\n', Path.Combine(modelsDir, "ControlNet"), Path.Combine(modelsDir, "T2IAdapter"))
+ string.Join(
+ '\n',
+ Path.Combine(modelsDir, "ControlNet"),
+ Path.Combine(modelsDir, "T2IAdapter")
+ )
},
{ "clip", Path.Combine(modelsDir, "CLIP") },
{ "clip_vision", Path.Combine(modelsDir, "InvokeClipVision") },
@@ -401,7 +428,9 @@ private static async Task RemoveConfigSection(DirectoryPath installDirectory)
mappingNode.Children.Remove("stability_matrix");
- var serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build();
+ var serializer = new SerializerBuilder()
+ .WithNamingConvention(UnderscoredNamingConvention.Instance)
+ .Build();
var yamlData = serializer.Serialize(mappingNode);
await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false);
From 661e387a88eccbce6f8959673abb74c37b61d96a Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:06:26 +0800
Subject: [PATCH 037/276] Add cross attention method args for comfy
---
StabilityMatrix.Core/Models/Packages/ComfyUI.cs | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index 5fbef8ac1..fdb0fde1b 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -118,6 +118,18 @@ IPrerequisiteHelper prerequisiteHelper
Options = ["--cpu"]
},
new LaunchOptionDefinition
+ {
+ Name = "Cross Attention Method",
+ Type = LaunchOptionType.Bool,
+ InitialValue = Compat.IsMacOS ? "--use-pytorch-cross-attention" : null,
+ Options =
+ [
+ "--use-split-cross-attention",
+ "--use-quad-cross-attention",
+ "--use-pytorch-cross-attention"
+ ]
+ },
+ new LaunchOptionDefinition
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
From 7d247166eedd25e1b90c7e4946ae55ff390b98e6 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:17:12 +0800
Subject: [PATCH 038/276] Add Mps recommend in BasePackage
---
.../Models/Packages/BasePackage.cs | 35 +++++++++++++++----
1 file changed, 29 insertions(+), 6 deletions(-)
diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs
index 063693e7d..10aab608e 100644
--- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs
@@ -85,11 +85,20 @@ public abstract Task Update(
public abstract SharedFolderMethod RecommendedSharedFolderMethod { get; }
- public abstract Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod);
+ public abstract Task SetupModelFolders(
+ DirectoryPath installDirectory,
+ SharedFolderMethod sharedFolderMethod
+ );
- public abstract Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod);
+ public abstract Task UpdateModelFolders(
+ DirectoryPath installDirectory,
+ SharedFolderMethod sharedFolderMethod
+ );
- public abstract Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod);
+ public abstract Task RemoveModelFolderLinks(
+ DirectoryPath installDirectory,
+ SharedFolderMethod sharedFolderMethod
+ );
public abstract Task SetupOutputFolderLinks(DirectoryPath installDirectory);
public abstract Task RemoveOutputFolderLinks(DirectoryPath installDirectory);
@@ -117,6 +126,11 @@ public virtual TorchVersion GetRecommendedTorchVersion()
return TorchVersion.DirectMl;
}
+ if (Compat.IsMacOS && Compat.IsArm && AvailableTorchVersions.Contains(TorchVersion.Mps))
+ {
+ return TorchVersion.Mps;
+ }
+
return TorchVersion.Cpu;
}
@@ -142,7 +156,11 @@ public virtual TorchVersion GetRecommendedTorchVersion()
public abstract Dictionary>? SharedOutputFolders { get; }
public abstract Task GetAllVersionOptions();
- public abstract Task?> GetAllCommits(string branch, int page = 1, int perPage = 10);
+ public abstract Task?> GetAllCommits(
+ string branch,
+ int page = 1,
+ int perPage = 10
+ );
public abstract Task GetLatestVersion(bool includePrerelease = false);
public abstract string MainBranch { get; }
public event EventHandler? Exited;
@@ -153,7 +171,9 @@ public virtual TorchVersion GetRecommendedTorchVersion()
public void OnStartupComplete(string url) => StartupComplete?.Invoke(this, url);
public virtual PackageVersionType AvailableVersionTypes =>
- ShouldIgnoreReleases ? PackageVersionType.Commit : PackageVersionType.GithubRelease | PackageVersionType.Commit;
+ ShouldIgnoreReleases
+ ? PackageVersionType.Commit
+ : PackageVersionType.GithubRelease | PackageVersionType.Commit;
protected async Task InstallCudaTorch(
PyVenvRunner venvRunner,
@@ -194,6 +214,9 @@ protected Task InstallCpuTorch(
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CPU", isIndeterminate: true));
- return venvRunner.PipInstall(new PipInstallArgs().WithTorch("==2.0.1").WithTorchVision(), onConsoleOutput);
+ return venvRunner.PipInstall(
+ new PipInstallArgs().WithTorch("==2.0.1").WithTorchVision(),
+ onConsoleOutput
+ );
}
}
From 5e0a9480090acfc72958b34e1a6a945972b69133 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:17:28 +0800
Subject: [PATCH 039/276] Add force fp config for comfy
---
StabilityMatrix.Core/Models/Packages/ComfyUI.cs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index fdb0fde1b..5352c1b71 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -130,6 +130,13 @@ IPrerequisiteHelper prerequisiteHelper
]
},
new LaunchOptionDefinition
+ {
+ Name = "Force Floating Point Precision",
+ Type = LaunchOptionType.Bool,
+ InitialValue = Compat.IsMacOS ? "--force-fp16" : null,
+ Options = ["--force-fp32", "--force-fp16"]
+ },
+ new LaunchOptionDefinition
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
From a125a0bd594742753ad56c41e06f8229424fc48a Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:19:15 +0800
Subject: [PATCH 040/276] Add VAE precision to comfy args
---
StabilityMatrix.Core/Models/Packages/ComfyUI.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index 5352c1b71..517a20327 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -137,6 +137,12 @@ IPrerequisiteHelper prerequisiteHelper
Options = ["--force-fp32", "--force-fp16"]
},
new LaunchOptionDefinition
+ {
+ Name = "VAE Precision",
+ Type = LaunchOptionType.Bool,
+ Options = ["--fp16-vae", "--fp32-vae", "--bf16-vae"]
+ },
+ new LaunchOptionDefinition
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
From 384d1f37462cf905b87e6f1c57c47424e242bd9b Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:23:26 +0800
Subject: [PATCH 041/276] No set cpu on macos
---
StabilityMatrix.Core/Models/Packages/ComfyUI.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index 517a20327..18d2e97ca 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -114,7 +114,8 @@ IPrerequisiteHelper prerequisiteHelper
{
Name = "Use CPU only",
Type = LaunchOptionType.Bool,
- InitialValue = !HardwareHelper.HasNvidiaGpu() && !HardwareHelper.HasAmdGpu(),
+ InitialValue =
+ !Compat.IsMacOS && !HardwareHelper.HasNvidiaGpu() && !HardwareHelper.HasAmdGpu(),
Options = ["--cpu"]
},
new LaunchOptionDefinition
From d12cab97b16723dcbe8b267913460ac88de35716 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:23:57 +0800
Subject: [PATCH 042/276] Update CHANGELOG.md
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f95aaa67..dac83e8cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
## v2.7.7
+### Added
+- Added ComfyUI launch argument configs: Cross Attention Method, Force Floating Point Precision, VAE Precision
### Changed
- Python Packages install dialog now allows entering multiple arguments or option flags
From 790b2043d6a474d67b2d0e385f1e96c7add7f419 Mon Sep 17 00:00:00 2001
From: Ionite
Date: Wed, 27 Dec 2023 23:37:48 -0500
Subject: [PATCH 043/276] Add model index functions
---
.../DesignData/MockModelIndexService.cs | 17 ++++-
.../Models/CommandItem.cs | 33 +++++++++
.../Views/Settings/MainSettingsPage.axaml | 18 +++++
.../Models/Database/LocalModelFile.cs | 45 +++++++-----
.../Services/IModelIndexService.cs | 18 +++--
.../Services/ModelIndexService.cs | 68 +++++++++++++------
6 files changed, 153 insertions(+), 46 deletions(-)
create mode 100644 StabilityMatrix.Avalonia/Models/CommandItem.cs
diff --git a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
index 3081441ca..92bfc564d 100644
--- a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
+++ b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
@@ -25,9 +26,21 @@ public IEnumerable GetFromModelIndex(SharedFolderType types)
}
///
- public Task> GetModelsOfType(SharedFolderType type)
+ public Task> FindAsync(SharedFolderType type)
{
- return Task.FromResult>(new List());
+ return Task.FromResult(Enumerable.Empty());
+ }
+
+ ///
+ public Task> FindByHashAsync(string hashBlake3)
+ {
+ return Task.FromResult(Enumerable.Empty());
+ }
+
+ ///
+ public Task RemoveModelAsync(LocalModelFile model)
+ {
+ return Task.FromResult(false);
}
///
diff --git a/StabilityMatrix.Avalonia/Models/CommandItem.cs b/StabilityMatrix.Avalonia/Models/CommandItem.cs
new file mode 100644
index 000000000..5c2089d9b
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/CommandItem.cs
@@ -0,0 +1,33 @@
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+using System.Windows.Input;
+using StabilityMatrix.Core.Extensions;
+
+namespace StabilityMatrix.Avalonia.Models;
+
+public partial record CommandItem
+{
+ public ICommand Command { get; init; }
+
+ public string DisplayName { get; init; }
+
+ public CommandItem(ICommand command, [CallerArgumentExpression("command")] string? commandName = null)
+ {
+ Command = command;
+ DisplayName = commandName == null ? "" : ProcessName(commandName);
+ }
+
+ [Pure]
+ private static string ProcessName(string name)
+ {
+ name = name.StripEnd("Command");
+
+ name = SpaceTitleCaseRegex().Replace(name, "$1 $2");
+
+ return name;
+ }
+
+ [GeneratedRegex("([a-z])_?([A-Z])")]
+ private static partial Regex SpaceTitleCaseRegex();
+}
diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
index ccf9c439f..abb3a4d05 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
@@ -20,6 +20,8 @@
xmlns:update="clr-namespace:StabilityMatrix.Core.Models.Update;assembly=StabilityMatrix.Core"
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings"
+ xmlns:input="clr-namespace:System.Windows.Input;assembly=System.ObjectModel"
+ xmlns:ct="clr-namespace:CommunityToolkit.Mvvm.Input;assembly=CommunityToolkit.Mvvm"
d:DataContext="{x:Static mocks:DesignData.MainSettingsViewModel}"
d:DesignHeight="700"
d:DesignWidth="800"
@@ -566,6 +568,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
index 462b9def3..01db400bc 100644
--- a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
@@ -6,13 +6,13 @@ namespace StabilityMatrix.Core.Models.Database;
///
/// Represents a locally indexed model file.
///
-public class LocalModelFile
+public record LocalModelFile
{
///
/// Relative path to the file from the root model directory.
///
[BsonId]
- public required string RelativePath { get; set; }
+ public required string RelativePath { get; init; }
///
/// Type of the model file.
@@ -53,20 +53,10 @@ public class LocalModelFile
public string RelativePathFromSharedFolder =>
Path.GetRelativePath(SharedFolderType.GetStringValue(), RelativePath);
- public string GetFullPath(string rootModelDirectory)
- {
- return Path.Combine(rootModelDirectory, RelativePath);
- }
-
- public string? GetPreviewImageFullPath(string rootModelDirectory)
- {
- if (PreviewImageFullPath != null)
- return PreviewImageFullPath;
-
- return PreviewImageRelativePath == null
- ? null
- : Path.Combine(rootModelDirectory, PreviewImageRelativePath);
- }
+ ///
+ /// Blake3 hash of the file.
+ ///
+ public string? HashBlake3 => ConnectedModelInfo?.Hashes.BLAKE3;
[BsonIgnore]
public string FullPathGlobal => GetFullPath(GlobalConfig.LibraryDir.JoinDir("Models"));
@@ -75,6 +65,10 @@ public string GetFullPath(string rootModelDirectory)
public string? PreviewImageFullPathGlobal =>
PreviewImageFullPath ?? GetPreviewImageFullPath(GlobalConfig.LibraryDir.JoinDir("Models"));
+ [BsonIgnore]
+ public Uri? PreviewImageUriGlobal =>
+ PreviewImageFullPathGlobal == null ? null : new Uri(PreviewImageFullPathGlobal);
+
[BsonIgnore]
public string DisplayModelName => ConnectedModelInfo?.ModelName ?? FileNameWithoutExtension;
@@ -84,7 +78,22 @@ public string GetFullPath(string rootModelDirectory)
[BsonIgnore]
public string DisplayModelFileName => FileName;
- protected bool Equals(LocalModelFile other)
+ public string GetFullPath(string rootModelDirectory)
+ {
+ return Path.Combine(rootModelDirectory, RelativePath);
+ }
+
+ public string? GetPreviewImageFullPath(string rootModelDirectory)
+ {
+ if (PreviewImageFullPath != null)
+ return PreviewImageFullPath;
+
+ return PreviewImageRelativePath == null
+ ? null
+ : Path.Combine(rootModelDirectory, PreviewImageRelativePath);
+ }
+
+ /*protected bool Equals(LocalModelFile other)
{
return RelativePath == other.RelativePath;
}
@@ -105,7 +114,7 @@ public override bool Equals(object? obj)
public override int GetHashCode()
{
return RelativePath.GetHashCode();
- }
+ }*/
public static readonly HashSet SupportedCheckpointExtensions =
[
diff --git a/StabilityMatrix.Core/Services/IModelIndexService.cs b/StabilityMatrix.Core/Services/IModelIndexService.cs
index 83c7940b4..69013b5b8 100644
--- a/StabilityMatrix.Core/Services/IModelIndexService.cs
+++ b/StabilityMatrix.Core/Services/IModelIndexService.cs
@@ -12,18 +12,28 @@ public interface IModelIndexService
///
Task RefreshIndex();
+ ///
+ /// Starts a background task to refresh the local model file index.
+ ///
+ void BackgroundRefreshIndex();
+
///
/// Get all models of the specified type from the existing (in-memory) index.
///
IEnumerable GetFromModelIndex(SharedFolderType types);
///
- /// Get all models of the specified type from the existing index.
+ /// Find all models of the specified SharedFolderType.
///
- Task> GetModelsOfType(SharedFolderType type);
+ Task> FindAsync(SharedFolderType type);
///
- /// Starts a background task to refresh the local model file index.
+ /// Find all models with the specified Blake3 hash.
///
- void BackgroundRefreshIndex();
+ Task> FindByHashAsync(string hashBlake3);
+
+ ///
+ /// Remove a model from the index.
+ ///
+ Task RemoveModelAsync(LocalModelFile model);
}
diff --git a/StabilityMatrix.Core/Services/ModelIndexService.cs b/StabilityMatrix.Core/Services/ModelIndexService.cs
index 67a676697..bd0182b35 100644
--- a/StabilityMatrix.Core/Services/ModelIndexService.cs
+++ b/StabilityMatrix.Core/Services/ModelIndexService.cs
@@ -1,6 +1,9 @@
-using System.Diagnostics;
+using System.Collections.Concurrent;
+using System.Collections.Immutable;
+using System.Diagnostics;
using System.Text;
using AsyncAwaitBestPractices;
+using AutoCtor;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Database;
@@ -13,45 +16,44 @@
namespace StabilityMatrix.Core.Services;
[Singleton(typeof(IModelIndexService))]
-public class ModelIndexService : IModelIndexService
+[AutoConstruct]
+public partial class ModelIndexService : IModelIndexService
{
private readonly ILogger logger;
- private readonly ILiteDbContext liteDbContext;
private readonly ISettingsManager settingsManager;
+ private readonly ILiteDbContext liteDbContext;
public Dictionary> ModelIndex { get; private set; } = new();
- public ModelIndexService(
- ILogger logger,
- ILiteDbContext liteDbContext,
- ISettingsManager settingsManager
- )
+ [AutoPostConstruct]
+ private void Initialize()
{
- this.logger = logger;
- this.liteDbContext = liteDbContext;
- this.settingsManager = settingsManager;
+ // Start background index when library dir is set
+ settingsManager.RegisterOnLibraryDirSet(_ => BackgroundRefreshIndex());
}
- ///
- /// Deletes all entries in the local model file index.
- ///
- private async Task ClearIndex()
+ public IEnumerable GetFromModelIndex(SharedFolderType types)
{
- await liteDbContext.LocalModelFiles.DeleteAllAsync().ConfigureAwait(false);
+ return ModelIndex.Where(kvp => (kvp.Key & types) != 0).SelectMany(kvp => kvp.Value);
}
- public IEnumerable GetFromModelIndex(SharedFolderType types)
+ ///
+ public async Task> FindAsync(SharedFolderType type)
{
- return ModelIndex.Where(kvp => (kvp.Key & types) != 0).SelectMany(kvp => kvp.Value);
+ await liteDbContext.LocalModelFiles.EnsureIndexAsync(m => m.SharedFolderType).ConfigureAwait(false);
+
+ return await liteDbContext
+ .LocalModelFiles.FindAsync(m => m.SharedFolderType == type)
+ .ConfigureAwait(false);
}
///
- public async Task> GetModelsOfType(SharedFolderType type)
+ public async Task> FindByHashAsync(string hashBlake3)
{
+ await liteDbContext.LocalModelFiles.EnsureIndexAsync(m => m.HashBlake3).ConfigureAwait(false);
+
return await liteDbContext
- .LocalModelFiles.Query()
- .Where(m => m.SharedFolderType == type)
- .ToArrayAsync()
+ .LocalModelFiles.FindAsync(m => m.HashBlake3 == hashBlake3)
.ConfigureAwait(false);
}
@@ -86,6 +88,7 @@ public async Task RefreshIndex()
var added = 0;
var newIndex = new Dictionary>();
+
foreach (
var file in modelsDir
.Info.EnumerateFiles("*.*", SearchOption.AllDirectories)
@@ -161,6 +164,7 @@ await jsonPath.ReadAllTextAsync().ConfigureAwait(false)
// Add to index
var list = newIndex.GetOrAdd(sharedFolderType);
list.Add(localModel);
+
added++;
}
@@ -195,4 +199,24 @@ public void BackgroundRefreshIndex()
logger.LogError(ex, "Error in background model indexing");
});
}
+
+ ///
+ public async Task RemoveModelAsync(LocalModelFile model)
+ {
+ // Remove from database
+ if (await liteDbContext.LocalModelFiles.DeleteAsync(model.RelativePath).ConfigureAwait(false))
+ {
+ // Remove from index
+ if (ModelIndex.TryGetValue(model.SharedFolderType, out var list))
+ {
+ list.Remove(model);
+ }
+
+ EventManager.Instance.OnModelIndexChanged();
+
+ return true;
+ }
+
+ return false;
+ }
}
From a69177337521cb7d014c6deb1f6b068f5947d1d7 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 12:59:35 +0800
Subject: [PATCH 044/276] Add AutoCtor
---
StabilityMatrix.Core/StabilityMatrix.Core.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
index 12b0de97d..44f76ba69 100644
--- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj
+++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
@@ -17,6 +17,7 @@
+
From b9efeb46b28cd92836da6f0aba5416f0a605d1da Mon Sep 17 00:00:00 2001
From: ionite34
Date: Thu, 28 Dec 2023 13:01:59 +0800
Subject: [PATCH 045/276] Add find local model from index debug command
---
.../Settings/MainSettingsViewModel.cs | 66 +++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index bf241a123..7f39fb441 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -48,6 +48,7 @@
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Settings;
using StabilityMatrix.Core.Python;
@@ -754,6 +755,71 @@ private async Task DebugTrackedDownload()
}
#endregion
+ #region Debug Commands
+
+ public CommandItem[] DebugCommands => [new CommandItem(DebugFindLocalModelFromIndexCommand)];
+
+ [RelayCommand]
+ private async Task DebugFindLocalModelFromIndex()
+ {
+ var textFields = new TextBoxField[]
+ {
+ new() { Label = "Blake3 Hash" },
+ new() { Label = "SharedFolderType" }
+ };
+
+ var dialog = DialogHelper.CreateTextEntryDialog("Find Local Model", "", textFields);
+
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ {
+ Func>> modelGetter;
+
+ if (textFields.ElementAtOrDefault(0)?.Text is { } hash && !string.IsNullOrWhiteSpace(hash))
+ {
+ modelGetter = () => modelIndexService.FindByHashAsync(hash);
+ }
+ else if (textFields.ElementAtOrDefault(1)?.Text is { } type && !string.IsNullOrWhiteSpace(type))
+ {
+ modelGetter = () => modelIndexService.FindAsync(Enum.Parse(type));
+ }
+ else
+ {
+ return;
+ }
+
+ var timer = Stopwatch.StartNew();
+
+ var result = (await modelGetter()).ToImmutableArray();
+
+ timer.Stop();
+
+ if (result.Length != 0)
+ {
+ await DialogHelper
+ .CreateMarkdownDialog(
+ string.Join(
+ "\n\n",
+ result.Select(
+ (model, i) =>
+ $"[{i + 1}] {model.RelativePath.ToRepr()} "
+ + $"({model.DisplayModelName}, {model.DisplayModelVersion})"
+ )
+ ),
+ $"Found Models ({CodeTimer.FormatTime(timer.Elapsed)})"
+ )
+ .ShowAsync();
+ }
+ else
+ {
+ await DialogHelper
+ .CreateMarkdownDialog(":(", $"No models found ({CodeTimer.FormatTime(timer.Elapsed)})")
+ .ShowAsync();
+ }
+ }
+ }
+
+ #endregion
+
#region Info Section
public void OnVersionClick()
From 24ad22463f644562769921d9e1bdd32cb6a8ddc3 Mon Sep 17 00:00:00 2001
From: JT
Date: Wed, 27 Dec 2023 22:58:59 -0800
Subject: [PATCH 046/276] Added Delete button to model browser
SelectModelVersionDialog & added 'copy link to clipboard' for connected
models
---
CHANGELOG.md | 2 +
.../CheckpointManager/CheckpointFile.cs | 39 +++++--
.../Dialogs/ModelVersionViewModel.cs | 38 +++++--
.../Dialogs/SelectModelVersionViewModel.cs | 102 +++++++++++++++++-
.../Views/CheckpointsPage.axaml | 17 ++-
.../Dialogs/SelectModelVersionDialog.axaml | 24 +++++
6 files changed, 202 insertions(+), 20 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bbb882064..383a00d6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
## v2.8.0-dev.3
### Added
- Added ComfyUI launch argument configs: Cross Attention Method, Force Floating Point Precision, VAE Precision
+- Added Delete button to the CivitAI Model Browser details dialog
+- Added "Copy Link to Clipboard" for connected models in the Checkpoints page
### Changed
- Python Packages install dialog now allows entering multiple arguments or option flags
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
index 333a3a02a..33ed693d1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
@@ -70,7 +70,14 @@ public partial class CheckpointFile : ViewModelBase
public ObservableCollection Badges { get; set; } = new();
- public static readonly string[] SupportedCheckpointExtensions = { ".safetensors", ".pt", ".ckpt", ".pth", ".bin" };
+ public static readonly string[] SupportedCheckpointExtensions =
+ {
+ ".safetensors",
+ ".pt",
+ ".ckpt",
+ ".pth",
+ ".bin"
+ };
private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".gif" };
private static readonly string[] SupportedMetadataExtensions = { ".json" };
@@ -95,7 +102,9 @@ private string GetConnectedModelInfoFilePath()
{
if (string.IsNullOrEmpty(FilePath))
{
- throw new InvalidOperationException("Cannot get connected model info file path when FilePath is empty");
+ throw new InvalidOperationException(
+ "Cannot get connected model info file path when FilePath is empty"
+ );
}
var modelNameNoExt = Path.GetFileNameWithoutExtension((string?)FilePath);
var modelDir = Path.GetDirectoryName((string?)FilePath) ?? "";
@@ -224,10 +233,21 @@ private Task CopyTriggerWords()
return App.Clipboard.SetTextAsync(words);
}
+ [RelayCommand]
+ private Task CopyModelUrl()
+ {
+ return ConnectedModel == null
+ ? Task.CompletedTask
+ : App.Clipboard.SetTextAsync($"https://civitai.com/models/{ConnectedModel.ModelId}");
+ }
+
[RelayCommand]
private async Task FindConnectedMetadata(bool forceReimport = false)
{
- if (App.Services.GetService(typeof(IMetadataImportService)) is not IMetadataImportService importService)
+ if (
+ App.Services.GetService(typeof(IMetadataImportService))
+ is not IMetadataImportService importService
+ )
return;
IsLoading = true;
@@ -298,7 +318,9 @@ public static IEnumerable FromDirectoryIndex(
}
checkpointFile.PreviewImagePath = SupportedImageExtensions
- .Select(ext => Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(file)}.preview{ext}"))
+ .Select(
+ ext => Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(file)}.preview{ext}")
+ )
.Where(File.Exists)
.FirstOrDefault();
@@ -324,7 +346,11 @@ public static IEnumerable GetAllCheckpointFiles(string modelsDir
)
continue;
- var checkpointFile = new CheckpointFile { Title = Path.GetFileNameWithoutExtension(file), FilePath = file };
+ var checkpointFile = new CheckpointFile
+ {
+ Title = Path.GetFileNameWithoutExtension(file),
+ FilePath = file
+ };
var jsonPath = Path.Combine(
Path.GetDirectoryName(file) ?? "",
@@ -432,5 +458,6 @@ public int GetHashCode(CheckpointFile obj)
}
}
- public static IEqualityComparer FilePathComparer { get; } = new FilePathEqualityComparer();
+ public static IEqualityComparer FilePathComparer { get; } =
+ new FilePathEqualityComparer();
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs
index 17740c40a..73922420f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs
@@ -8,20 +8,42 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
public partial class ModelVersionViewModel : ObservableObject
{
- [ObservableProperty] private CivitModelVersion modelVersion;
- [ObservableProperty] private ObservableCollection civitFileViewModels;
- [ObservableProperty] private bool isInstalled;
+ private readonly HashSet installedModelHashes;
+
+ [ObservableProperty]
+ private CivitModelVersion modelVersion;
+
+ [ObservableProperty]
+ private ObservableCollection civitFileViewModels;
+
+ [ObservableProperty]
+ private bool isInstalled;
public ModelVersionViewModel(HashSet installedModelHashes, CivitModelVersion modelVersion)
{
+ this.installedModelHashes = installedModelHashes;
ModelVersion = modelVersion;
- IsInstalled = ModelVersion.Files?.Any(file =>
- file is {Type: CivitFileType.Model, Hashes.BLAKE3: not null} &&
- installedModelHashes.Contains(file.Hashes.BLAKE3)) ?? false;
+ IsInstalled =
+ ModelVersion.Files?.Any(
+ file =>
+ file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }
+ && installedModelHashes.Contains(file.Hashes.BLAKE3)
+ ) ?? false;
CivitFileViewModels = new ObservableCollection(
- ModelVersion.Files?.Select(file => new CivitFileViewModel(installedModelHashes, file)) ??
- new List());
+ ModelVersion.Files?.Select(file => new CivitFileViewModel(installedModelHashes, file))
+ ?? new List()
+ );
+ }
+
+ public void RefreshInstallStatus()
+ {
+ IsInstalled =
+ ModelVersion.Files?.Any(
+ file =>
+ file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }
+ && installedModelHashes.Contains(file.Hashes.BLAKE3)
+ ) ?? false;
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
index 24b4327e0..a7d2ff58b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
@@ -3,11 +3,16 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls.Notifications;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
@@ -22,6 +27,8 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase
{
private readonly ISettingsManager settingsManager;
private readonly IDownloadService downloadService;
+ private readonly IModelIndexService modelIndexService;
+ private readonly INotificationService notificationService;
public required ContentDialog Dialog { get; set; }
public required IReadOnlyList Versions { get; set; }
@@ -58,10 +65,17 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase
public int DisplayedPageNumber => SelectedImageIndex + 1;
- public SelectModelVersionViewModel(ISettingsManager settingsManager, IDownloadService downloadService)
+ public SelectModelVersionViewModel(
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IModelIndexService modelIndexService,
+ INotificationService notificationService
+ )
{
this.settingsManager = settingsManager;
this.downloadService = downloadService;
+ this.modelIndexService = modelIndexService;
+ this.notificationService = notificationService;
}
public override void OnLoaded()
@@ -101,6 +115,8 @@ partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value)
partial void OnSelectedFileChanged(CivitFileViewModel? value)
{
+ if (value is { IsInstalled: true }) { }
+
var canImport = true;
if (settingsManager.IsLibraryDirSet)
{
@@ -129,6 +145,90 @@ public void Import()
Dialog.Hide(ContentDialogResult.Primary);
}
+ public async Task Delete()
+ {
+ if (SelectedFile == null)
+ return;
+
+ var fileToDelete = SelectedFile;
+ var originalSelectedVersionVm = SelectedVersionViewModel;
+
+ var hash = fileToDelete.CivitFile.Hashes.BLAKE3;
+ if (string.IsNullOrWhiteSpace(hash))
+ {
+ notificationService.Show(
+ "Error deleting file",
+ "Could not delete model, hash is missing.",
+ NotificationType.Error
+ );
+ return;
+ }
+
+ var matchingModels = (await modelIndexService.FindByHashAsync(hash)).ToList();
+
+ if (matchingModels.Count == 0)
+ {
+ await modelIndexService.RefreshIndex();
+ matchingModels = (await modelIndexService.FindByHashAsync(hash)).ToList();
+
+ if (matchingModels.Count == 0)
+ {
+ notificationService.Show(
+ "Error deleting file",
+ "Could not delete model, model not found in index.",
+ NotificationType.Error
+ );
+ return;
+ }
+ }
+
+ var dialog = new BetterContentDialog
+ {
+ Title = Resources.Label_AreYouSure,
+ MaxDialogWidth = 750,
+ MaxDialogHeight = 850,
+ PrimaryButtonText = Resources.Action_Yes,
+ IsPrimaryButtonEnabled = true,
+ IsSecondaryButtonEnabled = false,
+ CloseButtonText = Resources.Action_Cancel,
+ DefaultButton = ContentDialogButton.Close,
+ Content =
+ $"The following files:\n{string.Join('\n', matchingModels.Select(x => $"- {x.FileName}"))}\n"
+ + "and all associated metadata files will be deleted. Are you sure?",
+ };
+
+ var result = await dialog.ShowAsync();
+ if (result == ContentDialogResult.Primary)
+ {
+ foreach (var localModel in matchingModels)
+ {
+ var checkpointPath = new FilePath(localModel.GetFullPath(settingsManager.ModelsDirectory));
+ if (File.Exists(checkpointPath))
+ {
+ File.Delete(checkpointPath);
+ }
+
+ var previewPath = localModel.GetPreviewImageFullPath(settingsManager.ModelsDirectory);
+ if (File.Exists(previewPath))
+ {
+ File.Delete(previewPath);
+ }
+
+ var cmInfoPath = checkpointPath.ToString().Replace(checkpointPath.Extension, ".cm-info.json");
+ if (File.Exists(cmInfoPath))
+ {
+ File.Delete(cmInfoPath);
+ }
+
+ await modelIndexService.RemoveModelAsync(localModel);
+ }
+
+ settingsManager.Transaction(settings => settings.InstalledModelHashes?.Remove(hash));
+ fileToDelete.IsInstalled = false;
+ originalSelectedVersionVm?.RefreshInstallStatus();
+ }
+ }
+
public void PreviousImage()
{
if (SelectedImageIndex > 0)
diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
index a044be5d6..021dc27f8 100644
--- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
@@ -61,15 +61,19 @@
-
-
+
+
+
True
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml
index 632c8fd45..f118e90e2 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml
@@ -12,6 +12,8 @@
xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:markupExtensions="clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions"
+ xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
d:DataContext="{x:Static designData:DesignData.SelectModelVersionViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.SelectModelVersionDialog">
@@ -21,6 +23,11 @@
MinWidth="700"
RowDefinitions="Auto, *, Auto"
ColumnDefinitions="*,Auto,*">
+
+
+
+
+
+
+
+
+
+
+
From 1f6c9471023fc8a67276cc42bea27e60104e56a9 Mon Sep 17 00:00:00 2001
From: JT
Date: Thu, 28 Dec 2023 01:18:11 -0800
Subject: [PATCH 047/276] add onetrainer & moved tk/tcl env vars to
BaseGitPackage
---
CHANGELOG.md | 1 +
.../Models/Packages/BaseGitPackage.cs | 17 +++-
.../Models/Packages/KohyaSs.cs | 32 ++-----
.../Models/Packages/OneTrainer.cs | 92 +++++++++++++++++++
4 files changed, 117 insertions(+), 25 deletions(-)
create mode 100644 StabilityMatrix.Core/Models/Packages/OneTrainer.cs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 383a00d6b..ba629a63e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
## v2.8.0-dev.3
### Added
+- Added new package: [OneTrainer](https://github.com/Nerogar/OneTrainer)
- Added ComfyUI launch argument configs: Cross Attention Method, Force Floating Point Precision, VAE Precision
- Added Delete button to the CivitAI Model Browser details dialog
- Added "Copy Link to Clipboard" for connected models in the Checkpoints page
diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
index 373c19f45..ebe7856fa 100644
--- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
@@ -2,6 +2,7 @@
using System.IO.Compression;
using NLog;
using Octokit;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models.Database;
@@ -147,10 +148,24 @@ public async Task SetupVenv(
await VenvRunner.DisposeAsync().ConfigureAwait(false);
}
+ // Set additional required environment variables
+ var env = new Dictionary();
+ if (SettingsManager.Settings.EnvironmentVariables is not null)
+ {
+ env.Update(SettingsManager.Settings.EnvironmentVariables);
+ }
+
+ if (Compat.IsWindows)
+ {
+ var tkPath = Path.Combine(SettingsManager.LibraryDir, "Assets", "Python310", "tcl", "tcl8.6");
+ env["TCL_LIBRARY"] = tkPath;
+ env["TK_LIBRARY"] = tkPath;
+ }
+
VenvRunner = new PyVenvRunner(venvPath)
{
WorkingDirectory = installedPackagePath,
- EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables,
+ EnvironmentVariables = env
};
if (!VenvRunner.Exists() || forceRecreate)
diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
index fa3adb34f..2ca3cdc1c 100644
--- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
+++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
@@ -40,14 +40,16 @@ IPyRunner runner
public override TorchVersion GetRecommendedTorchVersion() => TorchVersion.Cuda;
- public override string Disclaimer => "Nvidia GPU with at least 8GB VRAM is recommended. May be unstable on Linux.";
+ public override string Disclaimer =>
+ "Nvidia GPU with at least 8GB VRAM is recommended. May be unstable on Linux.";
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare;
public override bool OfferInOneClickInstaller => false;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
- public override IEnumerable AvailableTorchVersions => new[] { TorchVersion.Cuda };
- public override IEnumerable AvailableSharedFolderMethods => new[] { SharedFolderMethod.None };
+ public override IEnumerable AvailableTorchVersions => [TorchVersion.Cuda];
+ public override IEnumerable AvailableSharedFolderMethods =>
+ new[] { SharedFolderMethod.None };
public override List LaunchOptions =>
[
@@ -189,7 +191,9 @@ def rewrite_module(self, module_text: str) -> str:
"""
);
- var replacementAcceleratePath = Compat.IsWindows ? @".\venv\scripts\accelerate" : "./venv/bin/accelerate";
+ var replacementAcceleratePath = Compat.IsWindows
+ ? @".\venv\scripts\accelerate"
+ : "./venv/bin/accelerate";
var replacer = scope.InvokeMethod(
"StringReplacer",
@@ -238,7 +242,6 @@ void HandleConsoleOutput(ProcessOutput s)
var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}";
- VenvRunner.EnvironmentVariables = GetEnvVars(installedPackagePath);
VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit);
}
@@ -246,23 +249,4 @@ void HandleConsoleOutput(ProcessOutput s)
public override Dictionary>? SharedOutputFolders { get; }
public override string MainBranch => "master";
-
- private Dictionary GetEnvVars(string installDirectory)
- {
- // Set additional required environment variables
- var env = new Dictionary();
- if (SettingsManager.Settings.EnvironmentVariables is not null)
- {
- env.Update(SettingsManager.Settings.EnvironmentVariables);
- }
-
- if (!Compat.IsWindows)
- return env;
-
- var tkPath = Path.Combine(SettingsManager.LibraryDir, "Assets", "Python310", "tcl", "tcl8.6");
- env["TCL_LIBRARY"] = tkPath;
- env["TK_LIBRARY"] = tkPath;
-
- return env;
- }
}
diff --git a/StabilityMatrix.Core/Models/Packages/OneTrainer.cs b/StabilityMatrix.Core/Models/Packages/OneTrainer.cs
new file mode 100644
index 000000000..fd339fa45
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Packages/OneTrainer.cs
@@ -0,0 +1,92 @@
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Core.Models.Packages;
+
+[Singleton(typeof(BasePackage))]
+public class OneTrainer(
+ IGithubApiCache githubApi,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IPrerequisiteHelper prerequisiteHelper
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+{
+ public override string Name => "OneTrainer";
+ public override string DisplayName { get; set; } = "OneTrainer";
+ public override string Author => "Nerogar";
+ public override string Blurb =>
+ "OneTrainer is a one-stop solution for all your stable diffusion training needs";
+ public override string LicenseType => "AGPL-3.0";
+ public override string LicenseUrl => "https://github.com/Nerogar/OneTrainer/blob/master/LICENSE.txt";
+ public override string LaunchCommand => "scripts/train_ui.py";
+
+ public override Uri PreviewImageUri =>
+ new("https://github.com/Nerogar/OneTrainer/blob/master/resources/icons/icon.png?raw=true");
+
+ public override string OutputFolderName => string.Empty;
+ public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
+ public override IEnumerable AvailableTorchVersions => [TorchVersion.Cuda];
+ public override IEnumerable AvailableSharedFolderMethods =>
+ new[] { SharedFolderMethod.None };
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Nightmare;
+ public override bool OfferInOneClickInstaller => false;
+ public override bool ShouldIgnoreReleases => true;
+
+ public override async Task InstallPackage(
+ string installLocation,
+ TorchVersion torchVersion,
+ SharedFolderMethod selectedSharedFolderMethod,
+ DownloadPackageVersionOptions versionOptions,
+ IProgress? progress = null,
+ Action? onConsoleOutput = null
+ )
+ {
+ progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
+
+ await using var venvRunner = new PyVenvRunner(Path.Combine(installLocation, "venv"));
+ venvRunner.WorkingDirectory = installLocation;
+ await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);
+
+ progress?.Report(new ProgressReport(-1f, "Installing requirements", isIndeterminate: true));
+
+ var pipArgs = new PipInstallArgs("-r", "requirements.txt");
+ await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ }
+
+ public override async Task RunPackage(
+ string installedPackagePath,
+ string command,
+ string arguments,
+ Action? onConsoleOutput
+ )
+ {
+ await SetupVenv(installedPackagePath).ConfigureAwait(false);
+ var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}";
+
+ VenvRunner?.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit);
+ return;
+
+ void HandleExit(int i)
+ {
+ Debug.WriteLine($"Venv process exited with code {i}");
+ OnExit(i);
+ }
+
+ void HandleConsoleOutput(ProcessOutput s)
+ {
+ onConsoleOutput?.Invoke(s);
+ }
+ }
+
+ public override List LaunchOptions => [LaunchOptionDefinition.Extras];
+ public override Dictionary>? SharedFolders { get; }
+ public override Dictionary>? SharedOutputFolders { get; }
+ public override string MainBranch => "master";
+}
From c0d5e2acd8a1d877b008f01440a9153138e5175e Mon Sep 17 00:00:00 2001
From: ionite34
Date: Fri, 29 Dec 2023 11:17:27 +0800
Subject: [PATCH 048/276] Add macos sign notarize release CIs
---
.github/workflows/release.yml | 109 +++++++++++++++++++++++++++++++++-
Build/build_macos_app.sh | 26 ++++++++
Build/codesign_macos.sh | 21 +++++++
Build/notarize_macos.sh | 32 ++++++++++
4 files changed, 187 insertions(+), 1 deletion(-)
create mode 100755 Build/build_macos_app.sh
create mode 100644 Build/codesign_macos.sh
create mode 100644 Build/notarize_macos.sh
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 88b5eae37..d68aa4805 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -171,7 +171,114 @@ jobs:
with:
name: StabilityMatrix-${{ env.platform-id }}
path: ./out/${{ env.out-name }}
-
+
+ release-macos:
+ name: Release (macos-arm64)
+ env:
+ platform-id: osx-arm64
+ app-name: "Stability Matrix.app"
+ out-name: "Stability Matrix.dmg"
+ runs-on: macos-13
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: olegtarasov/get-tag@v2.1.2
+ if: github.event_name == 'release'
+ id: tag_name
+ with:
+ tagRegex: "v(.*)"
+
+ - name: Set Version from Tag
+ if: github.event_name == 'release'
+ run: |
+ echo "Using tag ${{ env.GIT_TAG_NAME }}"
+ echo "RELEASE_VERSION=${{ env.GIT_TAG_NAME }}" >> $env:GITHUB_ENV
+
+ - name: Set Version from manual input
+ if: github.event_name == 'workflow_dispatch'
+ run: |
+ echo "Using version ${{ github.event.inputs.version }}"
+ echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $env:GITHUB_ENV
+
+ - name: Set up .NET 8
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: .NET Msbuild (App)
+ env:
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ run: >
+ dotnet msbuild StabilityMatrix.Avalonia
+ -t:BundleApp -p:UseAppHost=true -p:SelfContained=true
+ -p:Configuration=Release -p:RuntimeIdentifier=${{ env.platform-id }}
+ -p:Version=$env:RELEASE_VERSION
+ -p:PublishDir=out
+ -p:PublishReadyToRun=true
+ -p:CFBundleShortVersionString=$env:RELEASE_VERSION
+ -p:CFBundleName="Stability Matrix"
+ -p:CFBundleDisplayName="Stability Matrix"
+ -p:CFBundleVersion=$env:RELEASE_VERSION
+ -p:SentryOrg=${{ secrets.SENTRY_ORG }} -p:SentryProject=${{ secrets.SENTRY_PROJECT }}
+ -p:SentryUploadSymbols=true -p:SentryUploadSources=true
+
+ - name: Post Build (App)
+ run: >
+ mkdir -p signing
+ mv "./out/Stability Matrix.app" ./signing/${{ env.app-name }}
+
+ - name: Codesign app bundle
+ env:
+ MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
+ MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
+ MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
+ MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
+ run: ./Build/codesign_macos.sh "./signing/${{ env.app-name }}"
+
+ - name: Notarize app bundle
+ env:
+ MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
+ MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
+ MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
+ run: ./Build/notarize_macos.sh "./signing/${{ env.app-name }}"
+
+ - name: Upload Artifact (App)
+ uses: actions/upload-artifact@v2
+ with:
+ name: StabilityMatrix-${{ env.platform-id }}-app
+ path: ./signing/${{ env.app-name }}
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+
+ - name: Install dependencies for dmg creation
+ run: >
+ npm install --global create-dmg
+ brew install graphicsmagick imagemagick
+
+ - name: Create dmg
+ run: >
+ cd signing
+ create-dmg ${{ env.app-name }} --overwrite --identity ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
+ # Rename to remove version number
+ for f in *.dmg; do mv "$f" "${{ env.out-name }}"; done
+
+ - name: Notarize dmg
+ env:
+ MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
+ MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
+ MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
+ run: ./Build/notarize_macos.sh "./signing/${{ env.out-name }}"
+
+ - name: Upload Artifact (dmg)
+ uses: actions/upload-artifact@v2
+ with:
+ name: StabilityMatrix-${{ env.platform-id }}-dmg
+ path: ./signing/${{ env.out-name }}
publish-release:
name: Publish GitHub Release
diff --git a/Build/build_macos_app.sh b/Build/build_macos_app.sh
new file mode 100755
index 000000000..acdfcd4b3
--- /dev/null
+++ b/Build/build_macos_app.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+while getopts v: flag
+do
+ case "${flag}" in
+ v) version=${OPTARG};;
+ *) echo "Invalid option";;
+ esac
+done
+
+dotnet \
+msbuild \
+StabilityMatrix.Avalonia \
+-t:BundleApp \
+-p:RuntimeIdentifier=osx-arm64 \
+-p:UseAppHost=true \
+-p:Configuration=Release \
+-p:CFBundleShortVersionString="$version" \
+-p:SelfContained=true \
+-p:CFBundleName="Stability Matrix" \
+-p:CFBundleDisplayName="Stability Matrix" \
+-p:CFBundleVersion="$version" \
+-p:PublishDir="$(pwd)/out/osx-arm64/bin" \
+
+# Copy the app out of bin
+cp -r ./out/osx-arm64/bin/Stability\ Matrix.app ./out/osx-arm64/Stability\ Matrix.app
diff --git a/Build/codesign_macos.sh b/Build/codesign_macos.sh
new file mode 100644
index 000000000..91698b393
--- /dev/null
+++ b/Build/codesign_macos.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+echo "Signing file: $1"
+
+# Turn our base64-encoded certificate back to a regular .p12 file
+
+echo "$MACOS_CERTIFICATE" | base64 --decode -o certificate.p12
+
+# We need to create a new keychain, otherwise using the certificate will prompt
+# with a UI dialog asking for the certificate password, which we can't
+# use in a headless CI environment
+
+security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
+security default-keychain -s build.keychain
+security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
+security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
+security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
+
+# We finally codesign our app bundle, specifying the Hardened runtime option
+
+/usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime "$1" -v
diff --git a/Build/notarize_macos.sh b/Build/notarize_macos.sh
new file mode 100644
index 000000000..0b4d134a5
--- /dev/null
+++ b/Build/notarize_macos.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+echo "Notarizing file: $1"
+
+# Store the notarization credentials so that we can prevent a UI password dialog
+# from blocking the CI
+
+echo "Create keychain profile"
+xcrun notarytool store-credentials "notarytool-profile" \
+--apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" \
+--team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" \
+--password "$PROD_MACOS_NOTARIZATION_PWD"
+
+# We can't notarize an app bundle directly, but we need to compress it as an archive.
+# Therefore, we create a zip file containing our app bundle, so that we can send it to the
+# notarization service
+
+echo "Creating temp notarization archive"
+ditto -c -k --keepParent "$1" "notarization.zip"
+
+# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
+# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
+# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
+# you're curious
+
+echo "Notarize app"
+xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
+
+# Finally, we need to "attach the staple" to our executable, which will allow our app to be
+# validated by macOS even when an internet connection is not available.
+echo "Attach staple"
+xcrun stapler staple "$1"
From d70e5c922ac6d22cddf7768ee6dc5a03dc435d93 Mon Sep 17 00:00:00 2001
From: ionite34
Date: Fri, 29 Dec 2023 11:17:36 +0800
Subject: [PATCH 049/276] Add macos app icon
---
StabilityMatrix.Avalonia/Assets/AppIcon.icns | Bin 0 -> 871612 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 StabilityMatrix.Avalonia/Assets/AppIcon.icns
diff --git a/StabilityMatrix.Avalonia/Assets/AppIcon.icns b/StabilityMatrix.Avalonia/Assets/AppIcon.icns
new file mode 100644
index 0000000000000000000000000000000000000000..079c0b1f0db20f2bb4cef8db60445cfb369bf531
GIT binary patch
literal 871612
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