Using Cloudinary to convert an animated GIF to a video

I like animated GIFs, like most people these days I think, but they are really heavy, hurting the performance of web pages, and consuming data plans faster than should be needed. So we need to convert them to videos, which are much lighter, for the same visual result. Let's use Cloudinary.

The animations in this post are animated GIFs provided by Giphy and (obviously) converted by Cloudinary.

You need a Cloudinary account to try all of this, but no fear, the free plan is really confortable for personnal use or just testing: signup for Cloudinary.

So, what's the issue with animated GIFs?

Animated GIFs are really popular, thanks to nice cinemagraphs, or funny reaction images. But all these animated GIFs tend to be really really heavy.

This means:

  • once the animated GIF starts downloading and rendering, it will increase the load on the browser CPU and memory and make it less responsive
  • the user will consume her data-plan faster

What's the solution?

Current state of the art for dealing with heavy animations is to replace animated GIFs with videos, which are much lighter. Twitter has been doing for years, for example. Giphy even provides MP4 alternatives to their animated GIFs.

Videos decoding can even be done by the device GPU, which means the CPU will not suffer like with animated GIFs, and the rendering will be smooth.

What about the easy autoplay of animated GIFs?

Inline videos without a sound track, or muted, can be set to autoplay since:

Ok, so which video format?

We only need one video format, MP4 encoded as H.264/AAC, because (almost) all browsers now support it:

Browser support for feature “mpeg4“
Can I Use mpeg4?

But adding WebM encoded as VP9/Opus can save a few more bytes, which is what we try to do here. And WebM support is pretty good:

Browser support for feature “webm“
Can I Use webm?

If you encode and host video files yourself, you could choose to use only MP4, to ease video creation and lower storage capacity requirements.

But if you don't have these concerns, for example because you use a CDN where storage is cheap and costs depend more on bandwidth, you should use both formats.

How can Cloudinary help?

Cloudinary has been providing animated GIFs to video conversion for a while, as this blog post from 2014 shows: Reduce size of animated GIFs, automatically convert to WebM and MP4.

Here is the simple process it described:

First upload the animated GIF to Cloudinary, so that it is available at this URL:

https://res.cloudinary.com/demo/image/upload/kitten_fighting.gif

Then, change the file extension at the end of the URL to ask Cloudinary to convert it into WebM or MP4 video:

https://res.cloudinary.com/demo/image/upload/kitten_fighting.webm
https://res.cloudinary.com/demo/image/upload/kitten_fighting.mp4

Easy! Magical!

But I want my publication process to be even easier, not requiring any upload (manual or automated) of my digital assets.

That's why I find Cloudinary's Fetch API awesome!

You can also use Cloudinary's Auto-Upload, which provides a lot more features, but I like to keep things simple, and my master pristine images are hosted on my site anyway. Actually, my Jekyll-Cloudinary plugin uses the Fetch API to provide simple and efficient responsive images to Jekyll users.

So, how can we use the Fetch API to convert animated GIFs to videos?

Let's say the pristine animated GIF is located at https://example.com/anim.gif.

The simple Fetch API URL to serve this image though Cloudinary, but untouched, would be this:

https://res.cloudinary.com/<cloud_name>/image/fetch/https://example.com/anim.gif

<cloud_name> should be replaced by your own cloud_name.

If we try to replace .gif with .mp4 at the end of this URL, like in the 4 years old Cloudinary post, it won't work, because Cloudinary will try to fetch a video located at https://example.com/anim.mp4, which doesn't exist.

The solution is to use the explicit format conversion parameter (f_) you can set in your Fetch URL, before the pristine image URL:

https://res.cloudinary.com/<cloud_name>/image/fetch/f_mp4/https://example.com/anim.gif

So, we can replace this:

<img src="https://example.com/anim.gif" alt="an animation">

With this:

<video autoplay loop muted playsinline>
<source src="https://res.cloudinary.com/<cloud_name>/image/fetch/f_webm/https://example.com/anim.gif" type="video/webm">
<source src="https://res.cloudinary.com/<cloud_name>/image/fetch/f_mp4/https://example.com/anim.gif" type="video/mp4">
<p>Your browser doesn't support HTML5 video, <a href="https://example.com/anim.gif">download the animated GIF</a>.</p>
</video>

