1
// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
2
// vim:ts=8:sw=8:noet:ai:
4
Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
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.
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.
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
28
#include <sys/types.h>
34
#include "subreader.h" // for guess_buffer_cp
41
extern int extract_embedded_fonts;
42
extern char** ass_force_style_list;
46
#include "ass_utils.h"
47
#include "libvo/sub.h" // for utf8_get_char
49
char *get_path(char *);
51
typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
53
struct parser_priv_s {
61
#define ASS_STYLES_ALLOC 20
62
#define ASS_EVENTS_ALLOC 200
64
void ass_free_track(ass_track_t* track) {
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);
74
if (track->style_format)
75
free(track->style_format);
76
if (track->event_format)
77
free(track->event_format);
79
for (i = 0; i < track->n_styles; ++i)
80
ass_free_style(track, i);
84
for (i = 0; i < track->n_events; ++i)
85
ass_free_event(track, i);
90
/// \brief Allocate a new style struct
91
/// \param track track
93
int ass_alloc_style(ass_track_t* track) {
96
assert(track->n_styles <= track->max_styles);
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);
103
sid = track->n_styles++;
104
memset(track->styles + sid, 0, sizeof(ass_style_t));
108
/// \brief Allocate a new event struct
109
/// \param track track
111
int ass_alloc_event(ass_track_t* track) {
114
assert(track->n_events <= track->max_events);
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);
121
eid = track->n_events++;
122
memset(track->events + eid, 0, sizeof(ass_event_t));
126
void ass_free_event(ass_track_t* track, int eid) {
127
ass_event_t* event = track->events + eid;
134
if (event->render_priv)
135
free(event->render_priv);
138
void ass_free_style(ass_track_t* track, int sid) {
139
ass_style_t* style = track->styles + sid;
143
free(style->FontName);
146
// ==============================================================================================
148
static void skip_spaces(char** str) {
150
while ((*p==' ') || (*p=='\t'))
155
static void rskip_spaces(char** str, char* limit) {
157
while ((p >= limit) && ((*p==' ') || (*p=='\t')))
163
* \brief find style by name
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.
170
static int lookup_style(ass_track_t* track, char* name) {
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)
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
183
static uint32_t string2color(char* p) {
185
(void)strtocolor(&p, &tmp);
189
static long long string2timecode(char* p) {
190
unsigned h, m, s, ms;
192
int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
194
mp_msg(MSGT_GLOBAL, MSGL_WARN, "bad timestamp\n");
197
tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
202
* \brief converts numpad-style align to align.
204
static int numpad2align(int val) {
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
213
#define NEXT(str,token) \
214
token = next_token(&str); \
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);
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);
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);
237
#define ALIAS(alias,name) \
238
if (strcasecmp(tname, #alias) == 0) {tname = #name;}
240
static char* next_token(char** str) {
248
start = p; // start of the token
249
for (; (*p != '\0') && (*p != ','); ++p) {}
251
*str = p; // eos found, str will point to '\0' at exit
254
*str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
256
--p; // end of current token
257
rskip_spaces(&p, start);
259
p = start; // empty token
261
++p; // the first space character, or '\0'
266
* \brief Parse the tail of Dialogue line
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
272
static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
278
ass_event_t* target = event;
280
char* format = strdup(track->event_format);
281
char* q = format; // format scanning pointer
283
for (i = 0; i < n_ignored; ++i) {
289
if (strcasecmp(tname, "Text") == 0) {
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')
297
mp_msg(MSGT_GLOBAL, MSGL_DBG2, "Text = %s\n", event->Text);
298
event->Duration -= event->Start;
300
return 0; // "Text" is always the last
304
ALIAS(End,Duration) // temporarily store end timecode in event->Duration
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
326
void process_force_style(ass_track_t* track) {
327
char **fs, *eq, *dt, *style, *tname, *token;
331
if (!ass_force_style_list) return;
333
for (fs = ass_force_style_list; *fs; ++fs) {
334
eq = strchr(*fs, '=');
340
dt = strchr(*fs, '.');
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;
354
COLORVAL(PrimaryColour)
355
COLORVAL(SecondaryColour)
356
COLORVAL(OutlineColour)
384
* \brief Parse the Style line
386
* \param str string to parse, zero-terminated
387
* Allocates a new style struct.
389
static int process_style(ass_track_t* track, char *str)
396
char* q; // format scanning pointer
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");
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");
415
q = format = strdup(track->style_format);
417
mp_msg(MSGT_GLOBAL, MSGL_V, "[%p] Style: %s\n", track, str);
419
sid = ass_alloc_style(track);
421
style = track->styles + sid;
423
// fill style with some default values
424
style->ScaleX = 100.;
425
style->ScaleY = 100.;
431
// ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
435
if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
436
track->default_style = sid;
438
COLORVAL(PrimaryColour)
439
COLORVAL(SecondaryColour)
440
COLORVAL(OutlineColour) // TertiaryColor
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;
455
if (track->track_type == TRACK_TYPE_ASS)
456
target->Alignment = numpad2align(target->Alignment);
467
style->ScaleX /= 100.;
468
style->ScaleY /= 100.;
470
style->Name = strdup("Default");
471
if (!style->FontName)
472
style->FontName = strdup("Arial");
478
static int process_styles_line(ass_track_t* track, char *str)
480
if (!strncmp(str,"Format:", 7)) {
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)) {
488
process_style(track, p);
493
static int process_info_line(ass_track_t* track, char *str)
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);
507
static int process_events_line(ass_track_t* track, char *str)
509
if (!strncmp(str, "Format:", 7)) {
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
524
eid = ass_alloc_event(track);
525
event = track->events + eid;
527
process_event_tail(track, event, str, 0);
529
mp_msg(MSGT_GLOBAL, MSGL_V, "Not understood: %s \n", str);
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)
539
unsigned char bytes[3];
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;
547
for (i = 0; i < cnt; ++i)
552
static int decode_font(ass_track_t* track)
557
int size; // original size
558
int dsize; // decoded size
559
unsigned char* buf = 0;
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;
564
mp_msg(MSGT_GLOBAL, MSGL_ERR, "bad encoded data size\n");
565
goto error_decode_font;
567
buf = malloc(size / 4 * 3 + 2);
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);
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);
578
assert(dsize <= size / 4 * 3 + 2);
580
if (extract_embedded_fonts)
581
ass_process_font(track->parser_priv->fontname, (char*)buf, dsize);
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;
594
static char* validate_fname(char* name);
596
static int process_fonts_line(ass_track_t* track, char *str)
600
if (!strncmp(str, "fontname:", 9)) {
603
if (track->parser_priv->fontname) {
606
track->parser_priv->fontname = validate_fname(p);
607
mp_msg(MSGT_GLOBAL, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
611
if (!track->parser_priv->fontname) {
612
mp_msg(MSGT_GLOBAL, MSGL_V, "Not understood: %s \n", str);
618
mp_msg(MSGT_GLOBAL, MSGL_WARN, "Font line too long: %d, %s\n", len, str);
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);
625
memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
626
track->parser_priv->fontdata_used += len;
632
* \brief Parse a header line
634
* \param str string to parse, zero-terminated
636
static int process_line(ass_track_t* track, char *str)
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;
651
switch (track->parser_priv->state) {
653
process_info_line(track, str);
656
process_styles_line(track, str);
659
process_events_line(track, str);
662
process_fonts_line(track, str);
669
// there is no explicit end-of-font marker in ssa/ass
670
if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
676
static int process_text(ass_track_t* track, char* str)
681
for (;((*p=='\r')||(*p=='\n'));++p) {}
682
for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
687
process_line(track, p);
696
* \brief Process CodecPrivate section of subtitle stream
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
702
void ass_process_codec_private(ass_track_t* track, char *data, int size)
704
char* str = malloc(size + 1);
707
memcpy(str, data, size);
710
process_text(track, str);
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");
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");
726
track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
729
process_force_style(track);
732
static int check_duplicate_event(ass_track_t* track, int ReadOrder)
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)
742
* \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary)
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)
749
void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
757
if (!track->event_format) {
758
mp_msg(MSGT_GLOBAL, MSGL_WARN, "Event format header missing\n");
762
str = malloc(size + 1);
763
memcpy(str, data, size);
765
mp_msg(MSGT_GLOBAL, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
767
eid = ass_alloc_event(track);
768
event = track->events + eid;
774
event->ReadOrder = atoi(token);
775
if (check_duplicate_event(track, event->ReadOrder))
779
event->Layer = atoi(token);
781
process_event_tail(track, event, p, 3);
783
event->Start = timecode;
784
event->Duration = duration;
791
ass_free_event(track, eid);
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
803
static char* sub_recode(char* data, size_t size)
805
static iconv_t icdsc = (iconv_t)(-1);
806
char* tocp = "UTF-8";
811
char* cp_tmp = sub_cp;
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);
819
if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
820
mp_msg(MSGT_SUBREADER,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
822
mp_msg(MSGT_SUBREADER,MSGL_ERR,"LIBSUB: error opening iconv descriptor.\n");
824
if (cp_tmp) free(cp_tmp);
831
size_t oleft = size - 1;
836
outbuf = malloc(size);
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;
850
mp_msg(MSGT_SUBREADER, MSGL_WARN, "LIBSUB: error recoding file.\n");
855
outbuf[osize - oleft - 1] = 0;
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");
869
* \brief read file contents into newly allocated buffer, recoding to utf-8
871
static char* read_file(char* fname)
878
FILE* fp = fopen(fname, "rb");
880
mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fopen failed\n", fname);
883
res = fseek(fp, 0, SEEK_END);
885
mp_msg(MSGT_GLOBAL, MSGL_WARN, "ass_read_file(%s): fseek failed\n", fname);
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);
899
mp_msg(MSGT_GLOBAL, MSGL_V, "file size: %ld\n", sz);
901
buf = malloc(sz + 1);
905
res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
907
mp_msg(MSGT_GLOBAL, MSGL_INFO, "Read failed, %d: %s\n", errno, strerror(errno));
913
} while (sz - bytes_read > 0);
919
char* tmpbuf = sub_recode(buf, sz);
928
* \brief Read subtitles from file.
929
* \param fname file name
930
* \return newly allocated track
932
ass_track_t* ass_read_file(char* fname)
938
buf = read_file(fname);
942
track = ass_new_track();
943
track->name = strdup(fname);
946
process_text(track, buf);
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;
952
// there is no explicit end-of-font marker in ssa/ass
953
if (track->parser_priv->fontname)
958
if (track->track_type == TRACK_TYPE_UNKNOWN) {
959
ass_free_track(track);
963
process_force_style(track);
965
mp_msg(MSGT_GLOBAL, MSGL_INFO, "LIBASS: added subtitle file: %s (%d styles, %d events)\n", fname, track->n_styles, track->n_events);
967
// dump_events(forced_tid);
972
* \brief read styles from file into already initialized track
974
int ass_read_styles(ass_track_t* track, char* fname)
977
parser_state_t old_state;
979
buf = read_file(fname);
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;
991
static char* validate_fname(char* name)
997
int sz = strlen(name);
999
q = fname = malloc(sz + 1);
1002
code = utf8_get_char(&p);
1005
if ( (code > 0x7F) ||
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
1033
void ass_process_font(const char* name, char* data, int data_size)
1041
char* fonts_dir = get_path("fonts");
1042
rc = stat(fonts_dir, &st);
1046
res = mkdir(fonts_dir, 0700);
1048
res = mkdir(fonts_dir);
1051
mp_msg(MSGT_GLOBAL, MSGL_WARN, "Failed to create: %s\n", fonts_dir);
1053
} else if (!S_ISDIR(st.st_mode)) {
1054
mp_msg(MSGT_GLOBAL, MSGL_WARN, "Not a directory: %s\n", fonts_dir);
1057
fname = validate_fname((char*)name);
1059
snprintf(buf, 1000, "%s/%s", fonts_dir, fname);
1063
fp = fopen(buf, "wb");
1066
fwrite(data, data_size, 1, fp);
1070
long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1073
if (movement == 0) return 0;
1074
if (track->n_events == 0) return 0;
1077
for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1079
for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1081
// -1 and n_events are ok
1082
assert(i >= -1); assert(i <= track->n_events);
1085
if (i >= track->n_events) i = track->n_events - 1;
1086
return ((long long)track->events[i].Start) - now;
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));