1/* 2 * Animated GIF muxer 3 * Copyright (c) 2000 Fabrice Bellard 4 * 5 * first version by Francois Revol <revol@free.fr> 6 * 7 * This file is part of FFmpeg. 8 * 9 * FFmpeg is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Lesser General Public 11 * License as published by the Free Software Foundation; either 12 * version 2.1 of the License, or (at your option) any later version. 13 * 14 * FFmpeg is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with FFmpeg; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 */ 23 24#include "avformat.h" 25#include "internal.h" 26#include "libavutil/imgutils.h" 27#include "libavutil/log.h" 28#include "libavutil/opt.h" 29#include "libavcodec/bytestream.h" 30#include "libavcodec/gif.h" 31 32typedef struct GIFContext { 33 AVClass *class; 34 int loop; 35 int last_delay; 36 int duration; 37 int64_t last_pos; 38 int have_end; 39 AVPacket *prev_pkt; 40} GIFContext; 41 42static int gif_write_header(AVFormatContext *s) 43{ 44 if (s->nb_streams != 1 || 45 s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO || 46 s->streams[0]->codecpar->codec_id != AV_CODEC_ID_GIF) { 47 av_log(s, AV_LOG_ERROR, 48 "GIF muxer supports only a single video GIF stream.\n"); 49 return AVERROR(EINVAL); 50 } 51 52 avpriv_set_pts_info(s->streams[0], 64, 1, 100); 53 54 return 0; 55} 56 57static int gif_parse_packet(AVFormatContext *s, const uint8_t *data, int size) 58{ 59 GetByteContext gb; 60 int x; 61 62 bytestream2_init(&gb, data, size); 63 64 while (bytestream2_get_bytes_left(&gb) > 0) { 65 x = bytestream2_get_byte(&gb); 66 if (x != GIF_EXTENSION_INTRODUCER) 67 return 0; 68 69 x = bytestream2_get_byte(&gb); 70 while (x != GIF_GCE_EXT_LABEL && bytestream2_get_bytes_left(&gb) > 0) { 71 int block_size = bytestream2_get_byte(&gb); 72 if (!block_size) 73 break; 74 bytestream2_skip(&gb, block_size); 75 } 76 77 if (x == GIF_GCE_EXT_LABEL) 78 return bytestream2_tell(&gb) + 2; 79 } 80 81 return 0; 82} 83 84static int gif_get_delay(GIFContext *gif, AVPacket *prev, AVPacket *new) 85{ 86 if (new && new->pts != AV_NOPTS_VALUE) 87 gif->duration = av_clip_uint16(new->pts - prev->pts); 88 else if (!new && gif->last_delay >= 0) 89 gif->duration = gif->last_delay; 90 91 return gif->duration; 92} 93 94static int gif_write_packet(AVFormatContext *s, AVPacket *new_pkt) 95{ 96 GIFContext *gif = s->priv_data; 97 AVIOContext *pb = s->pb; 98 AVPacket *pkt = gif->prev_pkt; 99 100 if (!gif->prev_pkt) { 101 gif->prev_pkt = av_packet_alloc(); 102 if (!gif->prev_pkt) 103 return AVERROR(ENOMEM); 104 return av_packet_ref(gif->prev_pkt, new_pkt); 105 } 106 107 gif->last_pos = avio_tell(pb); 108 if (pkt->size > 0) 109 gif->have_end = pkt->data[pkt->size - 1] == GIF_TRAILER; 110 111 if (!gif->last_pos) { 112 int delay_pos; 113 int off = 13; 114 115 if (pkt->size < 13) 116 return AVERROR(EINVAL); 117 118 if (pkt->data[10] & 0x80) 119 off += 3 * (1 << ((pkt->data[10] & 0x07) + 1)); 120 121 if (pkt->size < off + 2) 122 return AVERROR(EINVAL); 123 124 avio_write(pb, pkt->data, off); 125 126 if (pkt->data[off] == GIF_EXTENSION_INTRODUCER && pkt->data[off + 1] == 0xff) 127 off += 19; 128 129 if (pkt->size <= off) 130 return AVERROR(EINVAL); 131 132 /* "NETSCAPE EXTENSION" for looped animation GIF */ 133 if (gif->loop >= 0) { 134 avio_w8(pb, GIF_EXTENSION_INTRODUCER); /* GIF Extension code */ 135 avio_w8(pb, GIF_APP_EXT_LABEL); /* Application Extension Label */ 136 avio_w8(pb, 0x0b); /* Length of Application Block */ 137 avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); 138 avio_w8(pb, 0x03); /* Length of Data Sub-Block */ 139 avio_w8(pb, 0x01); 140 avio_wl16(pb, (uint16_t)gif->loop); 141 avio_w8(pb, 0x00); /* Data Sub-block Terminator */ 142 } 143 144 delay_pos = gif_parse_packet(s, pkt->data + off, pkt->size - off); 145 if (delay_pos > 0 && delay_pos < pkt->size - off - 2) { 146 avio_write(pb, pkt->data + off, delay_pos); 147 avio_wl16(pb, gif_get_delay(gif, pkt, new_pkt)); 148 avio_write(pb, pkt->data + off + delay_pos + 2, pkt->size - off - delay_pos - 2); 149 } else { 150 avio_write(pb, pkt->data + off, pkt->size - off); 151 } 152 } else { 153 int delay_pos = gif_parse_packet(s, pkt->data, pkt->size); 154 155 if (delay_pos > 0 && delay_pos < pkt->size - 2) { 156 avio_write(pb, pkt->data, delay_pos); 157 avio_wl16(pb, gif_get_delay(gif, pkt, new_pkt)); 158 avio_write(pb, pkt->data + delay_pos + 2, pkt->size - delay_pos - 2); 159 } else { 160 avio_write(pb, pkt->data, pkt->size); 161 } 162 } 163 164 av_packet_unref(gif->prev_pkt); 165 if (new_pkt) 166 return av_packet_ref(gif->prev_pkt, new_pkt); 167 168 return 0; 169} 170 171static int gif_write_trailer(AVFormatContext *s) 172{ 173 GIFContext *gif = s->priv_data; 174 AVIOContext *pb = s->pb; 175 176 if (!gif->prev_pkt) 177 return AVERROR(EINVAL); 178 179 gif_write_packet(s, NULL); 180 181 if (!gif->have_end) 182 avio_w8(pb, GIF_TRAILER); 183 av_packet_free(&gif->prev_pkt); 184 185 return 0; 186} 187 188#define OFFSET(x) offsetof(GIFContext, x) 189#define ENC AV_OPT_FLAG_ENCODING_PARAM 190static const AVOption options[] = { 191 { "loop", "Number of times to loop the output: -1 - no loop, 0 - infinite loop", OFFSET(loop), 192 AV_OPT_TYPE_INT, { .i64 = 0 }, -1, 65535, ENC }, 193 { "final_delay", "Force delay (in centiseconds) after the last frame", OFFSET(last_delay), 194 AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 65535, ENC }, 195 { NULL }, 196}; 197 198static const AVClass gif_muxer_class = { 199 .class_name = "GIF muxer", 200 .item_name = av_default_item_name, 201 .version = LIBAVUTIL_VERSION_INT, 202 .option = options, 203}; 204 205const AVOutputFormat ff_gif_muxer = { 206 .name = "gif", 207 .long_name = NULL_IF_CONFIG_SMALL("CompuServe Graphics Interchange Format (GIF)"), 208 .mime_type = "image/gif", 209 .extensions = "gif", 210 .priv_data_size = sizeof(GIFContext), 211 .audio_codec = AV_CODEC_ID_NONE, 212 .video_codec = AV_CODEC_ID_GIF, 213 .write_header = gif_write_header, 214 .write_packet = gif_write_packet, 215 .write_trailer = gif_write_trailer, 216 .priv_class = &gif_muxer_class, 217 .flags = AVFMT_VARIABLE_FPS, 218}; 219