xref: /third_party/ffmpeg/libavformat/mpsubdec.c (revision cabdff1a)
1/*
2 * Copyright (c) 2012 Clément Bœsch
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/**
22 * @file
23 * MPlayer subtitles format demuxer
24 */
25
26#include "avformat.h"
27#include "internal.h"
28#include "subtitles.h"
29
30#define TSBASE 10000000
31
32typedef struct {
33    FFDemuxSubtitlesQueue q;
34} MPSubContext;
35
36static int mpsub_probe(const AVProbeData *p)
37{
38    const char *ptr     = p->buf;
39    const char *ptr_end = p->buf + p->buf_size;
40
41    while (ptr < ptr_end) {
42        int inc;
43
44        if (!memcmp(ptr, "FORMAT=TIME", 11))
45            return AVPROBE_SCORE_EXTENSION;
46        if (!memcmp(ptr, "FORMAT=", 7))
47            return AVPROBE_SCORE_EXTENSION / 3;
48        inc = ff_subtitles_next_line(ptr);
49        if (!inc)
50            break;
51        ptr += inc;
52    }
53    return 0;
54}
55
56static int parse_line(const char *line, int64_t *value, int64_t *value2)
57{
58    int vi, p1, p2;
59
60    for (vi = 0; vi < 2; vi++) {
61        long long intval, fracval;
62        int n = av_sscanf(line, "%lld%n.%lld%n", &intval, &p1, &fracval, &p2);
63        if (n <= 0 || intval < INT64_MIN / TSBASE || intval > INT64_MAX / TSBASE)
64            return AVERROR_INVALIDDATA;
65
66        intval *= TSBASE;
67
68        if (n == 2) {
69            if (fracval < 0)
70                return AVERROR_INVALIDDATA;
71            for (;p2 - p1 < 7 + 1; p1--)
72                fracval *= 10;
73            for (;p2 - p1 > 7 + 1; p1++)
74                fracval /= 10;
75            if (intval > 0) intval = av_sat_add64(intval, fracval);
76            else            intval = av_sat_sub64(intval, fracval);
77            line += p2;
78        } else
79            line += p1;
80
81        *value = intval;
82
83        value = value2;
84    }
85
86    return 0;
87}
88
89static int mpsub_read_header(AVFormatContext *s)
90{
91    MPSubContext *mpsub = s->priv_data;
92    AVStream *st;
93    AVBPrint buf;
94    AVRational pts_info = (AVRational){ TSBASE, 1 }; // ts based by default
95    int res = 0;
96    int64_t current_pts = 0;
97    int i;
98    int common_factor = 0;
99
100    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
101
102    while (!avio_feof(s->pb)) {
103        char line[1024];
104        int64_t start, duration;
105        int fps, len = ff_get_line(s->pb, line, sizeof(line));
106
107        if (!len)
108            break;
109
110        line[strcspn(line, "\r\n")] = 0;
111
112        if (sscanf(line, "FORMAT=%d", &fps) == 1 && fps > 3 && fps < 100) {
113            /* frame based timing */
114            pts_info = (AVRational){ TSBASE * fps, 1 };
115        } else if (parse_line(line, &start, &duration) >= 0) {
116            AVPacket *sub;
117            const int64_t pos = avio_tell(s->pb);
118
119            ff_subtitles_read_chunk(s->pb, &buf);
120            if (buf.len) {
121                sub = ff_subtitles_queue_insert(&mpsub->q, buf.str, buf.len, 0);
122                if (!sub) {
123                    res = AVERROR(ENOMEM);
124                    goto end;
125                }
126                if (   current_pts < 0 && start < INT64_MIN - current_pts
127                    || current_pts > 0 && start > INT64_MAX - current_pts) {
128                    res = AVERROR_INVALIDDATA;
129                    goto end;
130                }
131                sub->pts = current_pts + start;
132                if (duration < 0 || sub->pts > INT64_MAX - duration) {
133                    res = AVERROR_INVALIDDATA;
134                    goto end;
135                }
136                sub->duration = duration;
137
138                common_factor = av_gcd(duration, common_factor);
139                common_factor = av_gcd(sub->pts, common_factor);
140
141                current_pts = sub->pts + duration;
142                sub->pos = pos;
143            }
144        }
145    }
146
147    if (common_factor > 1) {
148        common_factor = av_gcd(pts_info.num, common_factor);
149        for (i = 0; i < mpsub->q.nb_subs; i++) {
150            mpsub->q.subs[i]->pts      /= common_factor;
151            mpsub->q.subs[i]->duration /= common_factor;
152        }
153        pts_info.num /= common_factor;
154    }
155
156    st = avformat_new_stream(s, NULL);
157    if (!st) {
158        res = AVERROR(ENOMEM);
159        goto end;
160    }
161    avpriv_set_pts_info(st, 64, pts_info.den, pts_info.num);
162    st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
163    st->codecpar->codec_id   = AV_CODEC_ID_TEXT;
164
165    ff_subtitles_queue_finalize(s, &mpsub->q);
166
167end:
168    av_bprint_finalize(&buf, NULL);
169    return res;
170}
171
172const AVInputFormat ff_mpsub_demuxer = {
173    .name           = "mpsub",
174    .long_name      = NULL_IF_CONFIG_SMALL("MPlayer subtitles"),
175    .priv_data_size = sizeof(MPSubContext),
176    .flags_internal = FF_FMT_INIT_CLEANUP,
177    .read_probe     = mpsub_probe,
178    .read_header    = mpsub_read_header,
179    .extensions     = "sub",
180    .read_packet    = ff_subtitles_read_packet,
181    .read_seek2     = ff_subtitles_read_seek,
182    .read_close     = ff_subtitles_read_close,
183};
184