Browse Source

faster gif conversion

pull/438/head
JT 11 months ago
parent
commit
a2d8040310
  1. 19
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
  2. 14
      StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
  3. 13
      StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml
  4. 48
      StabilityMatrix.Core/Animation/GifConverter.cs
  5. 20
      StabilityMatrix.Core/Models/Database/LocalImageFile.cs
  6. 3
      StabilityMatrix.Core/StabilityMatrix.Core.csproj

19
StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs

@ -409,13 +409,20 @@ public abstract partial class InferenceGenerationViewModelBase : InferenceTabVie
); );
// convert to gif // convert to gif
await GifConverter.ConvertWebpToGif(webpFilePath); var inputStream = File.OpenRead(webpFilePath);
var gifFilePath = webpFilePath.ToString().Replace(".webp", ".gif"); var gifFilePath = webpFilePath.ToString().Replace(".webp", ".gif");
if (File.Exists(gifFilePath)) var outputStream = File.OpenWrite(gifFilePath);
{
// delete webp await GifConverter.ConvertAnimatedWebpToGifAsync(inputStream, outputStream);
File.Delete(webpFilePath); await inputStream.DisposeAsync();
} await outputStream.FlushAsync();
await outputStream.DisposeAsync();
// if (File.Exists(gifFilePath))
// {
// // delete webp
// File.Delete(webpFilePath);
// }
outputImages.Add(new ImageSource(gifFilePath)); outputImages.Add(new ImageSource(gifFilePath));
EventManager.Instance.OnImageFileAdded(gifFilePath); EventManager.Instance.OnImageFileAdded(gifFilePath);

14
StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs

@ -65,6 +65,10 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
[JsonIgnore] [JsonIgnore]
private string outputUri; private string outputUri;
[ObservableProperty]
[JsonIgnore]
private bool isGenerating;
public InferenceImageToVideoViewModel( public InferenceImageToVideoViewModel(
INotificationService notificationService, INotificationService notificationService,
IInferenceClientManager inferenceClientManager, IInferenceClientManager inferenceClientManager,
@ -94,6 +98,7 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
samplerCard.SelectedSampler = ComfySampler.Euler; samplerCard.SelectedSampler = ComfySampler.Euler;
samplerCard.SelectedScheduler = ComfyScheduler.Karras; samplerCard.SelectedScheduler = ComfyScheduler.Karras;
samplerCard.IsDenoiseStrengthEnabled = true; samplerCard.IsDenoiseStrengthEnabled = true;
samplerCard.DenoiseStrength = 1.0f;
}); });
BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>(); BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>();
@ -118,6 +123,11 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
EventManager.Instance.ImageFileAdded += OnImageFileAdded; EventManager.Instance.ImageFileAdded += OnImageFileAdded;
} }
public override void OnUnloaded()
{
EventManager.Instance.ImageFileAdded -= OnImageFileAdded;
}
private void OnImageFileAdded(object? sender, FilePath e) private void OnImageFileAdded(object? sender, FilePath e)
{ {
if (!e.Extension.Contains("gif")) if (!e.Extension.Contains("gif"))
@ -219,11 +229,15 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
batchArgs.Add(generationArgs); batchArgs.Add(generationArgs);
} }
IsGenerating = true;
// Run batches // Run batches
foreach (var args in batchArgs) foreach (var args in batchArgs)
{ {
await RunGeneration(args, cancellationToken); await RunGeneration(args, cancellationToken);
} }
IsGenerating = false;
} }
/// <inheritdoc /> /// <inheritdoc />

13
StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml

@ -94,10 +94,17 @@
<Grid <Grid
x:CompileBindings="False" x:CompileBindings="False"
DataContext="{Binding ElementName=Dock, Path=DataContext}"> DataContext="{Binding ElementName=Dock, Path=DataContext}">
<gif:GifImage <controls:ImageGalleryCard
Grid.Row="0" Grid.Row="0"
SourceUri="{Binding OutputUri}" /> IsVisible="{Binding ElementName=Dock, Path=DataContext.IsGenerating}"
DataContext="{Binding ImageGalleryCardViewModel}"/>
<controls:Card Grid.Row="0" VerticalAlignment="Stretch"
IsVisible="{Binding ElementName=Dock, Path=!DataContext.IsGenerating}">
<gif:GifImage
SourceUri="{Binding OutputUri}"
Stretch="Uniform" />
</controls:Card>
<StackPanel <StackPanel
DataContext="{Binding OutputProgress}" DataContext="{Binding OutputProgress}"
Margin="2,1,2,4" Margin="2,1,2,4"

