Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * SubRip subtitle decoder | |
3 | * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org> | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #include "libavutil/avstring.h" | |
23 | #include "libavutil/common.h" | |
24 | #include "libavutil/intreadwrite.h" | |
25 | #include "libavutil/parseutils.h" | |
26 | #include "avcodec.h" | |
27 | #include "ass.h" | |
28 | ||
29 | static int html_color_parse(AVCodecContext *avctx, const char *str) | |
30 | { | |
31 | uint8_t rgba[4]; | |
32 | if (av_parse_color(rgba, str, strcspn(str, "\" >"), avctx) < 0) | |
33 | return -1; | |
34 | return rgba[0] | rgba[1] << 8 | rgba[2] << 16; | |
35 | } | |
36 | ||
37 | enum { | |
38 | PARAM_UNKNOWN = -1, | |
39 | PARAM_SIZE, | |
40 | PARAM_COLOR, | |
41 | PARAM_FACE, | |
42 | PARAM_NUMBER | |
43 | }; | |
44 | ||
45 | typedef struct { | |
46 | char tag[128]; | |
47 | char param[PARAM_NUMBER][128]; | |
48 | } SrtStack; | |
49 | ||
f6fa7814 DM |
50 | static void rstrip_spaces_buf(AVBPrint *buf) |
51 | { | |
52 | while (buf->len > 0 && buf->str[buf->len - 1] == ' ') | |
53 | buf->str[--buf->len] = 0; | |
54 | } | |
55 | ||
56 | static void srt_to_ass(AVCodecContext *avctx, AVBPrint *dst, | |
57 | const char *in, int x1, int y1, int x2, int y2) | |
2ba45a60 DM |
58 | { |
59 | char *param, buffer[128], tmp[128]; | |
60 | int len, tag_close, sptr = 1, line_start = 1, an = 0, end = 0; | |
61 | SrtStack stack[16]; | |
62 | ||
63 | stack[0].tag[0] = 0; | |
64 | strcpy(stack[0].param[PARAM_SIZE], "{\\fs}"); | |
65 | strcpy(stack[0].param[PARAM_COLOR], "{\\c}"); | |
66 | strcpy(stack[0].param[PARAM_FACE], "{\\fn}"); | |
67 | ||
68 | if (x1 >= 0 && y1 >= 0) { | |
69 | if (x2 >= 0 && y2 >= 0 && (x2 != x1 || y2 != y1)) | |
f6fa7814 | 70 | av_bprintf(dst, "{\\an1}{\\move(%d,%d,%d,%d)}", x1, y1, x2, y2); |
2ba45a60 | 71 | else |
f6fa7814 | 72 | av_bprintf(dst, "{\\an1}{\\pos(%d,%d)}", x1, y1); |
2ba45a60 DM |
73 | } |
74 | ||
f6fa7814 | 75 | for (; !end && *in; in++) { |
2ba45a60 DM |
76 | switch (*in) { |
77 | case '\r': | |
78 | break; | |
79 | case '\n': | |
80 | if (line_start) { | |
81 | end = 1; | |
82 | break; | |
83 | } | |
f6fa7814 DM |
84 | rstrip_spaces_buf(dst); |
85 | av_bprintf(dst, "\\N"); | |
2ba45a60 DM |
86 | line_start = 1; |
87 | break; | |
88 | case ' ': | |
89 | if (!line_start) | |
f6fa7814 | 90 | av_bprint_chars(dst, *in, 1); |
2ba45a60 DM |
91 | break; |
92 | case '{': /* skip all {\xxx} substrings except for {\an%d} | |
93 | and all microdvd like styles such as {Y:xxx} */ | |
94 | len = 0; | |
95 | an += sscanf(in, "{\\an%*1u}%n", &len) >= 0 && len > 0; | |
96 | if ((an != 1 && (len = 0, sscanf(in, "{\\%*[^}]}%n", &len) >= 0 && len > 0)) || | |
97 | (len = 0, sscanf(in, "{%*1[CcFfoPSsYy]:%*[^}]}%n", &len) >= 0 && len > 0)) { | |
98 | in += len - 1; | |
99 | } else | |
f6fa7814 | 100 | av_bprint_chars(dst, *in, 1); |
2ba45a60 DM |
101 | break; |
102 | case '<': | |
103 | tag_close = in[1] == '/'; | |
104 | len = 0; | |
105 | if (sscanf(in+tag_close+1, "%127[^>]>%n", buffer, &len) >= 1 && len > 0) { | |
106 | if ((param = strchr(buffer, ' '))) | |
107 | *param++ = 0; | |
108 | if ((!tag_close && sptr < FF_ARRAY_ELEMS(stack)) || | |
109 | ( tag_close && sptr > 0 && !strcmp(stack[sptr-1].tag, buffer))) { | |
110 | int i, j, unknown = 0; | |
111 | in += len + tag_close; | |
112 | if (!tag_close) | |
113 | memset(stack+sptr, 0, sizeof(*stack)); | |
114 | if (!strcmp(buffer, "font")) { | |
115 | if (tag_close) { | |
116 | for (i=PARAM_NUMBER-1; i>=0; i--) | |
117 | if (stack[sptr-1].param[i][0]) | |
118 | for (j=sptr-2; j>=0; j--) | |
119 | if (stack[j].param[i][0]) { | |
f6fa7814 | 120 | av_bprintf(dst, "%s", stack[j].param[i]); |
2ba45a60 DM |
121 | break; |
122 | } | |
123 | } else { | |
124 | while (param) { | |
125 | if (!strncmp(param, "size=", 5)) { | |
126 | unsigned font_size; | |
127 | param += 5 + (param[5] == '"'); | |
128 | if (sscanf(param, "%u", &font_size) == 1) { | |
129 | snprintf(stack[sptr].param[PARAM_SIZE], | |
130 | sizeof(stack[0].param[PARAM_SIZE]), | |
131 | "{\\fs%u}", font_size); | |
132 | } | |
133 | } else if (!strncmp(param, "color=", 6)) { | |
134 | param += 6 + (param[6] == '"'); | |
135 | snprintf(stack[sptr].param[PARAM_COLOR], | |
136 | sizeof(stack[0].param[PARAM_COLOR]), | |
137 | "{\\c&H%X&}", | |
138 | html_color_parse(avctx, param)); | |
139 | } else if (!strncmp(param, "face=", 5)) { | |
140 | param += 5 + (param[5] == '"'); | |
141 | len = strcspn(param, | |
142 | param[-1] == '"' ? "\"" :" "); | |
143 | av_strlcpy(tmp, param, | |
144 | FFMIN(sizeof(tmp), len+1)); | |
145 | param += len; | |
146 | snprintf(stack[sptr].param[PARAM_FACE], | |
147 | sizeof(stack[0].param[PARAM_FACE]), | |
148 | "{\\fn%s}", tmp); | |
149 | } | |
150 | if ((param = strchr(param, ' '))) | |
151 | param++; | |
152 | } | |
153 | for (i=0; i<PARAM_NUMBER; i++) | |
f6fa7814 DM |
154 | if (stack[sptr].param[i][0]) |
155 | av_bprintf(dst, "%s", stack[sptr].param[i]); | |
2ba45a60 DM |
156 | } |
157 | } else if (!buffer[1] && strspn(buffer, "bisu") == 1) { | |
f6fa7814 | 158 | av_bprintf(dst, "{\\%c%d}", buffer[0], !tag_close); |
2ba45a60 DM |
159 | } else { |
160 | unknown = 1; | |
161 | snprintf(tmp, sizeof(tmp), "</%s>", buffer); | |
162 | } | |
163 | if (tag_close) { | |
164 | sptr--; | |
165 | } else if (unknown && !strstr(in, tmp)) { | |
166 | in -= len + tag_close; | |
f6fa7814 | 167 | av_bprint_chars(dst, *in, 1); |
2ba45a60 DM |
168 | } else |
169 | av_strlcpy(stack[sptr++].tag, buffer, | |
170 | sizeof(stack[0].tag)); | |
171 | break; | |
172 | } | |
173 | } | |
174 | default: | |
f6fa7814 | 175 | av_bprint_chars(dst, *in, 1); |
2ba45a60 DM |
176 | break; |
177 | } | |
178 | if (*in != ' ' && *in != '\r' && *in != '\n') | |
179 | line_start = 0; | |
180 | } | |
181 | ||
f6fa7814 DM |
182 | while (dst->len >= 2 && !strncmp(&dst->str[dst->len - 2], "\\N", 2)) |
183 | dst->len -= 2; | |
184 | dst->str[dst->len] = 0; | |
185 | rstrip_spaces_buf(dst); | |
2ba45a60 DM |
186 | } |
187 | ||
188 | static int srt_decode_frame(AVCodecContext *avctx, | |
189 | void *data, int *got_sub_ptr, AVPacket *avpkt) | |
190 | { | |
191 | AVSubtitle *sub = data; | |
f6fa7814 | 192 | AVBPrint buffer; |
2ba45a60 | 193 | int ts_start, ts_end, x1 = -1, y1 = -1, x2 = -1, y2 = -1; |
f6fa7814 | 194 | int size, ret; |
2ba45a60 DM |
195 | const uint8_t *p = av_packet_get_side_data(avpkt, AV_PKT_DATA_SUBTITLE_POSITION, &size); |
196 | ||
197 | if (p && size == 16) { | |
198 | x1 = AV_RL32(p ); | |
199 | y1 = AV_RL32(p + 4); | |
200 | x2 = AV_RL32(p + 8); | |
201 | y2 = AV_RL32(p + 12); | |
202 | } | |
203 | ||
204 | if (avpkt->size <= 0) | |
205 | return avpkt->size; | |
206 | ||
f6fa7814 DM |
207 | av_bprint_init(&buffer, 0, AV_BPRINT_SIZE_UNLIMITED); |
208 | ||
209 | // TODO: reindent | |
2ba45a60 DM |
210 | // Do final divide-by-10 outside rescale to force rounding down. |
211 | ts_start = av_rescale_q(avpkt->pts, | |
212 | avctx->time_base, | |
213 | (AVRational){1,100}); | |
214 | ts_end = av_rescale_q(avpkt->pts + avpkt->duration, | |
215 | avctx->time_base, | |
216 | (AVRational){1,100}); | |
f6fa7814 DM |
217 | |
218 | srt_to_ass(avctx, &buffer, avpkt->data, x1, y1, x2, y2); | |
219 | ret = ff_ass_add_rect_bprint(sub, &buffer, ts_start, ts_end-ts_start); | |
220 | av_bprint_finalize(&buffer, NULL); | |
221 | if (ret < 0) | |
222 | return ret; | |
2ba45a60 DM |
223 | |
224 | *got_sub_ptr = sub->num_rects > 0; | |
225 | return avpkt->size; | |
226 | } | |
227 | ||
228 | #if CONFIG_SRT_DECODER | |
229 | /* deprecated decoder */ | |
230 | AVCodec ff_srt_decoder = { | |
231 | .name = "srt", | |
f6fa7814 | 232 | .long_name = NULL_IF_CONFIG_SMALL("SubRip subtitle"), |
2ba45a60 | 233 | .type = AVMEDIA_TYPE_SUBTITLE, |
f6fa7814 | 234 | .id = AV_CODEC_ID_SUBRIP, |
2ba45a60 DM |
235 | .init = ff_ass_subtitle_header_default, |
236 | .decode = srt_decode_frame, | |
237 | }; | |
238 | #endif | |
239 | ||
240 | #if CONFIG_SUBRIP_DECODER | |
241 | AVCodec ff_subrip_decoder = { | |
242 | .name = "subrip", | |
243 | .long_name = NULL_IF_CONFIG_SMALL("SubRip subtitle"), | |
244 | .type = AVMEDIA_TYPE_SUBTITLE, | |
245 | .id = AV_CODEC_ID_SUBRIP, | |
246 | .init = ff_ass_subtitle_header_default, | |
247 | .decode = srt_decode_frame, | |
248 | }; | |
249 | #endif |