~ubuntu-dev/mplayer/ubuntu-feisty

« back to all changes in this revision

Viewing changes to libass/ass.c

  • Committer: William Grant
  • Date: 2007-02-03 03:16:07 UTC
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: william.grant@ubuntu.org.au-20070203031607-08gc2ompbz6spt9i
Update to 1.0rc1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
 
2
// vim:ts=8:sw=8:noet:ai:
 
3
/*
 
4
  Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
 
5
 
 
6
  This program is free software; you can redistribute it and/or modify
 
7
  it under the terms of the GNU General Public License as published by
 
8
  the Free Software Foundation; either version 2 of the License, or
 
9
  (at your option) any later version.
 
10
 
 
11
  This program is distributed in the hope that it will be useful,
 
12
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
  GNU General Public License for more details.
 
15
 
 
16
  You should have received a copy of the GNU General Public License
 
17
  along with this program; if not, write to the Free Software
 
18
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 
19
*/
 
20
 
 
21
#include "config.h"
 
22
 
 
23
#include <stdio.h>
 
24
#include <stdlib.h>
 
25
#include <string.h>
 
26
#include <assert.h>
 
27
#include <errno.h>
 
28
#include <sys/types.h>
 
29
#include <sys/stat.h>
 
30
#include <unistd.h>
 
31
#include <inttypes.h>
 
32
 
 
33
#ifdef HAVE_ENCA
 
34
#include "subreader.h" // for guess_buffer_cp
 
35
#endif
 
36
 
 
37
#ifdef USE_ICONV
 
38
#include <iconv.h>
 
39
extern char *sub_cp;
 
40
#endif
 
41
extern int extract_embedded_fonts;
 
42
extern char** ass_force_style_list;
 
43
 
 
44
#include "mp_msg.h"
 
45
#include "ass.h"
 
46
#include "ass_utils.h"
 
47
#include "libvo/sub.h" // for utf8_get_char
 
48
 
 
49
char *get_path(char *);
 
50
 
 
51
typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
 
52
 
 
53
struct parser_priv_s {
 
54
        parser_state_t state;
 
55
        char* fontname;
 
56
        char* fontdata;
 
57
        int fontdata_size;
 
58
        int fontdata_used;
 
59
};
 
60
 
 
61
#define ASS_STYLES_ALLOC 20
 
62
#define ASS_EVENTS_ALLOC 200
 
63
 
 
64
void ass_free_track(ass_track_t* track) {
 
65
        int i;
 
66
        
 
67
        if (track->parser_priv) {
 
68
                if (track->parser_priv->fontname)
 
69
                        free(track->parser_priv->fontname);
 
70
                if (track->parser_priv->fontdata)
 
71
                        free(track->parser_priv->fontdata);
 
72
                free(track->parser_priv);
 
73
        }
 
74
        if (track->style_format)
 
75
                free(track->style_format);
 
76
        if (track->event_format)
 
77
                free(track->event_format);
 
78
        if (track->styles) {
 
79
                for (i = 0; i < track->n_styles; ++i)
 
80
                        ass_free_style(track, i);
 
81
                free(track->styles);
 
82
        }
 
83
        if (track->events) {
 
84
                for (i = 0; i < track->n_events; ++i)
 
85
                        ass_free_event(track, i);
 
86
                free(track->events);
 
87
        }
 
88
}
 
89
 
 
90
/// \brief Allocate a new style struct
 
91
/// \param track track
 
92
/// \return style id
 
93
int ass_alloc_style(ass_track_t* track) {
 
94
        int sid;
 
95
        
 
96
        assert(track->n_styles <= track->max_styles);
 
97
 
 
98
        if (track->n_styles == track->max_styles) {
 
99
                track->max_styles += ASS_STYLES_ALLOC;
 
100
                track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
 
101
        }
 
102
        
 
103
        sid = track->n_styles++;
 
104
        memset(track->styles + sid, 0, sizeof(ass_style_t));
 
105
        return sid;
 
106
}
 
107
 
 
108
/// \brief Allocate a new event struct
 
109
/// \param track track
 
110
/// \return event id
 
111
int ass_alloc_event(ass_track_t* track) {
 
112
        int eid;
 
113
        
 
114
        assert(track->n_events <= track->max_events);
 
115
 
 
116
        if (track->n_events == track->max_events) {
 
117
                track->max_events += ASS_EVENTS_ALLOC;
 
118
                track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
 
119
        }
 
120
        
 
121
        eid = track->n_events++;
 
122
        memset(track->events + eid, 0, sizeof(ass_event_t));
 
123
        return eid;
 
124
}
 
125
 
 
126
void ass_free_event(ass_track_t* track, int eid) {
 
127
        ass_event_t* event = track->events + eid;
 
128
        if (event->Name)
 
129
                free(event->Name);
 
130
        if (event->Effect)
 
131
                free(event->Effect);
 
132
        if (event->Text)
 
133
                free(event->Text);
 
134
        if (event->render_priv)
 
135
                free(event->render_priv);
 
136
}
 
