Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> |
3 | | * |
4 | | * This file is part of libass. |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include "config.h" |
20 | | #include "ass_compat.h" |
21 | | |
22 | | #include <stdio.h> |
23 | | #include <stdlib.h> |
24 | | #include <string.h> |
25 | | #include <assert.h> |
26 | | #include <errno.h> |
27 | | #include <sys/types.h> |
28 | | #include <sys/stat.h> |
29 | | #include <inttypes.h> |
30 | | |
31 | | #ifdef CONFIG_ICONV |
32 | | #include <iconv.h> |
33 | | #endif |
34 | | |
35 | | #include "ass.h" |
36 | | #include "ass_utils.h" |
37 | | #include "ass_library.h" |
38 | | #include "ass_priv.h" |
39 | | #include "ass_shaper.h" |
40 | | #include "ass_string.h" |
41 | | |
42 | 51.3k | #define ass_atof(STR) (ass_strtod((STR),NULL)) |
43 | | |
44 | | static const char *const ass_style_format = |
45 | | "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, " |
46 | | "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, " |
47 | | "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, " |
48 | | "Alignment, MarginL, MarginR, MarginV, Encoding"; |
49 | | static const char *const ass_event_format = |
50 | | "Layer, Start, End, Style, Name, " |
51 | | "MarginL, MarginR, MarginV, Effect, Text"; |
52 | | static const char *const ssa_style_format = |
53 | | "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, " |
54 | | "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, " |
55 | | "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"; |
56 | | static const char *const ssa_event_format = |
57 | | "Marked, Start, End, Style, Name, " |
58 | | "MarginL, MarginR, MarginV, Effect, Text"; |
59 | | |
60 | 1.42k | #define ASS_STYLES_ALLOC 20 |
61 | | |
62 | | int ass_library_version(void) |
63 | 0 | { |
64 | 0 | return LIBASS_VERSION; |
65 | 0 | } |
66 | | |
67 | | void ass_free_track(ASS_Track *track) |
68 | 196 | { |
69 | 196 | int i; |
70 | | |
71 | 196 | if (!track) |
72 | 0 | return; |
73 | | |
74 | 196 | if (track->parser_priv) { |
75 | 196 | free(track->parser_priv->read_order_bitmap); |
76 | 196 | free(track->parser_priv->fontname); |
77 | 196 | free(track->parser_priv->fontdata); |
78 | 196 | free(track->parser_priv); |
79 | 196 | } |
80 | 196 | free(track->style_format); |
81 | 196 | free(track->event_format); |
82 | 196 | free(track->Language); |
83 | 196 | if (track->styles) { |
84 | 10.9k | for (i = 0; i < track->n_styles; ++i) |
85 | 10.7k | ass_free_style(track, i); |
86 | 196 | } |
87 | 196 | free(track->styles); |
88 | 196 | if (track->events) { |
89 | 7.86k | for (i = 0; i < track->n_events; ++i) |
90 | 7.73k | ass_free_event(track, i); |
91 | 132 | } |
92 | 196 | free(track->events); |
93 | 196 | free(track->name); |
94 | 196 | free(track); |
95 | 196 | } |
96 | | |
97 | | /// \brief Allocate a new style struct |
98 | | /// \param track track |
99 | | /// \return style id or negative value on failure |
100 | | int ass_alloc_style(ASS_Track *track) |
101 | 10.7k | { |
102 | 10.7k | int sid; |
103 | | |
104 | 10.7k | assert(track->n_styles <= track->max_styles); |
105 | | |
106 | 10.7k | if (track->n_styles == track->max_styles) { |
107 | 711 | if (track->max_styles >= FFMIN(SIZE_MAX, INT_MAX) - ASS_STYLES_ALLOC) |
108 | 0 | return -1; |
109 | 711 | int new_max = track->max_styles + ASS_STYLES_ALLOC; |
110 | 711 | if (!ASS_REALLOC_ARRAY(track->styles, new_max)) |
111 | 0 | return -1; |
112 | 711 | track->max_styles = new_max; |
113 | 711 | } |
114 | | |
115 | 10.7k | sid = track->n_styles++; |
116 | 10.7k | memset(track->styles + sid, 0, sizeof(ASS_Style)); |
117 | 10.7k | return sid; |
118 | 10.7k | } |
119 | | |
120 | | /// \brief Allocate a new event struct |
121 | | /// \param track track |
122 | | /// \return event id or negative value on failure |
123 | | int ass_alloc_event(ASS_Track *track) |
124 | 8.68k | { |
125 | 8.68k | int eid; |
126 | | |
127 | 8.68k | assert(track->n_events <= track->max_events); |
128 | | |
129 | 8.68k | if (track->n_events == track->max_events) { |
130 | 694 | if (track->max_events >= FFMIN(SIZE_MAX, INT_MAX) / 2) |
131 | 0 | return -1; |
132 | 694 | int new_max = track->max_events * 2 + 1; |
133 | 694 | if (!ASS_REALLOC_ARRAY(track->events, new_max)) |
134 | 0 | return -1; |
135 | 694 | track->max_events = new_max; |
136 | 694 | } |
137 | | |
138 | 8.68k | eid = track->n_events++; |
139 | 8.68k | memset(track->events + eid, 0, sizeof(ASS_Event)); |
140 | 8.68k | return eid; |
141 | 8.68k | } |
142 | | |
143 | | void ass_free_event(ASS_Track *track, int eid) |
144 | 8.68k | { |
145 | 8.68k | ASS_Event *event = track->events + eid; |
146 | | |
147 | 8.68k | free(event->Name); |
148 | 8.68k | free(event->Effect); |
149 | 8.68k | free(event->Text); |
150 | 8.68k | free(event->render_priv); |
151 | 8.68k | } |
152 | | |
153 | | void ass_free_style(ASS_Track *track, int sid) |
154 | 10.7k | { |
155 | 10.7k | ASS_Style *style = track->styles + sid; |
156 | | |
157 | 10.7k | free(style->Name); |
158 | 10.7k | free(style->FontName); |
159 | 10.7k | } |
160 | | |
161 | | static int resize_read_order_bitmap(ASS_Track *track, int max_id) |
162 | 0 | { |
163 | | // Don't allow malicious files to OOM us easily. Also avoids int overflows. |
164 | 0 | if (max_id < 0 || max_id >= 10 * 1024 * 1024 * 8) |
165 | 0 | goto fail; |
166 | 0 | assert(track->parser_priv->read_order_bitmap || !track->parser_priv->read_order_elems); |
167 | 0 | if (max_id >= track->parser_priv->read_order_elems * 32) { |
168 | 0 | int oldelems = track->parser_priv->read_order_elems; |
169 | 0 | int elems = ((max_id + 31) / 32 + 1) * 2; |
170 | 0 | assert(elems >= oldelems); |
171 | 0 | track->parser_priv->read_order_elems = elems; |
172 | 0 | void *new_bitmap = |
173 | 0 | realloc(track->parser_priv->read_order_bitmap, elems * 4); |
174 | 0 | if (!new_bitmap) |
175 | 0 | goto fail; |
176 | 0 | track->parser_priv->read_order_bitmap = new_bitmap; |
177 | 0 | memset(track->parser_priv->read_order_bitmap + oldelems, 0, |
178 | 0 | (elems - oldelems) * 4); |
179 | 0 | } |
180 | 0 | return 0; |
181 | | |
182 | 0 | fail: |
183 | 0 | free(track->parser_priv->read_order_bitmap); |
184 | 0 | track->parser_priv->read_order_bitmap = NULL; |
185 | 0 | track->parser_priv->read_order_elems = 0; |
186 | 0 | return -1; |
187 | 0 | } |
188 | | |
189 | | static int test_and_set_read_order_bit(ASS_Track *track, int id) |
190 | 0 | { |
191 | 0 | if (resize_read_order_bitmap(track, id) < 0) |
192 | 0 | return -1; |
193 | 0 | int index = id >> 5; |
194 | 0 | uint32_t bit = 1u << (id & 0x1F); |
195 | 0 | if (track->parser_priv->read_order_bitmap[index] & bit) |
196 | 0 | return 1; |
197 | 0 | track->parser_priv->read_order_bitmap[index] |= bit; |
198 | 0 | return 0; |
199 | 0 | } |
200 | | |
201 | | static inline void clear_read_order_bit(ASS_Track *track, int id) |
202 | 0 | { |
203 | | // we previously loaded a packet with an invalid ReadOrder field |
204 | | // (or int overflow) and decided to just skip checking for duplicates |
205 | 0 | if (id < 0) |
206 | 0 | return; |
207 | | |
208 | 0 | int index = id >> 5; |
209 | 0 | if (index < track->parser_priv->read_order_elems) { |
210 | 0 | uint32_t mask = ~(1u << (id & 0x1F)); |
211 | 0 | track->parser_priv->read_order_bitmap[index] &= mask; |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | static inline void update_prune_ts(ASS_Track *track, const long long ts) |
216 | 7.73k | { |
217 | 7.73k | if (ts < track->parser_priv->prune_next_ts) |
218 | 261 | track->parser_priv->prune_next_ts = ts; |
219 | 7.73k | } |
220 | | |
221 | | // ============================================================================================== |
222 | | |
223 | | /** |
224 | | * \brief Set up default style |
225 | | * \param style style to edit to defaults |
226 | | * The parameters are mostly taken directly from VSFilter source for |
227 | | * best compatibility. |
228 | | */ |
229 | | static void set_default_style(ASS_Style *style) |
230 | 196 | { |
231 | 196 | style->Name = strdup("Default"); |
232 | 196 | style->FontName = strdup("Arial"); |
233 | 196 | style->FontSize = 18; |
234 | 196 | style->PrimaryColour = 0xffffff00; |
235 | 196 | style->SecondaryColour = 0x00ffff00; |
236 | 196 | style->OutlineColour = 0x00000000; |
237 | 196 | style->BackColour = 0x00000080; |
238 | 196 | style->Bold = 200; |
239 | 196 | style->ScaleX = 1.0; |
240 | 196 | style->ScaleY = 1.0; |
241 | 196 | style->Spacing = 0; |
242 | 196 | style->BorderStyle = 1; |
243 | 196 | style->Outline = 2; |
244 | 196 | style->Shadow = 3; |
245 | 196 | style->Alignment = 2; |
246 | 196 | style->MarginL = style->MarginR = style->MarginV = 20; |
247 | 196 | } |
248 | | |
249 | | static long long string2timecode(ASS_Library *library, char *p) |
250 | 11.9k | { |
251 | 11.9k | int32_t h, m, s, ms; |
252 | 11.9k | long long tm; |
253 | 11.9k | int res = sscanf(p, "%" SCNd32 ":%" SCNd32 ":%" SCNd32 ".%" SCNd32, &h, &m, &s, &ms); |
254 | 11.9k | if (res < 4) { |
255 | 7.48k | ass_msg(library, MSGL_WARN, "Bad timestamp"); |
256 | 7.48k | return 0; |
257 | 7.48k | } |
258 | 4.51k | tm = ((h * 60LL + m) * 60 + s) * 1000 + ms * 10LL; |
259 | 4.51k | return tm; |
260 | 11.9k | } |
261 | | |
262 | | static int read_digits(char **str, unsigned base, uint32_t *res) |
263 | 30.3k | { |
264 | 30.3k | char *p = *str; |
265 | 30.3k | char *start = p; |
266 | 30.3k | uint32_t val = 0; |
267 | | |
268 | 33.6k | while (1) { |
269 | 33.6k | unsigned digit; |
270 | 33.6k | if (*p >= '0' && *p < FFMIN(base, 10) + '0') |
271 | 3.24k | digit = *p - '0'; |
272 | 30.3k | else if (*p >= 'a' && *p < base - 10 + 'a') |
273 | 0 | digit = *p - 'a' + 10; |
274 | 30.3k | else if (*p >= 'A' && *p < base - 10 + 'A') |
275 | 0 | digit = *p - 'A' + 10; |
276 | 30.3k | else |
277 | 30.3k | break; |
278 | 3.24k | val = val * base + digit; |
279 | 3.24k | ++p; |
280 | 3.24k | } |
281 | | |
282 | 30.3k | *res = val; |
283 | 30.3k | *str = p; |
284 | 30.3k | return p != start; |
285 | 30.3k | } |
286 | | |
287 | | /** |
288 | | * \brief Convert a string to an integer reduced modulo 2**32 |
289 | | * Follows the rules for strtoul but reduces the number modulo 2**32 |
290 | | * instead of saturating it to 2**32 - 1. |
291 | | */ |
292 | | static int mystrtou32_modulo(char **p, unsigned base, uint32_t *res) |
293 | 30.3k | { |
294 | | // This emulates scanf with %d or %x format as it works on |
295 | | // Windows, because that's what is used by VSFilter. In practice, |
296 | | // scanf works the same way on other platforms too, but |
297 | | // the standard leaves its behavior on overflow undefined. |
298 | | |
299 | | // Unlike scanf and like strtoul, produce 0 for invalid inputs. |
300 | | |
301 | 30.3k | char *start = *p; |
302 | 30.3k | int sign = 1; |
303 | | |
304 | 30.3k | skip_spaces(p); |
305 | | |
306 | 30.3k | if (**p == '+') |
307 | 75 | ++*p; |
308 | 30.2k | else if (**p == '-') |
309 | 198 | sign = -1, ++*p; |
310 | | |
311 | 30.3k | if (base == 16 && !ass_strncasecmp(*p, "0x", 2)) |
312 | 0 | *p += 2; |
313 | | |
314 | 30.3k | if (read_digits(p, base, res)) { |
315 | 1.22k | *res *= sign; |
316 | 1.22k | return 1; |
317 | 29.1k | } else { |
318 | 29.1k | *p = start; |
319 | 29.1k | return 0; |
320 | 29.1k | } |
321 | 30.3k | } |
322 | | |
323 | | static int32_t parse_int_header(char *str) |
324 | 30.3k | { |
325 | 30.3k | uint32_t val = 0; |
326 | 30.3k | unsigned base; |
327 | | |
328 | 30.3k | if (!ass_strncasecmp(str, "&h", 2) || !ass_strncasecmp(str, "0x", 2)) { |
329 | 6 | str += 2; |
330 | 6 | base = 16; |
331 | 6 | } else |
332 | 30.3k | base = 10; |
333 | | |
334 | 30.3k | mystrtou32_modulo(&str, base, &val); |
335 | 30.3k | return val; |
336 | 30.3k | } |
337 | | |
338 | | static uint32_t parse_color_header(char *str) |
339 | 5.63k | { |
340 | 5.63k | uint32_t color = parse_int_header(str); |
341 | 5.63k | return ass_bswap32(color); |
342 | 5.63k | } |
343 | | |
344 | | // Return a boolean value for a string |
345 | | static char parse_bool(char *str) |
346 | 386 | { |
347 | 386 | skip_spaces(&str); |
348 | 386 | return !ass_strncasecmp(str, "yes", 3) || strtol(str, NULL, 10) > 0; |
349 | 386 | } |
350 | | |
351 | | static int parse_ycbcr_matrix(char *str) |
352 | 1.54k | { |
353 | 1.54k | skip_spaces(&str); |
354 | 1.54k | if (*str == '\0') |
355 | 3 | return YCBCR_DEFAULT; |
356 | | |
357 | 1.54k | char *end = str + strlen(str); |
358 | 1.54k | rskip_spaces(&end, str); |
359 | | |
360 | | // Trim a local copy of the input that we know is safe to |
361 | | // modify. The buffer is larger than any valid string + NUL, |
362 | | // so we can simply chop off the rest of the input. |
363 | 1.54k | char buffer[16]; |
364 | 1.54k | size_t n = FFMIN(end - str, sizeof buffer - 1); |
365 | 1.54k | memcpy(buffer, str, n); |
366 | 1.54k | buffer[n] = '\0'; |
367 | | |
368 | 1.54k | if (!ass_strcasecmp(buffer, "none")) |
369 | 128 | return YCBCR_NONE; |
370 | 1.41k | if (!ass_strcasecmp(buffer, "tv.601")) |
371 | 128 | return YCBCR_BT601_TV; |
372 | 1.28k | if (!ass_strcasecmp(buffer, "pc.601")) |
373 | 277 | return YCBCR_BT601_PC; |
374 | 1.00k | if (!ass_strcasecmp(buffer, "tv.709")) |
375 | 128 | return YCBCR_BT709_TV; |
376 | 881 | if (!ass_strcasecmp(buffer, "pc.709")) |
377 | 129 | return YCBCR_BT709_PC; |
378 | 752 | if (!ass_strcasecmp(buffer, "tv.240m")) |
379 | 128 | return YCBCR_SMPTE240M_TV; |
380 | 624 | if (!ass_strcasecmp(buffer, "pc.240m")) |
381 | 128 | return YCBCR_SMPTE240M_PC; |
382 | 496 | if (!ass_strcasecmp(buffer, "tv.fcc")) |
383 | 129 | return YCBCR_FCC_TV; |
384 | 367 | if (!ass_strcasecmp(buffer, "pc.fcc")) |
385 | 130 | return YCBCR_FCC_PC; |
386 | 237 | return YCBCR_UNKNOWN; |
387 | 367 | } |
388 | | |
389 | | #define NEXT(str,token,rtrim) \ |
390 | 179k | token = next_token(&str, rtrim); \ |
391 | 179k | if (!token) break; |
392 | 94.0k | #define NEXTNAME(str,token) NEXT(str, token, true) |
393 | 85.5k | #define NEXTVAL(str,token) NEXT(str, token, false) |
394 | | |
395 | | |
396 | | #define ALIAS(alias,name) \ |
397 | 101k | if (ass_strcasecmp(tname, #alias) == 0) {tname = #name;} |
398 | | |
399 | | /* One section started with PARSE_START and PARSE_END parses a single token |
400 | | * (contained in the variable named token) for the header indicated by the |
401 | | * variable tname. It does so by chaining a number of else-if statements, each |
402 | | * of which checks if the tname variable indicates that this header should be |
403 | | * parsed. The first parameter of the macro gives the name of the header. |
404 | | * |
405 | | * The string that is passed is in str. str is advanced to the next token if |
406 | | * a header could be parsed. The parsed results are stored in the variable |
407 | | * target, which has the type ASS_Style* or ASS_Event*. |
408 | | */ |
409 | 74.8k | #define PARSE_START if (0) { |
410 | 7.27k | #define PARSE_END } |
411 | | |
412 | | #define ANYVAL(name,func) \ |
413 | 332k | } else if (ass_strcasecmp(tname, #name) == 0) { \ |
414 | 51.3k | target->name = func(token); |
415 | | |
416 | | #define STRVAL(name) \ |
417 | 104k | } else if (ass_strcasecmp(tname, #name) == 0) { \ |
418 | 12.5k | char *new_str = strdup(token); \ |
419 | 12.5k | if (new_str) { \ |
420 | 12.5k | free(target->name); \ |
421 | 12.5k | target->name = new_str; \ |
422 | 12.5k | } |
423 | | |
424 | | #define STARREDSTRVAL(name) \ |
425 | 23.8k | } else if (ass_strcasecmp(tname, #name) == 0) { \ |
426 | 1.80k | while (*token == '*') ++token; \ |
427 | 1.80k | char *new_str = strdup(token); \ |
428 | 1.80k | if (new_str) { \ |
429 | 1.80k | free(target->name); \ |
430 | 1.80k | target->name = new_str; \ |
431 | 1.80k | } |
432 | | |
433 | 5.65k | #define COLORVAL(name) ANYVAL(name,parse_color_header) |
434 | 24.8k | #define INTVAL(name) ANYVAL(name,parse_int_header) |
435 | 51.3k | #define FPVAL(name) ANYVAL(name,ass_atof) |
436 | | #define TIMEVAL(name) \ |
437 | 27.9k | } else if (ass_strcasecmp(tname, #name) == 0) { \ |
438 | 11.9k | target->name = string2timecode(track->library, token); |
439 | | |
440 | | #define STYLEVAL(name) \ |
441 | 49.8k | } else if (ass_strcasecmp(tname, #name) == 0) { \ |
442 | 5.79k | target->name = ass_lookup_style(track, token); |
443 | | |
444 | | // skip spaces in str beforehand, or trim leading spaces afterwards |
445 | | static inline void advance_token_pos(const char **const str, |
446 | | const char **const start, |
447 | | const char **const end) |
448 | 168k | { |
449 | 168k | *start = *str; |
450 | 168k | *end = *start; |
451 | 929k | while (**end != '\0' && **end != ',') ++*end; |
452 | 168k | *str = *end + (**end == ','); |
453 | 168k | } |
454 | | |
455 | | static char *next_token(char **str, bool rtrim) |
456 | 179k | { |
457 | 179k | char *p; |
458 | 179k | char *start; |
459 | 179k | skip_spaces(str); |
460 | 179k | if (**str == '\0') { |
461 | 11.4k | return 0; |
462 | 11.4k | } |
463 | | |
464 | 168k | advance_token_pos((const char**)str, |
465 | 168k | (const char**)&start, |
466 | 168k | (const char**)&p); |
467 | | |
468 | 168k | if (rtrim) |
469 | 93.2k | rskip_spaces(&p, start); |
470 | 168k | *p = '\0'; |
471 | 168k | return start; |
472 | 179k | } |
473 | | |
474 | | /** |
475 | | * \brief Parse the tail of Dialogue line |
476 | | * \param track track |
477 | | * \param event parsed data goes here |
478 | | * \param str string to parse, zero-terminated |
479 | | * \param n_ignored number of format options to skip at the beginning |
480 | | */ |
481 | | static int process_event_tail(ASS_Track *track, ASS_Event *event, |
482 | | char *str, int n_ignored) |
483 | 8.68k | { |
484 | 8.68k | char *token; |
485 | 8.68k | char *tname; |
486 | 8.68k | char *p = str; |
487 | 8.68k | int i; |
488 | 8.68k | ASS_Event *target = event; |
489 | | |
490 | 8.68k | char *format = strdup(track->event_format); |
491 | 8.68k | if (!format) |
492 | 0 | return -1; |
493 | 8.68k | char *q = format; // format scanning pointer |
494 | | |
495 | 8.68k | for (i = 0; i < n_ignored; ++i) { |
496 | 0 | NEXTVAL(q, tname); |
497 | 0 | } |
498 | | |
499 | 59.6k | while (1) { |
500 | 59.6k | NEXTNAME(q, tname); |
501 | 59.6k | if (ass_strcasecmp(tname, "Text") == 0) { |
502 | 7.73k | event->Text = strdup(p); |
503 | 7.73k | if (event->Text && *event->Text != 0) { |
504 | 2.94k | char *end = event->Text + strlen(event->Text); |
505 | 3.31k | while (end > event->Text && |
506 | 3.04k | (end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' ')) |
507 | 362 | *--end = 0; |
508 | 2.94k | } |
509 | 7.73k | event->Duration -= event->Start; |
510 | 7.73k | free(format); |
511 | 7.73k | return event->Text ? 0 : -1; // "Text" is always the last |
512 | 7.73k | } |
513 | 51.8k | NEXTVAL(p, token); |
514 | | |
515 | 50.9k | ALIAS(End, Duration) // temporarily store end timecode in event->Duration |
516 | 50.9k | ALIAS(Actor, Name) // both variants are used in files |
517 | 50.9k | PARSE_START |
518 | 1.12k | INTVAL(Layer) |
519 | 5.79k | STYLEVAL(Style) |
520 | 5.79k | STRVAL(Name) |
521 | 5.63k | STRVAL(Effect) |
522 | 5.57k | INTVAL(MarginL) |
523 | 5.57k | INTVAL(MarginR) |
524 | 5.37k | INTVAL(MarginV) |
525 | 6.01k | TIMEVAL(Start) |
526 | 16.9k | TIMEVAL(Duration) |
527 | 16.9k | PARSE_END |
528 | 50.9k | } |
529 | 949 | free(format); |
530 | 949 | return 1; |
531 | 8.68k | } |
532 | | |
533 | | static void set_style_alpha(ASS_Style *style, int32_t front_alpha, int32_t back_alpha) |
534 | 7.12k | { |
535 | 7.12k | front_alpha = FFMAX(FFMIN(front_alpha, 0xFF), 0); |
536 | 7.12k | back_alpha = FFMAX(FFMIN(back_alpha, 0xFF), 0); |
537 | 7.12k | style->PrimaryColour = (style->PrimaryColour & 0xFFFFFF00) | front_alpha; |
538 | 7.12k | style->SecondaryColour = (style->SecondaryColour & 0xFFFFFF00) | front_alpha; |
539 | 7.12k | style->OutlineColour = (style->OutlineColour & 0xFFFFFF00) | front_alpha; |
540 | 7.12k | style->BackColour = (style->BackColour & 0xFFFFFF00) | back_alpha; |
541 | 7.12k | } |
542 | | |
543 | | /** |
544 | | * \brief Parse command line style overrides (--ass-force-style option) |
545 | | * \param track track to apply overrides to |
546 | | * The format for overrides is [StyleName.]Field=Value |
547 | | */ |
548 | | void ass_process_force_style(ASS_Track *track) |
549 | 165 | { |
550 | 165 | char **fs, *eq, *dt, *style, *tname, *token; |
551 | 165 | ASS_Style *target; |
552 | 165 | int sid; |
553 | 165 | char **list = track->library->style_overrides; |
554 | | |
555 | 165 | if (!list) |
556 | 165 | return; |
557 | | |
558 | 0 | for (fs = list; *fs; ++fs) { |
559 | 0 | eq = strrchr(*fs, '='); |
560 | 0 | if (!eq) |
561 | 0 | continue; |
562 | 0 | *eq = '\0'; |
563 | 0 | token = eq + 1; |
564 | |
|
565 | 0 | if (!ass_strcasecmp(*fs, "PlayResX")) |
566 | 0 | track->PlayResX = parse_int_header(token); |
567 | 0 | else if (!ass_strcasecmp(*fs, "PlayResY")) |
568 | 0 | track->PlayResY = parse_int_header(token); |
569 | 0 | else if (!ass_strcasecmp(*fs, "LayoutResX")) |
570 | 0 | track->LayoutResX = parse_int_header(token); |
571 | 0 | else if (!ass_strcasecmp(*fs, "LayoutResY")) |
572 | 0 | track->LayoutResY = parse_int_header(token); |
573 | 0 | else if (!ass_strcasecmp(*fs, "Timer")) |
574 | 0 | track->Timer = ass_atof(token); |
575 | 0 | else if (!ass_strcasecmp(*fs, "WrapStyle")) |
576 | 0 | track->WrapStyle = parse_int_header(token); |
577 | 0 | else if (!ass_strcasecmp(*fs, "ScaledBorderAndShadow")) |
578 | 0 | track->ScaledBorderAndShadow = parse_bool(token); |
579 | 0 | else if (!ass_strcasecmp(*fs, "Kerning")) |
580 | 0 | track->Kerning = parse_bool(token); |
581 | 0 | else if (!ass_strcasecmp(*fs, "YCbCr Matrix")) |
582 | 0 | track->YCbCrMatrix = parse_ycbcr_matrix(token); |
583 | |
|
584 | 0 | dt = strrchr(*fs, '.'); |
585 | 0 | if (dt) { |
586 | 0 | *dt = '\0'; |
587 | 0 | style = *fs; |
588 | 0 | tname = dt + 1; |
589 | 0 | } else { |
590 | 0 | style = NULL; |
591 | 0 | tname = *fs; |
592 | 0 | } |
593 | 0 | for (sid = 0; sid < track->n_styles; ++sid) { |
594 | 0 | if (style == NULL |
595 | 0 | || ass_strcasecmp(track->styles[sid].Name, style) == 0) { |
596 | 0 | target = track->styles + sid; |
597 | 0 | PARSE_START |
598 | 0 | STRVAL(FontName) |
599 | 0 | COLORVAL(PrimaryColour) |
600 | 0 | COLORVAL(SecondaryColour) |
601 | 0 | COLORVAL(OutlineColour) |
602 | 0 | COLORVAL(BackColour) |
603 | 0 | } else if (ass_strcasecmp(tname, "AlphaLevel") == 0) { |
604 | 0 | int32_t alpha = parse_int_header(token); |
605 | 0 | set_style_alpha(target, alpha, alpha); |
606 | 0 | FPVAL(FontSize) |
607 | 0 | INTVAL(Bold) |
608 | 0 | INTVAL(Italic) |
609 | 0 | INTVAL(Underline) |
610 | 0 | INTVAL(StrikeOut) |
611 | 0 | FPVAL(Spacing) |
612 | 0 | FPVAL(Angle) |
613 | 0 | INTVAL(BorderStyle) |
614 | 0 | INTVAL(Alignment) |
615 | 0 | INTVAL(Justify) |
616 | 0 | INTVAL(MarginL) |
617 | 0 | INTVAL(MarginR) |
618 | 0 | INTVAL(MarginV) |
619 | 0 | INTVAL(Encoding) |
620 | 0 | FPVAL(ScaleX) |
621 | 0 | FPVAL(ScaleY) |
622 | 0 | FPVAL(Outline) |
623 | 0 | FPVAL(Shadow) |
624 | 0 | FPVAL(Blur) |
625 | 0 | PARSE_END |
626 | 0 | } |
627 | 0 | } |
628 | 0 | *eq = '='; |
629 | 0 | if (dt) |
630 | 0 | *dt = '.'; |
631 | 0 | } |
632 | 0 | } |
633 | | |
634 | | /** |
635 | | * \brief Parse the Style line |
636 | | * \param track track |
637 | | * \param str string to parse, zero-terminated |
638 | | * Allocates a new style struct. |
639 | | */ |
640 | | static int process_style(ASS_Track *track, char *str) |
641 | 10.5k | { |
642 | 10.5k | char *token; |
643 | 10.5k | char *tname; |
644 | 10.5k | char *p = str; |
645 | 10.5k | char *format; |
646 | 10.5k | char *q; // format scanning pointer |
647 | 10.5k | int sid; |
648 | 10.5k | ASS_Style *style; |
649 | 10.5k | ASS_Style *target; |
650 | | |
651 | 10.5k | if (!track->style_format) { |
652 | | // no style format header |
653 | | // probably an ancient script version |
654 | 52 | if (track->track_type == TRACK_TYPE_SSA) |
655 | 24 | track->style_format = strdup(ssa_style_format); |
656 | 28 | else |
657 | 28 | track->style_format = strdup(ass_style_format); |
658 | 52 | if (!track->style_format) |
659 | 0 | return -1; |
660 | 52 | } |
661 | | |
662 | 10.5k | q = format = strdup(track->style_format); |
663 | 10.5k | if (!q) |
664 | 0 | return -1; |
665 | | |
666 | 10.5k | ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str); |
667 | | |
668 | 10.5k | sid = ass_alloc_style(track); |
669 | 10.5k | if (sid < 0) { |
670 | 0 | free(format); |
671 | 0 | return -1; |
672 | 0 | } |
673 | | |
674 | 10.5k | style = track->styles + sid; |
675 | 10.5k | target = style; |
676 | | |
677 | | // fill style with some default values |
678 | 10.5k | style->ScaleX = 100.; |
679 | 10.5k | style->ScaleY = 100.; |
680 | | |
681 | 10.5k | int32_t ssa_alpha = 0; |
682 | | |
683 | 34.4k | while (1) { |
684 | 34.4k | NEXTNAME(q, tname); |
685 | 33.6k | NEXTVAL(p, token); |
686 | | |
687 | 23.8k | PARSE_START |
688 | 1.80k | STARREDSTRVAL(Name) |
689 | 1.80k | if (!target->Name) |
690 | 0 | goto fail; |
691 | 1.80k | STRVAL(FontName) |
692 | 1.75k | COLORVAL(PrimaryColour) |
693 | 1.74k | COLORVAL(SecondaryColour) |
694 | 1.74k | COLORVAL(OutlineColour) // TertiaryColor |
695 | 1.73k | COLORVAL(BackColour) |
696 | | // SSA uses BackColour for both outline and shadow |
697 | | // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway |
698 | 1.73k | if (track->track_type == TRACK_TYPE_SSA) |
699 | 678 | target->OutlineColour = target->BackColour; |
700 | 14.6k | } else if (ass_strcasecmp(tname, "AlphaLevel") == 0) { |
701 | 74 | ssa_alpha = parse_int_header(token); |
702 | 1.74k | FPVAL(FontSize) |
703 | 1.74k | INTVAL(Bold) |
704 | 1.72k | INTVAL(Italic) |
705 | 1.71k | INTVAL(Underline) |
706 | 416 | INTVAL(StrikeOut) |
707 | 414 | FPVAL(Spacing) |
708 | 393 | FPVAL(Angle) |
709 | 1.43k | INTVAL(BorderStyle) |
710 | 1.43k | INTVAL(Alignment) |
711 | 1.24k | if (track->track_type == TRACK_TYPE_ASS) |
712 | 712 | target->Alignment = numpad2align(target->Alignment); |
713 | | // VSFilter compatibility |
714 | 534 | else if (target->Alignment == 8) |
715 | 141 | target->Alignment = 3; |
716 | 393 | else if (target->Alignment == 4) |
717 | 128 | target->Alignment = 11; |
718 | 1.24k | INTVAL(MarginL) |
719 | 185 | INTVAL(MarginR) |
720 | 52 | INTVAL(MarginV) |
721 | 42 | INTVAL(Encoding) |
722 | 412 | FPVAL(ScaleX) |
723 | 412 | FPVAL(ScaleY) |
724 | 1.42k | FPVAL(Outline) |
725 | 4.12k | FPVAL(Shadow) |
726 | 4.12k | PARSE_END |
727 | 23.8k | } |
728 | | |
729 | 10.5k | free(format); |
730 | 10.5k | format = NULL; |
731 | | |
732 | | // VSF compat: always set BackColour Alpha to 0x80 in SSA |
733 | 10.5k | if (track->track_type == TRACK_TYPE_SSA) |
734 | 7.12k | set_style_alpha(style, ssa_alpha, 0x80); |
735 | 10.5k | style->ScaleX = FFMAX(style->ScaleX, 0.) / 100.; |
736 | 10.5k | style->ScaleY = FFMAX(style->ScaleY, 0.) / 100.; |
737 | 10.5k | style->Spacing = FFMAX(style->Spacing, 0.); |
738 | 10.5k | style->Outline = FFMAX(style->Outline, 0.); |
739 | 10.5k | style->Shadow = FFMAX(style->Shadow, 0.); |
740 | 10.5k | style->Bold = !!style->Bold; |
741 | 10.5k | style->Italic = !!style->Italic; |
742 | 10.5k | style->Underline = !!style->Underline; |
743 | 10.5k | style->StrikeOut = !!style->StrikeOut; |
744 | 10.5k | if (!style->Name || !*style->Name) { |
745 | 10.4k | free(style->Name); |
746 | 10.4k | style->Name = strdup("Default"); |
747 | 10.4k | } |
748 | 10.5k | if (!style->FontName) |
749 | 8.78k | style->FontName = strdup("Arial"); |
750 | 10.5k | if (!style->Name || !style->FontName) |
751 | 0 | goto fail; |
752 | 10.5k | if (strcmp(target->Name, "Default") == 0) |
753 | 10.4k | track->default_style = sid; |
754 | 10.5k | return 0; |
755 | | |
756 | 0 | fail: |
757 | 0 | free(format); |
758 | 0 | ass_free_style(track, sid); |
759 | 0 | track->n_styles--; |
760 | 0 | return -1; |
761 | 10.5k | } |
762 | | |
763 | | static bool format_line_compare(const char *fmt1, const char *fmt2) |
764 | 116 | { |
765 | 116 | #define TOKEN_ALIAS1(token, name, alias) \ |
766 | 120 | if (token ## _end - token ## _start == sizeof( #alias ) - 1 && \ |
767 | 120 | !strncmp(token ## _start, #alias, sizeof( #alias ) - 1)) { \ |
768 | 0 | token ## _start = #name; \ |
769 | 0 | token ## _end = token ## _start + sizeof( #name ) - 1; \ |
770 | 0 | } |
771 | 116 | #define TOKEN_ALIAS(name, alias) TOKEN_ALIAS1(tk1, name, alias) TOKEN_ALIAS1(tk2, name, alias) |
772 | | |
773 | 120 | while (true) { |
774 | 120 | const char *tk1_start, *tk2_start; |
775 | 120 | const char *tk1_end, *tk2_end; |
776 | | |
777 | 120 | skip_spaces((char**)&fmt1); |
778 | 120 | skip_spaces((char**)&fmt2); |
779 | 120 | if (!*fmt1 || !*fmt2) |
780 | 60 | break; |
781 | | |
782 | 60 | advance_token_pos(&fmt1, &tk1_start, &tk1_end); |
783 | 60 | advance_token_pos(&fmt2, &tk2_start, &tk2_end); |
784 | 60 | rskip_spaces((char**)&tk1_end, (char*)tk1_start); |
785 | 60 | rskip_spaces((char**)&tk2_end, (char*)tk2_start); |
786 | | |
787 | 60 | TOKEN_ALIAS(Name, Actor) |
788 | 60 | if ((tk1_end-tk1_start) != (tk2_end-tk2_start)) |
789 | 56 | return false; |
790 | 4 | if (ass_strncasecmp(tk1_start, tk2_start, tk1_end-tk1_start)) |
791 | 0 | return false; |
792 | 4 | } |
793 | 60 | return *fmt1 == *fmt2; |
794 | | |
795 | 116 | #undef TOKEN_ALIAS |
796 | 116 | #undef TOKEN_ALIAS1 |
797 | 116 | } |
798 | | |
799 | | |
800 | | /** |
801 | | * \brief Set SBAS=1 if not set explicitly in case of custom format line |
802 | | * \param track track |
803 | | * \param fmt format line of file |
804 | | * \param std standard format line |
805 | | * |
806 | | * As of writing libass is the only renderer accepting custom format lines. |
807 | | * For years libass defaulted SBAS to yes instead of no. |
808 | | * To avoid breaking released scripts with custom format lines, |
809 | | * keep SBAS=1 default for custom format files. |
810 | | */ |
811 | | static void custom_format_line_compatibility(ASS_Track *const track, |
812 | | const char *const fmt, |
813 | | const char *const std) |
814 | 116 | { |
815 | 116 | if (!(track->parser_priv->header_flags & SINFO_SCALEDBORDER) |
816 | 116 | && !format_line_compare(fmt, std)) { |
817 | 116 | ass_msg(track->library, MSGL_INFO, |
818 | 116 | "Track has custom format line(s). " |
819 | 116 | "'ScaledBorderAndShadow' will default to 'yes'."); |
820 | 116 | track->ScaledBorderAndShadow = 1; |
821 | 116 | } |
822 | 116 | } |
823 | | |
824 | | static int process_styles_line(ASS_Track *track, char *str) |
825 | 12.1k | { |
826 | 12.1k | int ret = 0; |
827 | 12.1k | if (!strncmp(str, "Format:", 7)) { |
828 | 73 | char *p = str + 7; |
829 | 73 | skip_spaces(&p); |
830 | 73 | free(track->style_format); |
831 | 73 | track->style_format = strdup(p); |
832 | 73 | if (!track->style_format) |
833 | 0 | return -1; |
834 | 73 | ass_msg(track->library, MSGL_DBG2, "Style format: %s", |
835 | 73 | track->style_format); |
836 | 73 | if (track->track_type == TRACK_TYPE_ASS) |
837 | 64 | custom_format_line_compatibility(track, p, ass_style_format); |
838 | 9 | else |
839 | 9 | custom_format_line_compatibility(track, p, ssa_style_format); |
840 | 12.0k | } else if (!strncmp(str, "Style:", 6)) { |
841 | 10.5k | char *p = str + 6; |
842 | 10.5k | skip_spaces(&p); |
843 | 10.5k | ret = process_style(track, p); |
844 | 10.5k | } |
845 | 12.1k | return ret; |
846 | 12.1k | } |
847 | | |
848 | | static inline void parse_script_type(ASS_Track *track, const char *str) |
849 | 3 | { |
850 | | // VSF compat: don't check for leading 'v' and |
851 | | // parse value from the last non-space backwards |
852 | 3 | const char *p = str + strlen(str); |
853 | 3 | rskip_spaces((char **) &p, (char *) str); |
854 | 3 | size_t len = p - str; |
855 | 3 | if (len < 4) // rskip_spaces stops _at_ last space |
856 | 3 | return; |
857 | | |
858 | 0 | int ver = TRACK_TYPE_SSA; |
859 | 0 | if (*(p-1) == '+') { |
860 | 0 | ver = TRACK_TYPE_ASS; |
861 | 0 | --len; --p; |
862 | 0 | } |
863 | |
|
864 | 0 | if (len >= 4 && !strncmp(p-4, "4.00", 4)) |
865 | 0 | track->track_type = ver; |
866 | 0 | } |
867 | | |
868 | | static inline void check_duplicate_info_line(const ASS_Track *const track, |
869 | | const ScriptInfo si, |
870 | | const char *const name) |
871 | 1.94k | { |
872 | 1.94k | if (track->parser_priv->header_flags & si) |
873 | 1.91k | ass_msg(track->library, MSGL_WARN, |
874 | 1.91k | "Duplicate Script Info Header '%s'. Previous value overwritten!", |
875 | 1.91k | name); |
876 | 31 | else |
877 | 31 | track->parser_priv->header_flags |= si; |
878 | 1.94k | } |
879 | | |
880 | | static int process_info_line(ASS_Track *track, char *str) |
881 | 2.52k | { |
882 | 2.52k | if (!strncmp(str, "PlayResX:", 9)) { |
883 | 5 | check_duplicate_info_line(track, SINFO_PLAYRESX, "PlayResX"); |
884 | 5 | track->PlayResX = parse_int_header(str + 9); |
885 | 2.51k | } else if (!strncmp(str, "PlayResY:", 9)) { |
886 | 6 | check_duplicate_info_line(track, SINFO_PLAYRESY, "PlayResY"); |
887 | 6 | track->PlayResY = parse_int_header(str + 9); |
888 | 2.51k | } else if (!strncmp(str, "LayoutResX:", 11)) { |
889 | 1 | check_duplicate_info_line(track, SINFO_LAYOUTRESX, "LayoutResX"); |
890 | 1 | track->LayoutResX = parse_int_header(str + 11); |
891 | 2.50k | } else if (!strncmp(str, "LayoutResY:", 11)) { |
892 | 0 | check_duplicate_info_line(track, SINFO_LAYOUTRESY, "LayoutResY"); |
893 | 0 | track->LayoutResY = parse_int_header(str + 11); |
894 | 2.50k | } else if (!strncmp(str, "Timer:", 6)) { |
895 | 1 | check_duplicate_info_line(track, SINFO_TIMER, "Timer"); |
896 | 1 | track->Timer = ass_atof(str + 6); |
897 | 2.50k | } else if (!strncmp(str, "WrapStyle:", 10)) { |
898 | 0 | check_duplicate_info_line(track, SINFO_WRAPSTYLE, "WrapStyle"); |
899 | 0 | track->WrapStyle = parse_int_header(str + 10); |
900 | 2.50k | } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) { |
901 | 385 | check_duplicate_info_line(track, SINFO_SCALEDBORDER, |
902 | 385 | "ScaledBorderAndShadow"); |
903 | 385 | track->ScaledBorderAndShadow = parse_bool(str + 22); |
904 | 2.12k | } else if (!strncmp(str, "Kerning:", 8)) { |
905 | 1 | check_duplicate_info_line(track, SINFO_KERNING, "Kerning"); |
906 | 1 | track->Kerning = parse_bool(str + 8); |
907 | 2.12k | } else if (!strncmp(str, "YCbCr Matrix:", 13)) { |
908 | 1.54k | check_duplicate_info_line(track, SINFO_COLOURMATRIX, "YCbCr Matrix"); |
909 | 1.54k | track->YCbCrMatrix = parse_ycbcr_matrix(str + 13); |
910 | 1.54k | } else if (!strncmp(str, "Language:", 9)) { |
911 | 0 | check_duplicate_info_line(track, SINFO_LANGUAGE, "Language"); |
912 | 0 | char *p = str + 9; |
913 | 0 | while (*p && ass_isspace(*p)) p++; |
914 | 0 | free(track->Language); |
915 | 0 | track->Language = strndup(p, 2); |
916 | 577 | } else if (!strncmp(str, "ScriptType:", 11)) { |
917 | 3 | check_duplicate_info_line(track, SINFO_SCRIPTTYPE, "ScriptType"); |
918 | 3 | parse_script_type(track, str + 11); |
919 | 574 | } else if (!strncmp(str, "; Script generated by ", 22)) { |
920 | 267 | if (!strncmp(str + 22,"FFmpeg/Lavc", 11)) |
921 | 132 | track->parser_priv->header_flags |= GENBY_FFMPEG; |
922 | 267 | } |
923 | 2.52k | return 0; |
924 | 2.52k | } |
925 | | |
926 | | static void event_format_fallback(ASS_Track *track) |
927 | 126 | { |
928 | 126 | track->parser_priv->state = PST_EVENTS; |
929 | 126 | if (track->track_type == TRACK_TYPE_SSA) |
930 | 105 | track->event_format = strdup(ssa_event_format); |
931 | 21 | else |
932 | 21 | track->event_format = strdup(ass_event_format); |
933 | 126 | ass_msg(track->library, MSGL_V, |
934 | 126 | "No event format found, using fallback"); |
935 | 126 | } |
936 | | |
937 | | /** |
938 | | * \brief Return if track is post-signature and pre-SBAS ffmpeg track |
939 | | * \param track track |
940 | | */ |
941 | | static bool detect_legacy_conv_subs(ASS_Track *track) |
942 | 43 | { |
943 | | /* |
944 | | * FFmpeg and libav convert srt subtitles to ass. |
945 | | * In legacy versions, they did not set the 'ScaledBorderAndShadow' header, |
946 | | * but expected it to default to yes (which libass did). |
947 | | * To avoid breaking them, we try to detect these |
948 | | * converted subs by common properties of ffmpeg/libav's converted subs. |
949 | | * Since files with custom format lines (-2014.10.11) default to SBAS=1 |
950 | | * regardless of being ffmpeg generated or not, we are only concerned with |
951 | | * post-signature and pre-SBAS ffmpeg-files (2014.10.11-2020.04.17). |
952 | | * We want to avoid matching modified ffmpeg files though. |
953 | | * |
954 | | * Relevant ffmpeg commits are: |
955 | | * 2c77c90684e24ef16f7e7c4462e011434cee6a98 2010.12.29 |
956 | | * Initial conversion format. |
957 | | * Style "Format:" line is mix of SSA and ASS |
958 | | * Event "Format:" line |
959 | | * "Format: Layer, Start, End, Text\r\n" |
960 | | * Only Header in ScriptInfo is "ScriptType: v4.00+" |
961 | | * 0e7782c08ec77739edb0b98ba5d896b45e98235f 2012.06.15 |
962 | | * Adds 'Style' to Event "Format:" line |
963 | | * 5039aadf68deb9ad6dd0737ea11259fe53d3727b 2014.06.18 |
964 | | * Adds PlayerRes(X|Y) (384x288) |
965 | | * (moved below ScriptType: a few minutes later) |
966 | | * 40b9f28641b696c6bb73ce49dc97c2ce2700cbdb 2014.10.11 14:31:23 +0200 |
967 | | * Regular full ASS Event and Style "Format:" lines |
968 | | * 52b0a0ecaa02e17f7e01bead8c3f215f1cfd48dc 2014.10.11 18:37:43 +0200 <== |
969 | | * Signature comment |
970 | | * 56bc0a6736cdc7edab837ff8f304661fd16de0e4 2015.02.08 |
971 | | * Allow custom PlayRes(X|Y) |
972 | | * a8ba2a2c1294a330a0e79ae7f0d3a203a7599166 2020.04.17 |
973 | | * Set 'ScaledBorderAndShadow: yes' |
974 | | * |
975 | | * libav outputs initial ffmpeg format. (no longer maintained) |
976 | | */ |
977 | | |
978 | | // GENBY_FFMPEG and exact ffmpeg headers required |
979 | 43 | if (track->parser_priv->header_flags |
980 | 43 | != (SINFO_SCRIPTTYPE | SINFO_PLAYRESX | SINFO_PLAYRESY | GENBY_FFMPEG)) |
981 | 19 | return false; |
982 | | |
983 | | // Legacy ffmpeg only ever has one style |
984 | | // Check 2 not 1 because libass also adds a def style |
985 | 24 | if (track->n_styles != 2 |
986 | 4 | || strncmp(track->styles[1].Name, "Default", 7)) |
987 | 20 | return false; |
988 | | |
989 | 4 | return true; |
990 | 24 | } |
991 | | |
992 | | |
993 | | static int process_events_line(ASS_Track *track, char *str) |
994 | 12.8k | { |
995 | 12.8k | if (!strncmp(str, "Format:", 7)) { |
996 | 43 | char *p = str + 7; |
997 | 43 | skip_spaces(&p); |
998 | 43 | free(track->event_format); |
999 | 43 | track->event_format = strdup(p); |
1000 | 43 | if (!track->event_format) |
1001 | 0 | return -1; |
1002 | 43 | ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format); |
1003 | 43 | if (track->track_type == TRACK_TYPE_ASS) |
1004 | 1 | custom_format_line_compatibility(track, p, ass_event_format); |
1005 | 42 | else |
1006 | 42 | custom_format_line_compatibility(track, p, ssa_event_format); |
1007 | | |
1008 | | // Guess if we are dealing with legacy ffmpeg subs and change accordingly |
1009 | | // If file has no event format it was probably not created by ffmpeg/libav |
1010 | 43 | if (detect_legacy_conv_subs(track)) { |
1011 | 4 | track->ScaledBorderAndShadow = 1; |
1012 | 4 | ass_msg(track->library, MSGL_INFO, |
1013 | 4 | "Track treated as legacy ffmpeg sub."); |
1014 | 4 | } |
1015 | 12.7k | } else if (!strncmp(str, "Dialogue:", 9)) { |
1016 | | // This should never be reached for embedded subtitles. |
1017 | | // They have slightly different format and are parsed in ass_process_chunk, |
1018 | | // called directly from demuxer |
1019 | 8.68k | int eid; |
1020 | 8.68k | ASS_Event *event; |
1021 | | |
1022 | | // We can't parse events without event_format |
1023 | 8.68k | if (!track->event_format) { |
1024 | 126 | event_format_fallback(track); |
1025 | 126 | if (!track->event_format) |
1026 | 0 | return -1; |
1027 | 126 | } |
1028 | | |
1029 | 8.68k | str += 9; |
1030 | 8.68k | skip_spaces(&str); |
1031 | | |
1032 | 8.68k | eid = ass_alloc_event(track); |
1033 | 8.68k | if (eid < 0) |
1034 | 0 | return -1; |
1035 | 8.68k | event = track->events + eid; |
1036 | | |
1037 | 8.68k | int ret = process_event_tail(track, event, str, 0); |
1038 | 8.68k | if (!ret) { |
1039 | 7.73k | update_prune_ts(track, event->Start + event->Duration); |
1040 | 7.73k | return 0; |
1041 | 7.73k | } |
1042 | | // If something went wrong, discard the useless Event |
1043 | 949 | ass_free_event(track, eid); |
1044 | 949 | track->n_events--; |
1045 | 949 | return ret; |
1046 | 8.68k | } else if (!strncmp(str, "Comment:", 8)) { |
1047 | | // Ignore Comments |
1048 | 3.95k | } else { |
1049 | 3.95k | ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str); |
1050 | 3.95k | } |
1051 | 4.12k | return 0; |
1052 | 12.8k | } |
1053 | | |
1054 | | static unsigned char *decode_chars(const unsigned char *src, |
1055 | | unsigned char *dst, size_t cnt_in) |
1056 | 3.07k | { |
1057 | 3.07k | uint32_t value = 0; |
1058 | 15.3k | for (size_t i = 0; i < cnt_in; i++) |
1059 | 12.2k | value |= (uint32_t) ((src[i] - 33u) & 63) << 6 * (3 - i); |
1060 | | |
1061 | 3.07k | *dst++ = value >> 16; |
1062 | 3.07k | if (cnt_in >= 3) |
1063 | 3.07k | *dst++ = value >> 8 & 0xff; |
1064 | 3.07k | if (cnt_in >= 4) |
1065 | 3.06k | *dst++ = value & 0xff; |
1066 | 3.07k | return dst; |
1067 | 3.07k | } |
1068 | | |
1069 | | static void reset_embedded_font_parsing(ASS_ParserPriv *parser_priv) |
1070 | 4 | { |
1071 | 4 | free(parser_priv->fontname); |
1072 | 4 | free(parser_priv->fontdata); |
1073 | 4 | parser_priv->fontname = NULL; |
1074 | 4 | parser_priv->fontdata = NULL; |
1075 | 4 | parser_priv->fontdata_size = 0; |
1076 | 4 | parser_priv->fontdata_used = 0; |
1077 | 4 | } |
1078 | | |
1079 | | static int decode_font(ASS_Track *track) |
1080 | 4 | { |
1081 | 4 | unsigned char *p; |
1082 | 4 | unsigned char *q; |
1083 | 4 | size_t i; |
1084 | 4 | size_t size; // original size |
1085 | 4 | size_t dsize; // decoded size |
1086 | 4 | unsigned char *buf = 0; |
1087 | | |
1088 | 4 | ass_msg(track->library, MSGL_V, "Font: %zu bytes encoded data", |
1089 | 4 | track->parser_priv->fontdata_used); |
1090 | 4 | size = track->parser_priv->fontdata_used; |
1091 | 4 | if (size % 4 == 1) { |
1092 | 1 | ass_msg(track->library, MSGL_ERR, "Bad encoded data size"); |
1093 | 1 | goto error_decode_font; |
1094 | 1 | } |
1095 | 3 | buf = malloc(size / 4 * 3 + FFMAX(size % 4, 1) - 1); |
1096 | 3 | if (!buf) |
1097 | 0 | goto error_decode_font; |
1098 | 3 | q = buf; |
1099 | 3 | for (i = 0, p = (unsigned char *) track->parser_priv->fontdata; |
1100 | 3.07k | i < size / 4; i++, p += 4) { |
1101 | 3.06k | q = decode_chars(p, q, 4); |
1102 | 3.06k | } |
1103 | 3 | if (size % 4 == 2) { |
1104 | 0 | q = decode_chars(p, q, 2); |
1105 | 3 | } else if (size % 4 == 3) { |
1106 | 2 | q = decode_chars(p, q, 3); |
1107 | 2 | } |
1108 | 3 | dsize = q - buf; |
1109 | 3 | assert(dsize == size / 4 * 3 + FFMAX(size % 4, 1) - 1); |
1110 | | |
1111 | 3 | if (track->library->extract_fonts) { |
1112 | 0 | ass_add_font(track->library, track->parser_priv->fontname, |
1113 | 0 | (char *) buf, dsize); |
1114 | 0 | } |
1115 | | |
1116 | 4 | error_decode_font: |
1117 | 4 | free(buf); |
1118 | 4 | reset_embedded_font_parsing(track->parser_priv); |
1119 | 4 | return 0; |
1120 | 3 | } |
1121 | | |
1122 | | static int process_fonts_line(ASS_Track *track, char *str) |
1123 | 12 | { |
1124 | 12 | size_t len; |
1125 | | |
1126 | 12 | if (!strncmp(str, "fontname:", 9)) { |
1127 | 4 | char *p = str + 9; |
1128 | 4 | skip_spaces(&p); |
1129 | 4 | if (track->parser_priv->fontname) { |
1130 | 0 | decode_font(track); |
1131 | 0 | } |
1132 | 4 | track->parser_priv->fontname = strdup(p); |
1133 | 4 | if (!track->parser_priv->fontname) |
1134 | 0 | return -1; |
1135 | 4 | ass_msg(track->library, MSGL_V, "Fontname: %s", |
1136 | 4 | track->parser_priv->fontname); |
1137 | 4 | return 0; |
1138 | 4 | } |
1139 | | |
1140 | 8 | if (!track->parser_priv->fontname) { |
1141 | 1 | ass_msg(track->library, MSGL_V, "Not understood: '%s'", str); |
1142 | 1 | return 1; |
1143 | 1 | } |
1144 | | |
1145 | 7 | len = strlen(str); |
1146 | 7 | if (track->parser_priv->fontdata_used >= |
1147 | 7 | SIZE_MAX - FFMAX(len, 100 * 1024)) { |
1148 | 0 | goto mem_fail; |
1149 | 7 | } else if (track->parser_priv->fontdata_used + len > |
1150 | 7 | track->parser_priv->fontdata_size) { |
1151 | 4 | size_t new_size = |
1152 | 4 | track->parser_priv->fontdata_size + FFMAX(len, 100 * 1024); |
1153 | 4 | if (!ASS_REALLOC_ARRAY(track->parser_priv->fontdata, new_size)) |
1154 | 0 | goto mem_fail; |
1155 | 4 | track->parser_priv->fontdata_size = new_size; |
1156 | 4 | } |
1157 | 7 | if (!track->parser_priv->fontdata) |
1158 | 0 | return 0; |
1159 | 7 | memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, |
1160 | 7 | str, len); |
1161 | 7 | track->parser_priv->fontdata_used += len; |
1162 | | |
1163 | 7 | return 0; |
1164 | | |
1165 | 0 | mem_fail: |
1166 | 0 | reset_embedded_font_parsing(track->parser_priv); |
1167 | 0 | return -1; |
1168 | 7 | } |
1169 | | |
1170 | | /** |
1171 | | * \brief Parse a header line |
1172 | | * \param track track |
1173 | | * \param str string to parse, zero-terminated |
1174 | | */ |
1175 | | static int process_line(ASS_Track *track, char *str) |
1176 | 28.4k | { |
1177 | 28.4k | skip_spaces(&str); |
1178 | 28.4k | if (!ass_strncasecmp(str, "[Script Info]", 13)) { |
1179 | 37 | track->parser_priv->state = PST_INFO; |
1180 | 28.4k | } else if (!ass_strncasecmp(str, "[V4 Styles]", 11)) { |
1181 | 161 | track->parser_priv->state = PST_STYLES; |
1182 | 161 | track->track_type = TRACK_TYPE_SSA; |
1183 | 28.2k | } else if (!ass_strncasecmp(str, "[V4+ Styles]", 12)) { |
1184 | 59 | track->parser_priv->state = PST_STYLES; |
1185 | 59 | track->track_type = TRACK_TYPE_ASS; |
1186 | 28.2k | } else if (!ass_strncasecmp(str, "[Events]", 8)) { |
1187 | 607 | track->parser_priv->state = PST_EVENTS; |
1188 | 27.6k | } else if (!ass_strncasecmp(str, "[Fonts]", 7)) { |
1189 | 5 | track->parser_priv->state = PST_FONTS; |
1190 | 27.6k | } else { |
1191 | 27.6k | switch (track->parser_priv->state) { |
1192 | 2.52k | case PST_INFO: |
1193 | 2.52k | process_info_line(track, str); |
1194 | 2.52k | break; |
1195 | 12.1k | case PST_STYLES: |
1196 | 12.1k | process_styles_line(track, str); |
1197 | 12.1k | break; |
1198 | 12.8k | case PST_EVENTS: |
1199 | 12.8k | process_events_line(track, str); |
1200 | 12.8k | break; |
1201 | 12 | case PST_FONTS: |
1202 | 12 | process_fonts_line(track, str); |
1203 | 12 | break; |
1204 | 126 | default: |
1205 | 126 | break; |
1206 | 27.6k | } |
1207 | 27.6k | } |
1208 | 28.4k | return 0; |
1209 | 28.4k | } |
1210 | | |
1211 | | static int process_text(ASS_Track *track, char *str) |
1212 | 196 | { |
1213 | 196 | char *p = str; |
1214 | 28.4k | while (1) { |
1215 | 28.4k | char *q; |
1216 | 28.6k | while (1) { |
1217 | 28.6k | if ((*p == '\r') || (*p == '\n')) |
1218 | 166 | ++p; |
1219 | 28.4k | else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf') |
1220 | 0 | p += 3; // U+FFFE (BOM) |
1221 | 28.4k | else |
1222 | 28.4k | break; |
1223 | 28.6k | } |
1224 | 770k | for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) { |
1225 | 741k | }; |
1226 | 28.4k | if (q == p) |
1227 | 0 | break; |
1228 | 28.4k | if (*q != '\0') |
1229 | 28.3k | *(q++) = '\0'; |
1230 | 28.4k | process_line(track, p); |
1231 | 28.4k | if (*q == '\0') |
1232 | 196 | break; |
1233 | 28.2k | p = q; |
1234 | 28.2k | } |
1235 | | // there is no explicit end-of-font marker in ssa/ass |
1236 | 196 | if (track->parser_priv->fontname) |
1237 | 4 | decode_font(track); |
1238 | 196 | return 0; |
1239 | 196 | } |
1240 | | |
1241 | | /** |
1242 | | * \brief Process a chunk of subtitle stream data. |
1243 | | * \param track track |
1244 | | * \param data string to parse |
1245 | | * \param size length of data |
1246 | | */ |
1247 | | void ass_process_data(ASS_Track *track, const char *data, int size) |
1248 | 0 | { |
1249 | 0 | char *str = malloc(size + 1); |
1250 | 0 | if (!str) |
1251 | 0 | return; |
1252 | | |
1253 | 0 | memcpy(str, data, size); |
1254 | 0 | str[size] = '\0'; |
1255 | |
|
1256 | 0 | ass_msg(track->library, MSGL_V, "Event: %s", str); |
1257 | 0 | process_text(track, str); |
1258 | 0 | free(str); |
1259 | 0 | } |
1260 | | |
1261 | | /** |
1262 | | * \brief Process CodecPrivate section of subtitle stream |
1263 | | * \param track track |
1264 | | * \param data string to parse |
1265 | | * \param size length of data |
1266 | | CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections |
1267 | | */ |
1268 | | void ass_process_codec_private(ASS_Track *track, const char *data, int size) |
1269 | 0 | { |
1270 | 0 | ass_process_data(track, data, size); |
1271 | | |
1272 | | // probably an mkv produced by ancient mkvtoolnix |
1273 | | // such files don't have [Events] and Format: headers |
1274 | 0 | if (!track->event_format) |
1275 | 0 | event_format_fallback(track); |
1276 | |
|
1277 | 0 | ass_process_force_style(track); |
1278 | 0 | } |
1279 | | |
1280 | | static int check_duplicate_event(ASS_Track *track, int ReadOrder) |
1281 | 0 | { |
1282 | 0 | if (track->parser_priv->read_order_bitmap) |
1283 | 0 | return test_and_set_read_order_bit(track, ReadOrder) > 0; |
1284 | | // ignoring last event, it is the one we are comparing with |
1285 | 0 | for (int i = 0; i < track->n_events - 1; i++) |
1286 | 0 | if (track->events[i].ReadOrder == ReadOrder) |
1287 | 0 | return 1; |
1288 | 0 | return 0; |
1289 | 0 | } |
1290 | | |
1291 | | void ass_set_check_readorder(ASS_Track *track, int check_readorder) |
1292 | 0 | { |
1293 | 0 | track->parser_priv->check_readorder = check_readorder == 1; |
1294 | 0 | } |
1295 | | |
1296 | | /** |
1297 | | * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary). |
1298 | | * \param track track |
1299 | | * \param data string to parse |
1300 | | * \param size length of data |
1301 | | * \param timecode starting time of the event (milliseconds) |
1302 | | * \param duration duration of the event (milliseconds) |
1303 | | */ |
1304 | | void ass_process_chunk(ASS_Track *track, const char *data, int size, |
1305 | | long long timecode, long long duration) |
1306 | 0 | { |
1307 | 0 | char *str = NULL; |
1308 | 0 | int eid; |
1309 | 0 | char *p; |
1310 | 0 | char *token; |
1311 | 0 | ASS_Event *event; |
1312 | 0 | int check_readorder = track->parser_priv->check_readorder; |
1313 | |
|
1314 | 0 | if (check_readorder && !track->parser_priv->read_order_bitmap) { |
1315 | 0 | for (int i = 0; i < track->n_events; i++) { |
1316 | 0 | if (test_and_set_read_order_bit(track, track->events[i].ReadOrder) < 0) |
1317 | 0 | break; |
1318 | 0 | } |
1319 | 0 | } |
1320 | |
|
1321 | 0 | if (!track->event_format) { |
1322 | 0 | ass_msg(track->library, MSGL_WARN, "Event format header missing"); |
1323 | 0 | goto cleanup; |
1324 | 0 | } |
1325 | | |
1326 | 0 | str = malloc(size + 1); |
1327 | 0 | if (!str) |
1328 | 0 | goto cleanup; |
1329 | 0 | memcpy(str, data, size); |
1330 | 0 | str[size] = '\0'; |
1331 | 0 | ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s", |
1332 | 0 | (int64_t) timecode, (int64_t) duration, str); |
1333 | |
|
1334 | 0 | eid = ass_alloc_event(track); |
1335 | 0 | if (eid < 0) |
1336 | 0 | goto cleanup; |
1337 | 0 | event = track->events + eid; |
1338 | |
|
1339 | 0 | p = str; |
1340 | |
|
1341 | 0 | do { |
1342 | 0 | NEXTVAL(p, token); |
1343 | 0 | event->ReadOrder = atoi(token); |
1344 | 0 | if (check_readorder && check_duplicate_event(track, event->ReadOrder)) |
1345 | 0 | break; |
1346 | | |
1347 | 0 | NEXTVAL(p, token); |
1348 | 0 | event->Layer = parse_int_header(token); |
1349 | |
|
1350 | 0 | if (process_event_tail(track, event, p, 3)) |
1351 | 0 | break; |
1352 | | |
1353 | 0 | event->Start = timecode; |
1354 | 0 | event->Duration = duration; |
1355 | 0 | update_prune_ts(track, event->Start + event->Duration); |
1356 | 0 | goto cleanup; |
1357 | | // dump_events(tid); |
1358 | 0 | } while (0); |
1359 | | // some error |
1360 | 0 | ass_free_event(track, eid); |
1361 | 0 | track->n_events--; |
1362 | |
|
1363 | 0 | cleanup: |
1364 | 0 | free(str); |
1365 | 0 | } |
1366 | | |
1367 | | /** |
1368 | | * \brief Flush buffered events. |
1369 | | * \param track track |
1370 | | */ |
1371 | | void ass_flush_events(ASS_Track *track) |
1372 | 0 | { |
1373 | 0 | if (track->events) { |
1374 | 0 | int eid; |
1375 | 0 | for (eid = 0; eid < track->n_events; eid++) |
1376 | 0 | ass_free_event(track, eid); |
1377 | 0 | track->n_events = 0; |
1378 | 0 | } |
1379 | 0 | free(track->parser_priv->read_order_bitmap); |
1380 | 0 | track->parser_priv->read_order_bitmap = NULL; |
1381 | 0 | track->parser_priv->read_order_elems = 0; |
1382 | 0 | } |
1383 | | |
1384 | | void ass_configure_prune(ASS_Track *track, long long delay) |
1385 | 0 | { |
1386 | 0 | track->parser_priv->prune_delay = delay; |
1387 | 0 | } |
1388 | | |
1389 | | void ass_prune_events(ASS_Track *track, long long deadline) |
1390 | 0 | { |
1391 | 0 | if (deadline < track->parser_priv->prune_next_ts) |
1392 | 0 | return; |
1393 | | |
1394 | 0 | const bool check_readorder = track->parser_priv->check_readorder; |
1395 | 0 | const int old_n_events = track->n_events; |
1396 | |
|
1397 | 0 | int n_kept = 0; |
1398 | 0 | ASS_Event *events = track->events; |
1399 | |
|
1400 | 0 | track->parser_priv->prune_next_ts = LLONG_MAX; |
1401 | 0 | for (int k = 0; k < old_n_events;) { |
1402 | | // discardable sequence |
1403 | 0 | for (; k < old_n_events && events[k].Start + events[k].Duration < deadline; k++) { |
1404 | 0 | if (check_readorder) |
1405 | 0 | clear_read_order_bit(track, events[k].ReadOrder); |
1406 | 0 | ass_free_event(track, k); |
1407 | 0 | } |
1408 | | |
1409 | | // to-be-kept sequence |
1410 | 0 | int move_from = k; |
1411 | 0 | for (long long ts; k < old_n_events && (ts = events[k].Start + events[k].Duration) >= deadline; k++) |
1412 | 0 | update_prune_ts(track, ts); |
1413 | | |
1414 | | // Relocate kept events |
1415 | 0 | if (move_from < k) { |
1416 | 0 | int cnt = k - move_from; |
1417 | 0 | memmove(events + n_kept, events + move_from, cnt * sizeof(*track->events)); |
1418 | 0 | n_kept += cnt; |
1419 | 0 | } |
1420 | 0 | } |
1421 | 0 | track->n_events = n_kept; |
1422 | 0 | } |
1423 | | |
1424 | | #ifdef CONFIG_ICONV |
1425 | | /** \brief recode buffer to utf-8 |
1426 | | * constraint: codepage != 0 |
1427 | | * \param data pointer to text buffer |
1428 | | * \param size buffer size |
1429 | | * \return a pointer to recoded buffer, caller is responsible for freeing it |
1430 | | **/ |
1431 | | static char *sub_recode(ASS_Library *library, char *data, size_t size, |
1432 | | const char *codepage) |
1433 | 0 | { |
1434 | 0 | iconv_t icdsc; |
1435 | 0 | char *tocp = "UTF-8"; |
1436 | 0 | char *outbuf; |
1437 | 0 | assert(codepage); |
1438 | |
|
1439 | 0 | if ((icdsc = iconv_open(tocp, codepage)) != (iconv_t) (-1)) { |
1440 | 0 | ass_msg(library, MSGL_V, "Opened iconv descriptor"); |
1441 | 0 | } else { |
1442 | 0 | ass_msg(library, MSGL_ERR, "Error opening iconv descriptor"); |
1443 | 0 | return NULL; |
1444 | 0 | } |
1445 | | |
1446 | 0 | { |
1447 | 0 | size_t osize = size; |
1448 | 0 | size_t ileft = size; |
1449 | 0 | size_t oleft = size - 1; |
1450 | 0 | char *ip; |
1451 | 0 | char *op; |
1452 | 0 | size_t rc; |
1453 | 0 | int clear = 0; |
1454 | |
|
1455 | 0 | outbuf = malloc(osize); |
1456 | 0 | if (!outbuf) |
1457 | 0 | goto out; |
1458 | 0 | ip = data; |
1459 | 0 | op = outbuf; |
1460 | |
|
1461 | 0 | while (1) { |
1462 | 0 | if (ileft) |
1463 | 0 | rc = iconv(icdsc, &ip, &ileft, &op, &oleft); |
1464 | 0 | else { // clear the conversion state and leave |
1465 | 0 | clear = 1; |
1466 | 0 | rc = iconv(icdsc, NULL, NULL, &op, &oleft); |
1467 | 0 | } |
1468 | 0 | if (rc == (size_t) (-1)) { |
1469 | 0 | if (errno == E2BIG) { |
1470 | 0 | size_t offset = op - outbuf; |
1471 | 0 | char *nbuf = realloc(outbuf, osize + size); |
1472 | 0 | if (!nbuf) { |
1473 | 0 | free(outbuf); |
1474 | 0 | outbuf = 0; |
1475 | 0 | goto out; |
1476 | 0 | } |
1477 | 0 | outbuf = nbuf; |
1478 | 0 | op = outbuf + offset; |
1479 | 0 | osize += size; |
1480 | 0 | oleft += size; |
1481 | 0 | } else { |
1482 | 0 | ass_msg(library, MSGL_WARN, "Error recoding file"); |
1483 | 0 | free(outbuf); |
1484 | 0 | outbuf = NULL; |
1485 | 0 | goto out; |
1486 | 0 | } |
1487 | 0 | } else if (clear) |
1488 | 0 | break; |
1489 | 0 | } |
1490 | 0 | outbuf[osize - oleft - 1] = 0; |
1491 | 0 | } |
1492 | | |
1493 | 0 | out: |
1494 | 0 | if (icdsc != (iconv_t) (-1)) { |
1495 | 0 | (void) iconv_close(icdsc); |
1496 | 0 | ass_msg(library, MSGL_V, "Closed iconv descriptor"); |
1497 | 0 | } |
1498 | |
|
1499 | 0 | return outbuf; |
1500 | 0 | } |
1501 | | #endif // ICONV |
1502 | | |
1503 | | /** |
1504 | | * \brief read file contents into newly allocated buffer |
1505 | | * \param fname file name |
1506 | | * \param hint file name origin |
1507 | | * \param bufsize out: file size |
1508 | | * \return pointer to file contents. Caller is responsible for its deallocation. |
1509 | | */ |
1510 | | char *ass_load_file(ASS_Library *library, const char *fname, FileNameSource hint, size_t *bufsize) |
1511 | 0 | { |
1512 | 0 | int res; |
1513 | 0 | long sz; |
1514 | 0 | long bytes_read; |
1515 | 0 | char *buf; |
1516 | |
|
1517 | 0 | FILE *fp = ass_open_file(fname, hint); |
1518 | 0 | if (!fp) { |
1519 | 0 | ass_msg(library, MSGL_WARN, |
1520 | 0 | "ass_read_file(%s): fopen failed", fname); |
1521 | 0 | return 0; |
1522 | 0 | } |
1523 | 0 | res = fseek(fp, 0, SEEK_END); |
1524 | 0 | if (res == -1) { |
1525 | 0 | ass_msg(library, MSGL_WARN, |
1526 | 0 | "ass_read_file(%s): fseek failed", fname); |
1527 | 0 | fclose(fp); |
1528 | 0 | return 0; |
1529 | 0 | } |
1530 | | |
1531 | 0 | sz = ftell(fp); |
1532 | 0 | rewind(fp); |
1533 | |
|
1534 | 0 | ass_msg(library, MSGL_V, "File size: %ld", sz); |
1535 | |
|
1536 | 0 | buf = sz < SIZE_MAX ? malloc(sz + 1) : NULL; |
1537 | 0 | if (!buf) { |
1538 | 0 | fclose(fp); |
1539 | 0 | return NULL; |
1540 | 0 | } |
1541 | 0 | assert(buf); |
1542 | 0 | bytes_read = 0; |
1543 | 0 | do { |
1544 | 0 | res = fread(buf + bytes_read, 1, sz - bytes_read, fp); |
1545 | 0 | if (res <= 0) { |
1546 | 0 | ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno, |
1547 | 0 | strerror(errno)); |
1548 | 0 | fclose(fp); |
1549 | 0 | free(buf); |
1550 | 0 | return 0; |
1551 | 0 | } |
1552 | 0 | bytes_read += res; |
1553 | 0 | } while (sz - bytes_read > 0); |
1554 | 0 | buf[sz] = '\0'; |
1555 | 0 | fclose(fp); |
1556 | |
|
1557 | 0 | if (bufsize) |
1558 | 0 | *bufsize = sz; |
1559 | 0 | return buf; |
1560 | 0 | } |
1561 | | |
1562 | | /* |
1563 | | * \param buf pointer to subtitle text in utf-8 |
1564 | | */ |
1565 | | static ASS_Track *parse_memory(ASS_Library *library, char *buf) |
1566 | 196 | { |
1567 | 196 | ASS_Track *track; |
1568 | 196 | int i; |
1569 | | |
1570 | 196 | track = ass_new_track(library); |
1571 | 196 | if (!track) |
1572 | 0 | return NULL; |
1573 | | |
1574 | | // process header |
1575 | 196 | process_text(track, buf); |
1576 | | |
1577 | | // external SSA/ASS subs does not have ReadOrder field |
1578 | 7.93k | for (i = 0; i < track->n_events; ++i) |
1579 | 7.73k | track->events[i].ReadOrder = i; |
1580 | | |
1581 | 196 | if (track->track_type == TRACK_TYPE_UNKNOWN) { |
1582 | 31 | ass_free_track(track); |
1583 | 31 | return 0; |
1584 | 31 | } |
1585 | | |
1586 | 165 | ass_process_force_style(track); |
1587 | | |
1588 | 165 | return track; |
1589 | 196 | } |
1590 | | |
1591 | | /** |
1592 | | * \brief Read subtitles from memory. |
1593 | | * \param library libass library object |
1594 | | * \param buf pointer to subtitles text |
1595 | | * \param bufsize size of buffer |
1596 | | * \param codepage recode buffer contents from given codepage |
1597 | | * \return newly allocated track |
1598 | | */ |
1599 | | ASS_Track *ass_read_memory(ASS_Library *library, char *buf, |
1600 | | size_t bufsize, const char *codepage) |
1601 | 196 | { |
1602 | 196 | ASS_Track *track; |
1603 | 196 | int copied = 0; |
1604 | | |
1605 | 196 | if (!buf) |
1606 | 0 | return 0; |
1607 | | |
1608 | 196 | #ifdef CONFIG_ICONV |
1609 | 196 | if (codepage) { |
1610 | 0 | buf = sub_recode(library, buf, bufsize, codepage); |
1611 | 0 | if (!buf) |
1612 | 0 | return 0; |
1613 | 0 | else |
1614 | 0 | copied = 1; |
1615 | 0 | } |
1616 | 196 | #endif |
1617 | 196 | if (!copied) { |
1618 | 196 | char *newbuf = malloc(bufsize + 1); |
1619 | 196 | if (!newbuf) |
1620 | 0 | return 0; |
1621 | 196 | memcpy(newbuf, buf, bufsize); |
1622 | 196 | newbuf[bufsize] = '\0'; |
1623 | 196 | buf = newbuf; |
1624 | 196 | } |
1625 | 196 | track = parse_memory(library, buf); |
1626 | 196 | free(buf); |
1627 | 196 | if (!track) |
1628 | 31 | return 0; |
1629 | | |
1630 | 165 | ass_msg(library, MSGL_INFO, "Added subtitle file: " |
1631 | 165 | "<memory> (%d styles, %d events)", |
1632 | 165 | track->n_styles, track->n_events); |
1633 | 165 | return track; |
1634 | 196 | } |
1635 | | |
1636 | | static char *read_file_recode(ASS_Library *library, const char *fname, |
1637 | | const char *codepage, size_t *size) |
1638 | 0 | { |
1639 | 0 | char *buf; |
1640 | 0 | size_t bufsize; |
1641 | |
|
1642 | 0 | buf = ass_load_file(library, fname, FN_EXTERNAL, &bufsize); |
1643 | 0 | if (!buf) |
1644 | 0 | return 0; |
1645 | 0 | #ifdef CONFIG_ICONV |
1646 | 0 | if (codepage) { |
1647 | 0 | char *tmpbuf = sub_recode(library, buf, bufsize, codepage); |
1648 | 0 | free(buf); |
1649 | 0 | buf = tmpbuf; |
1650 | 0 | } |
1651 | 0 | if (!buf) |
1652 | 0 | return 0; |
1653 | 0 | #endif |
1654 | 0 | *size = bufsize; |
1655 | 0 | return buf; |
1656 | 0 | } |
1657 | | |
1658 | | /** |
1659 | | * \brief Read subtitles from file. |
1660 | | * \param library libass library object |
1661 | | * \param fname file name |
1662 | | * \param codepage recode buffer contents from given codepage |
1663 | | * \return newly allocated track |
1664 | | */ |
1665 | | ASS_Track *ass_read_file(ASS_Library *library, const char *fname, |
1666 | | const char *codepage) |
1667 | 0 | { |
1668 | 0 | char *buf; |
1669 | 0 | ASS_Track *track; |
1670 | 0 | size_t bufsize; |
1671 | |
|
1672 | 0 | buf = read_file_recode(library, fname, codepage, &bufsize); |
1673 | 0 | if (!buf) |
1674 | 0 | return 0; |
1675 | 0 | track = parse_memory(library, buf); |
1676 | 0 | free(buf); |
1677 | 0 | if (!track) |
1678 | 0 | return 0; |
1679 | | |
1680 | 0 | track->name = strdup(fname); |
1681 | |
|
1682 | 0 | ass_msg(library, MSGL_INFO, |
1683 | 0 | "Added subtitle file: '%s' (%d styles, %d events)", |
1684 | 0 | fname, track->n_styles, track->n_events); |
1685 | |
|
1686 | 0 | return track; |
1687 | 0 | } |
1688 | | |
1689 | | /** |
1690 | | * \brief read styles from file into already initialized track |
1691 | | */ |
1692 | | int ass_read_styles(ASS_Track *track, const char *fname, const char *codepage) |
1693 | 0 | { |
1694 | 0 | char *buf; |
1695 | 0 | ParserState old_state; |
1696 | 0 | size_t sz; |
1697 | |
|
1698 | 0 | buf = ass_load_file(track->library, fname, FN_EXTERNAL, &sz); |
1699 | 0 | if (!buf) |
1700 | 0 | return 1; |
1701 | 0 | #ifdef CONFIG_ICONV |
1702 | 0 | if (codepage) { |
1703 | 0 | char *tmpbuf; |
1704 | 0 | tmpbuf = sub_recode(track->library, buf, sz, codepage); |
1705 | 0 | free(buf); |
1706 | 0 | buf = tmpbuf; |
1707 | 0 | } |
1708 | 0 | if (!buf) |
1709 | 0 | return 1; |
1710 | 0 | #endif |
1711 | | |
1712 | 0 | old_state = track->parser_priv->state; |
1713 | 0 | track->parser_priv->state = PST_STYLES; |
1714 | 0 | process_text(track, buf); |
1715 | 0 | free(buf); |
1716 | 0 | track->parser_priv->state = old_state; |
1717 | |
|
1718 | 0 | return 0; |
1719 | 0 | } |
1720 | | |
1721 | | long long ass_step_sub(ASS_Track *track, long long now, int movement) |
1722 | 0 | { |
1723 | 0 | int i; |
1724 | 0 | ASS_Event *best = NULL; |
1725 | 0 | long long target = now; |
1726 | 0 | int direction = (movement > 0 ? 1 : -1) * !!movement; |
1727 | |
|
1728 | 0 | if (track->n_events == 0) |
1729 | 0 | return 0; |
1730 | | |
1731 | 0 | do { |
1732 | 0 | ASS_Event *closest = NULL; |
1733 | 0 | long long closest_time = now; |
1734 | 0 | for (i = 0; i < track->n_events; i++) { |
1735 | 0 | if (direction < 0) { |
1736 | 0 | long long end = |
1737 | 0 | track->events[i].Start + track->events[i].Duration; |
1738 | 0 | if (end < target) { |
1739 | 0 | if (!closest || end > closest_time) { |
1740 | 0 | closest = &track->events[i]; |
1741 | 0 | closest_time = end; |
1742 | 0 | } |
1743 | 0 | } |
1744 | 0 | } else if (direction > 0) { |
1745 | 0 | long long start = track->events[i].Start; |
1746 | 0 | if (start > target) { |
1747 | 0 | if (!closest || start < closest_time) { |
1748 | 0 | closest = &track->events[i]; |
1749 | 0 | closest_time = start; |
1750 | 0 | } |
1751 | 0 | } |
1752 | 0 | } else { |
1753 | 0 | long long start = track->events[i].Start; |
1754 | 0 | if (start < target) { |
1755 | 0 | if (!closest || start >= closest_time) { |
1756 | 0 | closest = &track->events[i]; |
1757 | 0 | closest_time = start; |
1758 | 0 | } |
1759 | 0 | } |
1760 | 0 | } |
1761 | 0 | } |
1762 | 0 | target = closest_time + direction; |
1763 | 0 | movement -= direction; |
1764 | 0 | if (closest) |
1765 | 0 | best = closest; |
1766 | 0 | } while (movement); |
1767 | |
|
1768 | 0 | return best ? best->Start - now : 0; |
1769 | 0 | } |
1770 | | |
1771 | | ASS_Track *ass_new_track(ASS_Library *library) |
1772 | 196 | { |
1773 | 196 | int def_sid = -1; |
1774 | 196 | ASS_Track *track = calloc(1, sizeof(ASS_Track)); |
1775 | 196 | if (!track) |
1776 | 0 | goto fail; |
1777 | 196 | track->library = library; |
1778 | 196 | track->ScaledBorderAndShadow = 0; |
1779 | 196 | track->parser_priv = calloc(1, sizeof(ASS_ParserPriv)); |
1780 | 196 | if (!track->parser_priv) |
1781 | 0 | goto fail; |
1782 | 196 | def_sid = ass_alloc_style(track); |
1783 | 196 | if (def_sid < 0) |
1784 | 0 | goto fail; |
1785 | 196 | set_default_style(track->styles + def_sid); |
1786 | 196 | track->default_style = def_sid; |
1787 | 196 | if (!track->styles[def_sid].Name || !track->styles[def_sid].FontName) |
1788 | 0 | goto fail; |
1789 | 196 | track->parser_priv->check_readorder = 1; |
1790 | 196 | track->parser_priv->prune_delay = -1; |
1791 | 196 | track->parser_priv->prune_next_ts = LLONG_MAX; |
1792 | 196 | return track; |
1793 | | |
1794 | 0 | fail: |
1795 | 0 | if (track) { |
1796 | 0 | if (def_sid >= 0) { |
1797 | 0 | ass_free_style(track, def_sid); |
1798 | 0 | free(track->styles); |
1799 | 0 | } |
1800 | 0 | free(track->parser_priv); |
1801 | 0 | free(track); |
1802 | 0 | } |
1803 | 0 | return NULL; |
1804 | 196 | } |
1805 | | |
1806 | | int ass_track_set_feature(ASS_Track *track, ASS_Feature feature, int enable) |
1807 | 0 | { |
1808 | 0 | if (feature >= sizeof(track->parser_priv->feature_flags) * CHAR_BIT || feature < 0) |
1809 | 0 | return -1; |
1810 | | |
1811 | | // all supported non-meta features |
1812 | 0 | static const uint32_t supported = |
1813 | 0 | #ifdef USE_FRIBIDI_EX_API |
1814 | 0 | FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS) | |
1815 | 0 | #endif |
1816 | | #ifdef CONFIG_UNIBREAK |
1817 | | FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE) | |
1818 | | #endif |
1819 | 0 | FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT) | |
1820 | 0 | 0; |
1821 | 0 | uint32_t requested = 0; |
1822 | |
|
1823 | 0 | switch (feature) { |
1824 | 0 | case ASS_FEATURE_INCOMPATIBLE_EXTENSIONS: |
1825 | 0 | requested = supported; |
1826 | 0 | break; |
1827 | 0 | default: |
1828 | 0 | if (!(FEATURE_MASK(feature) & supported)) |
1829 | 0 | return -1; |
1830 | 0 | requested = FEATURE_MASK(feature); |
1831 | 0 | } |
1832 | | |
1833 | 0 | if (enable) |
1834 | 0 | track->parser_priv->feature_flags |= requested; |
1835 | 0 | else |
1836 | 0 | track->parser_priv->feature_flags &= ~requested; |
1837 | |
|
1838 | 0 | return 0; |
1839 | 0 | } |
1840 | | |
1841 | | /** |
1842 | | * \brief Prepare track for rendering |
1843 | | */ |
1844 | | void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track) |
1845 | 14.2k | { |
1846 | 14.2k | if (track->PlayResX > 0 && track->PlayResY > 0) |
1847 | 14.0k | return; |
1848 | 125 | if (track->PlayResX <= 0 && track->PlayResY <= 0) { |
1849 | 124 | ass_msg(lib, MSGL_WARN, |
1850 | 124 | "Neither PlayResX nor PlayResY defined. Assuming 384x288"); |
1851 | 124 | track->PlayResX = 384; |
1852 | 124 | track->PlayResY = 288; |
1853 | 124 | } else { |
1854 | 1 | if (track->PlayResY <= 0 && track->PlayResX == 1280) { |
1855 | 0 | track->PlayResY = 1024; |
1856 | 0 | ass_msg(lib, MSGL_WARN, |
1857 | 0 | "PlayResY undefined, setting to %d", track->PlayResY); |
1858 | 1 | } else if (track->PlayResY <= 0) { |
1859 | | // overflow-safe `* 3/4` |
1860 | 1 | track->PlayResY = FFMAX(1, (track->PlayResX - 1u) - (track->PlayResX - 1u) / 4u); |
1861 | 1 | ass_msg(lib, MSGL_WARN, |
1862 | 1 | "PlayResY undefined, setting to %d", track->PlayResY); |
1863 | 1 | } else if (track->PlayResX <= 0 && track->PlayResY == 1024) { |
1864 | 0 | track->PlayResX = 1280; |
1865 | 0 | ass_msg(lib, MSGL_WARN, |
1866 | 0 | "PlayResX undefined, setting to %d", track->PlayResX); |
1867 | 0 | } else if (track->PlayResX <= 0) { |
1868 | 0 | track->PlayResX = FFMIN(track->PlayResY + track->PlayResY / 3u, (unsigned) INT_MAX); |
1869 | 0 | ass_msg(lib, MSGL_WARN, |
1870 | 0 | "PlayResX undefined, setting to %d", track->PlayResX); |
1871 | 0 | } |
1872 | 1 | } |
1873 | 125 | } |