18937
|
1 #include "config.h"
|
|
2
|
|
3 #include <stdio.h>
|
|
4 #include <stdlib.h>
|
|
5 #include <string.h>
|
|
6 #include <assert.h>
|
|
7 #include <errno.h>
|
|
8 #include <sys/types.h>
|
|
9 #include <sys/stat.h>
|
|
10 #include <unistd.h>
|
19374
|
11 #include <inttypes.h>
|
18937
|
12
|
|
13 #ifdef HAVE_ENCA
|
|
14 #include "subreader.h" // for guess_buffer_cp
|
|
15 #endif
|
|
16
|
|
17 #ifdef USE_ICONV
|
|
18 #include <iconv.h>
|
|
19 extern char *sub_cp;
|
|
20 #endif
|
|
21
|
|
22 #include "mp_msg.h"
|
|
23 #include "ass.h"
|
|
24 #include "ass_utils.h"
|
|
25 #include "libvo/sub.h" // for utf8_get_char
|
|
26
|
|
27 char *get_path(char *);
|
|
28
|
|
29 #define ASS_STYLES_ALLOC 20
|
|
30 #define ASS_EVENTS_ALLOC 200
|
|
31
|
|
32 void ass_free_track(ass_track_t* track) {
|
|
33 int i;
|
|
34
|
|
35 if (track->style_format)
|
|
36 free(track->style_format);
|
|
37 if (track->event_format)
|
|
38 free(track->event_format);
|
|
39 if (track->styles) {
|
|
40 for (i = 0; i < track->n_styles; ++i) {
|
|
41 ass_style_t* style = track->styles + i;
|
|
42 if (style->Name)
|
|
43 free(style->Name);
|
|
44 if (style->FontName)
|
|
45 free(style->FontName);
|
|
46 }
|
|
47 free(track->styles);
|
|
48 }
|
|
49 if (track->events) {
|
|
50 for (i = 0; i < track->n_events; ++i) {
|
|
51 ass_event_t* event = track->events + i;
|
|
52 if (event->Name)
|
|
53 free(event->Name);
|
|
54 if (event->Effect)
|
|
55 free(event->Effect);
|
|
56 if (event->Text)
|
|
57 free(event->Text);
|
|
58 }
|
|
59 free(track->events);
|
|
60 }
|
|
61 }
|
|
62
|
|
63 /// \brief Allocate a new style struct
|
|
64 /// \param track track
|
|
65 /// \return style id
|
|
66 int ass_alloc_style(ass_track_t* track) {
|
|
67 int sid;
|
|
68
|
|
69 assert(track->n_styles <= track->max_styles);
|
|
70
|
|
71 if (track->n_styles == track->max_styles) {
|
|
72 track->max_styles += ASS_STYLES_ALLOC;
|
|
73 track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
|
|
74 }
|
|
75
|
|
76 sid = track->n_styles++;
|
|
77 memset(track->styles + sid, 0, sizeof(ass_style_t));
|
|
78 return sid;
|
|
79 }
|
|
80
|
|
81 /// \brief Allocate a new event struct
|
|
82 /// \param track track
|
|
83 /// \return event id
|
|
84 int ass_alloc_event(ass_track_t* track) {
|
|
85 int eid;
|
|
86
|
|
87 assert(track->n_events <= track->max_events);
|
|
88
|
|
89 if (track->n_events == track->max_events) {
|
|
90 track->max_events += ASS_EVENTS_ALLOC;
|
|
91 track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
|
|
92 }
|
|
93
|
|
94 eid = track->n_events++;
|
|
95 memset(track->events + eid, 0, sizeof(ass_event_t));
|
|
96 return eid;
|
|
97 }
|
|
98
|
|
99 static void free_event(ass_track_t* track, int eid) {
|
|
100 if (track->n_events > eid + 1) // not last event
|
|
101 memcpy(track->events + eid, track->events + eid + 1, sizeof(ass_event_t) * (track->n_events - eid - 1));
|
|
102 track->n_events--;
|
|
103 }
|
|
104
|
|
105 static int events_compare_f(const void* a_, const void* b_) {
|
|
106 ass_event_t* a = (ass_event_t*)a_;
|
|
107 ass_event_t* b = (ass_event_t*)b_;
|
|
108 if (a->Start < b->Start)
|
|
109 return -1;
|
|
110 else if (a->Start > b->Start)
|
|
111 return 1;
|
|
112 else
|
|
113 return 0;
|
|
114 }
|
|
115
|
|
116 /// \brief Sort events by start time
|
|
117 /// \param tid track id
|
|
118 static void sort_events(ass_track_t* track) {
|
|
119 qsort(track->events, track->n_events, sizeof(ass_event_t), events_compare_f);
|
|
120 }
|
|
121
|
|
122 // ==============================================================================================
|
|
123
|
|
124 static void skip_spaces(char** str) {
|
|
125 char* p = *str;
|
|
126 while ((*p==' ') || (*p=='\t'))
|
|
127 ++p;
|
|
128 *str = p;
|
|
129 }
|
|
130
|
|
131 static void rskip_spaces(char** str, char* limit) {
|
|
132 char* p = *str;
|
|
133 while ((p >= limit) && ((*p==' ') || (*p=='\t')))
|
|
134 --p;
|
|
135 *str = p;
|
|
136 }
|
|
137
|
|
138 /**
|
|
139 * \brief find style by name
|
|
140 * \param track track
|
|
141 * \param name style name
|
|
142 * \return index in track->styles
|
|
143 * Returnes 0 if no styles found => expects at least 1 style.
|
|
144 * Parsing code always adds "Default" style in the end.
|
|
145 */
|
|
146 static int lookup_style(ass_track_t* track, char* name) {
|
|
147 int i;
|
|
148 for (i=0; i<track->n_styles; ++i) {
|
|
149 // FIXME: mb strcasecmp ?
|
|
150 if (strcmp(track->styles[i].Name, name) == 0)
|
|
151 return i;
|
|
152 }
|
|
153 i = track->default_style;
|
|
154 mp_msg(MSGT_GLOBAL, MSGL_WARN, "[%p] Warning: no style named '%s' found, using '%s'\n", track, name, track->styles[i].Name);
|
|
155 return i; // use the first style
|
|
156 }
|
|
157
|
|
158 static uint32_t string2color(char* p) {
|
|
159 uint32_t tmp;
|
|
160 (void)strtocolor(&p, &tmp);
|
|
161 return tmp;
|
|
162 }
|
|
163
|
|
164 static long long string2timecode(char* p) {
|
|
165 unsigned h, m, s, ms;
|
|
166 long long tm;
|
|
167 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
|
|
168 if (res < 4) {
|
|
169 mp_msg(MSGT_GLOBAL, MSGL_WARN, "bad timestamp\n");
|
|
170 return 0;
|
|
171 }
|
|
172 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
|
|
173 return tm;
|
|
174 }
|
|
175
|
|
176 /**
|
|
177 * \brief converts numpad-style align to align.
|
|
178 */
|
|
179 static int numpad2align(int val) {
|
|
180 int res, v;
|
|
181 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
|
|
182 if (v != 0) v = 3 - v;
|
|
183 res = ((val - 1) % 3) + 1; // horizontal alignment
|
|
184 res += v*4;
|
|
185 return res;
|
|
186 }
|
|
187
|
|
188 #define NEXT(str,token) \
|
|
189 token = next_token(&str); \
|
|
190 if (!token) break;
|
|
191
|
|
192 #define ANYVAL(name,func) \
|
|
193 } else if (strcasecmp(tname, #name) == 0) { \
|
|
194 target->name = func(token); \
|
|
195 mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token);
|
|
196 #define STRVAL(name) ANYVAL(name,strdup)
|
|
197 #define COLORVAL(name) ANYVAL(name,string2color)
|
|
198 #define INTVAL(name) ANYVAL(name,atoi)
|
|
199 #define FPVAL(name) ANYVAL(name,atof)
|
|
200 #define TIMEVAL(name) ANYVAL(name,string2timecode)
|
|
201 #define STYLEVAL(name) \
|
|
202 } else if (strcasecmp(tname, #name) == 0) { \
|
|
203 target->name = lookup_style(track, token); \
|
|
204 mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token);
|
|
205
|
|
206 #define ALIAS(alias,name) \
|
|
207 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
|
|
208
|
|
209 static char* next_token(char** str) {
|
|
210 char* p = *str;
|
|
211 char* start;
|
|
212 skip_spaces(&p);
|
|
213 if (*p == '\0') {
|
|
214 *str = p;
|
|
215 return 0;
|
|
216 }
|
|
217 start = p; // start of the token
|
|
218 for (; (*p != '\0') && (*p != ','); ++p) {}
|
|
219 if (*p == '\0') {
|
|
220 *str = p; // eos found, str will point to '\0' at exit
|
|
221 } else {
|
|
222 *p = '\0';
|
|
223 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
|
|
224 }
|
|
225 --p; // end of current token
|
|
226 rskip_spaces(&p, start);
|
|
227 if (p < start)
|
|
228 p = start; // empty token
|
|
229 else
|
|
230 ++p; // the first space character, or '\0'
|
|
231 *p = '\0';
|
|
232 return start;
|
|
233 }
|
|
234 /**
|
|
235 * \brief Parse the tail of Dialogue line
|
|
236 * \param track track
|
|
237 * \param event parsed data goes here
|
|
238 * \param str string to parse, zero-terminated
|
|
239 * \param n_ignored number of format options to skip at the beginning
|
|
240 */
|
|
241 static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
|
|
242 {
|
|
243 char* token;
|
|
244 char* tname;
|
|
245 char* p = str;
|
|
246 int i;
|
|
247 ass_event_t* target = event;
|
|
248
|
|
249 char* format = strdup(track->event_format);
|
|
250 char* q = format; // format scanning pointer
|
|
251
|
|
252 for (i = 0; i < n_ignored; ++i) {
|
|
253 NEXT(q, tname);
|
|
254 }
|
|
255
|
|
256 while (1) {
|
|
257 NEXT(q, tname);
|
|
258 if (strcasecmp(tname, "Text") == 0) {
|
|
259 char* last;
|
|
260 event->Text = strdup(p);
|
|
261 last = event->Text + strlen(event->Text) - 1;
|
|
262 if (*last == '\r')
|
|
263 *last = 0;
|
|
264 mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Text = %s\n", event->Text);
|
|
265 event->Duration -= event->Start;
|
|
266 free(format);
|
|
267 return 0; // "Text" is always the last
|
|
268 }
|
|
269 NEXT(p, token);
|
|
270
|
|
271 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
|
|
272 if (0) { // cool ;)
|
|
273 STYLEVAL(Style)
|
|
274 STRVAL(Name)
|
|
275 STRVAL(Effect)
|
|
276 INTVAL(MarginL)
|
|
277 INTVAL(MarginR)
|
|
278 INTVAL(MarginV)
|
|
279 TIMEVAL(Start)
|
|
280 TIMEVAL(Duration)
|
|
281 }
|
|
282 }
|
|
283 free(format);
|
|
284 return 1;
|
|
285 }
|
|
286
|
|
287 /**
|
|
288 * \brief Parse the Style line
|
|
289 * \param track track
|
|
290 * \param str string to parse, zero-terminated
|
|
291 * Allocates a new style struct.
|
|
292 */
|
|
293 static int process_style(ass_track_t* track, char *str)
|
|
294 {
|
|
295
|
|
296 char* token;
|
|
297 char* tname;
|
|
298 char* p = str;
|
|
299 char* format;
|
|
300 char* q; // format scanning pointer
|
|
301 int sid;
|
|
302 ass_style_t* style;
|
|
303 ass_style_t* target;
|
|
304
|
|
305 if (!track->style_format) {
|
|
306 // no style format header
|
|
307 // probably an ancient script version
|
|
308 if (track->track_type == TRACK_TYPE_SSA)
|
|
309 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
|
|
310 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
|
|
311 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
|
|
312 else
|
|
313 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
|
|
314 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
|
|
315 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
|
|
316 "Alignment, MarginL, MarginR, MarginV, Encoding");
|
|
317 }
|
|
318
|
|
319 q = format = strdup(track->style_format);
|
|
320
|
|
321 mp_msg(MSGT_GLOBAL, MSGL_V, "[%p] Style: %s\n", track, str);
|
|
322
|
|
323 sid = ass_alloc_style(track);
|
|
324
|
|
325 style = track->styles + sid;
|
|
326 target = style;
|
|
327 // fill style with some default values
|
|
328 style->ScaleX = 100.;
|
|
329 style->ScaleY = 100.;
|
|
330
|
|
331 while (1) {
|
|
332 NEXT(q, tname);
|
|
333 NEXT(p, token);
|
|
334
|
|
335 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
|
|
336
|
|
337 if (0) { // cool ;)
|
|
338 STRVAL(Name)
|
|
339 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
|
|
340 track->default_style = sid;
|
|
341 STRVAL(FontName)
|
|
342 COLORVAL(PrimaryColour)
|
|
343 COLORVAL(SecondaryColour)
|
|
344 COLORVAL(OutlineColour) // TertiaryColor
|
|
345 COLORVAL(BackColour)
|
|
346 // SSA uses BackColour for both outline and shadow
|
|
347 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
|
|
348 if (track->track_type == TRACK_TYPE_SSA)
|
|
349 target->OutlineColour = target->BackColour;
|
|
350 INTVAL(FontSize)
|
|
351 INTVAL(Bold)
|
|
352 INTVAL(Italic)
|
|
353 INTVAL(Underline)
|
|
354 INTVAL(StrikeOut)
|
|
355 INTVAL(Spacing)
|
|
356 INTVAL(Angle)
|
|
357 INTVAL(BorderStyle)
|
|
358 INTVAL(Alignment)
|
|
359 if (track->track_type == TRACK_TYPE_ASS)
|
|
360 target->Alignment = numpad2align(target->Alignment);
|
|
361 INTVAL(MarginL)
|
|
362 INTVAL(MarginR)
|
|
363 INTVAL(MarginV)
|
|
364 INTVAL(Encoding)
|
|
365 FPVAL(ScaleX)
|
|
366 FPVAL(ScaleY)
|
|
367 FPVAL(Outline)
|
|
368 FPVAL(Shadow)
|
|
369 }
|
|
370 }
|
|
371 style->ScaleX /= 100.;
|
|
372 style->ScaleY /= 100.;
|
|
373 if (!style->Name)
|
|
374 style->Name = strdup("Default");
|
|
375 if (!style->FontName)
|
|
376 style->FontName = strdup("Arial");
|
|
377 free(format);
|
|
378 return 0;
|
|
379
|
|
380 }
|
|
381
|
|
382 /**
|
|
383 * \brief Parse a header line
|
|
384 * \param track track
|
|
385 * \param str string to parse, zero-terminated
|
|
386 */
|
|
387 static int process_header_line(ass_track_t* track, char *str)
|
|
388 {
|
|
389 static int events_section_started = 0;
|
|
390
|
|
391 mp_msg(MSGT_GLOBAL, MSGL_DBG2, "=== Header: %s\n", str);
|
|
392 if (strncmp(str, "PlayResX:", 9)==0) {
|
|
393 track->PlayResX = atoi(str + 9);
|
|
394 } else if (strncmp(str,"PlayResY:", 9)==0) {
|
|
395 track->PlayResY = atoi(str + 9);
|
|
396 } else if (strncmp(str,"Timer:", 6)==0) {
|
|
397 track->Timer = atof(str + 6);
|
|
398 } else if (strstr(str,"Styles]")) {
|
|
399 events_section_started = 0;
|
|
400 if (strchr(str, '+'))
|
|
401 track->track_type = TRACK_TYPE_ASS;
|
|
402 else
|
|
403 track->track_type = TRACK_TYPE_SSA;
|
|
404 } else if (strncmp(str,"[Events]", 8)==0) {
|
|
405 events_section_started = 1;
|
|
406 } else if (strncmp(str,"Format:", 7)==0) {
|
|
407 char* p = str + 7;
|
|
408 skip_spaces(&p);
|
|
409 if (events_section_started) {
|
|
410 track->event_format = strdup(p);
|
|
411 mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Event format: %s\n", track->event_format);
|
|
412 } else {
|
|
413 track->style_format = strdup(p);
|
|
414 mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Style format: %s\n", track->style_format);
|
|
415 }
|
|
416 } else if (strncmp(str,"Style:", 6)==0) {
|
|
417 char* p = str + 6;
|
|
418 skip_spaces(&p);
|
|
419 process_style(track, p);
|
|
420 } else if (strncmp(str,"WrapStyle:", 10)==0) {
|
|
421 track->WrapStyle = atoi(str + 10);
|
|
422 }
|
|
423 return 0;
|
|
424 }
|
|
425
|
|
426 /**
|
|
427 * \brief Process CodecPrivate section of subtitle stream
|
|
428 * \param track track
|
|
429 * \param data string to parse
|
|
430 * \param size length of data
|
|
431 CodecPrivate section contains [Stream Info] and [V4+ Styles] sections
|
|
432 */
|
|
433 void ass_process_chunk(ass_track_t* track, char *data, int size)
|
|
434 {
|
|
435 char* str = malloc(size + 1);
|
|
436 char* p;
|
|
437 int sid;
|
|
438
|
|
439 memcpy(str, data, size);
|
|
440 str[size] = '\0';
|
|
441
|
|
442 p = str;
|
|
443 while(1) {
|
|
444 char* q;
|
|
445 for (;((*p=='\r')||(*p=='\n'));++p) {}
|
|
446 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
|
|
447 if (q==p)
|
|
448 break;
|
|
449 if (*q != '\0')
|
|
450 *(q++) = '\0';
|
|
451 process_header_line(track, p);
|
|
452 if (*q == '\0')
|
|
453 break;
|
|
454 p = q;
|
|
455 }
|
|
456 free(str);
|
|
457
|
|
458 // add "Default" style to the end
|
|
459 // will be used if track does not contain a default style (or even does not contain styles at all)
|
|
460 sid = ass_alloc_style(track);
|
|
461 track->styles[sid].Name = strdup("Default");
|
|
462 track->styles[sid].FontName = strdup("Arial");
|
|
463
|
|
464 if (!track->event_format) {
|
|
465 // probably an mkv produced by ancient mkvtoolnix
|
|
466 // such files don't have [Events] and Format: headers
|
|
467 if (track->track_type == TRACK_TYPE_SSA)
|
|
468 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
|
|
469 else
|
|
470 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
|
|
471 }
|
|
472 }
|
|
473
|
|
474 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
|
|
475 {
|
|
476 int i;
|
|
477 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
|
|
478 if (track->events[i].ReadOrder == ReadOrder)
|
|
479 return 1;
|
|
480 return 0;
|
|
481 }
|
|
482
|
|
483 /**
|
|
484 * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary)
|
|
485 * \param track track
|
|
486 * \param data string to parse
|
|
487 * \param size length of data
|
|
488 * \param timecode starting time of the event (milliseconds)
|
|
489 * \param duration duration of the event (milliseconds)
|
|
490 */
|
|
491 void ass_process_line(ass_track_t* track, char *data, int size, long long timecode, long long duration)
|
|
492 {
|
|
493 char* str;
|
|
494 int eid;
|
|
495 char* p;
|
|
496 char* token;
|
|
497 ass_event_t* event;
|
|
498
|
|
499 if (!track->event_format) {
|
|
500 mp_msg(MSGT_GLOBAL, MSGL_WARN, "Event format header missing\n");
|
|
501 return;
|
|
502 }
|
|
503
|
|
504 str = malloc(size + 1);
|
|
505 memcpy(str, data, size);
|
|
506 str[size] = '\0';
|
19378
|
507 mp_msg(MSGT_GLOBAL, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
|
18937
|
508
|
|
509 eid = ass_alloc_event(track);
|
|
510 event = track->events + eid;
|
|
511
|
|
512 p = str;
|
|
513
|
|
514 do {
|
|
515 NEXT(p, token);
|
|
516 event->ReadOrder = atoi(token);
|
|
517 if (check_duplicate_event(track, event->ReadOrder))
|
|
518 break;
|
|
519
|
|
520 NEXT(p, token);
|
|
521 event->Layer = atoi(token);
|
|
522
|
|
523 process_event_tail(track, event, p, 3);
|
|
524
|
|
525 event->Start = timecode;
|
|
526 event->Duration = duration;
|
|
527
|
|
528 free(str);
|
|
529 return;
|
|
530 // dump_events(tid);
|
|
531 } while (0);
|
|
532 // some error
|
|
533 free_event(track, eid);
|
|
534 free(str);
|
|
535 }
|
|
536
|
|
537 /**
|
|
538 * \brief Process a line from external file.
|
|
539 * \param track track
|
|
540 * \param str string to parse
|
|
541 * \param size length of data
|
|
542 */
|
|
543 static void ass_process_external_line(ass_track_t* track, char *str, int size)
|
|
544 {
|
|
545 int eid;
|
|
546 ass_event_t* event;
|
|
547
|
|
548 eid = ass_alloc_event(track);
|
|
549 event = track->events + eid;
|
|
550
|
|
551 if (strncmp("Dialogue:", str, 9) != 0)
|
|
552 return;
|
|
553
|
|
554 str += 9;
|
|
555 while (*str == ' ') {++str;}
|
|
556
|
|
557 process_event_tail(track, event, str, 0);
|
|
558 }
|
|
559
|
|
560 #ifdef USE_ICONV
|
|
561 /** \brief recode buffer to utf-8
|
|
562 * constraint: sub_cp != 0
|
|
563 * \param data pointer to text buffer
|
|
564 * \param size buffer size
|
|
565 * \return a pointer to recoded buffer, caller is responsible for freeing it
|
|
566 **/
|
|
567 static char* sub_recode(char* data, size_t size)
|
|
568 {
|
|
569 static iconv_t icdsc = (iconv_t)(-1);
|
|
570 char* tocp = "UTF-8";
|
|
571 char* outbuf;
|
|
572 assert(sub_cp);
|
|
573
|
|
574 {
|
|
575 char* cp_tmp = sub_cp;
|
|
576 #ifdef HAVE_ENCA
|
|
577 char enca_lang[3], enca_fallback[100];
|
|
578 if (sscanf(sub_cp, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
|
|
579 || sscanf(sub_cp, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
|
|
580 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
|
|
581 }
|
|
582 #endif
|
|
583 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
|
|
584 mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
|
|
585 } else
|
|
586 mp_msg(MSGT_SUBREADER,MSGL_ERR,"LIBSUB: error opening iconv descriptor.\n");
|
|
587 #ifdef HAVE_ENCA
|
|
588 if (cp_tmp) free(cp_tmp);
|
|
589 #endif
|
|
590 }
|
|
591
|
|
592 {
|
|
593 size_t osize = size;
|
|
594 size_t ileft = size;
|
|
595 size_t oleft = size - 1;
|
|
596 char* ip;
|
|
597 char* op;
|
|
598 size_t rc;
|
|
599
|
|
600 outbuf = malloc(size);
|
|
601 ip = data;
|
|
602 op = outbuf;
|
|
603
|
|
604 while (ileft) {
|
|
605 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
|
|
606 if (rc == (size_t)(-1)) {
|
|
607 if (errno == E2BIG) {
|
|
608 int offset = op - outbuf;
|
|
609 outbuf = (char*)realloc(outbuf, osize + size);
|
|
610 op = outbuf + offset;
|
|
611 osize += size;
|
|
612 oleft += size;
|
|
613 } else {
|
|
614 mp_msg(MSGT_SUBREADER, MSGL_WARN, "LIBSUB: error recoding file.\n");
|
|
615 return NULL;
|
|
616 }
|
|
617 }
|
|
618 }
|
|
619 outbuf[osize - oleft - 1] = 0;
|
|
620 }
|
|
621
|
|
622 if (icdsc != (iconv_t)(-1)) {
|
|
623 (void)iconv_close(icdsc);
|
|
624 icdsc = (iconv_t)(-1);
|
|
625 mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
|
|
626 }
|
|
627
|
|
628 return outbuf;
|
|
629 }
|
|
630 #endif // ICONV
|
|
631
|
|
632 /**
|
|
633 * \brief Read subtitles from file.
|
|
634 * \param fname file name
|
|
635 * \return newly allocated track
|
|
636 */
|
|
637 ass_track_t* ass_read_file(char* fname)
|
|
638 {
|
|
639 int res;
|
|
640 long sz;
|
|
641 long bytes_read;
|
|
642 char* buf;
|
|
643 char* p;
|
|
644 int events_reached;
|
|
645 ass_track_t* track;
|
|
646
|
|
647 FILE* fp = fopen(fname, "rb");
|
|
648 if (!fp) {
|
|
649 mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fopen failed\n", fname);
|
|
650 return 0;
|
|
651 }
|
|
652 res = fseek(fp, 0, SEEK_END);
|
|
653 if (res == -1) {
|
|
654 mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fseek failed\n", fname);
|
|
655 fclose(fp);
|
|
656 return 0;
|
|
657 }
|
|
658
|
|
659 sz = ftell(fp);
|
|
660 rewind(fp);
|
|
661
|
|
662 if (sz > 10*1024*1024) {
|
|
663 mp_msg(MSGT_GLOBAL, MSGL_INFO, "ass_read_file(%s): Refusing to load subtitles larger than 10M\n", fname);
|
|
664 fclose(fp);
|
|
665 return 0;
|
|
666 }
|
|
667
|
|
668 mp_msg(MSGT_GLOBAL, MSGL_V, "file size: %ld\n", sz);
|
|
669
|
|
670 buf = malloc(sz + 1);
|
|
671 assert(buf);
|
|
672 bytes_read = 0;
|
|
673 do {
|
|
674 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
|
|
675 if (res <= 0) {
|
|
676 mp_msg(MSGT_GLOBAL, MSGL_INFO, "Read failed, %d: %s\n", errno, strerror(errno));
|
|
677 fclose(fp);
|
|
678 free(buf);
|
|
679 return 0;
|
|
680 }
|
|
681 bytes_read += res;
|
|
682 } while (sz - bytes_read > 0);
|
|
683 buf[sz] = '\0';
|
|
684 fclose(fp);
|
|
685
|
|
686 #ifdef USE_ICONV
|
|
687 if (sub_cp) {
|
|
688 char* tmpbuf = sub_recode(buf, sz);
|
|
689 free(buf);
|
|
690 if (!tmpbuf)
|
|
691 return 0;
|
|
692 buf = tmpbuf;
|
|
693 }
|
|
694 #endif
|
|
695
|
|
696 track = ass_new_track();
|
|
697 track->name = strdup(fname);
|
|
698
|
|
699 // process header
|
|
700 events_reached = 0;
|
|
701 p = buf;
|
|
702 while (p && (*p)) {
|
|
703 while (*p == '\n') {++p;}
|
|
704 if (strncmp(p, "[Events]", 8) == 0) {
|
|
705 events_reached = 1;
|
|
706 } else if ((strncmp(p, "Format:", 7) == 0) && (events_reached)) {
|
|
707 p = strchr(p, '\n');
|
|
708 if (p == 0) {
|
|
709 mp_msg(MSGT_GLOBAL, MSGL_WARN, "Incomplete subtitles\n");
|
|
710 free(buf);
|
|
711 return 0;
|
|
712 }
|
|
713 ass_process_chunk(track, buf, p - buf + 1);
|
|
714 ++p;
|
|
715 break;
|
|
716 }
|
|
717 p = strchr(p, '\n');
|
|
718 }
|
|
719 // process events
|
|
720 while (p && (*p)) {
|
|
721 char* next;
|
|
722 int len;
|
|
723 while (*p == '\n') {++p;}
|
|
724 next = strchr(p, '\n');
|
|
725 len = 0;
|
|
726 if (next) {
|
|
727 len = next - p;
|
|
728 *next = 0;
|
|
729 } else {
|
|
730 len = strlen(p);
|
|
731 }
|
|
732 ass_process_external_line(track, p, len);
|
|
733 if (next) {
|
|
734 p = next + 1;
|
|
735 continue;
|
|
736 } else
|
|
737 break;
|
|
738 }
|
|
739
|
|
740 free(buf);
|
|
741
|
|
742 if (!events_reached) {
|
|
743 ass_free_track(track);
|
|
744 return 0;
|
|
745 }
|
|
746
|
|
747 mp_msg(MSGT_GLOBAL, MSGL_INFO, "LIBASS: added subtitle file: %s (%d styles, %d events)\n", fname, track->n_styles, track->n_events);
|
|
748
|
|
749 sort_events(track);
|
|
750
|
|
751 // dump_events(forced_tid);
|
|
752 return track;
|
|
753 }
|
|
754
|
|
755 static char* validate_fname(char* name)
|
|
756 {
|
|
757 char* fname;
|
|
758 char* p;
|
|
759 char* q;
|
|
760 unsigned code;
|
|
761 int sz = strlen(name);
|
|
762
|
|
763 q = fname = malloc(sz + 1);
|
|
764 p = name;
|
|
765 while (*p) {
|
|
766 code = utf8_get_char(&p);
|
|
767 if (code == 0)
|
|
768 break;
|
|
769 if ( (code > 0x7F) ||
|
|
770 (code == '\\') ||
|
|
771 (code == '/') ||
|
|
772 (code == ':') ||
|
|
773 (code == '*') ||
|
|
774 (code == '?') ||
|
|
775 (code == '<') ||
|
|
776 (code == '>') ||
|
|
777 (code == '|') ||
|
|
778 (code == 0))
|
|
779 {
|
|
780 *q++ = '_';
|
|
781 } else {
|
|
782 *q++ = code;
|
|
783 }
|
|
784 if (p - name > sz)
|
|
785 break;
|
|
786 }
|
|
787 *q = 0;
|
|
788 return fname;
|
|
789 }
|
|
790
|
|
791 /**
|
|
792 * \brief Process embedded matroska font. Saves it to ~/.mplayer/fonts.
|
|
793 * \param name attachment name
|
|
794 * \param data binary font data
|
|
795 * \param data_size data size
|
|
796 */
|
|
797 void ass_process_font(const char* name, char* data, int data_size)
|
|
798 {
|
|
799 char buf[1000];
|
|
800 FILE* fp = 0;
|
|
801 int rc;
|
|
802 struct stat st;
|
|
803 char* fname;
|
|
804
|
|
805 char* fonts_dir = get_path("fonts");
|
|
806 rc = stat(fonts_dir, &st);
|
|
807 if (rc) {
|
|
808 int res;
|
|
809 #ifndef __MINGW32__
|
|
810 res = mkdir(fonts_dir, 0700);
|
|
811 #else
|
|
812 res = mkdir(fonts_dir);
|
|
813 #endif
|
|
814 if (res) {
|
|
815 mp_msg(MSGT_GLOBAL, MSGL_WARN, "Failed to create: %s\n", fonts_dir);
|
|
816 }
|
|
817 } else if (!S_ISDIR(st.st_mode)) {
|
|
818 mp_msg(MSGT_GLOBAL, MSGL_WARN, "Not a directory: %s\n", fonts_dir);
|
|
819 }
|
|
820
|
|
821 fname = validate_fname((char*)name);
|
|
822
|
|
823 snprintf(buf, 1000, "%s/%s", fonts_dir, fname);
|
|
824 free(fname);
|
|
825 free(fonts_dir);
|
|
826
|
|
827 fp = fopen(buf, "wb");
|
|
828 if (!fp) return;
|
|
829
|
|
830 fwrite(data, data_size, 1, fp);
|
|
831 fclose(fp);
|
|
832 }
|
|
833
|
|
834 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
|
|
835 int i;
|
|
836
|
|
837 if (movement == 0) return 0;
|
|
838 if (track->n_events == 0) return 0;
|
|
839
|
|
840 if (movement < 0)
|
|
841 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
|
|
842 else
|
|
843 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
|
|
844
|
|
845 // -1 and n_events are ok
|
|
846 assert(i >= -1); assert(i <= track->n_events);
|
|
847 i += movement;
|
|
848 if (i < 0) i = 0;
|
|
849 if (i >= track->n_events) i = track->n_events - 1;
|
|
850 return ((long long)track->events[i].Start) - now;
|
|
851 }
|
|
852
|
|
853 ass_track_t* ass_new_track(void) {
|
|
854 ass_track_t* track = calloc(1, sizeof(ass_track_t));
|
|
855 return track;
|
|
856 }
|
|
857
|