Coverage Report

Created: 2026-01-16 07:48

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
958k
{
42
958k
    unsigned char c;
43
958k
    const uint8_t *ptr = p->buf;
44
958k
    int i;
45
46
958k
    if (AV_RB24(ptr) == 0xEFBBBF)
47
807
        ptr += 3;  /* skip UTF-8 BOM */
48
49
959k
    for (i=0; i<3; i++) {
50
959k
        if (sscanf(ptr, "{%*d}{}%c",     &c) != 1 &&
51
958k
            sscanf(ptr, "{%*d}{%*d}%c",  &c) != 1 &&
52
958k
            sscanf(ptr, "{DEFAULT}{}%c", &c) != 1)
53
958k
            return 0;
54
896
        ptr += ff_subtitles_next_line(ptr);
55
896
    }
56
136
    return AVPROBE_SCORE_MAX;
57
958k
}
58
59
static int64_t get_pts(const char *buf)
60
1.79M
{
61
1.79M
    int frame;
62
1.79M
    char c;
63
64
1.79M
    if (sscanf(buf, "{%d}{%c", &frame, &c) == 2)
65
1.78M
        return frame;
66
10.6k
    return AV_NOPTS_VALUE;
67
1.79M
}
68
69
static int64_t get_duration(const char *buf)
70
1.78M
{
71
1.78M
    int frame_start, frame_end;
72
73
1.78M
    if (sscanf(buf, "{%d}{%d}", &frame_start, &frame_end) == 2)
74
27.8k
        return frame_end - (int64_t)frame_start;
75
1.75M
    return -1;
76
1.78M
}
77
78
static const char *bom = "\xEF\xBB\xBF";
79
80
static int microdvd_read_header(AVFormatContext *s)
81
1.80k
{
82
1.80k
    AVRational pts_info = (AVRational){ 2997, 125 };  /* default: 23.976 fps */
83
1.80k
    MicroDVDContext *microdvd = s->priv_data;
84
1.80k
    AVStream *st = avformat_new_stream(s, NULL);
85
1.80k
    int i = 0, ret;
86
1.80k
    char line_buf[MAX_LINESIZE];
87
1.80k
    int has_real_fps = 0;
88
89
1.80k
    if (!st)
90
0
        return AVERROR(ENOMEM);
91
92
5.00M
    while (!avio_feof(s->pb)) {
93
5.00M
        char *p;
94
5.00M
        AVPacket *sub;
95
5.00M
        int64_t pos = avio_tell(s->pb);
96
5.00M
        int len = ff_get_line(s->pb, line_buf, sizeof(line_buf));
97
5.00M
        char *line = line_buf;
98
5.00M
        int64_t pts;
99
100
5.00M
        if (!strncmp(line, bom, 3))
101
253
            line += 3;
102
5.00M
        p = line;
103
104
5.00M
        if (!len)
105
228
            break;
106
5.00M
        line[strcspn(line, "\r\n")] = 0;
107
5.00M
        if (!*p)
108
3.18M
            continue;
109
1.82M
        if (i++ < 3) {
110
3.12k
            int frame;
111
3.12k
            double fps;
112
3.12k
            char c;
113
114
3.12k
            if ((sscanf(line, "{%d}{}%6lf",    &frame, &fps) == 2 ||
115
2.28k
                 sscanf(line, "{%d}{%*d}%6lf", &frame, &fps) == 2)
116
1.04k
                && frame <= 1 && fps > 3 && fps < 100) {
117
334
                pts_info = av_d2q(fps, 100000);
118
334
                has_real_fps = 1;
119
334
                continue;
120
334
            }
121
2.79k
            if (!st->codecpar->extradata && sscanf(line, "{DEFAULT}{}%c", &c) == 1) {
122
53
                int size = strlen(line + 11);
123
53
                ret = ff_alloc_extradata(st->codecpar, size);
124
53
                if (ret < 0)
125
0
                    return ret;
126
53
                memcpy(st->codecpar->extradata, line + 11, size);
127
53
                continue;
128
53
            }
129
2.79k
        }
130
1.82M
#define SKIP_FRAME_ID                                       \
131
3.62M
    p = strchr(p, '}');                                     \
132
3.62M
    if (!p) {                                               \
133
28.3k
        av_log(s, AV_LOG_WARNING, "Invalid event \"%s\""    \
134
28.3k
               " at line %d\n", line, i);                   \
135
28.3k
        continue;                                           \
136
28.3k
    }                                                       \
137
3.62M
    p++
138
3.62M
        SKIP_FRAME_ID;
139
3.62M
        SKIP_FRAME_ID;
140
1.79M
        if (!*p)
141
2.54k
            continue;
142
1.79M
        pts = get_pts(line);
143
1.79M
        if (pts == AV_NOPTS_VALUE)
144
10.6k
            continue;
145
1.78M
        sub = ff_subtitles_queue_insert(&microdvd->q, p, strlen(p), 0);
146
1.78M
        if (!sub)
147
0
            return AVERROR(ENOMEM);
148
1.78M
        sub->pos = pos;
149
1.78M
        sub->pts = pts;
150
1.78M
        sub->duration = get_duration(line);
151
1.78M
    }
152
1.80k
    ff_subtitles_queue_finalize(s, &microdvd->q);
153
1.80k
    if (has_real_fps) {
154
        /* export the FPS info only if set in the file */
155
322
        microdvd->frame_rate = pts_info;
156
1.48k
    } else if (microdvd->frame_rate.num) {
157
        /* fallback on user specified frame rate */
158
0
        pts_info = microdvd->frame_rate;
159
0
    }
160
1.80k
    avpriv_set_pts_info(st, 64, pts_info.den, pts_info.num);
161
1.80k
    st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
162
1.80k
    st->codecpar->codec_id   = AV_CODEC_ID_MICRODVD;
163
1.80k
    return 0;
164
1.80k
}
165
166
static int microdvd_read_packet(AVFormatContext *s, AVPacket *pkt)
167
335k
{
168
335k
    MicroDVDContext *microdvd = s->priv_data;
169
335k
    return ff_subtitles_queue_read_packet(&microdvd->q, pkt);
170
335k
}
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.80k
{
182
1.80k
    MicroDVDContext *microdvd = s->priv_data;
183
1.80k
    ff_subtitles_queue_clean(&microdvd->q);
184
1.80k
    return 0;
185
1.80k
}
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
};