Commit | Line | Data |
---|---|---|
2ba45a60 DM |
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/avassert.h" | |
27 | #include "libavutil/imgutils.h" | |
28 | #include "libavutil/log.h" | |
29 | #include "libavutil/opt.h" | |
30 | ||
31 | static int gif_image_write_header(AVFormatContext *s, int width, int height, | |
32 | int loop_count, uint32_t *palette) | |
33 | { | |
34 | AVIOContext *pb = s->pb; | |
35 | AVRational sar = s->streams[0]->codec->sample_aspect_ratio; | |
36 | int i; | |
37 | int64_t aspect = 0; | |
38 | ||
39 | if (sar.num > 0 && sar.den > 0) { | |
40 | aspect = sar.num * 64LL / sar.den - 15; | |
41 | if (aspect < 0 || aspect > 255) | |
42 | aspect = 0; | |
43 | } | |
44 | ||
45 | avio_write(pb, "GIF", 3); | |
46 | avio_write(pb, "89a", 3); | |
47 | avio_wl16(pb, width); | |
48 | avio_wl16(pb, height); | |
49 | ||
50 | if (palette) { | |
51 | avio_w8(pb, 0xf7); /* flags: global clut, 256 entries */ | |
52 | avio_w8(pb, 0x1f); /* background color index */ | |
53 | avio_w8(pb, aspect); | |
54 | for (i = 0; i < 256; i++) { | |
55 | const uint32_t v = palette[i] & 0xffffff; | |
56 | avio_wb24(pb, v); | |
57 | } | |
58 | } else { | |
59 | avio_w8(pb, 0); /* flags */ | |
60 | avio_w8(pb, 0); /* background color index */ | |
61 | avio_w8(pb, aspect); | |
62 | } | |
63 | ||
64 | ||
65 | if (loop_count >= 0 ) { | |
66 | /* "NETSCAPE EXTENSION" for looped animation GIF */ | |
67 | avio_w8(pb, 0x21); /* GIF Extension code */ | |
68 | avio_w8(pb, 0xff); /* Application Extension Label */ | |
69 | avio_w8(pb, 0x0b); /* Length of Application Block */ | |
70 | avio_write(pb, "NETSCAPE2.0", sizeof("NETSCAPE2.0") - 1); | |
71 | avio_w8(pb, 0x03); /* Length of Data Sub-Block */ | |
72 | avio_w8(pb, 0x01); | |
73 | avio_wl16(pb, (uint16_t)loop_count); | |
74 | avio_w8(pb, 0x00); /* Data Sub-block Terminator */ | |
75 | } | |
76 | ||
77 | return 0; | |
78 | } | |
79 | ||
80 | typedef struct { | |
81 | AVClass *class; | |
82 | int loop; | |
83 | int last_delay; | |
84 | AVPacket *prev_pkt; | |
85 | int duration; | |
86 | } GIFContext; | |
87 | ||
88 | static int gif_write_header(AVFormatContext *s) | |
89 | { | |
90 | GIFContext *gif = s->priv_data; | |
91 | AVCodecContext *video_enc; | |
92 | int width, height; | |
93 | uint32_t palette[AVPALETTE_COUNT]; | |
94 | ||
95 | if (s->nb_streams != 1 || | |
96 | s->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || | |
97 | s->streams[0]->codec->codec_id != AV_CODEC_ID_GIF) { | |
98 | av_log(s, AV_LOG_ERROR, | |
99 | "GIF muxer supports only a single video GIF stream.\n"); | |
100 | return AVERROR(EINVAL); | |
101 | } | |
102 | ||
103 | video_enc = s->streams[0]->codec; | |
104 | width = video_enc->width; | |
105 | height = video_enc->height; | |
106 | ||
107 | avpriv_set_pts_info(s->streams[0], 64, 1, 100); | |
108 | if (avpriv_set_systematic_pal2(palette, video_enc->pix_fmt) < 0) { | |
109 | av_assert0(video_enc->pix_fmt == AV_PIX_FMT_PAL8); | |
110 | gif_image_write_header(s, width, height, gif->loop, NULL); | |
111 | } else { | |
112 | gif_image_write_header(s, width, height, gif->loop, palette); | |
113 | } | |
114 | ||
115 | avio_flush(s->pb); | |
116 | return 0; | |
117 | } | |
118 | ||
119 | static int flush_packet(AVFormatContext *s, AVPacket *new) | |
120 | { | |
121 | GIFContext *gif = s->priv_data; | |
122 | int size; | |
123 | AVIOContext *pb = s->pb; | |
124 | uint8_t flags = 0x4, transparent_color_index = 0x1f; | |
125 | const uint32_t *palette; | |
126 | AVPacket *pkt = gif->prev_pkt; | |
127 | ||
128 | if (!pkt) | |
129 | return 0; | |
130 | ||
131 | /* Mark one colour as transparent if the input palette contains at least | |
132 | * one colour that is more than 50% transparent. */ | |
133 | palette = (uint32_t*)av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); | |
134 | if (palette && size != AVPALETTE_SIZE) { | |
135 | av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); | |
136 | return AVERROR_INVALIDDATA; | |
137 | } | |
138 | if (palette) { | |
139 | unsigned i, smallest_alpha = 0xff; | |
140 | ||
141 | for (i = 0; i < AVPALETTE_COUNT; i++) { | |
142 | const uint32_t v = palette[i]; | |
143 | if (v >> 24 < smallest_alpha) { | |
144 | smallest_alpha = v >> 24; | |
145 | transparent_color_index = i; | |
146 | } | |
147 | } | |
148 | if (smallest_alpha < 128) | |
149 | flags |= 0x1; /* Transparent Color Flag */ | |
150 | } | |
151 | ||
152 | if (new && new->pts != AV_NOPTS_VALUE) | |
153 | gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts); | |
154 | else if (!new && gif->last_delay >= 0) | |
155 | gif->duration = gif->last_delay; | |
156 | ||
157 | /* graphic control extension block */ | |
158 | avio_w8(pb, 0x21); | |
159 | avio_w8(pb, 0xf9); | |
160 | avio_w8(pb, 0x04); /* block size */ | |
161 | avio_w8(pb, flags); | |
162 | avio_wl16(pb, gif->duration); | |
163 | avio_w8(pb, transparent_color_index); | |
164 | avio_w8(pb, 0x00); | |
165 | ||
166 | avio_write(pb, pkt->data, pkt->size); | |
167 | ||
168 | av_free_packet(gif->prev_pkt); | |
169 | if (new) | |
170 | av_copy_packet(gif->prev_pkt, new); | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static int gif_write_packet(AVFormatContext *s, AVPacket *pkt) | |
176 | { | |
177 | GIFContext *gif = s->priv_data; | |
178 | ||
179 | if (!gif->prev_pkt) { | |
180 | gif->prev_pkt = av_malloc(sizeof(*gif->prev_pkt)); | |
181 | if (!gif->prev_pkt) | |
182 | return AVERROR(ENOMEM); | |
183 | return av_copy_packet(gif->prev_pkt, pkt); | |
184 | } | |
185 | return flush_packet(s, pkt); | |
186 | } | |
187 | ||
188 | static int gif_write_trailer(AVFormatContext *s) | |
189 | { | |
190 | GIFContext *gif = s->priv_data; | |
191 | AVIOContext *pb = s->pb; | |
192 | ||
193 | flush_packet(s, NULL); | |
194 | av_freep(&gif->prev_pkt); | |
195 | avio_w8(pb, 0x3b); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | #define OFFSET(x) offsetof(GIFContext, x) | |
201 | #define ENC AV_OPT_FLAG_ENCODING_PARAM | |
202 | static const AVOption options[] = { | |
203 | { "loop", "Number of times to loop the output: -1 - no loop, 0 - infinite loop", OFFSET(loop), | |
204 | AV_OPT_TYPE_INT, { .i64 = 0 }, -1, 65535, ENC }, | |
205 | { "final_delay", "Force delay (in centiseconds) after the last frame", OFFSET(last_delay), | |
206 | AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 65535, ENC }, | |
207 | { NULL }, | |
208 | }; | |
209 | ||
210 | static const AVClass gif_muxer_class = { | |
211 | .class_name = "GIF muxer", | |
212 | .item_name = av_default_item_name, | |
213 | .version = LIBAVUTIL_VERSION_INT, | |
214 | .option = options, | |
215 | }; | |
216 | ||
217 | AVOutputFormat ff_gif_muxer = { | |
218 | .name = "gif", | |
219 | .long_name = NULL_IF_CONFIG_SMALL("GIF Animation"), | |
220 | .mime_type = "image/gif", | |
221 | .extensions = "gif", | |
222 | .priv_data_size = sizeof(GIFContext), | |
223 | .audio_codec = AV_CODEC_ID_NONE, | |
224 | .video_codec = AV_CODEC_ID_GIF, | |
225 | .write_header = gif_write_header, | |
226 | .write_packet = gif_write_packet, | |
227 | .write_trailer = gif_write_trailer, | |
228 | .priv_class = &gif_muxer_class, | |
229 | .flags = AVFMT_VARIABLE_FPS, | |
230 | }; |