5

how do I convert an animated gif to a video (e.g. h264@mp4) programmatically on a linux server?

I need this to process user generated content which should be output as several defined video formats; therefore its possible, that users may want to process animated gif files. I already have a set of working php scripts to transcode videofiles to specific formats (like vpx@webm and h264@mp4, scaled to specific resolutions) using avconv, but herefore I need video input.

Usual ways seem to be to extract the frames of the gif and then encode it, like

convert file.gif file%03d.png 
avconv -i file%03d.png file.mp4

But this discards the frame rate, determined by the pause-informations within the gif-file. Its possible to define a framerate to avconv with -r, but

  • this does not respect the pause between frames, as they can differ (like 1st frame 100ms pause, 2nd frame 250ms pause, 3rd frame 100ms pause, ...)
  • as the input comes from users, it may even vary, as some gifs may have 5fps and others 30fps

I noticed that avconv is able to process gifs by itself and therefore may respect the correct pauses, but when I do (like similarily described in How to convert GIF to Mp4 is it possible?)

avconv -i file.gif -r 30 file.mp4

avconv will only take the first frame of the gif, while it detects the file at least as video:

Duration: 00:00:00.04, start: 0.000000, bitrate: N/A
  Stream #0.0: Video: gif, pal8, 640x480, 25 tbn

(example gif 'file.gif' has 15 frames, each with 100ms pause => 1.5s duration, looping)

  • What am I missing? Whats going wrong?
  • Are there probably better tools for this use case?
  • What are big sites like e.g. 9gag using to transcode uploaded gifs to video?
Community
  • 1
  • 1
pmedia
  • 191
  • 2
  • 13
  • Do you have a sample GIF where the delay varies between frames? – llogan Apr 10 '15 at 17:52
  • @LordNeckbeard just created this with gimp: http://s.xi-intersection.de/test2.gif - takes heavy use of this feature.Pauses are per frame in ms: 100, 150, 200, 250, 300, 400, 50, 50, 500, 200, 100, 100, 50, 75, 200, 200, 200, 200, 50, 50, 50, 40 – pmedia Apr 10 '15 at 20:50
  • `avconv` sucks. Get a recent [static build of `ffmpeg`](http://ffmpeg.org/download.html) (the one from FFmpeg, not the counterfeit from Libav), then try: `ffmpeg -i input.gif -c:v libx264 -pix_fmt yuv420p -movflags +faststart output.mp4` – llogan Apr 10 '15 at 21:16
  • @LordNeckbeard Actually, this really solves the problem; seems to be a bug or like so in avconv. Would you please so kind and make an answer out of your comment? – pmedia Apr 13 '15 at 14:17

2 Answers2

11

Yet Another Avconv Bug (YAAB)

ffmpeg has better GIF demuxing support (and improved GIF encoding). I recommend ditching avconv and getting ffmpeg (the real one from FFmpeg; not the old charlatan from Libav). A static build is easy, or you can of course compile.

Example

ffmpeg -i in.gif -c:v libx264 -pix_fmt yuv420p -movflags +faststart out.mp4

See the FFmpeg Wiki: H.264 Encoding Guide for more examples.

llogan
  • 87,794
  • 21
  • 166
  • 190
  • 1
    If your animated GIF has an odd (not even) height or width, you need to add `-vf scale=x:y` to the line with x or y incremented by one if needed to make them even. The following worked fine for me with version 2.6.5: `ffmpeg -i in.gif -vf scale=x:y -pix_fmt yuv420p out.mp4` – Eric Klien Jan 01 '16 at 12:43
1

If for some reason you are required to use avconv and imagemagick, you may want to try something like this:

ticks_per_frame = subprocess.check_output('identify -verbose -format %T_ {0}'.format(gif_path).split()).split('_')[:-1]
ticks_per_frame = [int(i) for i in ticks_per_frame]
num_frames = len(ticks_per_frame)
min_ticks = min(ticks_per_frame)

subprocess.call('convert -coalesce {0} tmp%d.png'.format(gif_path).split())

if len(set(ticks_per_frame)) > 1:
    num_dup = 0
    num_dup_total = 0
    for frame, ticks in enumerate(ticks_per_frame):
        num_dup_total += num_dup
        frame += num_dup_total
        num_dup = 0
        if ticks > min_ticks:
            num_dup = (ticks / min_ticks) - 1
            for i in range(num_frames + num_dup_total - 1, frame, -1):
                orig = 'tmp%d.png' % i
                new = 'tmp%d.png' % (i + num_dup)
                subprocess.call(['mv', orig, new])
            for i in range(1, num_dup + 1):
                curr = 'tmp%d.png' % frame
                dup = 'tmp%d.png' % (i + frame)
                subprocess.call(['cp', curr, dup])

framerate = (100 / min_ticks) if min_ticks else 10

subprocess.call('avconv -r {0} -i tmp%d.png -c:v libx264 -crf {1} -pix_fmt yuv420p \
-vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -y {2}.mp4'.format(framerate, quality, STORAGE_DIR + mp4_name).split())

subprocess.call(['rm'] + glob('tmp*.png'))

So, get the ticks in centiseconds for each frame of the gif (via identify), convert to multiple pngs, and then go through them while making duplicates based on the tick values. And don't you worry, the png files will still remain in consecutive order. Using the real FFmpeg is still the best way to go.

Palisand
  • 1,169
  • 1
  • 12
  • 29