Don't use the animated GIF has a fallback

Be careful if you put the source animated GIF has a <img> fallback for the <video> for browsers that don't support HTML5 videos, because it will always be downloaded!

If you need to publish animated GIFs anyway, at least make sure they are not too heavy (500KB should really be a maximum IMHO). You can use tools like Lossy GIF to optimize these animated GIFs.

Be careful!

The transformation on Cloudinary can take some time if the animated GIF is really heavy, so you might have to consider uploading it and perform the transformation asynchronously, without using the Fetch API.

I didn't find any of this explained in Cloudinary documentation, maybe because it mixes images and videos.

One more thing…

If you want to dive deeper in this topic, you can discover how — in the near future — animated GIFs converted to videos could be better loaded in <img> tags, with Colin Bendell's post in the 2017 edition of Performance (Advent) Calendar: Evolution of <img>: Gif without the GIF.

Apple has been the first to implement support of soundless videos has src attribute of img elements in Safari.

Chomium intended to implement it, but it was closed as WontFix.

Additional resources

93 Webmentions

39 likes

6 reposts

  1. Carine avatar
  2. Cloudinary avatar
  3. Mickey Aharony avatar
  4. Eleventy avatar
  5. Moshe Njema avatar
  6. cloudsh avatar

33 replies

  1. Šime Vidas avatar Šime Vidas
    I checked the source code of the first video in the article. I think this is a typo: the type attribute value is the same for both <source> elements.
  2. Tim avatar Tim
    Chez moi ça marche (tm)

    Tu veux mon UA ?
  3. Yann avatar Yann
    sur mon iPhone (iOS 10.3) ca s’affiche bien
  4. Nicolas Hoizey avatar Nicolas Hoizey
    Je veux bien.
  5. Nicolas Hoizey avatar Nicolas Hoizey
    J’ai une version plus récente, j’espère que ce n’est pas un nouveau bug…
  6. A random dev avatar A random dev
    Bonjour Nicolas, je sais que safari requière le header "byte-range" pour la lecture des vidéos. stackoverflow.com/questions/3397…. ça dépend donc de la qualité du CDN utilisé
  7. Nicolas Hoizey avatar Nicolas Hoizey
    Mon problème est plutôt dû au Service Worker, en fait.
  8. Renaud Chaput avatar Renaud Chaput
    Ca ne passe pas sur Safari Desktop :(
  9. Nicolas Hoizey avatar Nicolas Hoizey
    Tu as peut-être encore l’ancien cache, il faut que j’améliore ma technique de mise à jour.
  10. Renaud Chaput avatar Renaud Chaput
    Ca a marché au 1er load de l’article (je suppose que le SW n’était pas chargé), puis ça pumarche après un reload.
  11. Nicolas Hoizey avatar Nicolas Hoizey
    Toujours un souci avec le SW alors, merci pour l’info.
  12. Jeff Posnick avatar Jeff Posnick
    Yup. Take a look at github.com/GoogleChrome/w… for some discussion of that. You can swap out the RegExp for a matchCallback function.
  13. Nicolas Hoizey avatar Nicolas Hoizey
    This discussion links to developers.google.com/web/tools/work… where there doesn’t seem to be any matchCallback, but I will dive into the docs, thanks.
  14. Boris Schapira 🚀 avatar Boris Schapira 🚀
    It's the universe trying to tell you to deactivate the autoloop.
  15. Nicolas Hoizey avatar Nicolas Hoizey
    You’re right, I should fix this! 👌



    (But I’m not sure it will fix my issue)
  16. Jeff Posnick avatar Jeff Posnick
    Does the advice at developers.google.com/web/tools/work… help?
  17. Boris Schapira 🚀 avatar Boris Schapira 🚀
    They helped me but know I must find a way to push the mp4 into the cache.
  18. Jeff Posnick avatar Jeff Posnick
    When do you want the .mp4 to be cached?



    If you want it to be cached in advance, then you can precache it in the SW's install handler.



    If you want it cached when someone clicks on a "save offline" button, then you can call `cache.add()` from the `window` context.
  19. Jeff Posnick avatar Jeff Posnick
    You can't consistently add .mp4s to the cache by saving the runtime responses, because they're usually HTTP 206 Partial Content, and if the user skips around a lot during playback, the bytes might not be consecutive.
  20. Nicolas Hoizey avatar Nicolas Hoizey
    On my side, I didn’t want to cache any videos, so I tried to use the NetworkOnly strategy, but I had issues anyway.



    I will try to build a reduced test case and make a screencast.
  21. Jeff Posnick avatar Jeff Posnick
    I can't reproduce those errors when I visit your page.



    Do you see the same failures in an Incognito window with all Chrome extensions disabled?
  22. Nicolas Hoizey avatar Nicolas Hoizey
    It looks like I don’t have the issue in an incognito Canary window without any extension… 🤔



    I’ll try with stable Chrome.
  23. Šime Vidas avatar Šime Vidas
    “First upload the animated GIF”



    Hol’ up a minute. Why is there an animated GIF in the first place?
  24. Nicolas Hoizey avatar Nicolas Hoizey
    Because reaction GIFs are easy to find, even more for me as I’m using direct Giphy links instead of uploading.



    Converting to a video before upload would require two conversions each time (WebM and h.264), I try to have the simplest workflow.
  25. Eric Portis avatar Eric Portis
    Hmmm running the URL through WebPageTest works, and that screenshot is derived from WebPageTest... something weird is afoot.


    Passed this along to the relevant folks and hopefully we can get to the bottom of it.


    Thank you!
  26. Nicolas Hoizey avatar Nicolas Hoizey
    Thanks! 👍
  27. Michal Čaplygin avatar Michal Čaplygin
    And for more control there is "ancient" slbkbs.org/jsgif/ .
  28. Nicolas Hoizey avatar Nicolas Hoizey
    Thanks!
  29. Shane Murphy avatar Shane Murphy
    HandBrake is a decent (easier than ffmpeg) option for converting animated GIFs to MP4, but yeah the CMS handling it would be awesome.
  30. Addy Osmani avatar Addy Osmani
    100% agree CMS implementing support for this natively (maybe auto-transcode to <video> if the user uploads an animated GIF) would help a lot. Today a little too much burden is placed on users to solve this themselves.
  31. Henrik Nyh avatar Henrik Nyh
    Off topic – clever to have a dynamic OG image like that :)
  32. Nicolas Hoizey avatar Nicolas Hoizey
    Indeed, that’s why too few do it.
  33. Nicolas Hoizey avatar Nicolas Hoizey
    Thanks! 🙏🏼