137
 
 
138
void ass_free_style(ass_track_t* track, int sid) {
 
139
        ass_style_t* style = track->styles + sid;
 
140
        if (style->Name)
 
141
                free(style->Name);
 
142
        if (style->FontName)
 
143
                free(style->FontName);
 
144
}
 
145
 
 
146
// ==============================================================================================
 
147
 
 
148
static void skip_spaces(char** str) {
 
149
        char* p = *str;
 
150
        while ((*p==' ') || (*p=='\t'))
 
151
                ++p;
 
152
        *str = p;
 
153
}
 
154
 
 
155
static void rskip_spaces(char** str, char* limit) {
 
156
        char* p = *str;
 
157
        while ((p >= limit) && ((*p==' ') || (*p=='\t')))
 
158
                --p;
 
159
        *str = p;
 
160
}
 
161
 
 
162
/**
 
163
 * \brief find style by name
 
164
 * \param track track
 
165
 * \param name style name
 
166
 * \return index in track->styles
 
167
 * Returnes 0 if no styles found => expects at least 1 style.
 
168
 * Parsing code always adds "Default" style in the end.
 
169
 */
 
170
static int lookup_style(ass_track_t* track, char* name) {
 
171
        int i;
 
172
        if (*name == '*') ++name; // FIXME: what does '*' really mean ?
 
173
        for (i=0; i<track->n_styles; ++i) {
 
174
                // FIXME: mb strcasecmp ?
 
175
                if (strcmp(track->styles[i].Name, name) == 0)
 
176
                        return i;
 
177
        }
 
178
        i = track->default_style;
 
179
        mp_msg(MSGT_GLOBAL, MSGL_WARN, "[%p] Warning: no style named '%s' found, using '%s'\n", track, name, track->styles[i].Name);
 
180
        return i; // use the first style
 
181
}
 
182
 
 
183
static uint32_t string2color(char* p) {
 
184
        uint32_t tmp;
 
185
        (void)strtocolor(&p, &tmp);
 
186
        return tmp;
 
187
}
 
188
 
 
189
static long long string2timecode(char* p) {
 
190
        unsigned h, m, s, ms;
 
191
        long long tm;
 
192
        int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
 
193
        if (res < 4) {
 
194
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "bad timestamp\n");
 
195
                return 0;
 
196
        }
 
197
        tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
 
198
        return tm;
 
199
}
 
200
 
 
201
/**
 
202
 * \brief converts numpad-style align to align.
 
203
 */
 
204
static int numpad2align(int val) {
 
205
        int res, v;
 
206
        v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
 
207
        if (v != 0) v = 3 - v;
 
208
        res = ((val - 1) % 3) + 1; // horizontal alignment
 
209
        res += v*4;
 
210
        return res;
 
211
}
 
212
 
 
213
#define NEXT(str,token) \
 
214
        token = next_token(&str); \
 
215
        if (!token) break;
 
216
 
 
217
#define ANYVAL(name,func) \
 
