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. 11
      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
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);

14
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 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
samplerCard.SelectedSampler = ComfySampler.Euler;
samplerCard.SelectedScheduler = ComfyScheduler.Karras;
samplerCard.IsDenoiseStrengthEnabled = true;
samplerCard.DenoiseStrength = 1.0f;
});
BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>();
@ -118,6 +123,11 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
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 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod
batchArgs.Add(generationArgs);
}
IsGenerating = true;
// Run batches
foreach (var args in batchArgs)
{
await RunGeneration(args, cancellationToken);
}
IsGenerating = false;
}
/// <inheritdoc />

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

@ -94,9 +94,16 @@
<Grid
x:CompileBindings="False"
DataContext="{Binding ElementName=Dock, Path=DataContext}">
<gif:GifImage
<controls:ImageGalleryCard
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
DataContext="{Binding OutputProgress}"

48
StabilityMatrix.Core/Animation/GifConverter.cs

@ -1,14 +1,50 @@
using ImageMagick;
using StabilityMatrix.Core.Models.FileInterfaces;
using KGySoft.Drawing.Imaging;
using KGySoft.Drawing.SkiaSharp;
using SkiaSharp;
namespace StabilityMatrix.Core.Animation;
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);
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
);
}
}

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

@ -48,19 +48,9 @@ public record LocalImageFile
/// </summary>
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 record LocalImageFile
};
}
public static readonly HashSet<string> SupportedImageExtensions =
new() { ".png", ".jpg", ".jpeg", ".webp" };
public static readonly HashSet<string> SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif"];
}

3
StabilityMatrix.Core/StabilityMatrix.Core.csproj

@ -29,6 +29,8 @@
<PackageReference Include="DynamicData" Version="8.1.1" />
<PackageReference Include="Hardware.Info" Version="100.0.0.1" />
<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.Async" Version="0.1.7" />
<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="Sentry.NLog" Version="3.41.0" />
<PackageReference Include="SharpCompress" Version="0.34.2" />
<PackageReference Include="SkiaSharp" Version="2.88.6" />
<PackageReference Include="Websocket.Client" Version="5.0.0" />
<PackageReference Include="YamlDotNet" Version="13.7.1" />
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.0.0" />

Loading…
Cancel
Save