GIFs as MP4s

Trying something new here on Ye Olde Blogge: animated GIFs are now served as MP4s in a VIDEO tag. That saves a vast amount of bandwidth since GIF is literally the worst of all possible video formats. Like, 830MB of images drops to 72MB. Twitter and Giphy and whatnot have been doing this for a while now (though Twitter, since they are dicks, make it impossible to download the original unconverted GIF data, even via the API).

It's actually pretty tricky to convert a GIF to an MP4 in the general case, since GIFs have variable frame rate (really, an arbitrary delay after each frame, which can be different for each one), plus they can be transparent, which MP4s cannot be. You'd think that ImageMagick or ffmpeg would just do the right thing on this common task that lots of people want these days, but no, that's crazy talk. I'm converting them using the all-singing all-dancing image-and-video resizer that I wrote, resize.pl, which uses ImageMagick to extract each frame as a PNG then constructs an incredibly hairy ffmpeg command to put it all back together with the proper frame timing.

(Seriously, it's insane, it looks like this, and this is for just 6 frames: -vf zoompan=d=0+'24*eq(in,0)'+'24*eq(in,1)'+'24*eq(in,2)'+'24*eq(in,3)'+'24*eq(in,4)'+'24*eq(in,5)',scale=600:92)

Then after that, part of my WordPress theme sees IMG tags with GIFs in them and converts those to VIDEO tags. (I had to go through and indicate the old GIFs that are not animations to make that work. Did you know that people used to use GIFs for things that were not animations? I know! So weird!)

People say that you should be able to do

    <VIDEO LOOP AUTOPLAY PLAYSINLINE MUTED>
      <SOURCE SRC="xxx.mp4" TYPE="video/mp4" />
      <IMG SRC="xxx.gif">
    </VIDEO>

and that should degrade kind of like <NOSCRIPT> does, using the SOURCE in browsers that understand VIDEO and falling back to the IMG in older browsers... but from watching the network, I could see that Safari was loading the video, and then loading the GIF as well! So that kind of defeats the entire purpose, so I'm omitting the IMG. If your browser doesn't support VIDEO I guess it sucks to be you. That does make it harder to copy images, though, and that's annoying and dumb.

Let me know if you see any problems.

If things are escaping their boxes or otherwise the wrong size, try emptying your cache and shift-reloading. I ticked the cachebuster number on my CSS but browser caches have been an incomprehensible disaster since 1994, so who the hell knows what they are doing.

I've noticed a couple of weird things. In Chrome, I had to add overflow:hidden to ".widget_jwz_previously a". This wasn't necessary in Safari, but in Chrome it also has the effect of clipping off the border on the left and right sides, which sucks.

I've also noticed that sometimes the AUTOPLAY does not autoplay. Or maybe it's that LOOP stops? No idea why that is. It's doing something inexplicable on desktop Safari and something doubly and differently inexplicable on iOS.

Actually I'm finding that "no autoplay" thing super annoying, and if I can't figure out what's going on there I may just go back to GIFs, even though they are more than 10× larger. Sigh.


Update:

I think I see what's going on with Safari: if a VIDEO tag is either above or below the fold at the time that you load the page, it will never autoplay, even after it scrolls into view. I can make videos in the comments here and in the "Previously" sidebar either play or not depending on where the scrollbar happened to be when I hit reload. That's fucked up.


Update 2:

Since Safari (both desktop and iOS) like to ignore AUTOPLAY, it looks like one way to work around that is to put this Javascript in the document footer:

    var really_autoplay = function() {
      var aa = document.getElementsByTagName("video");
      for (var i = 0; i < aa.length; i++) {
        if (aa[i].getAttributeNode("autoplay")) aa[i].play();
      }
    };
    if (document.readyState == "loading") {
      addEventListener ("DOMContentLoaded", really_autoplay);
    } else {
      really_autoplay();
    }

(I thought that merely being after all of the VIDEO tags would be sufficient, but no, being after DOMContentLoaded seems to also be required.)

This hack still means that most videos won't autoplay in browsers that have Javascript turned off, and videos won't show up at all in old browsers that don't support the VIDEO tag. Those might at first sound like weird cases you can ignore, except one or both of those cases probably includes "every RSS reader".

So VIDEO is not at all a drop-in replacement for IMG with animated GIFs. However, the bandwidth savings is so significant that it might be worth doing anyway.


Previously, previously, previously, previously.

Tags: , , , , ,

27 Responses:

  1. plwu says:

    Any chance OBJECT would work better for the fallback?

  2. Chas. Owens says:

    Well, in my RSS reader of choice (The Old Reader), the video tags appear to show up as blank space:

    I don't know yet if that is better or worse than the "Hotlinking, it's what's for dinner" image I have grown inured to.

    • extra88 says:

      It works in my self-hosted, web-based reader Tiny Tiny RSS, so the feed is fine.

  3. Leonardo Herrera says:

    It works perfectly (Chrome, Windows.)

    No autoplay when viewing through Feedly, which is kind of awesome.

  4. macbirdie says:

    There are some tips on gif fallback in WebKit policies for <video> but I guess you've seen this already.

    • jwz says:

      Yes, I saw that, and as far as I can tell it says "the behavior that you are seeing is not what you should be seeing."

  5. MetaRZA says:

    Some of us old folk don't like web pages littered with animated GIFs. We knew and implemented the arcane command that would turn off autoplay of these animations. That they are now MP4s means this no longer works.

    Grump.

      • jrrs says:

        Cool! Just the thing for whinging replies.

      • Daniel says:

        So, so disappointed that this isn't a gif^Wmp4.

        • Daniel says:

          Oh, it is. Weird. It's either not autoplaying for me, or it's not looping. The one above works, though. Yay Safari?

          • Daniel says:

            Oh, look. This is already covered in the bottom half of your post. I must have missed it because it was below the fold.

            • Daniel says:

              Oh, nice! I figured out how to make it work.

              jQuery("video").each(function(){ this.play() })

    • Doodpants says:

      I have the opposite problem; in Firefox's about:config, I have media.autoplay.enabled set to false. So these "gifs" don't animate until I right-click and choose Play. Which I wouldn't even know to do unless there was some hint that these were meant to be animations (such as the entire topic of this post).

      Of course, you might suggest that I just re-enable autoplay. But I sometimes visit YouTube, or read blogs that have embedded YouTube content in them, and I'd like my web browsing experience to actually be tolerable.

      • Daniel says:

        You could fix this with an extension or some custom CSS. Just set an outline or some other effect on elements that match video[autoplay]. I'm assuming that Firefox doesn't actually strip the attribute, though.

    • Glaurung says:

      Get the Flashstopper plugin, which disables all autoplay videos, whether flash based or html5 based. Problem solved.

    • ssl-3 says:

      Not one of us.

      You don't belong here.

  6. LockeCJ says:

    If you want to go one step further, you could always look at BPG.

    • jwz says:

      Do I strike you as the kind of person who wants to screw around with new file formats that no browser supports?

      • Jonathan says:

        No, but you strike us as the type of person who would write their own browser to support whatever file format they want :)

  7. foo says:

    If things are escaping their boxes or otherwise the wrong size

    I sure hope this doesn't happen with the critter at the top.

    (If this is actually a nod to some well-known cultural artifact, please educate me.)

  8. Kornel says:

    Unfortunately, the HTML-only fallback is not sufficient. Content of the video element is not shown if browser is merely video-"aware" (i.e. not old IE), but fails/refuses to play the video for any reason whatsoever. Having a bit of JS to handle source.onerorr is necessary in practice.

    Look at the example fallback code at the bottom of that page.

    • jwz says:

      Wait, there are browsers that understand the VIDEO tag but cannot use it to play MP4 videos? Huh?

      • Kornel says:

        Ye, there are even browsers that can play MP4 files, just not some of them (e.g. H264 main vs baseline profile).

        Test if video-supporting browsers download the inner GIF fallback in addition to downloading the video. Last time I checked that was the case, which is why I recommended using a link instead of img in the fallback markup.

        As for encoding, on server side I don't use ffmpeg, but a completely custom encoder that I haven't opensourced (merely because I like writing Rust too much, would not recommend following that path :)

        I wasn't aware of autoplay failing outside of the visible viewport. It looks like a job for the IntersectionObserver! (puts on a cape, runs away coding)

      • Soupdragon says:

        Yeah - the Blackberry browser is one of them, (FWIW)

    • jwz says:

      BTW, can any of you tell what ffmpeg options ImageOptim is using when converting GIFs to MP4s? I couldn't find any reference to MP4s in the GitHub source.

  9. Barry Kelly says:

    On a tangent, I'll just add that caches are almost always a disaster. If you have a performance problem and think, "I know, I'll just put a cache in front of this", you've almost certainly just created a source of bugs for decades to come. Cache invalidation really is hard, like global state + synchronization + distributed systems hard.

  • Previously