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
22 static char const * id3tagmap
[][2] =
27 {"TRCK", "Tracknumber"},
31 {"TPOS", "Discnumber"},
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
;
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);
73 sprintf(id3tag_buf
, "TPOS=%s", comment
);
74 p
->id3tag_set_fieldvalue(p
->gfp
, id3tag_buf
);
80 #endif /* HAVE_LAME */
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
);
89 union id3_field
const * field
= id3_frame_field(frame
, 1);
90 unsigned nstrings
= id3_field_getnstrings(field
);
92 id3_ucs4_t
const * ucs4
= id3_field_getstrings(field
, nstrings
);
94 return id3_ucs4_utf8duplicate(ucs4
); /* Must call free() on this */
102 struct tag_info_node
* next
;
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
;
117 id3_byte_t query
[ID3_TAG_QUERYSIZE
];
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
))) {
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) {
135 /* Don't read a tag more than once. */
136 start
= lsx_tell(info
->ft
);
138 for (current
= info
->head
; current
; current
= current
->next
) {
139 if (start
== current
->start
&& end
== current
->end
) {
141 } else if (start
< current
->end
&& current
->start
< end
) {
146 buffer
= lsx_malloc((size_t)size
);
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
);
155 current
= lsx_malloc(sizeof(struct tag_info_node
));
157 current
->next
= info
->head
;
158 current
->start
= start
;
160 info
->head
= current
;
161 if (info
->tag
&& (info
->tag
->extendedflags
& ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE
)) {
162 struct id3_frame
* frame
;
164 for (i
= 0; (frame
= id3_tag_findframe(tag
, NULL
, i
)); i
++) {
165 id3_tag_attachframe(info
->tag
, frame
);
170 id3_tag_delete(info
->tag
);
181 static void read_comments(sox_format_t
* ft
)
183 struct tag_info info
;
194 ID3v1 at end (EOF - 128).
196 ID3v2 at end (but before ID3v1 from end if there was one).
199 if (0 == lsx_seeki(ft
, -128, SEEK_END
)) {
202 1 == ID3_TAG_VERSION_MAJOR(id3_tag_version(info
.tag
));
204 if (0 == lsx_seeki(ft
, 0, SEEK_SET
)) {
207 if (0 == lsx_seeki(ft
, has_id3v1
? -138 : -10, SEEK_END
)) {
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
);
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");
230 struct tag_info_node
* head
= info
.head
;
231 info
.head
= head
->next
;
235 id3_tag_delete(info
.tag
);
239 #endif /* USING_ID3TAG */
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);
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
);
274 lsx_debug("got exact duration by scan to EOF (frames=%" PRIuPTR
" leftover=%" PRIuPTR
")", frames
, leftover
);
277 for (; !depadded
&& padding
< read
&& !p
->mp3_buffer
[padding
]; ++padding
);
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");
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
);
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");
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
);
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");
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
);
347 #endif /* HAVE_MAD_H */