在Linux服务器上将动态GIF转换为视频并保留帧速率。

6
如何在Linux服务器上以编程方式将动态gif转换为视频(例如h264@mp4)?
我需要处理用户生成的内容,并以几种定义好的视频格式输出。因此,可能会有用户想要处理动态gif文件。我已经有一组工作正常的PHP脚本,可以使用avconv将视频文件转码为特定格式(如vpx@webm和h264@mp4,并缩放到特定分辨率),但是需要视频输入。
通常的方法似乎是提取gif的帧,然后进行编码,例如:
convert file.gif file%03d.png 
avconv -i file%03d.png file.mp4

但这样会忽略由gif文件中的暂停信息确定的帧率。可以使用-r参数来定义avconv的帧率,但是:
  • 它不考虑帧之间的暂停时间,因为它们可能不同(例如第一帧100ms暂停,第二帧250ms暂停,第三帧100ms暂停等)
  • 由于输入来自用户,因此甚至可能有所变化,因为某些gif可能具有5fps和其他gif可能具有30fps
我注意到avconv能够自己处理gif,并且因此可以尊重正确的暂停时间,但是当我这样做时(如在如何将GIF转换为Mp4是否可能?中类似描述的方式)
avconv -i file.gif -r 30 file.mp4

avconv只会取第一帧GIF图像,因为它将其识别为视频文件:

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

(例如gif文件'file.gif'有15帧,每帧间隔100毫秒 => 1.5秒持续时间,循环播放)

  • 我错过了什么?出了什么问题?
  • 这种情况下可能有更好的工具吗?
  • 像9gag这样的大型网站使用什么工具将上传的gif转码为视频?

你有一个延迟在帧之间变化的GIF示例吗? - llogan
@LordNeckbeard刚用GIMP创建了这个:http://s.xi-intersection.de/test2.gif - 大量使用了这个功能。暂停时间以毫秒为单位:100、150、200、250、300、400、50、50、500、200、100、100、50、75、200、200、200、200、50、50、50、40。 - pmedia
avconv 很烂。从 FFmpeg 的官网 下载最新的静态版本(不要下载来自 Libav 的假冒版本),然后尝试运行以下命令:ffmpeg -i input.gif -c:v libx264 -pix_fmt yuv420p -movflags +faststart output.mp4 - llogan
@LordNeckbeard,实际上,这确实解决了问题;在avconv中似乎存在一个错误或类似的问题。您能否友好地将您的评论转化为答案? - pmedia
2个回答

12

又一个Avconv Bug(YAAB)

ffmpeg具有更好的GIF解复用支持(以及改进的GIF编码)。我建议放弃avconv并使用ffmpeg(来自FFmpeg的真正版本,而不是Libav的旧骗子)。可以使用静态版本,或者您当然可以进行编译

示例

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

查看FFmpeg Wiki:H.264编码指南获取更多示例。


1
如果您的动态GIF具有奇数高度或宽度,则需要在带有x或y的行中添加“-vf scale = x:y”,如果需要使它们成为偶数,则将其递增一次。以下指令在2.6.5版本中对我有效:ffmpeg -i in.gif -vf scale=x:y -pix_fmt yuv420p out.mp4 - Eric Klien

0
如果由于某些原因你需要使用avconv和imagemagick,你可能想尝试这样做:
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'))

因此,通过identify获取gif每帧的百分之一秒的刻度,转换为多个png文件,然后在制作基于刻度值的副本时遍历它们。不用担心,png文件仍将保持连续顺序。使用真正的FFmpeg仍然是最佳选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接