48
StabilityMatrix.Core/Animation/GifConverter.cs

@ -1,14 +1,50 @@
using ImageMagick; using KGySoft.Drawing.Imaging;
using StabilityMatrix.Core.Models.FileInterfaces; using KGySoft.Drawing.SkiaSharp;
using SkiaSharp;
namespace StabilityMatrix.Core.Animation; namespace StabilityMatrix.Core.Animation;
public class GifConverter public class GifConverter
{ {
public static async Task ConvertWebpToGif(FilePath filePath) public static IEnumerable<IReadableBitmapData> EnumerateAnimatedWebp(Stream webpSource)
{ {
using var webp = new MagickImageCollection(filePath, MagickFormat.WebP); using var webp = new SKManagedStream(webpSource);
var path = filePath.ToString().Replace(".webp", ".gif"); using var codec = SKCodec.Create(webp);
await webp.WriteAsync(path, MagickFormat.Gif).ConfigureAwait(false);
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
);
} }
} }

20
StabilityMatrix.Core/Models/Database/LocalImageFile.cs

@ -48,19 +48,9 @@ public record LocalImageFile
/// </summary> /// </summary>
public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath); public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath);
public ( public (string? Parameters, string? ParametersJson, string? SMProject, string? ComfyNodes) ReadMetadata()
string? Parameters,
string? ParametersJson,
string? SMProject,
string? ComfyNodes
) ReadMetadata()
{ {
using var stream = new FileStream( using var stream = new FileStream(AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read);
AbsolutePath,
FileMode.Open,
FileAccess.Read,
FileShare.Read
);
using var reader = new BinaryReader(stream); using var reader = new BinaryReader(stream);
var parameters = ImageMetadata.ReadTextChunk(reader, "parameters"); var parameters = ImageMetadata.ReadTextChunk(reader, "parameters");
@ -79,8 +69,7 @@ public record LocalImageFile
public static LocalImageFile FromPath(FilePath filePath) public static LocalImageFile FromPath(FilePath filePath)
{ {
// TODO: Support other types // TODO: Support other types
const LocalImageFileType imageType = const LocalImageFileType imageType = LocalImageFileType.Inference | LocalImageFileType.TextToImage;
LocalImageFileType.Inference | LocalImageFileType.TextToImage;
// Get metadata // Get metadata
using var stream = filePath.Info.OpenRead(); using var stream = filePath.Info.OpenRead();
@ -115,6 +104,5 @@ public record LocalImageFile
}; };
} }
public static readonly HashSet<string> SupportedImageExtensions = public static readonly HashSet<string> SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif"];
new() { ".png", ".jpg", ".jpeg", ".webp" };
} }

3
StabilityMatrix.Core/StabilityMatrix.Core.csproj

@ -29,6 +29,8 @@
<PackageReference Include="DynamicData" Version="8.1.1" /> <PackageReference Include="DynamicData" Version="8.1.1" />
<PackageReference Include="Hardware.Info" Version="100.0.0.1" /> <PackageReference Include="Hardware.Info" Version="100.0.0.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" /> <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="KGySoft.Drawing.Core" Version="8.0.0" />
<PackageReference Include="KGySoft.Drawing.SkiaSharp" Version="8.0.0" />
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="LiteDB.Async" Version="0.1.7" /> <PackageReference Include="LiteDB.Async" Version="0.1.7" />
<PackageReference Include="Magick.NET-Q8-x64" Version="13.5.0" /> <PackageReference Include="Magick.NET-Q8-x64" Version="13.5.0" />
@ -51,6 +53,7 @@
<PackageReference Include="Semver" Version="3.0.0-beta.1" /> <PackageReference Include="Semver" Version="3.0.0-beta.1" />
<PackageReference Include="Sentry.NLog" Version="3.41.0" /> <PackageReference Include="Sentry.NLog" Version="3.41.0" />
<PackageReference Include="SharpCompress" Version="0.34.2" /> <PackageReference Include="SharpCompress" Version="0.34.2" />
<PackageReference Include="SkiaSharp" Version="2.88.6" />
<PackageReference Include="Websocket.Client" Version="5.0.0" /> <PackageReference Include="Websocket.Client" Version="5.0.0" />
<PackageReference Include="YamlDotNet" Version="13.7.1" /> <PackageReference Include="YamlDotNet" Version="13.7.1" />
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.0.0" /> <PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.0.0" />

Loading…
Cancel
Save