]> git.sesse.net Git - ffmpeg/blob - libavformat/subtitles.c
Merge commit 'aa51b0492bfced6d650fb5ff419e2b13fde6833d'
[ffmpeg] / libavformat / subtitles.c
1 /*
2  * Copyright (c) 2012-2013 Clément Bœsch <u pkh me>
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "avformat.h"
22 #include "subtitles.h"
23 #include "libavutil/avassert.h"
24 #include "libavutil/avstring.h"
25
26 AVPacket *ff_subtitles_queue_insert(FFDemuxSubtitlesQueue *q,
27                                     const uint8_t *event, int len, int merge)
28 {
29     AVPacket *subs, *sub;
30
31     if (merge && q->nb_subs > 0) {
32         /* merge with previous event */
33
34         int old_len;
35         sub = &q->subs[q->nb_subs - 1];
36         old_len = sub->size;
37         if (av_grow_packet(sub, len) < 0)
38             return NULL;
39         memcpy(sub->data + old_len, event, len);
40     } else {
41         /* new event */
42
43         if (q->nb_subs >= INT_MAX/sizeof(*q->subs) - 1)
44             return NULL;
45         subs = av_fast_realloc(q->subs, &q->allocated_size,
46                                (q->nb_subs + 1) * sizeof(*q->subs));
47         if (!subs)
48             return NULL;
49         q->subs = subs;
50         sub = &subs[q->nb_subs++];
51         if (av_new_packet(sub, len) < 0)
52             return NULL;
53         sub->flags |= AV_PKT_FLAG_KEY;
54         sub->pts = sub->dts = 0;
55         memcpy(sub->data, event, len);
56     }
57     return sub;
58 }
59
60 static int cmp_pkt_sub_ts_pos(const void *a, const void *b)
61 {
62     const AVPacket *s1 = a;
63     const AVPacket *s2 = b;
64     if (s1->pts == s2->pts) {
65         if (s1->pos == s2->pos)
66             return 0;
67         return s1->pos > s2->pos ? 1 : -1;
68     }
69     return s1->pts > s2->pts ? 1 : -1;
70 }
71
72 static int cmp_pkt_sub_pos_ts(const void *a, const void *b)
73 {
74     const AVPacket *s1 = a;
75     const AVPacket *s2 = b;
76     if (s1->pos == s2->pos) {
77         if (s1->pts == s2->pts)
78             return 0;
79         return s1->pts > s2->pts ? 1 : -1;
80     }
81     return s1->pos > s2->pos ? 1 : -1;
82 }
83
84 void ff_subtitles_queue_finalize(FFDemuxSubtitlesQueue *q)
85 {
86     int i;
87
88     qsort(q->subs, q->nb_subs, sizeof(*q->subs),
89           q->sort == SUB_SORT_TS_POS ? cmp_pkt_sub_ts_pos
90                                      : cmp_pkt_sub_pos_ts);
91     for (i = 0; i < q->nb_subs; i++)
92         if (q->subs[i].duration == -1 && i < q->nb_subs - 1)
93             q->subs[i].duration = q->subs[i + 1].pts - q->subs[i].pts;
94 }
95
96 int ff_subtitles_queue_read_packet(FFDemuxSubtitlesQueue *q, AVPacket *pkt)
97 {
98     AVPacket *sub = q->subs + q->current_sub_idx;
99
100     if (q->current_sub_idx == q->nb_subs)
101         return AVERROR_EOF;
102     if (av_copy_packet(pkt, sub) < 0) {
103         return AVERROR(ENOMEM);
104     }
105
106     pkt->dts = pkt->pts;
107     q->current_sub_idx++;
108     return 0;
109 }
110
111 static int search_sub_ts(const FFDemuxSubtitlesQueue *q, int64_t ts)
112 {
113     int s1 = 0, s2 = q->nb_subs - 1;
114
115     if (s2 < s1)
116         return AVERROR(ERANGE);
117
118     for (;;) {
119         int mid;
120
121         if (s1 == s2)
122             return s1;
123         if (s1 == s2 - 1)
124             return q->subs[s1].pts <= q->subs[s2].pts ? s1 : s2;
125         mid = (s1 + s2) / 2;
126         if (q->subs[mid].pts <= ts)
127             s1 = mid;
128         else
129             s2 = mid;
130     }
131 }
132
133 int ff_subtitles_queue_seek(FFDemuxSubtitlesQueue *q, AVFormatContext *s, int stream_index,
134                             int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
135 {
136     if (flags & AVSEEK_FLAG_BYTE) {
137         return AVERROR(ENOSYS);
138     } else if (flags & AVSEEK_FLAG_FRAME) {
139         if (ts < 0 || ts >= q->nb_subs)
140             return AVERROR(ERANGE);
141         q->current_sub_idx = ts;
142     } else {
143         int i, idx = search_sub_ts(q, ts);
144         int64_t ts_selected;
145
146         if (idx < 0)
147             return idx;
148         for (i = idx; i < q->nb_subs && q->subs[i].pts < min_ts; i++)
149             if (stream_index == -1 || q->subs[i].stream_index == stream_index)
150                 idx = i;
151         for (i = idx; i > 0 && q->subs[i].pts > max_ts; i--)
152             if (stream_index == -1 || q->subs[i].stream_index == stream_index)
153                 idx = i;
154
155         ts_selected = q->subs[idx].pts;
156         if (ts_selected < min_ts || ts_selected > max_ts)
157             return AVERROR(ERANGE);
158
159         /* look back in the latest subtitles for overlapping subtitles */
160         for (i = idx - 1; i >= 0; i--) {
161             int64_t pts = q->subs[i].pts;
162             if (q->subs[i].duration <= 0 ||
163                 (stream_index != -1 && q->subs[i].stream_index != stream_index))
164                 continue;
165             if (pts >= min_ts && pts > ts_selected - q->subs[i].duration)
166                 idx = i;
167             else
168                 break;
169         }
170
171         /* If the queue is used to store multiple subtitles streams (like with
172          * VobSub) and the stream index is not specified, we need to make sure
173          * to focus on the smallest file position offset for a same timestamp;
174          * queue is ordered by pts and then filepos, so we can take the first
175          * entry for a given timestamp. */
176         if (stream_index == -1)
177             while (idx > 0 && q->subs[idx - 1].pts == q->subs[idx].pts)
178                 idx--;
179
180         q->current_sub_idx = idx;
181     }
182     return 0;
183 }
184
185 void ff_subtitles_queue_clean(FFDemuxSubtitlesQueue *q)
186 {
187     int i;
188
189     for (i = 0; i < q->nb_subs; i++)
190         av_free_packet(&q->subs[i]);
191     av_freep(&q->subs);
192     q->nb_subs = q->allocated_size = q->current_sub_idx = 0;
193 }
194
195 int ff_smil_extract_next_chunk(AVIOContext *pb, AVBPrint *buf, char *c)
196 {
197     int i = 0;
198     char end_chr;
199
200     if (!*c) // cached char?
201         *c = avio_r8(pb);
202     if (!*c)
203         return 0;
204
205     end_chr = *c == '<' ? '>' : '<';
206     do {
207         av_bprint_chars(buf, *c, 1);
208         *c = avio_r8(pb);
209         i++;
210     } while (*c != end_chr && *c);
211     if (end_chr == '>') {
212         av_bprint_chars(buf, '>', 1);
213         *c = 0;
214     }
215     return i;
216 }
217
218 const char *ff_smil_get_attr_ptr(const char *s, const char *attr)
219 {
220     int in_quotes = 0;
221     const int len = strlen(attr);
222
223     while (*s) {
224         while (*s) {
225             if (!in_quotes && av_isspace(*s))
226                 break;
227             in_quotes ^= *s == '"'; // XXX: support escaping?
228             s++;
229         }
230         while (av_isspace(*s))
231             s++;
232         if (!av_strncasecmp(s, attr, len) && s[len] == '=')
233             return s + len + 1 + (s[len + 1] == '"');
234     }
235     return NULL;
236 }
237
238 static inline int is_eol(char c)
239 {
240     return c == '\r' || c == '\n';
241 }
242
243 void ff_subtitles_read_chunk(AVIOContext *pb, AVBPrint *buf)
244 {
245     char eol_buf[5], last_was_cr = 0;
246     int n = 0, i = 0, nb_eol = 0;
247
248     av_bprint_clear(buf);
249
250     for (;;) {
251         char c = avio_r8(pb);
252
253         if (!c)
254             break;
255
256         /* ignore all initial line breaks */
257         if (n == 0 && is_eol(c))
258             continue;
259
260         /* line break buffering: we don't want to add the trailing \r\n */
261         if (is_eol(c)) {
262             nb_eol += c == '\n' || last_was_cr;
263             if (nb_eol == 2)
264                 break;
265             eol_buf[i++] = c;
266             if (i == sizeof(eol_buf) - 1)
267                 break;
268             last_was_cr = c == '\r';
269             continue;
270         }
271
272         /* only one line break followed by data: we flush the line breaks
273          * buffer */
274         if (i) {
275             eol_buf[i] = 0;
276             av_bprintf(buf, "%s", eol_buf);
277             i = nb_eol = 0;
278         }
279
280         av_bprint_chars(buf, c, 1);
281         n++;
282     }
283 }