Coverage Report

Created: 2026-05-16 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavformat/microdvddec.c
Line
Count
Source
1
/*
2
 * MicroDVD subtitle demuxer
3
 * Copyright (c) 2010  Aurelien Jacobs <aurel@gnuage.org>
4
 * Copyright (c) 2012  Clément Bœsch <u pkh me>
5
 *
6
 * This file is part of FFmpeg.
7
 *
8
 * FFmpeg is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * FFmpeg is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with FFmpeg; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 */
22
23
#include "avformat.h"
24
#include "demux.h"
25
#include "internal.h"
26
#include "subtitles.h"
27
#include "libavutil/intreadwrite.h"
28
#include "libavutil/opt.h"
29
30
#define MAX_LINESIZE 2048
31
32
33
typedef struct {
34
    const AVClass *class;
35
    FFDemuxSubtitlesQueue q;
36
    AVRational frame_rate;
37
} MicroDVDContext;
38
39
40
static int microdvd_probe(const AVProbeData *p)
41
971k
{
42
971k
    unsigned char c;
43
971k
    const uint8_t *ptr = p->buf;
44
971k
    int i;
45
46
971k
    if (AV_RB24(ptr) == 0xEFBBBF)
47
827
        ptr += 3;  /* skip UTF-8 BOM */
48
49
972k
    for (i=0; i<3; i++) {
50
972k
        if (sscanf(ptr, "{%*d}{}%c",     &c) != 1 &&
51
971k
            sscanf(ptr, "{%*d}{%*d}%c",  &c) != 1 &&
52
971k
            sscanf(ptr, "{DEFAULT}{}%c", &c) != 1)
53
971k
            return 0;
54
904
        ptr += ff_subtitles_next_line(ptr);
55
904
    }
56
147
    return AVPROBE_SCORE_MAX;
57
971k
}
58
59
static int64_t get_pts(const char *buf)
60
2.30M
{
61
2.30M
    int frame;
62
2.30M
    char c;
63
64
2.30M
    if (sscanf(buf, "{%d}{%c", &frame, &c) == 2)
65
2.29M
        return frame;
66
9.16k
    return AV_NOPTS_VALUE;
67
2.30M
}
68
69
static int64_t get_duration(const char *buf)
70
2.29M
{
71
2.29M
    int frame_start, frame_end;
72
73
2.29M
    if (sscanf(buf, "{%d}{%d}", &frame_start, &frame_end) == 2)
74
7.75k
        return frame_end - (int64_t)frame_start;
75
2.29M
    return -1;
76
2.29M
}
77
78
static const char *bom = "\xEF\xBB\xBF";
79
80
static int microdvd_read_header(AVFormatContext *s)
81
1.85k
{
82
1.85k
    AVRational pts_info = (AVRational){ 2997, 125 };  /* default: 23.976 fps */
83
1.85k
    MicroDVDContext *microdvd = s->priv_data;
84
1.85k
    AVStream *st = avformat_new_stream(s, NULL);
85
1.85k
    int i = 0, ret;
86
1.85k
    char line_buf[MAX_LINESIZE];
87
1.85k
    int has_real_fps = 0;
88
89
1.85k
    if (!st)
90
0
        return AVERROR(ENOMEM);
91
92
5.87M
    while (!avio_feof(s->pb)) {
93
5.86M
        char *p;
94
5.86M
        AVPacket *sub;
95
5.86M
        int64_t pos = avio_tell(s->pb);
96
5.86M
        int len = ff_get_line(s->pb, line_buf, sizeof(line_buf));
97
5.86M
        char *line = line_buf;
98
5.86M
        int64_t pts;
99
100
5.86M
        if (!strncmp(line, bom, 3))
101
218
            line += 3;
102
5.86M
        p = line;
103
104
5.86M
        if (!len)
105
232
            break;
106
5.86M
        line[strcspn(line, "\r\n")] = 0;
107
5.86M
        if (!*p)
108
3.34M
            continue;
109
2.52M
        if (i++ < 3) {
110
3.26k
            int frame;
111
3.26k
            double fps;
112
3.26k
            char c;
113
114
3.26k
            if ((sscanf(line, "{%d}{}%6lf",    &frame, &fps) == 2 ||
115
2.41k
                 sscanf(line, "{%d}{%*d}%6lf", &frame, &fps) == 2)
116
1.06k
                && frame <= 1 && fps > 3 && fps < 100) {
117
308
                pts_info = av_d2q(fps, 100000);
118
308
                has_real_fps = 1;
119
308
                continue;
120
308
            }
121
2.95k
            if (!st->codecpar->extradata && sscanf(line, "{DEFAULT}{}%c", &c) == 1) {
122
60
                int size = strlen(line + 11);
123
60
                ret = ff_alloc_extradata(st->codecpar, size);
124
60
                if (ret < 0)
125
0
                    return ret;
126
60
                memcpy(st->codecpar->extradata, line + 11, size);
127
60
                continue;
128
60
            }
129
2.95k
        }
130
2.51M
#define SKIP_FRAME_ID                                       \
131
4.84M
    p = strchr(p, '}');                                     \
132
4.84M
    if (!p) {                                               \
133
209k
        av_log(s, AV_LOG_WARNING, "Invalid event \"%s\""    \
134
209k
               " at line %d\n", line, i);                   \
135
209k
        continue;                                           \
136
209k
    }                                                       \
137
4.84M
    p++
138
4.84M
        SKIP_FRAME_ID;
139
4.84M
        SKIP_FRAME_ID;
140
2.31M
        if (!*p)
141
2.00k
            continue;
142
2.30M
        pts = get_pts(line);
143
2.30M
        if (pts == AV_NOPTS_VALUE)
144
9.16k
            continue;
145
2.29M
        sub = ff_subtitles_queue_insert(&microdvd->q, p, strlen(p), 0);
146
2.29M
        if (!sub)
147
0
            return AVERROR(ENOMEM);
148
2.29M
        sub->pos = pos;
149
2.29M
        sub->pts = pts;
150
2.29M
        sub->duration = get_duration(line);
151
2.29M
    }
152
1.85k
    ff_subtitles_queue_finalize(s, &microdvd->q);
153
1.85k
    if (has_real_fps) {
154
        /* export the FPS info only if set in the file */
155
296
        microdvd->frame_rate = pts_info;
156
1.56k
    } else if (microdvd->frame_rate.num) {
157
        /* fallback on user specified frame rate */
158
0
        pts_info = microdvd->frame_rate;
159
0
    }
160
1.85k
    avpriv_set_pts_info(st, 64, pts_info.den, pts_info.num);
161
1.85k
    st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
162
1.85k
    st->codecpar->codec_id   = AV_CODEC_ID_MICRODVD;
163
1.85k
    return 0;
164
1.85k
}
165
166
static int microdvd_read_packet(AVFormatContext *s, AVPacket *pkt)
167
439k
{
168
439k
    MicroDVDContext *microdvd = s->priv_data;
169
439k
    return ff_subtitles_queue_read_packet(&microdvd->q, pkt);
170
439k
}
171
172
static int microdvd_read_seek(AVFormatContext *s, int stream_index,
173
                             int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
174
0
{
175
0
    MicroDVDContext *microdvd = s->priv_data;
176
0
    return ff_subtitles_queue_seek(&microdvd->q, s, stream_index,
177
0
                                   min_ts, ts, max_ts, flags);
178
0
}
179
180
static int microdvd_read_close(AVFormatContext *s)
181
1.85k
{
182
1.85k
    MicroDVDContext *microdvd = s->priv_data;
183
1.85k
    ff_subtitles_queue_clean(&microdvd->q);
184
1.85k
    return 0;
185
1.85k
}
186
187
188
#define OFFSET(x) offsetof(MicroDVDContext, x)
189
#define SD AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_DECODING_PARAM
190
static const AVOption microdvd_options[] = {
191
    { "subfps", "set the movie frame rate fallback", OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, SD },
192
    { NULL }
193
};
194
195
static const AVClass microdvd_class = {
196
    .class_name = "microdvddec",
197
    .item_name  = av_default_item_name,
198
    .option     = microdvd_options,
199
    .version    = LIBAVUTIL_VERSION_INT,
200
};
201
202
const FFInputFormat ff_microdvd_demuxer = {
203
    .p.name         = "microdvd",
204
    .p.long_name    = NULL_IF_CONFIG_SMALL("MicroDVD subtitle format"),
205
    .p.priv_class   = &microdvd_class,
206
    .priv_data_size = sizeof(MicroDVDContext),
207
    .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
208
    .read_probe     = microdvd_probe,
209
    .read_header    = microdvd_read_header,
210
    .read_packet    = microdvd_read_packet,
211
    .read_seek2     = microdvd_read_seek,
212
    .read_close     = microdvd_read_close,
213
};