FFmpeg
htmlsubtitles.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org>
3  * Copyright (c) 2017 Clément Bœsch <u@pkh.me>
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/avassert.h"
23 #include "libavutil/avstring.h"
24 #include "libavutil/common.h"
25 #include "libavutil/parseutils.h"
26 #include "htmlsubtitles.h"
27 #include <ctype.h>
28 
29 static int html_color_parse(void *log_ctx, const char *str)
30 {
31  uint8_t rgba[4];
32  int nb_sharps = 0;
33  while (str[nb_sharps] == '#')
34  nb_sharps++;
35  str += FFMAX(0, nb_sharps - 1);
36  if (av_parse_color(rgba, str, strcspn(str, "\" >"), log_ctx) < 0)
37  return -1;
38  return rgba[0] | rgba[1] << 8 | rgba[2] << 16;
39 }
40 
41 static void rstrip_spaces_buf(AVBPrint *buf)
42 {
43  if (av_bprint_is_complete(buf))
44  while (buf->len > 0 && buf->str[buf->len - 1] == ' ')
45  buf->str[--buf->len] = 0;
46 }
47 
48 /*
49  * Fast code for scanning text enclosed in braces. Functionally
50  * equivalent to this sscanf call:
51  *
52  * sscanf(in, "{\\an%*1u}%n", &len) >= 0 && len > 0
53  */
54 static int scanbraces(const char* in) {
55  if (strncmp(in, "{\\an", 4) != 0) {
56  return 0;
57  }
58  if (!av_isdigit(in[4])) {
59  return 0;
60  }
61  if (in[5] != '}') {
62  return 0;
63  }
64  return 1;
65 }
66 
67 /* skip all {\xxx} substrings except for {\an%d}
68  and all microdvd like styles such as {Y:xxx} */
69 static void handle_open_brace(AVBPrint *dst, const char **inp, int *an, int *closing_brace_missing)
70 {
71  const char *in = *inp;
72 
73  *an += scanbraces(in);
74 
75  if (!*closing_brace_missing) {
76  if ( (*an != 1 && in[1] == '\\')
77  || (in[1] && strchr("CcFfoPSsYy", in[1]) && in[2] == ':')) {
78  char *bracep = strchr(in+2, '}');
79  if (bracep) {
80  *inp = bracep;
81  return;
82  } else
83  *closing_brace_missing = 1;
84  }
85  }
86 
87  av_bprint_chars(dst, *in, 1);
88 }
89 
90 struct font_tag {
91  char face[128];
92  int size;
93  uint32_t color;
94 };
95 
96 /*
97  * Fast code for scanning the rest of a tag. Functionally equivalent to
98  * this sscanf call:
99  *
100  * sscanf(in, "%127[^<>]>%n", buffer, lenp) == 2
101  */
102 static int scantag(const char* in, char* buffer, int* lenp) {
103  int len;
104 
105  for (len = 0; len < 128; len++) {
106  const char c = *in++;
107  switch (c) {
108  case '\0':
109  return 0;
110  case '<':
111  return 0;
112  case '>':
113  buffer[len] = '\0';
114  *lenp = len+1;
115  return 1;
116  default:
117  break;
118  }
119  buffer[len] = c;
120  }
121  return 0;
122 }
123 
124 /*
125  * The general politic of the convert is to mask unsupported tags or formatting
126  * errors (but still alert the user/subtitles writer with an error/warning)
127  * without dropping any actual text content for the final user.
128  */
129 int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)
130 {
131  char *param, buffer[128];
132  int len, tag_close, sptr = 0, line_start = 1, an = 0, end = 0;
133  int closing_brace_missing = 0;
134  int i, likely_a_tag;
135 
136  /*
137  * state stack is only present for fonts since they are the only tags where
138  * the state is not binary. Here is a typical use case:
139  *
140  * <font color="red" size=10>
141  * red 10
142  * <font size=50> RED AND BIG </font>
143  * red 10 again
144  * </font>
145  *
146  * On the other hand, using the state system for all the tags should be
147  * avoided because it breaks wrongly nested tags such as:
148  *
149  * <b> foo <i> bar </b> bla </i>
150  *
151  * We don't want to break here; instead, we will treat all these tags as
152  * binary state markers. Basically, "<b>" will activate bold, and "</b>"
153  * will deactivate it, whatever the current state.
154  *
155  * This will also prevents cases where we have a random closing tag
156  * remaining after the opening one was dropped. Yes, this happens and we
157  * still don't want to print a "</b>" at the end of the dialog event.
158  */
159  struct font_tag stack[16];
160 
161  memset(&stack[0], 0, sizeof(stack[0]));
162 
163  for (; !end && *in; in++) {
164  switch (*in) {
165  case '\r':
166  break;
167  case '\n':
168  if (line_start) {
169  end = 1;
170  break;
171  }
172  rstrip_spaces_buf(dst);
173  av_bprintf(dst, "\\N");
174  line_start = 1;
175  break;
176  case ' ':
177  if (!line_start)
178  av_bprint_chars(dst, *in, 1);
179  break;
180  case '{':
181  handle_open_brace(dst, &in, &an, &closing_brace_missing);
182  break;
183  case '<':
184  /*
185  * "<<" are likely latin guillemets in ASCII or some kind of random
186  * style effect; see sub/badsyntax.srt in the FATE samples
187  * directory for real test cases.
188  */
189 
190  likely_a_tag = 1;
191  for (i = 0; in[1] == '<'; i++) {
192  av_bprint_chars(dst, '<', 1);
193  likely_a_tag = 0;
194  in++;
195  }
196 
197  tag_close = in[1] == '/';
198  if (tag_close)
199  likely_a_tag = 1;
200 
201  av_assert0(in[0] == '<');
202 
203  len = 0;
204 
205  if (scantag(in+tag_close+1, buffer, &len) && len > 0) {
206  const int skip = len + tag_close;
207  const char *tagname = buffer;
208  while (*tagname == ' ') {
209  likely_a_tag = 0;
210  tagname++;
211  }
212  if ((param = strchr(tagname, ' ')))
213  *param++ = 0;
214 
215  /* Check if this is likely a tag */
216 #define LIKELY_A_TAG_CHAR(x) (((x) >= '0' && (x) <= '9') || \
217  ((x) >= 'a' && (x) <= 'z') || \
218  ((x) >= 'A' && (x) <= 'Z') || \
219  (x) == '_' || (x) == '/')
220  for (i = 0; tagname[i]; i++) {
221  if (!LIKELY_A_TAG_CHAR(tagname[i])) {
222  likely_a_tag = 0;
223  break;
224  }
225  }
226 
227  if (!av_strcasecmp(tagname, "font")) {
228  if (tag_close && sptr > 0) {
229  struct font_tag *cur_tag = &stack[sptr--];
230  struct font_tag *last_tag = &stack[sptr];
231 
232  if (cur_tag->size) {
233  if (!last_tag->size)
234  av_bprintf(dst, "{\\fs}");
235  else if (last_tag->size != cur_tag->size)
236  av_bprintf(dst, "{\\fs%d}", last_tag->size);
237  }
238 
239  if (cur_tag->color & 0xff000000) {
240  if (!(last_tag->color & 0xff000000))
241  av_bprintf(dst, "{\\c}");
242  else if (last_tag->color != cur_tag->color)
243  av_bprintf(dst, "{\\c&H%"PRIX32"&}", last_tag->color & 0xffffff);
244  }
245 
246  if (cur_tag->face[0]) {
247  if (!last_tag->face[0])
248  av_bprintf(dst, "{\\fn}");
249  else if (strcmp(last_tag->face, cur_tag->face))
250  av_bprintf(dst, "{\\fn%s}", last_tag->face);
251  }
252  } else if (!tag_close && sptr < FF_ARRAY_ELEMS(stack) - 1) {
253  struct font_tag *new_tag = &stack[sptr + 1];
254 
255  *new_tag = stack[sptr++];
256 
257  while (param) {
258  if (!av_strncasecmp(param, "size=", 5)) {
259  param += 5 + (param[5] == '"');
260  if (sscanf(param, "%u", &new_tag->size) == 1)
261  av_bprintf(dst, "{\\fs%u}", new_tag->size);
262  } else if (!av_strncasecmp(param, "color=", 6)) {
263  int color;
264  param += 6 + (param[6] == '"');
265  color = html_color_parse(log_ctx, param);
266  if (color >= 0) {
267  new_tag->color = 0xff000000 | color;
268  av_bprintf(dst, "{\\c&H%"PRIX32"&}", new_tag->color & 0xffffff);
269  }
270  } else if (!av_strncasecmp(param, "face=", 5)) {
271  param += 5 + (param[5] == '"');
272  len = strcspn(param,
273  param[-1] == '"' ? "\"" :" ");
274  av_strlcpy(new_tag->face, param,
275  FFMIN(sizeof(new_tag->face), len+1));
276  param += len;
277  av_bprintf(dst, "{\\fn%s}", new_tag->face);
278  }
279  if ((param = strchr(param, ' ')))
280  param++;
281  }
282  }
283  in += skip;
284  } else if (tagname[0] && !tagname[1] && strchr("bisu", av_tolower(tagname[0]))) {
285  av_bprintf(dst, "{\\%c%d}", (char)av_tolower(tagname[0]), !tag_close);
286  in += skip;
287  } else if (!av_strncasecmp(tagname, "br", 2) &&
288  (!tagname[2] || (tagname[2] == '/' && !tagname[3]))) {
289  av_bprintf(dst, "\\N");
290  in += skip;
291  } else if (likely_a_tag) {
292  if (!tag_close) // warn only once
293  av_log(log_ctx, AV_LOG_WARNING, "Unrecognized tag %s\n", tagname);
294  in += skip;
295  } else {
296  av_bprint_chars(dst, '<', 1);
297  }
298  } else {
299  av_bprint_chars(dst, *in, 1);
300  }
301  break;
302  default:
303  av_bprint_chars(dst, *in, 1);
304  break;
305  }
306  if (*in != ' ' && *in != '\r' && *in != '\n')
307  line_start = 0;
308  }
309 
310  if (!av_bprint_is_complete(dst))
311  return AVERROR(ENOMEM);
312 
313  while (dst->len >= 2 && !strncmp(&dst->str[dst->len - 2], "\\N", 2))
314  dst->len -= 2;
315  dst->str[dst->len] = 0;
316  rstrip_spaces_buf(dst);
317 
318  return 0;
319 }
AV_LOG_WARNING
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:186
av_bprint_is_complete
static int av_bprint_is_complete(const AVBPrint *buf)
Test if the print buffer is complete (not truncated).
Definition: bprint.h:218
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
color
Definition: vf_paletteuse.c:511
av_parse_color
int av_parse_color(uint8_t *rgba_color, const char *color_string, int slen, void *log_ctx)
Put the RGBA values that correspond to color_string in rgba_color.
Definition: parseutils.c:356
font_tag::face
char face[128]
Definition: htmlsubtitles.c:91
htmlsubtitles.h
av_strcasecmp
int av_strcasecmp(const char *a, const char *b)
Locale-independent case-insensitive compare.
Definition: avstring.c:207
FFMAX
#define FFMAX(a, b)
Definition: macros.h:47
font_tag::size
int size
Definition: htmlsubtitles.c:92
rstrip_spaces_buf
static void rstrip_spaces_buf(AVBPrint *buf)
Definition: htmlsubtitles.c:41
avassert.h
FF_ARRAY_ELEMS
#define FF_ARRAY_ELEMS(a)
Definition: sinewin_tablegen.c:29
font_tag
Definition: htmlsubtitles.c:90
av_assert0
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition: avassert.h:40
handle_open_brace
static void handle_open_brace(AVBPrint *dst, const char **inp, int *an, int *closing_brace_missing)
Definition: htmlsubtitles.c:69
font_tag::color
uint32_t color
Definition: htmlsubtitles.c:93
parseutils.h
scantag
static int scantag(const char *in, char *buffer, int *lenp)
Definition: htmlsubtitles.c:102
c
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
av_strncasecmp
int av_strncasecmp(const char *a, const char *b, size_t n)
Locale-independent case-insensitive compare.
Definition: avstring.c:217
color
static const uint32_t color[16+AV_CLASS_CATEGORY_NB]
Definition: log.c:94
html_color_parse
static int html_color_parse(void *log_ctx, const char *str)
Definition: htmlsubtitles.c:29
av_isdigit
static av_const int av_isdigit(int c)
Locale-independent conversion of ASCII isdigit.
Definition: avstring.h:202
i
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:255
common.h
ff_htmlmarkup_to_ass
int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)
Definition: htmlsubtitles.c:129
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
len
int len
Definition: vorbis_enc_data.h:426
av_bprintf
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:99
buffer
the frame and frame reference mechanism is intended to as much as expensive copies of that data while still allowing the filters to produce correct results The data is stored in buffers represented by AVFrame structures Several references can point to the same frame buffer
Definition: filter_design.txt:49
scanbraces
static int scanbraces(const char *in)
Definition: htmlsubtitles.c:54
av_strlcpy
size_t av_strlcpy(char *dst, const char *src, size_t size)
Copy the string src to dst, but no more than size - 1 bytes, and null-terminate dst.
Definition: avstring.c:85
LIKELY_A_TAG_CHAR
#define LIKELY_A_TAG_CHAR(x)
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
av_bprint_chars
void av_bprint_chars(AVBPrint *buf, char c, unsigned n)
Append char c n times to a print buffer.
Definition: bprint.c:145
avstring.h
av_tolower
static av_const int av_tolower(int c)
Locale-independent conversion of ASCII characters to lowercase.
Definition: avstring.h:237
skip
static void BS_FUNC() skip(BSCTX *bc, unsigned int n)
Skip n bits in the buffer.
Definition: bitstream_template.h:375