yading@11: /* yading@11: * Animated GIF muxer yading@11: * Copyright (c) 2000 Fabrice Bellard yading@11: * yading@11: * first version by Francois Revol yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: #include "avformat.h" yading@11: #include "internal.h" yading@11: #include "libavutil/avassert.h" yading@11: #include "libavutil/imgutils.h" yading@11: #include "libavutil/log.h" yading@11: #include "libavutil/opt.h" yading@11: yading@11: static int gif_image_write_header(AVIOContext *pb, int width, int height, yading@11: int loop_count, uint32_t *palette) yading@11: { yading@11: int i; yading@11: yading@11: avio_write(pb, "GIF", 3); yading@11: avio_write(pb, "89a", 3); yading@11: avio_wl16(pb, width); yading@11: avio_wl16(pb, height); yading@11: yading@11: if (palette) { yading@11: avio_w8(pb, 0xf7); /* flags: global clut, 256 entries */ yading@11: avio_w8(pb, 0x1f); /* background color index */ yading@11: avio_w8(pb, 0); /* aspect ratio */ yading@11: for (i = 0; i < 256; i++) { yading@11: const uint32_t v = palette[i] & 0xffffff; yading@11: avio_wb24(pb, v); yading@11: } yading@11: } else { yading@11: avio_w8(pb, 0); /* flags */ yading@11: avio_w8(pb, 0); /* background color index */ yading@11: avio_w8(pb, 0); /* aspect ratio */ yading@11: } yading@11: yading@11: /* "NETSCAPE EXTENSION" for looped animation GIF */ yading@11: avio_w8(pb, 0x21); /* GIF Extension code */ yading@11: avio_w8(pb, 0xff); /* Application Extension Label */ yading@11: avio_w8(pb, 0x0b); /* Length of Application Block */ yading@11: avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); yading@11: avio_w8(pb, 0x03); /* Length of Data Sub-Block */ yading@11: avio_w8(pb, 0x01); yading@11: avio_wl16(pb, (uint16_t)loop_count); yading@11: avio_w8(pb, 0x00); /* Data Sub-block Terminator */ yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: typedef struct { yading@11: AVClass *class; /** Class for private options. */ yading@11: int loop; yading@11: int last_delay; yading@11: AVPacket *prev_pkt; yading@11: int duration; yading@11: } GIFContext; yading@11: yading@11: static int gif_write_header(AVFormatContext *s) yading@11: { yading@11: GIFContext *gif = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: AVCodecContext *video_enc; yading@11: int width, height; yading@11: uint32_t palette[AVPALETTE_COUNT]; yading@11: yading@11: if (s->nb_streams != 1 || yading@11: s->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || yading@11: s->streams[0]->codec->codec_id != AV_CODEC_ID_GIF) { yading@11: av_log(s, AV_LOG_ERROR, yading@11: "GIF muxer supports only a single video GIF stream.\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: video_enc = s->streams[0]->codec; yading@11: width = video_enc->width; yading@11: height = video_enc->height; yading@11: yading@11: avpriv_set_pts_info(s->streams[0], 64, 1, 100); yading@11: if (avpriv_set_systematic_pal2(palette, video_enc->pix_fmt) < 0) { yading@11: av_assert0(video_enc->pix_fmt == AV_PIX_FMT_PAL8); yading@11: gif_image_write_header(pb, width, height, gif->loop, NULL); yading@11: } else { yading@11: gif_image_write_header(pb, width, height, gif->loop, palette); yading@11: } yading@11: yading@11: avio_flush(s->pb); yading@11: return 0; yading@11: } yading@11: yading@11: static int flush_packet(AVFormatContext *s, AVPacket *new) yading@11: { yading@11: GIFContext *gif = s->priv_data; yading@11: int size; yading@11: AVIOContext *pb = s->pb; yading@11: uint8_t flags = 0x4, transparent_color_index = 0x1f; yading@11: const uint32_t *palette; yading@11: AVPacket *pkt = gif->prev_pkt; yading@11: yading@11: if (!pkt) yading@11: return 0; yading@11: yading@11: /* Mark one colour as transparent if the input palette contains at least yading@11: * one colour that is more than 50% transparent. */ yading@11: palette = (uint32_t*)av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); yading@11: if (palette && size != AVPALETTE_SIZE) { yading@11: av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: if (palette) { yading@11: unsigned i, smallest_alpha = 0xff; yading@11: yading@11: for (i = 0; i < AVPALETTE_COUNT; i++) { yading@11: const uint32_t v = palette[i]; yading@11: if (v >> 24 < smallest_alpha) { yading@11: smallest_alpha = v >> 24; yading@11: transparent_color_index = i; yading@11: } yading@11: } yading@11: if (smallest_alpha < 128) yading@11: flags |= 0x1; /* Transparent Color Flag */ yading@11: } yading@11: yading@11: if (new && new->pts != AV_NOPTS_VALUE) yading@11: gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts); yading@11: else if (!new && gif->last_delay >= 0) yading@11: gif->duration = gif->last_delay; yading@11: yading@11: /* graphic control extension block */ yading@11: avio_w8(pb, 0x21); yading@11: avio_w8(pb, 0xf9); yading@11: avio_w8(pb, 0x04); /* block size */ yading@11: avio_w8(pb, flags); yading@11: avio_wl16(pb, gif->duration); yading@11: avio_w8(pb, transparent_color_index); yading@11: avio_w8(pb, 0x00); yading@11: yading@11: avio_write(pb, pkt->data, pkt->size); yading@11: yading@11: av_free_packet(gif->prev_pkt); yading@11: if (new) yading@11: av_copy_packet(gif->prev_pkt, new); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int gif_write_packet(AVFormatContext *s, AVPacket *pkt) yading@11: { yading@11: GIFContext *gif = s->priv_data; yading@11: yading@11: if (!gif->prev_pkt) { yading@11: gif->prev_pkt = av_malloc(sizeof(*gif->prev_pkt)); yading@11: if (!gif->prev_pkt) yading@11: return AVERROR(ENOMEM); yading@11: return av_copy_packet(gif->prev_pkt, pkt); yading@11: } yading@11: return flush_packet(s, pkt); yading@11: } yading@11: yading@11: static int gif_write_trailer(AVFormatContext *s) yading@11: { yading@11: GIFContext *gif = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: yading@11: flush_packet(s, NULL); yading@11: av_freep(&gif->prev_pkt); yading@11: avio_w8(pb, 0x3b); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: #define OFFSET(x) offsetof(GIFContext, x) yading@11: #define ENC AV_OPT_FLAG_ENCODING_PARAM yading@11: static const AVOption options[] = { yading@11: { "loop", "Number of times to loop the output.", OFFSET(loop), yading@11: AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 65535, ENC }, yading@11: { "final_delay", "Force delay (in ms) after the last frame", OFFSET(last_delay), yading@11: AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 65535, ENC }, yading@11: { NULL }, yading@11: }; yading@11: yading@11: static const AVClass gif_muxer_class = { yading@11: .class_name = "GIF muxer", yading@11: .item_name = av_default_item_name, yading@11: .version = LIBAVUTIL_VERSION_INT, yading@11: .option = options, yading@11: }; yading@11: yading@11: AVOutputFormat ff_gif_muxer = { yading@11: .name = "gif", yading@11: .long_name = NULL_IF_CONFIG_SMALL("GIF Animation"), yading@11: .mime_type = "image/gif", yading@11: .extensions = "gif", yading@11: .priv_data_size = sizeof(GIFContext), yading@11: .audio_codec = AV_CODEC_ID_NONE, yading@11: .video_codec = AV_CODEC_ID_GIF, yading@11: .write_header = gif_write_header, yading@11: .write_packet = gif_write_packet, yading@11: .write_trailer = gif_write_trailer, yading@11: .priv_class = &gif_muxer_class, yading@11: .flags = AVFMT_VARIABLE_FPS, yading@11: };