• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * SubViewer subtitle demuxer
24  * @see https://en.wikipedia.org/wiki/SubViewer
25  */
26 
27 #include "avformat.h"
28 #include "internal.h"
29 #include "subtitles.h"
30 #include "avio_internal.h"
31 #include "libavutil/avstring.h"
32 #include "libavutil/bprint.h"
33 #include "libavutil/intreadwrite.h"
34 
35 typedef struct {
36     FFDemuxSubtitlesQueue q;
37 } SubViewerContext;
38 
subviewer_probe(const AVProbeData * p)39 static int subviewer_probe(const AVProbeData *p)
40 {
41     char c;
42     const unsigned char *ptr = p->buf;
43 
44     if (AV_RB24(ptr) == 0xEFBBBF)
45         ptr += 3;  /* skip UTF-8 BOM */
46     if (sscanf(ptr, "%*u:%*u:%*u.%*u,%*u:%*u:%*u.%*u%c", &c) == 1)
47         return AVPROBE_SCORE_EXTENSION;
48     if (!strncmp(ptr, "[INFORMATION]", 13))
49         return AVPROBE_SCORE_MAX/3;
50     return 0;
51 }
52 
get_multiplier(int e)53 static int get_multiplier(int e) {
54     switch (e) {
55     case 1  : return 100;
56     case 2  : return 10;
57     case 3  : return 1;
58     default : return -1;
59     }
60 }
61 
read_ts(const char * s,int64_t * start,int * duration)62 static int read_ts(const char *s, int64_t *start, int *duration)
63 {
64     int64_t end;
65     int hh1, mm1, ss1, ms1;
66     int hh2, mm2, ss2, ms2;
67     int multiplier1, multiplier2;
68     int ms1p1, ms1p2, ms2p1, ms2p2;
69 
70     if (sscanf(s, "%u:%u:%u.%n%u%n,%u:%u:%u.%n%u%n",
71                &hh1, &mm1, &ss1, &ms1p1, &ms1, &ms1p2, &hh2, &mm2, &ss2, &ms2p1, &ms2, &ms2p2) == 8) {
72         multiplier1 = get_multiplier(ms1p2 - ms1p1);
73         multiplier2 = get_multiplier(ms2p2 - ms2p1);
74         if (multiplier1 <= 0 ||multiplier2 <= 0)
75             return -1;
76 
77         end    = (hh2*3600LL + mm2*60LL + ss2) * 1000LL + ms2 * multiplier2;
78         *start = (hh1*3600LL + mm1*60LL + ss1) * 1000LL + ms1 * multiplier1;
79         *duration = end - *start;
80         return 0;
81     }
82     return -1;
83 }
84 
subviewer_read_header(AVFormatContext * s)85 static int subviewer_read_header(AVFormatContext *s)
86 {
87     SubViewerContext *subviewer = s->priv_data;
88     AVStream *st = avformat_new_stream(s, NULL);
89     AVBPrint header;
90     int res = 0, new_event = 1;
91     int64_t pts_start = AV_NOPTS_VALUE;
92     int duration = -1;
93     AVPacket *sub = NULL;
94 
95     if (!st)
96         return AVERROR(ENOMEM);
97     res = ffio_ensure_seekback(s->pb, 3);
98     if (res < 0)
99         return res;
100     if (avio_rb24(s->pb) != 0xefbbbf)
101         avio_seek(s->pb, -3, SEEK_CUR);
102     avpriv_set_pts_info(st, 64, 1, 1000);
103     st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
104     st->codecpar->codec_id   = AV_CODEC_ID_SUBVIEWER;
105 
106     av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED);
107 
108     while (!avio_feof(s->pb)) {
109         char line[2048];
110         int64_t pos = 0;
111         int len = ff_get_line(s->pb, line, sizeof(line));
112 
113         if (!len)
114             break;
115 
116         line[strcspn(line, "\r\n")] = 0;
117 
118         if (line[0] == '[' && strncmp(line, "[br]", 4)) {
119 
120             /* ignore event style, XXX: add to side_data? */
121             if (strstr(line, "[COLF]") || strstr(line, "[SIZE]") ||
122                 strstr(line, "[FONT]") || strstr(line, "[STYLE]"))
123                 continue;
124 
125             if (!st->codecpar->extradata) { // header not finalized yet
126                 av_bprintf(&header, "%s\n", line);
127                 if (!strncmp(line, "[END INFORMATION]", 17) || !strncmp(line, "[SUBTITLE]", 10)) {
128                     /* end of header */
129                     res = ff_bprint_to_codecpar_extradata(st->codecpar, &header);
130                     if (res < 0)
131                         goto end;
132                 } else if (strncmp(line, "[INFORMATION]", 13)) {
133                     /* assume file metadata at this point */
134                     int i, j = 0;
135                     char key[32], value[128];
136 
137                     for (i = 1; i < sizeof(key) - 1 && line[i] && line[i] != ']'; i++)
138                         key[i - 1] = av_tolower(line[i]);
139                     key[i - 1] = 0;
140 
141                     if (line[i] == ']')
142                         i++;
143                     while (line[i] == ' ')
144                         i++;
145                     while (j < sizeof(value) - 1 && line[i] && line[i] != ']')
146                         value[j++] = line[i++];
147                     value[j] = 0;
148 
149                     av_dict_set(&s->metadata, key, value, 0);
150                 }
151             }
152         } else if (read_ts(line, &pts_start, &duration) >= 0) {
153             new_event = 1;
154             pos = avio_tell(s->pb);
155         } else if (*line) {
156             if (pts_start == AV_NOPTS_VALUE) {
157                 res = AVERROR_INVALIDDATA;
158                 goto end;
159             }
160             if (!new_event) {
161                 sub = ff_subtitles_queue_insert(&subviewer->q, "\n", 1, 1);
162                 if (!sub) {
163                     res = AVERROR(ENOMEM);
164                     goto end;
165                 }
166             }
167             sub = ff_subtitles_queue_insert(&subviewer->q, line, strlen(line), !new_event);
168             if (!sub) {
169                 res = AVERROR(ENOMEM);
170                 goto end;
171             }
172             if (new_event) {
173                 sub->pos = pos;
174                 sub->pts = pts_start;
175                 sub->duration = duration;
176             }
177             new_event = 0;
178         }
179     }
180 
181     ff_subtitles_queue_finalize(s, &subviewer->q);
182 
183 end:
184     av_bprint_finalize(&header, NULL);
185     return res;
186 }
187 
188 const AVInputFormat ff_subviewer_demuxer = {
189     .name           = "subviewer",
190     .long_name      = NULL_IF_CONFIG_SMALL("SubViewer subtitle format"),
191     .priv_data_size = sizeof(SubViewerContext),
192     .flags_internal = FF_FMT_INIT_CLEANUP,
193     .read_probe     = subviewer_probe,
194     .read_header    = subviewer_read_header,
195     .extensions     = "sub",
196     .read_packet    = ff_subtitles_read_packet,
197     .read_seek2     = ff_subtitles_read_seek,
198     .read_close     = ff_subtitles_read_close,
199 };
200