xref: /third_party/ffmpeg/libavformat/lrcenc.c (revision cabdff1a)
1/*
2 * LRC lyrics file format decoder
3 * Copyright (c) 2014 StarBrilliant <m13253@hotmail.com>
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include <inttypes.h>
23#include <stdint.h>
24#include <string.h>
25
26#include "avformat.h"
27#include "internal.h"
28#include "lrc.h"
29#include "metadata.h"
30#include "mux.h"
31#include "version.h"
32#include "libavutil/dict.h"
33#include "libavutil/log.h"
34#include "libavutil/macros.h"
35
36static int lrc_write_header(AVFormatContext *s)
37{
38    const AVDictionaryEntry *metadata_item;
39
40    if(s->nb_streams != 1 ||
41       s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
42        av_log(s, AV_LOG_ERROR,
43               "LRC supports only a single subtitle stream.\n");
44        return AVERROR(EINVAL);
45    }
46    if(s->streams[0]->codecpar->codec_id != AV_CODEC_ID_SUBRIP &&
47       s->streams[0]->codecpar->codec_id != AV_CODEC_ID_TEXT) {
48        av_log(s, AV_LOG_ERROR, "Unsupported subtitle codec: %s\n",
49               avcodec_get_name(s->streams[0]->codecpar->codec_id));
50        return AVERROR(EINVAL);
51    }
52    avpriv_set_pts_info(s->streams[0], 64, 1, 100);
53
54    ff_standardize_creation_time(s);
55    ff_metadata_conv_ctx(s, ff_lrc_metadata_conv, NULL);
56    if(!(s->flags & AVFMT_FLAG_BITEXACT)) { // avoid breaking regression tests
57        /* LRC provides a metadata slot for specifying encoder version
58         * in addition to encoder name. We will store LIBAVFORMAT_VERSION
59         * to it.
60         */
61        av_dict_set(&s->metadata, "ve", AV_STRINGIFY(LIBAVFORMAT_VERSION), 0);
62    } else {
63        av_dict_set(&s->metadata, "ve", NULL, 0);
64    }
65    for(metadata_item = NULL;
66       (metadata_item = av_dict_get(s->metadata, "", metadata_item,
67                                    AV_DICT_IGNORE_SUFFIX));) {
68        char *delim;
69        if(!metadata_item->value[0]) {
70            continue;
71        }
72        while((delim = strchr(metadata_item->value, '\n'))) {
73            *delim = ' ';
74        }
75        while((delim = strchr(metadata_item->value, '\r'))) {
76            *delim = ' ';
77        }
78        avio_printf(s->pb, "[%s:%s]\n",
79                    metadata_item->key, metadata_item->value);
80    }
81    avio_w8(s->pb, '\n');
82    return 0;
83}
84
85static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt)
86{
87    if(pkt->pts != AV_NOPTS_VALUE) {
88        const uint8_t *line = pkt->data;
89        const uint8_t *end  = pkt->data + pkt->size;
90
91        while (end > line && (end[-1] == '\n' || end[-1] == '\r'))
92            end--;
93        if (line != end) {
94            while (line[0] == '\n' || line[0] == '\r')
95                line++; // Skip first empty lines
96        }
97
98        while(line) {
99            const uint8_t *next_line = memchr(line, '\n', end - line);
100            size_t size = end - line;
101
102            if (next_line) {
103                size = next_line - line;
104                if (next_line > line && next_line[-1] == '\r')
105                    size--;
106                next_line++;
107            }
108            if(line[0] == '[') {
109                av_log(s, AV_LOG_WARNING,
110                       "Subtitle starts with '[', may cause problems with LRC format.\n");
111            }
112
113            /* Offset feature of LRC can easily make pts negative,
114             * we just output it directly and let the player drop it. */
115            avio_write(s->pb, "[-", 1 + (pkt->pts < 0));
116            avio_printf(s->pb, "%02"PRIu64":%02"PRIu64".%02"PRIu64"]",
117                        (FFABS64U(pkt->pts) / 6000),
118                        ((FFABS64U(pkt->pts) / 100) % 60),
119                        (FFABS64U(pkt->pts) % 100));
120
121            avio_write(s->pb, line, size);
122            avio_w8(s->pb, '\n');
123            line = next_line;
124        }
125    }
126    return 0;
127}
128
129const AVOutputFormat ff_lrc_muxer = {
130    .name           = "lrc",
131    .long_name      = NULL_IF_CONFIG_SMALL("LRC lyrics"),
132    .extensions     = "lrc",
133    .priv_data_size = 0,
134    .write_header   = lrc_write_header,
135    .write_packet   = lrc_write_packet,
136    .flags          = AVFMT_VARIABLE_FPS | AVFMT_GLOBALHEADER |
137                      AVFMT_TS_NEGATIVE | AVFMT_TS_NONSTRICT,
138    .subtitle_codec = AV_CODEC_ID_SUBRIP
139};
140