blob: c5fda64738e65116a0214eba684bd125665edc05
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 "subtitles.h" |
31 | #include "version.h" |
32 | #include "libavutil/bprint.h" |
33 | #include "libavutil/dict.h" |
34 | #include "libavutil/log.h" |
35 | #include "libavutil/macros.h" |
36 | |
37 | static int lrc_write_header(AVFormatContext *s) |
38 | { |
39 | const AVDictionaryEntry *metadata_item; |
40 | |
41 | if(s->nb_streams != 1 || |
42 | s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { |
43 | av_log(s, AV_LOG_ERROR, |
44 | "LRC supports only a single subtitle stream.\n"); |
45 | return AVERROR(EINVAL); |
46 | } |
47 | if(s->streams[0]->codecpar->codec_id != AV_CODEC_ID_SUBRIP && |
48 | s->streams[0]->codecpar->codec_id != AV_CODEC_ID_TEXT) { |
49 | av_log(s, AV_LOG_ERROR, "Unsupported subtitle codec: %s\n", |
50 | avcodec_get_name(s->streams[0]->codecpar->codec_id)); |
51 | return AVERROR(EINVAL); |
52 | } |
53 | avpriv_set_pts_info(s->streams[0], 64, 1, 100); |
54 | |
55 | ff_standardize_creation_time(s); |
56 | ff_metadata_conv_ctx(s, ff_lrc_metadata_conv, NULL); |
57 | if(!(s->flags & AVFMT_FLAG_BITEXACT)) { // avoid breaking regression tests |
58 | /* LRC provides a metadata slot for specifying encoder version |
59 | * in addition to encoder name. We will store LIBAVFORMAT_VERSION |
60 | * to it. |
61 | */ |
62 | av_dict_set(&s->metadata, "ve", AV_STRINGIFY(LIBAVFORMAT_VERSION), 0); |
63 | } else { |
64 | av_dict_set(&s->metadata, "ve", NULL, 0); |
65 | } |
66 | for(metadata_item = NULL; |
67 | (metadata_item = av_dict_get(s->metadata, "", metadata_item, |
68 | AV_DICT_IGNORE_SUFFIX));) { |
69 | char *delim; |
70 | if(!metadata_item->value[0]) { |
71 | continue; |
72 | } |
73 | while((delim = strchr(metadata_item->value, '\n'))) { |
74 | *delim = ' '; |
75 | } |
76 | while((delim = strchr(metadata_item->value, '\r'))) { |
77 | *delim = ' '; |
78 | } |
79 | avio_printf(s->pb, "[%s:%s]\n", |
80 | metadata_item->key, metadata_item->value); |
81 | } |
82 | avio_printf(s->pb, "\n"); |
83 | return 0; |
84 | } |
85 | |
86 | static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) |
87 | { |
88 | if(pkt->pts != AV_NOPTS_VALUE) { |
89 | char *data = av_malloc(pkt->size + 1); |
90 | char *line; |
91 | char *delim; |
92 | |
93 | if(!data) { |
94 | return AVERROR(ENOMEM); |
95 | } |
96 | memcpy(data, pkt->data, pkt->size); |
97 | data[pkt->size] = '\0'; |
98 | |
99 | for(delim = data + pkt->size - 1; |
100 | delim >= data && (delim[0] == '\n' || delim[0] == '\r'); delim--) { |
101 | delim[0] = '\0'; // Strip last empty lines |
102 | } |
103 | line = data; |
104 | while(line[0] == '\n' || line[0] == '\r') { |
105 | line++; // Skip first empty lines |
106 | } |
107 | |
108 | while(line) { |
109 | delim = strchr(line, '\n'); |
110 | if(delim) { |
111 | if(delim > line && delim[-1] == '\r') { |
112 | delim[-1] = '\0'; |
113 | } |
114 | delim[0] = '\0'; |
115 | delim++; |
116 | } |
117 | if(line[0] == '[') { |
118 | av_log(s, AV_LOG_WARNING, |
119 | "Subtitle starts with '[', may cause problems with LRC format.\n"); |
120 | } |
121 | |
122 | if(pkt->pts >= 0) { |
123 | avio_printf(s->pb, "[%02"PRId64":%02"PRId64".%02"PRId64"]", |
124 | (pkt->pts / 6000), |
125 | ((pkt->pts / 100) % 60), |
126 | (pkt->pts % 100)); |
127 | } else { |
128 | /* Offset feature of LRC can easily make pts negative, |
129 | * we just output it directly and let the player drop it. */ |
130 | avio_printf(s->pb, "[-%02"PRId64":%02"PRId64".%02"PRId64"]", |
131 | (-pkt->pts) / 6000, |
132 | ((-pkt->pts) / 100) % 60, |
133 | (-pkt->pts) % 100); |
134 | } |
135 | avio_printf(s->pb, "%s\n", line); |
136 | line = delim; |
137 | } |
138 | av_free(data); |
139 | } |
140 | return 0; |
141 | } |
142 | |
143 | AVOutputFormat ff_lrc_muxer = { |
144 | .name = "lrc", |
145 | .long_name = NULL_IF_CONFIG_SMALL("LRC lyrics"), |
146 | .extensions = "lrc", |
147 | .priv_data_size = 0, |
148 | .write_header = lrc_write_header, |
149 | .write_packet = lrc_write_packet, |
150 | .flags = AVFMT_VARIABLE_FPS | AVFMT_GLOBALHEADER | |
151 | AVFMT_TS_NEGATIVE | AVFMT_TS_NONSTRICT, |
152 | .subtitle_codec = AV_CODEC_ID_SUBRIP |
153 | }; |
154 |