1 /* libSoX MP3 utilities  Copyright (c) 2007-9 SoX contributors
2  *
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.
7  *
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.
12  *
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
16  */
17 
18 #include "sox_i.h"
19 #include "id3.h"
20 
21 #ifdef HAVE_ID3TAG
22 
23 #include <id3tag.h>
24 
25 static char const * id3tagmap[][2] =
26 {
27   {"TIT2", "Title"},
28   {"TPE1", "Artist"},
29   {"TALB", "Album"},
30   {"TCOM", "Composer"},
31   {"TRCK", "Tracknumber"},
32   {"TDRC", "Year"},
33   {"TCON", "Genre"},
34   {"COMM", "Comment"},
35   {"TPOS", "Discnumber"},
36   {NULL, NULL}
37 };
38 
utf8_id3tag_findframe(struct id3_tag * tag,const char * const frameid,unsigned index)39 static id3_utf8_t * utf8_id3tag_findframe(
40     struct id3_tag * tag, const char * const frameid, unsigned index)
41 {
42   struct id3_frame const * frame = id3_tag_findframe(tag, frameid, index);
43   if (frame) {
44     unsigned nfields = frame->nfields;
45 
46     while (nfields--) {
47       union id3_field const *field = id3_frame_field(frame, nfields);
48       int ftype = id3_field_type(field);
49       const id3_ucs4_t *ucs4 = NULL;
50       unsigned nstrings;
51 
52       switch (ftype) {
53       case ID3_FIELD_TYPE_STRING:
54         ucs4 = id3_field_getstring(field);
55         break;
56 
57       case ID3_FIELD_TYPE_STRINGFULL:
58         ucs4 = id3_field_getfullstring(field);
59         break;
60 
61       case ID3_FIELD_TYPE_STRINGLIST:
62         nstrings = id3_field_getnstrings(field);
63         while (nstrings--) {
64           ucs4 = id3_field_getstrings(field, nstrings);
65           if (ucs4)
66             break;
67         }
68         break;
69       }
70 
71       if (ucs4)
72         return id3_ucs4_utf8duplicate(ucs4); /* Must call free() on this */
73     }
74   }
75   return NULL;
76 }
77 
78 struct tag_info_node
79 {
80     struct tag_info_node * next;
81     off_t start;
82     off_t end;
83 };
84 
85 struct tag_info {
86   sox_format_t * ft;
87   struct tag_info_node * head;
88   struct id3_tag * tag;
89 };
90 
add_tag(struct tag_info * info)91 static int add_tag(struct tag_info * info)
92 {
93   struct tag_info_node * current;
94   off_t start, end;
95   id3_byte_t query[ID3_TAG_QUERYSIZE];
96   id3_byte_t * buffer;
97   long size;
98   int result = 0;
99 
100   /* Ensure we're at the start of a valid tag and get its size. */
101   if (ID3_TAG_QUERYSIZE != lsx_readbuf(info->ft, query, ID3_TAG_QUERYSIZE) ||
102       !(size = id3_tag_query(query, ID3_TAG_QUERYSIZE))) {
103     return 0;
104   }
105   if (size < 0) {
106     if (0 != lsx_seeki(info->ft, size, SEEK_CUR) ||
107         ID3_TAG_QUERYSIZE != lsx_readbuf(info->ft, query, ID3_TAG_QUERYSIZE) ||
108         (size = id3_tag_query(query, ID3_TAG_QUERYSIZE)) <= 0) {
109       return 0;
110     }
111   }
112 
113   /* Don't read a tag more than once. */
114   start = lsx_tell(info->ft);
115   end = start + size;
116   for (current = info->head; current; current = current->next) {
117     if (start == current->start && end == current->end) {
118       return 1;
119     } else if (start < current->end && current->start < end) {
120       return 0;
121     }
122   }
123 
124   buffer = lsx_malloc((size_t)size);
125   if (!buffer) {
126     return 0;
127   }
128   memcpy(buffer, query, ID3_TAG_QUERYSIZE);
129   if ((unsigned long)size - ID3_TAG_QUERYSIZE ==
130       lsx_readbuf(info->ft, buffer + ID3_TAG_QUERYSIZE, (size_t)size - ID3_TAG_QUERYSIZE)) {
131     struct id3_tag * tag = id3_tag_parse(buffer, (size_t)size);
132     if (tag) {
133       current = lsx_malloc(sizeof(struct tag_info_node));
134       if (current) {
135         current->next = info->head;
136         current->start = start;
137         current->end = end;
138         info->head = current;
139         if (info->tag && (info->tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)) {
140           struct id3_frame * frame;
141           unsigned i;
142           for (i = 0; (frame = id3_tag_findframe(tag, NULL, i)); i++) {
143             id3_tag_attachframe(info->tag, frame);
144           }
145           id3_tag_delete(tag);
146         } else {
147           if (info->tag) {
148             id3_tag_delete(info->tag);
149           }
150           info->tag = tag;
151         }
152       }
153     }
154   }
155   free(buffer);
156   return result;
157 }
158 
lsx_id3_read_tag(sox_format_t * ft,sox_bool search)159 void lsx_id3_read_tag(sox_format_t * ft, sox_bool search)
160 {
161   struct tag_info   info;
162   id3_utf8_t        * utf8;
163   int               i;
164   int               has_id3v1 = 0;
165 
166   info.ft = ft;
167   info.head = NULL;
168   info.tag = NULL;
169 
170   /*
171   We look for:
172   ID3v1 at end (EOF - 128).
173   ID3v2 at start.
174   ID3v2 at end (but before ID3v1 from end if there was one).
175   */
176 
177   if (search) {
178     if (0 == lsx_seeki(ft, -128, SEEK_END)) {
179       has_id3v1 =
180         add_tag(&info) &&
181         1 == ID3_TAG_VERSION_MAJOR(id3_tag_version(info.tag));
182     }
183     if (0 == lsx_seeki(ft, 0, SEEK_SET)) {
184       add_tag(&info);
185     }
186     if (0 == lsx_seeki(ft, has_id3v1 ? -138 : -10, SEEK_END)) {
187       add_tag(&info);
188     }
189   } else {
190     add_tag(&info);
191   }
192 
193   if (info.tag && info.tag->frames) {
194     for (i = 0; id3tagmap[i][0]; ++i) {
195       if ((utf8 = utf8_id3tag_findframe(info.tag, id3tagmap[i][0], 0))) {
196         char * comment = lsx_malloc(strlen(id3tagmap[i][1]) + 1 + strlen((char *)utf8) + 1);
197         sprintf(comment, "%s=%s", id3tagmap[i][1], utf8);
198         sox_append_comment(&ft->oob.comments, comment);
199         free(comment);
200         free(utf8);
201       }
202     }
203     if ((utf8 = utf8_id3tag_findframe(info.tag, "TLEN", 0))) {
204       unsigned long tlen = strtoul((char *)utf8, NULL, 10);
205       if (tlen > 0 && tlen < ULONG_MAX) {
206         ft->signal.length= tlen; /* In ms; convert to samples later */
207         lsx_debug("got exact duration from ID3 TLEN");
208       }
209       free(utf8);
210     }
211   }
212   while (info.head) {
213     struct tag_info_node * head = info.head;
214     info.head = head->next;
215     free(head);
216   }
217   if (info.tag) {
218     id3_tag_delete(info.tag);
219   }
220 }
221 
222 #else
223 
224 /* Stub for format modules */
lsx_id3_read_tag(sox_format_t * ft,sox_bool search)225 void lsx_id3_read_tag(sox_format_t *ft, sox_bool search) { }
226 
227 #endif
228