wav: simplify extended fmt chunk handling [bug #198]
[sox.git] / src / mp3-util.h
blobfeb53df91fb59e854c5ecc81208379b9736e8713
1 /* libSoX MP3 utilities Copyright (c) 2007-9 SoX contributors
3 * This library is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation; either version 2.1 of the License, or (at
6 * your option) any later version.
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11 * General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library; if not, write to the Free Software Foundation,
15 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 #include <sys/stat.h>
20 #ifdef USING_ID3TAG
22 static char const * id3tagmap[][2] =
24 {"TIT2", "Title"},
25 {"TPE1", "Artist"},
26 {"TALB", "Album"},
27 {"TRCK", "Tracknumber"},
28 {"TDRC", "Year"},
29 {"TCON", "Genre"},
30 {"COMM", "Comment"},
31 {"TPOS", "Discnumber"},
32 {NULL, NULL}
35 #endif /* USING_ID3TAG */
37 #if defined(HAVE_LAME)
39 static void write_comments(sox_format_t * ft)
41 priv_t *p = (priv_t *) ft->priv;
42 const char* comment;
44 p->id3tag_init(p->gfp);
45 p->id3tag_set_pad(p->gfp, (size_t)ID3PADDING);
47 /* Note: id3tag_set_fieldvalue is not present in LAME 3.97, so we're using
48 the 3.97-compatible methods for all of the tags that 3.97 supported. */
49 /* FIXME: This is no more necessary, since support for LAME 3.97 has ended. */
50 if ((comment = sox_find_comment(ft->oob.comments, "Title")))
51 p->id3tag_set_title(p->gfp, comment);
52 if ((comment = sox_find_comment(ft->oob.comments, "Artist")))
53 p->id3tag_set_artist(p->gfp, comment);
54 if ((comment = sox_find_comment(ft->oob.comments, "Album")))
55 p->id3tag_set_album(p->gfp, comment);
56 if ((comment = sox_find_comment(ft->oob.comments, "Tracknumber")))
57 p->id3tag_set_track(p->gfp, comment);
58 if ((comment = sox_find_comment(ft->oob.comments, "Year")))
59 p->id3tag_set_year(p->gfp, comment);
60 if ((comment = sox_find_comment(ft->oob.comments, "Comment")))
61 p->id3tag_set_comment(p->gfp, comment);
62 if ((comment = sox_find_comment(ft->oob.comments, "Genre")))
64 if (p->id3tag_set_genre(p->gfp, comment))
65 lsx_warn("\"%s\" is not a recognized ID3v1 genre.", comment);
68 if ((comment = sox_find_comment(ft->oob.comments, "Discnumber")))
70 char* id3tag_buf = lsx_malloc(strlen(comment) + 6);
71 if (id3tag_buf)
73 sprintf(id3tag_buf, "TPOS=%s", comment);
74 p->id3tag_set_fieldvalue(p->gfp, id3tag_buf);
75 free(id3tag_buf);
80 #endif /* HAVE_LAME */
82 #ifdef USING_ID3TAG
84 static id3_utf8_t * utf8_id3tag_findframe(
85 struct id3_tag * tag, const char * const frameid, unsigned index)
87 struct id3_frame const * frame = id3_tag_findframe(tag, frameid, index);
88 if (frame) {
89 union id3_field const * field = id3_frame_field(frame, 1);
90 unsigned nstrings = id3_field_getnstrings(field);
91 while (nstrings--){
92 id3_ucs4_t const * ucs4 = id3_field_getstrings(field, nstrings);
93 if (ucs4)
94 return id3_ucs4_utf8duplicate(ucs4); /* Must call free() on this */
97 return NULL;
100 struct tag_info_node
102 struct tag_info_node * next;
103 off_t start;
104 off_t end;
107 struct tag_info {
108 sox_format_t * ft;
109 struct tag_info_node * head;
110 struct id3_tag * tag;
113 static int add_tag(struct tag_info * info)
115 struct tag_info_node * current;
116 off_t start, end;
117 id3_byte_t query[ID3_TAG_QUERYSIZE];
118 id3_byte_t * buffer;
119 long size;
120 int result = 0;
122 /* Ensure we're at the start of a valid tag and get its size. */
123 if (ID3_TAG_QUERYSIZE != lsx_readbuf(info->ft, query, ID3_TAG_QUERYSIZE) ||
124 !(size = id3_tag_query(query, ID3_TAG_QUERYSIZE))) {
125 return 0;
127 if (size < 0) {
128 if (0 != lsx_seeki(info->ft, size, SEEK_CUR) ||
129 ID3_TAG_QUERYSIZE != lsx_readbuf(info->ft, query, ID3_TAG_QUERYSIZE) ||
130 (size = id3_tag_query(query, ID3_TAG_QUERYSIZE)) <= 0) {
131 return 0;
135 /* Don't read a tag more than once. */
136 start = lsx_tell(info->ft);
137 end = start + size;
138 for (current = info->head; current; current = current->next) {
139 if (start == current->start && end == current->end) {
140 return 1;
141 } else if (start < current->end && current->start < end) {
142 return 0;
146 buffer = lsx_malloc((size_t)size);
147 if (!buffer) {
148 return 0;
150 memcpy(buffer, query, ID3_TAG_QUERYSIZE);
151 if ((unsigned long)size - ID3_TAG_QUERYSIZE ==
152 lsx_readbuf(info->ft, buffer + ID3_TAG_QUERYSIZE, (size_t)size - ID3_TAG_QUERYSIZE)) {
153 struct id3_tag * tag = id3_tag_parse(buffer, (size_t)size);
154 if (tag) {
155 current = lsx_malloc(sizeof(struct tag_info_node));
156 if (current) {
157 current->next = info->head;
158 current->start = start;
159 current->end = end;
160 info->head = current;
161 if (info->tag && (info->tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)) {
162 struct id3_frame * frame;
163 unsigned i;
164 for (i = 0; (frame = id3_tag_findframe(tag, NULL, i)); i++) {
165 id3_tag_attachframe(info->tag, frame);
167 id3_tag_delete(tag);
168 } else {
169 if (info->tag) {
170 id3_tag_delete(info->tag);
172 info->tag = tag;
177 free(buffer);
178 return result;
181 static void read_comments(sox_format_t * ft)
183 struct tag_info info;
184 id3_utf8_t * utf8;
185 int i;
186 int has_id3v1 = 0;
188 info.ft = ft;
189 info.head = NULL;
190 info.tag = NULL;
193 We look for:
194 ID3v1 at end (EOF - 128).
195 ID3v2 at start.
196 ID3v2 at end (but before ID3v1 from end if there was one).
199 if (0 == lsx_seeki(ft, -128, SEEK_END)) {
200 has_id3v1 =
201 add_tag(&info) &&
202 1 == ID3_TAG_VERSION_MAJOR(id3_tag_version(info.tag));
204 if (0 == lsx_seeki(ft, 0, SEEK_SET)) {
205 add_tag(&info);
207 if (0 == lsx_seeki(ft, has_id3v1 ? -138 : -10, SEEK_END)) {
208 add_tag(&info);
210 if (info.tag && info.tag->frames) {
211 for (i = 0; id3tagmap[i][0]; ++i) {
212 if ((utf8 = utf8_id3tag_findframe(info.tag, id3tagmap[i][0], 0))) {
213 char * comment = lsx_malloc(strlen(id3tagmap[i][1]) + 1 + strlen((char *)utf8) + 1);
214 sprintf(comment, "%s=%s", id3tagmap[i][1], utf8);
215 sox_append_comment(&ft->oob.comments, comment);
216 free(comment);
217 free(utf8);
220 if ((utf8 = utf8_id3tag_findframe(info.tag, "TLEN", 0))) {
221 unsigned long tlen = strtoul((char *)utf8, NULL, 10);
222 if (tlen > 0 && tlen < ULONG_MAX) {
223 ft->signal.length= tlen; /* In ms; convert to samples later */
224 lsx_debug("got exact duration from ID3 TLEN");
226 free(utf8);
229 while (info.head) {
230 struct tag_info_node * head = info.head;
231 info.head = head->next;
232 free(head);
234 if (info.tag) {
235 id3_tag_delete(info.tag);
239 #endif /* USING_ID3TAG */
241 #ifdef HAVE_MAD_H
243 static unsigned long xing_frames(priv_t * p, struct mad_bitptr ptr, unsigned bitlen)
245 #define XING_MAGIC ( ('X' << 24) | ('i' << 16) | ('n' << 8) | 'g' )
246 if (bitlen >= 96 && p->mad_bit_read(&ptr, 32) == XING_MAGIC &&
247 (p->mad_bit_read(&ptr, 32) & 1 )) /* XING_FRAMES */
248 return p->mad_bit_read(&ptr, 32);
249 return 0;
252 static size_t mp3_duration(sox_format_t * ft)
254 priv_t * p = (priv_t *) ft->priv;
255 struct mad_stream mad_stream;
256 struct mad_header mad_header;
257 struct mad_frame mad_frame;
258 size_t initial_bitrate = 0; /* Initialised to prevent warning */
259 size_t tagsize = 0, consumed = 0, frames = 0;
260 sox_bool vbr = sox_false, depadded = sox_false;
261 size_t num_samples = 0;
263 p->mad_stream_init(&mad_stream);
264 p->mad_header_init(&mad_header);
265 p->mad_frame_init(&mad_frame);
267 do { /* Read data from the MP3 file */
268 int read, padding = 0;
269 size_t leftover = mad_stream.bufend - mad_stream.next_frame;
271 memmove(p->mp3_buffer, mad_stream.this_frame, leftover);
272 read = lsx_readbuf(ft, p->mp3_buffer + leftover, p->mp3_buffer_size - leftover);
273 if (read <= 0) {
274 lsx_debug("got exact duration by scan to EOF (frames=%" PRIuPTR " leftover=%" PRIuPTR ")", frames, leftover);
275 break;
277 for (; !depadded && padding < read && !p->mp3_buffer[padding]; ++padding);
278 depadded = sox_true;
279 p->mad_stream_buffer(&mad_stream, p->mp3_buffer + padding, leftover + read - padding);
281 while (sox_true) { /* Decode frame headers */
282 mad_stream.error = MAD_ERROR_NONE;
283 if (p->mad_header_decode(&mad_header, &mad_stream) == -1) {
284 if (mad_stream.error == MAD_ERROR_BUFLEN)
285 break; /* Normal behaviour; get some more data from the file */
286 if (!MAD_RECOVERABLE(mad_stream.error)) {
287 lsx_warn("unrecoverable MAD error");
288 break;
290 if (mad_stream.error == MAD_ERROR_LOSTSYNC) {
291 unsigned available = (mad_stream.bufend - mad_stream.this_frame);
292 tagsize = tagtype(mad_stream.this_frame, (size_t) available);
293 if (tagsize) { /* It's some ID3 tags, so just skip */
294 if (tagsize >= available) {
295 lsx_seeki(ft, (off_t)(tagsize - available), SEEK_CUR);
296 depadded = sox_false;
298 p->mad_stream_skip(&mad_stream, min(tagsize, available));
300 else lsx_warn("MAD lost sync");
302 else lsx_warn("recoverable MAD error");
303 continue; /* Not an audio frame */
306 num_samples += MAD_NSBSAMPLES(&mad_header) * 32;
307 consumed += mad_stream.next_frame - mad_stream.this_frame;
309 lsx_debug_more("bitrate=%lu", mad_header.bitrate);
310 if (!frames) {
311 initial_bitrate = mad_header.bitrate;
313 /* Get the precise frame count from the XING header if present */
314 mad_frame.header = mad_header;
315 if (p->mad_frame_decode(&mad_frame, &mad_stream) == -1)
316 if (!MAD_RECOVERABLE(mad_stream.error)) {
317 lsx_warn("unrecoverable MAD error");
318 break;
320 if ((frames = xing_frames(p, mad_stream.anc_ptr, mad_stream.anc_bitlen))) {
321 num_samples *= frames;
322 lsx_debug("got exact duration from XING frame count (%" PRIuPTR ")", frames);
323 break;
326 else vbr |= mad_header.bitrate != initial_bitrate;
328 /* If not VBR, we can time just a few frames then extrapolate */
329 if (++frames == 25 && !vbr) {
330 double frame_size = (double) consumed / frames;
331 size_t num_frames = (lsx_filelength(ft) - tagsize) / frame_size;
332 num_samples = num_samples / frames * num_frames;
333 lsx_debug("got approx. duration by CBR extrapolation");
334 break;
337 } while (mad_stream.error == MAD_ERROR_BUFLEN);
339 p->mad_frame_finish(&mad_frame);
340 mad_header_finish(&mad_header);
341 p->mad_stream_finish(&mad_stream);
342 lsx_rewind(ft);
344 return num_samples;
347 #endif /* HAVE_MAD_H */