218
        } else if (strcasecmp(tname, #name) == 0) { \
 
219
                target->name = func(token); \
 
220
                mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token);
 
221
 
 
222
#define STRVAL(name) \
 
223
        } else if (strcasecmp(tname, #name) == 0) { \
 
224
                if (target->name != NULL) free(target->name); \
 
225
                target->name = strdup(token); \
 
226
                mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token);
 
227
                
 
228
#define COLORVAL(name) ANYVAL(name,string2color)
 
229
#define INTVAL(name) ANYVAL(name,atoi)
 
230
#define FPVAL(name) ANYVAL(name,atof)
 
231
#define TIMEVAL(name) ANYVAL(name,string2timecode)
 
232
#define STYLEVAL(name) \
 
233
        } else if (strcasecmp(tname, #name) == 0) { \
 
234
                target->name = lookup_style(track, token); \
 
235
                mp_msg(MSGT_GLOBAL, MSGL_DBG2, "%s = %s\n", #name, token);
 
236
 
 
237
#define ALIAS(alias,name) \
 
238
        if (strcasecmp(tname, #alias) == 0) {tname = #name;}
 
239
 
 
240
static char* next_token(char** str) {
 
241
        char* p = *str;
 
242
        char* start;
 
243
        skip_spaces(&p);
 
244
        if (*p == '\0') {
 
245
                *str = p;
 
246
                return 0;
 
247
        }
 
248
        start = p; // start of the token
 
249
        for (; (*p != '\0') && (*p != ','); ++p) {}
 
250
        if (*p == '\0') {
 
251
                *str = p; // eos found, str will point to '\0' at exit
 
252
        } else {
 
253
                *p = '\0';
 
254
                *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
 
255
        }
 
256
        --p; // end of current token
 
257
        rskip_spaces(&p, start);
 
258
        if (p < start)
 
259
                p = start; // empty token
 
260
        else
 
261
                ++p; // the first space character, or '\0'
 
262
        *p = '\0';
 
263
        return start;
 
264
}
 
265
/**
 
266
 * \brief Parse the tail of Dialogue line
 
267
 * \param track track
 
268
 * \param event parsed data goes here
 
269
 * \param str string to parse, zero-terminated
 
270
 * \param n_ignored number of format options to skip at the beginning
 
271
*/ 
 
272
static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
 
273
{
 
274
        char* token;
 
275
        char* tname;
 
276
        char* p = str;
 
277
        int i;
 
278
        ass_event_t* target = event;
 
279
 
 
280
        char* format = strdup(track->event_format);
 
281
        char* q = format; // format scanning pointer
 
282
 
 
283
        for (i = 0; i < n_ignored; ++i) {
 
284
                NEXT(q, tname);
 
285
        }
 
286
 
 
287
        while (1) {
 
288
                NEXT(q, tname);
 
289
                if (strcasecmp(tname, "Text") == 0) {
 
290
                        char* last;
 
291
                        event->Text = strdup(p);
 
292
                        if (*event->Text != 0) {
 
293
                                last = event->Text + strlen(event->Text) - 1;
 
294
                                if (last >= event->Text && *last == '\r')
 
295
                                        *last = 0;
 
296
                        }
 
297
                        mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Text = %s\n", event->Text);
 
298
                        event->Duration -= event->Start;
 
299
                        free(format);
 
300
                        return 0; // "Text" is always the last
 
301
                }
 
302
                NEXT(p, token);
 
303
 
 
304
                ALIAS(End,Duration) // temporarily store end timecode in event->Duration
 
305
                if (0) { // cool ;)
 
306
                        INTVAL(Layer)
 
307
                        STYLEVAL(Style)
 
308
                        STRVAL(Name)
 
309
                        STRVAL(Effect)
 
310
                        INTVAL(MarginL)
 
311
                        INTVAL(MarginR)
 
312
                        INTVAL(MarginV)
 
313
                        TIMEVAL(Start)
 
314
                        TIMEVAL(Duration)
 
315
                }
 
316
        }
 
317
        free(format);
 
318
        return 1;
 
319
}
 
320
 
 
321
/**
 
322
 * \brief Parse command line style overrides (--ass-force-style option)
 
323
 * \param track track to apply overrides to
 
324
 * The format for overrides is [StyleName.]Field=Value
 
325
 */
 
326
void process_force_style(ass_track_t* track) {
 
327
        char **fs, *eq, *dt, *style, *tname, *token;
 
328
        ass_style_t* target;
 
329
        int sid;
 
330
        
 
331
        if (!ass_force_style_list) return;
 
332
        
 
333
        for (fs = ass_force_style_list; *fs; ++fs) {
 
334
                eq = strchr(*fs, '=');
 
335
                if (!eq)
 
336
                        continue;
 
337
                *eq = '\0';
 
338
                token = eq + 1;
 
339
 
 
340
                dt = strchr(*fs, '.');
 
341
                if (dt) {
 
342
                        *dt = '\0';
 
343
                        style = *fs;
 
344
                        tname = dt + 1;
 
345
                } else {
 
346
                        style = NULL;
 
347
                        tname = *fs;
 
348
                }
 
349
                for (sid = 0; sid < track->n_styles; ++sid) {
 
350
                        if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
 
351
                                target = track->styles + sid;
 
352
                                if (0) {
 
353
                                        STRVAL(FontName)
 
354
                                        COLORVAL(PrimaryColour)
 
355
                                        COLORVAL(SecondaryColour)
 
356
                                        COLORVAL(OutlineColour)
 
357
                                        COLORVAL(BackColour)
 
358
                                        INTVAL(FontSize)
 
359
                                        INTVAL(Bold)
 
360
                                        INTVAL(Italic)
 
361
                                        INTVAL(Underline)
 
362
                                        INTVAL(StrikeOut)
 
363
                                        INTVAL(Spacing)
 
364
                                        INTVAL(Angle)
 
365
                                        INTVAL(BorderStyle)
 
366
                                        INTVAL(Alignment)
 
367
                                        INTVAL(MarginL)
 
368
                                        INTVAL(MarginR)
 
369
                                        INTVAL(MarginV)
 
370
                                        INTVAL(Encoding)
 
371
                                        FPVAL(ScaleX)
 
372
                                        FPVAL(ScaleY)
 
373
                                        FPVAL(Outline)
 
374
                                        FPVAL(Shadow)
 
375
                                }
 
376
                        }
 
377
                }
 
378
                *eq = '=';
 
379
                if (dt) *dt = '.';
 
380
        }
 
381
}
 
382
 
 
383
/**
 
384
 * \brief Parse the Style line
 
385
 * \param track track
 
386
 * \param str string to parse, zero-terminated
 
387
 * Allocates a new style struct.
 
388
*/ 
 
389
static int process_style(ass_track_t* track, char *str)
 
390
{
 
391
 
 
392
        char* token;
 
393
        char* tname;
 
394
        char* p = str;
 
395
        char* format;
 
396
        char* q; // format scanning pointer
 
397
        int sid;
 
398
        ass_style_t* style;
 
399
        ass_style_t* target;
 
400
 
 
401
        if (!track->style_format) {
 
402
                // no style format header
 
403
                // probably an ancient script version
 
404
                if (track->track_type == TRACK_TYPE_SSA)
 
405
                        track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
 
406
                                        "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
 
407
                                        "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
 
408
                else
 
409
                        track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
 
410
                                        "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
 
411
                                        "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
 
412
                                        "Alignment, MarginL, MarginR, MarginV, Encoding");
 
413
        }
 
414
 
 
415
        q = format = strdup(track->style_format);
 
416
        
 
417
        mp_msg(MSGT_GLOBAL, MSGL_V, "[%p] Style: %s\n", track, str);
 
418
        
 
419
        sid = ass_alloc_style(track);
 
420
 
 
421
        style = track->styles + sid;
 
422
        target = style;
 
423
// fill style with some default values
 
424
        style->ScaleX = 100.;
 
425
        style->ScaleY = 100.;
 
426
        
 
427
        while (1) {
 
428
                NEXT(q, tname);
 
429
                NEXT(p, token);
 
430
                
 
431
//              ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
 
432
                        
 
433
                if (0) { // cool ;)
 
434
                        STRVAL(Name)
 
435
                                if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
 
436
                                        track->default_style = sid;
 
437
                        STRVAL(FontName)
 
438
                        COLORVAL(PrimaryColour)
 
439
                        COLORVAL(SecondaryColour)
 
440
                        COLORVAL(OutlineColour) // TertiaryColor
 
441
                        COLORVAL(BackColour)
 
442
                                // SSA uses BackColour for both outline and shadow
 
443
                                // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
 
444
                                if (track->track_type == TRACK_TYPE_SSA)
 
445
                                        target->OutlineColour = target->BackColour;
 
446
                        INTVAL(FontSize)
 
447
                        INTVAL(Bold)
 
448
                        INTVAL(Italic)
 
449
                        INTVAL(Underline)
 
450
                        INTVAL(StrikeOut)
 
451
                        INTVAL(Spacing)
 
452
                        INTVAL(Angle)
 
453
                        INTVAL(BorderStyle)
 
454
                        INTVAL(Alignment)
 
455
                                if (track->track_type == TRACK_TYPE_ASS)
 
456
                                        target->Alignment = numpad2align(target->Alignment);
 
457
                        INTVAL(MarginL)
 
458
                        INTVAL(MarginR)
 
459
                        INTVAL(MarginV)
 
460
                        INTVAL(Encoding)
 
461
                        FPVAL(ScaleX)
 
462
                        FPVAL(ScaleY)
 
463
                        FPVAL(Outline)
 
464
                        FPVAL(Shadow)
 
465
                }
 
466
        }
 
467
        style->ScaleX /= 100.;
 
468
        style->ScaleY /= 100.;
 
469
        if (!style->Name)
 
470
                style->Name = strdup("Default");
 
471
        if (!style->FontName)
 
472
                style->FontName = strdup("Arial");
 
473
        free(format);
 
474
        return 0;
 
475
        
 
476
}
 
477
 
 
478
static int process_styles_line(ass_track_t* track, char *str)
 
479
{
 
480
        if (!strncmp(str,"Format:", 7)) {
 
481
                char* p = str + 7;
 
482
                skip_spaces(&p);
 
483
                track->style_format = strdup(p);
 
484
                mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Style format: %s\n", track->style_format);
 
485
        } else if (!strncmp(str,"Style:", 6)) {
 
486
                char* p = str + 6;
 
487
                skip_spaces(&p);
 
488
                process_style(track, p);
 
489
        }
 
490
        return 0;
 
491
}
 
492
 
 
493
static int process_info_line(ass_track_t* track, char *str)
 
494
{
 
495
        if (!strncmp(str, "PlayResX:", 9)) {
 
496
                track->PlayResX = atoi(str + 9);
 
497
        } else if (!strncmp(str,"PlayResY:", 9)) {
 
498
                track->PlayResY = atoi(str + 9);
 
499
        } else if (!strncmp(str,"Timer:", 6)) {
 
500
                track->Timer = atof(str + 6);
 
501
        } else if (!strncmp(str,"WrapStyle:", 10)) {
 
502
                track->WrapStyle = atoi(str + 10);
 
503
        }
 
504
        return 0;
 
505
}
 
506
 
 
507
static int process_events_line(ass_track_t* track, char *str)
 
508
{
 
509
        if (!strncmp(str, "Format:", 7)) {
 
510
                char* p = str + 7;
 
511
                skip_spaces(&p);
 
512
                track->event_format = strdup(p);
 
513
                mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Event format: %s\n", track->event_format);
 
514
        } else if (!strncmp(str, "Dialogue:", 9)) {
 
515
                // This should never be reached for embedded subtitles.
 
516
                // They have slightly different format and are parsed in ass_process_chunk,
 
517
                // called directly from demuxer
 
518
                int eid;
 
519
                ass_event_t* event;
 
520
                
 
521
                str += 9;
 
522
                skip_spaces(&str);
 
523
 
 
524
                eid = ass_alloc_event(track);
 
525
                event = track->events + eid;
 
526
 
 
527
                process_event_tail(track, event, str, 0);
 
528
        } else {
 
529
                mp_msg(MSGT_GLOBAL, MSGL_V, "Not understood: %s  \n", str);
 
530
        }
 
531
        return 0;
 
532
}
 
533
 
 
534
// Copied from mkvtoolnix
 
535
static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
 
536
                unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
 
537
{
 
538
        uint32_t value;
 
539
        unsigned char bytes[3];
 
540
        int i;
 
541
 
 
542
        value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
 
543
        bytes[2] = value & 0xff;
 
544
        bytes[1] = (value & 0xff00) >> 8;
 
545
        bytes[0] = (value & 0xff0000) >> 16;
 
546
 
 
547
        for (i = 0; i < cnt; ++i)
 
548
                *dst++ = bytes[i];
 
549
        return dst;
 
550
}
 
551
 
 
552
static int decode_font(ass_track_t* track)
 
553
{
 
554
        unsigned char* p;
 
555
        unsigned char* q;
 
556
        int i;
 
557
        int size; // original size
 
558
        int dsize; // decoded size
 
559
        unsigned char* buf = 0;
 
560
 
 
561
        mp_msg(MSGT_GLOBAL, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
 
562
        size = track->parser_priv->fontdata_used;
 
563
        if (size % 4 == 1) {
 
564
                mp_msg(MSGT_GLOBAL, MSGL_ERR, "bad encoded data size\n");
 
565
                goto error_decode_font;
 
566
        }
 
567
        buf = malloc(size / 4 * 3 + 2);
 
568
        q = buf;
 
569
        for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
 
570
                q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
 
571
        }
 
572
        if (size % 4 == 2) {
 
573
                q = decode_chars(p[0], p[1], 0, 0, q, 1);
 
574
        } else if (size % 4 == 3) {
 
575
                q = decode_chars(p[0], p[1], p[2], 0, q, 2);
 
576
        }
 
577
        dsize = q - buf;
 
578
        assert(dsize <= size / 4 * 3 + 2);
 
579
        
 
580
        if (extract_embedded_fonts)
 
581
                ass_process_font(track->parser_priv->fontname, (char*)buf, dsize);
 
582
 
 
583
error_decode_font:
 
584
        if (buf) free(buf);
 
585
        free(track->parser_priv->fontname);
 
586
        free(track->parser_priv->fontdata);
 
587
        track->parser_priv->fontname = 0;
 
588
        track->parser_priv->fontdata = 0;
 
589
        track->parser_priv->fontdata_size = 0;
 
590
        track->parser_priv->fontdata_used = 0;
 
591
        return 0;
 
592
}
 
593
 
 
594
static char* validate_fname(char* name);
 
595
 
 
596
static int process_fonts_line(ass_track_t* track, char *str)
 
597
{
 
598
        int len;
 
599
 
 
600
        if (!strncmp(str, "fontname:", 9)) {
 
601
                char* p = str + 9;
 
602
                skip_spaces(&p);
 
603
                if (track->parser_priv->fontname) {
 
604
                        decode_font(track);
 
605
                }
 
606
                track->parser_priv->fontname = validate_fname(p);
 
607
                mp_msg(MSGT_GLOBAL, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
 
608
                return 0;
 
609
        }
 
610
        
 
611
        if (!track->parser_priv->fontname) {
 
612
                mp_msg(MSGT_GLOBAL, MSGL_V, "Not understood: %s  \n", str);
 
613
                return 0;
 
614
        }
 
615
 
 
616
        len = strlen(str);
 
617
        if (len > 80) {
 
618
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "Font line too long: %d, %s\n", len, str);
 
619
                return 0;
 
620
        }
 
621
        if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
 
622
                track->parser_priv->fontdata_size += 100 * 1024;
 
623
                track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
 
624
        }
 
625
        memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
 
626
        track->parser_priv->fontdata_used += len;
 
627
        
 
628
        return 0;
 
629
}
 
630
 
 
631
/**
 
632
 * \brief Parse a header line
 
633
 * \param track track
 
634
 * \param str string to parse, zero-terminated
 
635
*/ 
 
636
static int process_line(ass_track_t* track, char *str)
 
637
{
 
638
        if (strstr(str, "[Script Info]")) { // FIXME: strstr to skip possible BOM at the beginning of the script
 
639
                track->parser_priv->state = PST_INFO;
 
640
        } else if (!strncmp(str, "[V4 Styles]", 11)) {
 
641
                track->parser_priv->state = PST_STYLES;
 
642
                track->track_type = TRACK_TYPE_SSA;
 
643
        } else if (!strncmp(str, "[V4+ Styles]", 12)) {
 
644
                track->parser_priv->state = PST_STYLES;
 
645
                track->track_type = TRACK_TYPE_ASS;
 
646
        } else if (!strncmp(str, "[Events]", 8)) {
 
647
                track->parser_priv->state = PST_EVENTS;
 
648
        } else if (!strncmp(str, "[Fonts]", 7)) {
 
649
                track->parser_priv->state = PST_FONTS;
 
650
        } else {
 
651
                switch (track->parser_priv->state) {
 
652
                case PST_INFO:
 
653
                        process_info_line(track, str);
 
654
                        break;
 
655
                case PST_STYLES:
 
656
                        process_styles_line(track, str);
 
657
                        break;
 
658
                case PST_EVENTS:
 
659
                        process_events_line(track, str);
 
660
                        break;
 
661
                case PST_FONTS:
 
662
                        process_fonts_line(track, str);
 
663
                        break;
 
664
                default:
 
665
                        break;
 
666
                }
 
667
        }
 
668
 
 
669
        // there is no explicit end-of-font marker in ssa/ass
 
670
        if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
 
671
                decode_font(track);
 
672
 
 
673
        return 0;
 
674
}
 
675
 
 
676
static int process_text(ass_track_t* track, char* str)
 
677
{
 
678
        char* p = str;
 
679
        while(1) {
 
680
                char* q;
 
681
                for (;((*p=='\r')||(*p=='\n'));++p) {}
 
682
                for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
 
683
                if (q==p)
 
684
                        break;
 
685
                if (*q != '\0')
 
686
                        *(q++) = '\0';
 
687
                process_line(track, p);
 
688
                if (*q == '\0')
 
689
                        break;
 
690
                p = q;
 
691
        }
 
692
        return 0;
 
693
}
 
694
 
 
695
/**
 
696
 * \brief Process CodecPrivate section of subtitle stream
 
697
 * \param track track
 
698
 * \param data string to parse
 
699
 * \param size length of data
 
700
 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
 
701
*/ 
 
702
void ass_process_codec_private(ass_track_t* track, char *data, int size)
 
703
{
 
704
        char* str = malloc(size + 1);
 
705
        int sid;
 
706
 
 
707
        memcpy(str, data, size);
 
708
        str[size] = '\0';
 
709
 
 
710
        process_text(track, str);
 
711
        free(str);
 
712
 
 
713
        // add "Default" style to the end
 
714
        // will be used if track does not contain a default style (or even does not contain styles at all)
 
715
        sid = ass_alloc_style(track);
 
716
        track->styles[sid].Name = strdup("Default");
 
717
        track->styles[sid].FontName = strdup("Arial");
 
718
        
 
719
        if (!track->event_format) {
 
720
                // probably an mkv produced by ancient mkvtoolnix
 
721
                // such files don't have [Events] and Format: headers
 
722
                track->parser_priv->state = PST_EVENTS;
 
723
                if (track->track_type == TRACK_TYPE_SSA)
 
724
                        track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
 
725
                else
 
726
                        track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
 
727
        }
 
728
 
 
729
        process_force_style(track);
 
730
}
 
731
 
 
732
static int check_duplicate_event(ass_track_t* track, int ReadOrder)
 
733
{
 
734
        int i;
 
735
        for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
 
736
                if (track->events[i].ReadOrder == ReadOrder)
 
737
                        return 1;
 
738
        return 0;
 
739
}
 
740
 
 
741
/**
 
742
 * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary)
 
743
 * \param track track
 
744
 * \param data string to parse
 
745
 * \param size length of data
 
746
 * \param timecode starting time of the event (milliseconds)
 
747
 * \param duration duration of the event (milliseconds)
 
748
*/ 
 
749
void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
 
750
{
 
751
        char* str;
 
752
        int eid;
 
753
        char* p;
 
754
        char* token;
 
755
        ass_event_t* event;
 
756
 
 
757
        if (!track->event_format) {
 
758
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "Event format header missing\n");
 
759
                return;
 
760
        }
 
761
        
 
762
        str = malloc(size + 1);
 
763
        memcpy(str, data, size);
 
764
        str[size] = '\0';
 
765
        mp_msg(MSGT_GLOBAL, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s  \n", (int64_t)timecode, (int64_t)duration, str);
 
766
 
 
767
        eid = ass_alloc_event(track);
 
768
        event = track->events + eid;
 
769
 
 
770
        p = str;
 
771
        
 
772
        do { 
 
773
                NEXT(p, token);
 
774
                event->ReadOrder = atoi(token);
 
775
                if (check_duplicate_event(track, event->ReadOrder))
 
776
                        break;
 
777
 
 
778
                NEXT(p, token);
 
779
                event->Layer = atoi(token);
 
780
 
 
781
                process_event_tail(track, event, p, 3);
 
782
 
 
783
                event->Start = timecode;
 
784
                event->Duration = duration;
 
785
                
 
786
                free(str);
 
787
                return;
 
788
//              dump_events(tid);
 
789
        } while (0);
 
790
        // some error
 
791
        ass_free_event(track, eid);
 
792
        track->n_events--;
 
793
        free(str);
 
794
}
 
795
 
 
796
#ifdef USE_ICONV
 
797
/** \brief recode buffer to utf-8
 
798
 * constraint: sub_cp != 0
 
799
 * \param data pointer to text buffer
 
800
 * \param size buffer size
 
801
 * \return a pointer to recoded buffer, caller is responsible for freeing it
 
802
**/
 
803
static char* sub_recode(char* data, size_t size)
 
804
{
 
805
        static iconv_t icdsc = (iconv_t)(-1);
 
806
        char* tocp = "UTF-8";
 
807
        char* outbuf;
 
808
        assert(sub_cp);
 
809
 
 
810
        {
 
811
                char* cp_tmp = sub_cp;
 
812
#ifdef HAVE_ENCA
 
813
                char enca_lang[3], enca_fallback[100];
 
814
                if (sscanf(sub_cp, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
 
815
                                || sscanf(sub_cp, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
 
816
                        cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
 
817
                }
 
818
#endif
 
819
                if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
 
820
                        mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
 
821
                } else
 
822
                        mp_msg(MSGT_SUBREADER,MSGL_ERR,"LIBSUB: error opening iconv descriptor.\n");
 
823
#ifdef HAVE_ENCA
 
824
                if (cp_tmp) free(cp_tmp);
 
825
#endif
 
826
        }
 
827
 
 
828
        {
 
829
                size_t osize = size;
 
830
                size_t ileft = size;
 
831
                size_t oleft = size - 1;
 
832
                char* ip;
 
833
                char* op;
 
834
                size_t rc;
 
835
                
 
836
                outbuf = malloc(size);
 
837
                ip = data;
 
838
                op = outbuf;
 
839
                
 
840
                while (ileft) {
 
841
                        rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
 
842
                        if (rc == (size_t)(-1)) {
 
843
                                if (errno == E2BIG) {
 
844
                                        int offset = op - outbuf;
 
845
                                        outbuf = (char*)realloc(outbuf, osize + size);
 
846
                                        op = outbuf + offset;
 
847
                                        osize += size;
 
848
                                        oleft += size;
 
849
                                } else {
 
850
                                        mp_msg(MSGT_SUBREADER, MSGL_WARN, "LIBSUB: error recoding file.\n");
 
851
                                        return NULL;
 
852
                                }
 
853
                        }
 
854
                }
 
855
                outbuf[osize - oleft - 1] = 0;
 
856
        }
 
857
 
 
858
        if (icdsc != (iconv_t)(-1)) {
 
859
                (void)iconv_close(icdsc);
 
860
                icdsc = (iconv_t)(-1);
 
861
                mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
 
862
        }
 
863
        
 
864
        return outbuf;
 
865
}
 
866
#endif // ICONV
 
867
 
 
868
/**
 
869
 * \brief read file contents into newly allocated buffer, recoding to utf-8
 
870
 */
 
871
static char* read_file(char* fname)
 
872
{
 
873
        int res;
 
874
        long sz;
 
875
        long bytes_read;
 
876
        char* buf;
 
877
 
 
878
        FILE* fp = fopen(fname, "rb");
 
879
        if (!fp) {
 
880
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fopen failed\n", fname);
 
881
                return 0;
 
882
        }
 
883
        res = fseek(fp, 0, SEEK_END);
 
884
        if (res == -1) {
 
885
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fseek failed\n", fname);
 
886
                fclose(fp);
 
887
                return 0;
 
888
        }
 
889
        
 
890
        sz = ftell(fp);
 
891
        rewind(fp);
 
892
 
 
893
        if (sz > 10*1024*1024) {
 
894
                mp_msg(MSGT_GLOBAL, MSGL_INFO, "ass_read_file(%s): Refusing to load subtitles larger than 10M\n", fname);
 
895
                fclose(fp);
 
896
                return 0;
 
897
        }
 
898
        
 
899
        mp_msg(MSGT_GLOBAL, MSGL_V, "file size: %ld\n", sz);
 
900
        
 
901
        buf = malloc(sz + 1);
 
902
        assert(buf);
 
903
        bytes_read = 0;
 
904
        do {
 
905
                res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
 
906
                if (res <= 0) {
 
907
                        mp_msg(MSGT_GLOBAL, MSGL_INFO, "Read failed, %d: %s\n", errno, strerror(errno));
 
908
                        fclose(fp);
 
909
                        free(buf);
 
910
                        return 0;
 
911
                }
 
912
                bytes_read += res;
 
913
        } while (sz - bytes_read > 0);
 
914
        buf[sz] = '\0';
 
915
        fclose(fp);
 
916
        
 
917
#ifdef USE_ICONV
 
918
        if (sub_cp) {
 
919
                char* tmpbuf = sub_recode(buf, sz);
 
920
                free(buf);
 
921
                buf = tmpbuf;
 
922
        }
 
923
#endif
 
924
        return buf;
 
925
}
 
926
 
 
927
/**
 
928
 * \brief Read subtitles from file.
 
929
 * \param fname file name
 
930
 * \return newly allocated track
 
931
*/ 
 
932
ass_track_t* ass_read_file(char* fname)
 
933
{
 
934
        char* buf;
 
935
        ass_track_t* track;
 
936
        int i;
 
937
        
 
938
        buf = read_file(fname);
 
939
        if (!buf)
 
940
                return 0;
 
941
        
 
942
        track = ass_new_track();
 
943
        track->name = strdup(fname);
 
944
        
 
945
        // process header
 
946
        process_text(track, buf);
 
947
 
 
948
        // external SSA/ASS subs does not have ReadOrder field
 
949
        for (i = 0; i < track->n_events; ++i)
 
950
                track->events[i].ReadOrder = i;
 
951
 
 
952
        // there is no explicit end-of-font marker in ssa/ass
 
953
        if (track->parser_priv->fontname)
 
954
                decode_font(track);
 
955
 
 
956
        free(buf);
 
957
 
 
958
        if (track->track_type == TRACK_TYPE_UNKNOWN) {
 
959
                ass_free_track(track);
 
960
                return 0;
 
961
        }
 
962
 
 
963
        process_force_style(track);
 
964
 
 
965
        mp_msg(MSGT_GLOBAL, MSGL_INFO, "LIBASS: added subtitle file: %s (%d styles, %d events)\n", fname, track->n_styles, track->n_events);
 
966
        
 
967
//      dump_events(forced_tid);
 
968
        return track;
 
969
}
 
970
 
 
971
/**
 
972
 * \brief read styles from file into already initialized track
 
973
 */
 
974
int ass_read_styles(ass_track_t* track, char* fname)
 
975
{
 
976
        char* buf;
 
977
        parser_state_t old_state;
 
978
 
 
979
        buf = read_file(fname);
 
980
        if (!buf)
 
981
                return 1;
 
982
 
 
983
        old_state = track->parser_priv->state;
 
984
        track->parser_priv->state = PST_STYLES;
 
985
        process_text(track, buf);
 
986
        track->parser_priv->state = old_state;
 
987
 
 
988
        return 0;
 
989
}
 
990
 
 
991
static char* validate_fname(char* name)
 
992
{
 
993
        char* fname;
 
994
        char* p;
 
995
        char* q;
 
996
        unsigned code;
 
997
        int sz = strlen(name);
 
998
 
 
999
        q = fname = malloc(sz + 1);
 
1000
        p = name;
 
1001
        while (*p) {
 
1002
                code = utf8_get_char(&p);
 
1003
                if (code == 0)
 
1004
                        break;
 
1005
                if (    (code > 0x7F) ||
 
1006
                        (code == '\\') ||
 
1007
                        (code == '/') ||
 
1008
                        (code == ':') ||
 
1009
                        (code == '*') ||
 
1010
                        (code == '?') ||
 
1011
                        (code == '<') ||
 
1012
                        (code == '>') ||
 
1013
                        (code == '|') ||
 
1014
                        (code == 0))
 
1015
                {
 
1016
                        *q++ = '_';
 
1017
                } else {
 
1018
                        *q++ = code;
 
1019
                }
 
1020
                if (p - name > sz)
 
1021
                        break;
 
1022
        }
 
1023
        *q = 0;
 
1024
        return fname;
 
1025
}
 
1026
 
 
1027
/**
 
1028
 * \brief Process embedded matroska font. Saves it to ~/.mplayer/fonts.
 
1029
 * \param name attachment name
 
1030
 * \param data binary font data
 
1031
 * \param data_size data size
 
1032
*/ 
 
1033
void ass_process_font(const char* name, char* data, int data_size)
 
1034
{
 
1035
        char buf[1000];
 
1036
        FILE* fp = 0;
 
1037
        int rc;
 
1038
        struct stat st;
 
1039
        char* fname;
 
1040
 
 
1041
        char* fonts_dir = get_path("fonts");
 
1042
        rc = stat(fonts_dir, &st);
 
1043
        if (rc) {
 
1044
                int res;
 
1045
#ifndef __MINGW32__
 
1046
                res = mkdir(fonts_dir, 0700);
 
1047
#else
 
1048
                res = mkdir(fonts_dir);
 
1049
#endif
 
1050
                if (res) {
 
1051
                        mp_msg(MSGT_GLOBAL, MSGL_WARN, "Failed to create: %s\n", fonts_dir);
 
1052
                }
 
1053
        } else if (!S_ISDIR(st.st_mode)) {
 
1054
                mp_msg(MSGT_GLOBAL, MSGL_WARN, "Not a directory: %s\n", fonts_dir);
 
1055
        }
 
1056
        
 
1057
        fname = validate_fname((char*)name);
 
1058
 
 
1059
        snprintf(buf, 1000, "%s/%s", fonts_dir, fname);
 
1060
        free(fname);
 
1061
        free(fonts_dir);
 
1062
 
 
1063
        fp = fopen(buf, "wb");
 
1064
        if (!fp) return;
 
1065
 
 
1066
        fwrite(data, data_size, 1, fp);
 
1067
        fclose(fp);
 
1068
}
 
1069
 
 
1070
long long ass_step_sub(ass_track_t* track, long long now, int movement) {
 
1071
        int i;
 
1072
 
 
1073
        if (movement == 0) return 0;
 
1074
        if (track->n_events == 0) return 0;
 
1075
        
 
1076
        if (movement < 0)
 
1077
                for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
 
1078
        else
 
1079
                for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
 
1080
        
 
1081
        // -1 and n_events are ok
 
1082
        assert(i >= -1); assert(i <= track->n_events);
 
1083
        i += movement;
 
1084
        if (i < 0) i = 0;
 
1085
        if (i >= track->n_events) i = track->n_events - 1;
 
1086
        return ((long long)track->events[i].Start) - now;
 
1087
}
 
1088
 
 
1089
ass_track_t* ass_new_track(void) {
 
1090
        ass_track_t* track = calloc(1, sizeof(ass_track_t));
 
1091
        track->parser_priv = calloc(1, sizeof(parser_priv_t));
 
1092
        return track;
 
1093
}
 
1094