15 mentions

  1. Eric Portis avatar Eric Portis
    How @nhoizey turns GIFs into WebMs and MP4s, using static templates and @cloudinary URLs nicolas-hoizey.com/2018/08/using-…
  2. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  3. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  4. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  5. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  6. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  7. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  8. Nicolas Hoizey avatar Nicolas Hoizey
    Animated GIFs (videos, actually) coming from Giphy are not autoplayed anymore if you asked to reduce motion!



    youtu.be/vb6cUPivA5o



    Try there: nicolas-hoizey.com/2018/08/using-…



    WIP tested on macOS and iOS with Firefox, Safari and Chrome Canary.



    I guess I’ll have to write an article…
  9. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  10. Nicolas Hoizey avatar Nicolas Hoizey
    My blog’s code samples now use the same syntax highlighting theme (One Dark Pro Vivid) as my @code editor.



    Example in nicolas-hoizey.com/2018/08/using-…



    Thanks @mgyongyosi for mgyongyosi.com/2016/atom-one-…
  11. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  12. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  13. Nicolas Hoizey avatar Nicolas Hoizey
    @etportis hello Eric, it looks like there is an issue in Website Speed Test screenshot:
    webspeedtest.cloudinary.com/results/190617…


    This is for this page:
    nicolas-hoizey.com/2018/08/using-…
  14. Cloudinary avatar Cloudinary
    Using #Cloudinary’s fetch API to convert an animated #GIF to a video via @nhoizey ow.ly/VqPb30lCKLd
  15. Page screenshot https://medium.com/@nhoizey/i-was-using-your-trick-to-detect-image-req...