Animated GIF thumbnails from a video

This blog post isn’t about Dynamics AX / Dynamics 365, although it may occasionally be useful for some AX/D365 developers as well.

Let’s say I have a 30 minutes long movie that I want to share on a website and I want to give people an idea about the content of the video. Therefore I want to create a slideshow of screenshots taken from the movie at regular intervals. An animated GIF is an obvious choice.

I spent quite some time trying to figure out how to do it, because I had little idea. After some research, I came to a conclusion that a reasonable approach is extracting screenshots with FFmpeg and then combining them to a GIF file with ImageMagick.

Because installing those console applications and dealing with them directly would be quite cumbersome, I also looked for some wrapper libraries available via NuGet. I chose these:

Now let’s build a simple application to show the code. Create a console application for .NET Framework and add those two NuGet packages.

Add using statements for namespaces that we’ll need in a moment:

using System.IO;
using ImageMagick;
using MediaToolkit;
using MediaToolkit.Model;
using MediaToolkit.Options;

The structure of your program will be following:

class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }
 
    void Run()
    {
        string videoFilePath = @"c:\temp\input.mp4";
 
        List<string> thumbnails = ExtractThumbnails(videoFilePath, 10);
 
        if (thumbnails?.Any() == true)
        {
            string gifFilePath = Path.Combine(
                Path.GetDirectoryName(videoFilePath),
                $"{Path.GetFileName(videoFilePath)}.gif");
 
            CombineThumbnails(gifFilePath, thumbnails);
 
            // Delete temporary files
            thumbnails.ForEach(file => File.Delete(file));
        }
    }
}

ExtractThumbnails() takes screenshots from the video as JPG files.

CombineThumbnails() then take these files and put them into an animated GIF.

Finally we delete JPG files, because they aren’t needed anymore.

Here is the implementation of ExtractThumbnails(). The key part is the call of engine.GetThumbnail() at the end.

List<string> ExtractThumbnails(string inputFilePath, int numOfPics)
{
    MediaFile inputFile = new MediaFile { Filename = inputFilePath };
    List<string> thumbnails = new List<string>(numOfPics);
 
    using (var engine = new Engine())
    {
        engine.GetMetadata(inputFile);
        if (inputFile.Metadata == null)
        {
            throw new InvalidOperationException("Invalid file");
        }
 
        int duration = (int)inputFile.Metadata.Duration.TotalSeconds;
        int picDistance = duration / (numOfPics + 1);
 
        for (int i = 1; i <= numOfPics; i++)
        {
            var options = new ConversionOptions { Seek = TimeSpan.FromSeconds(i * picDistance) };
 
            string outputFilePath = $"{inputFilePath}{i}.jpg";
 
            var outputFile = new MediaFile { Filename = outputFilePath };
            engine.GetThumbnail(inputFile, outputFile, options);
            thumbnails.Add(outputFilePath);
        }
    }
 
    return thumbnails;
}

The last missing piece is the method creating the animated GIF. It’s straightforward, thanks to Magick.NET:

void CombineThumbnails(string gifFilePath, List<string> thumbnails)
{
    using (var collection = new MagickImageCollection())
    {
        for (int i = 0; i < thumbnails.Count; i++)
        {
            collection.Add(thumbnails[i]);
            collection[i].AnimationDelay = 70;
            collection[i].Resize(800, 600);
        }
 
        collection.Write(gifFilePath);
    }
}

Colored tabs in Visual Studio

Do you have many tabs in Visual Studio with designers and code editors and do you struggle to make sense of them? It’s not surprising – we often work with many things at once and some elements even have same names (e.g. VendTable table and form), which means that tabs for them look identical.

Original view of tabs (no colors)

Wouldn’t it be great if, for example, tabs for tables had a different color than tabs for forms?

It can be done…

Open Visual Studio and go to Tools > Extensions and Updates. Switch to Online and find and install Productivity Power Tools.

Note that Power Tools contain many more features than just tab coloring. You probably want to review them and configure them as needed (which might also mean disabling some of them).

Open Tools > Options and switch to Productivity Power Tools > Custom Document Well. Uncheck Color tabs by project and tick Color tabs by regular expression.

Then go to Productivity Power Tools > Custom Document Well > Advanced and tick Use full document path for regular expression matching.

Then it’s time to define colors and rules for using them. The setup we’ve just done allows us to find patterns in paths of files used in tabs. For example, if I open VendTable table in the designer, the path (in my environment) is K:\AosService\PackagesLocalDirectory\ApplicationSuite\Foundation\AxTable\VendTable.xml. If I want to assign a color to all tabs with table designers, I can use a regular expression like .*\\AxTable\\.*. It finds paths containing \AxTable\.

This setup is done under Productivity Power Tools > Custom Document Well > Color Coding.

Here is an example of how we can use it.

Configuration of tab colors

Here I’ve decided to use different colors for designers and code editors. For example, a tab with table editor has a dark red color, while opening code for the table creates a tab with a lighter red color. The same is done for classes, just using green color instead of red.

This is what I can see in Visual Studio:

Colored tabs

Now I can clearly distinguish element types and if I remember my setup, I can even immediately say which tab is for which type.

By the way, I could also go to Custom Document Well options and set Place tabs on the Left to display tabs vertically:

Vertical tabs

If you prefer, you can set colors based on something else than element types. For example, you could use different colors for elements from different packages. You’re limited only by what information you can extract from file paths.

It works nicely in both Visual Studio 2015 and Visual Studio 2017. Unfortunately it’s not supported in Visual Studio 2019, although there seem to be ways how to get it working.