1 #include <vector>
2 #include <cstdio>
3 #include <cstring>
4 #include "char_ucs.h"
5 #include "char_utf8.h"
6 #include "id3v2.h"
7 #include "getid3v2.h"
8
9 /*
10
11 copyright (c) 2004, 2005, 2015 squell <squell@alumina.nl>
12
13 use, modification, copying and distribution of this software is permitted
14 under the conditions described in the file 'COPYING'.
15
16 */
17
18 using namespace std;
19 using namespace charset;
20
21 using tag::read::ID3v2;
22 using tag::ID3field;
23
24 static bool getframe(ID3FRAME f, const char* field, size_t n);
25
26 // setid3v2 also needs the following function; but the outside world doesn't
27 namespace tag {
28 extern ID3v2::value_string unbinarize(ID3FRAME, charset::conv<>& descriptor);
29 }
30
ID3v2(const char * fn)31 ID3v2::ID3v2(const char* fn) : tag(ID3_readf(fn,0))
32 {
33 }
34
ID3v2(const void * id3v2_data)35 ID3v2::ID3v2(const void* id3v2_data) : tag(id3v2_data)
36 {
37 }
38
~ID3v2()39 ID3v2::~ID3v2()
40 {
41 ID3_free(tag);
42 }
43
operator [](ID3field field) const44 ID3v2::value_string ID3v2::operator[](ID3field field) const
45 {
46 const char* const fieldtag[FIELD_MAX][2] = { // ID3field order!
47 { "TT2" "TT3" "TOF",
48 "TIT2" "TIT3" "TOFN" "TRSN" },
49 { "TP1" "TCM" "TP2" "TP3" "TP4" "TXT" "TOA" "TOL" "TPB",
50 "TPE1" "TCOM" "TPE2" "TPE3" "TPE4" "TEXT" "TOPE" "TOLY" "TPUB" "TRSO" },
51 { "TAL" "TOT",
52 "TALB" "TOAL" },
53 { "TYE" "TOR",
54 "TYER" "TORY" },
55 { "COM" "TCR",
56 "COMM" "TCOP" "USER" },
57 { "TRK" "TPA",
58 "TRCK" "TPOS" },
59 { "TCO" "TT1",
60 "TCON" "TIT1" }
61 };
62
63 ID3FRAME f;
64 if(tag && field < FIELD_MAX) {
65 const bool version = ID3_start(f, tag) > 2;
66 const char* id_str = fieldtag[field][version];
67 const size_t id_len = 3+version;
68
69 // return the first ID of the above list that produces a clear match
70 for( ; *id_str != '\0'; id_str += id_len) {
71 ID3_start(f, tag);
72 while( getframe(f, id_str, id_len) ) {
73 charset::conv<local> desc;
74 value_string val = unbinarize(f, desc);
75 if(desc.length() <= 5) // allow for "::lan"; which works because
76 return val; // in fieldtag: has_desc() implies has_lang()
77 }
78 }
79 } else if(tag && field == FIELD_MAX) {
80 ID3_start(f, tag);
81 char buf[] = "ID3v2._";
82 buf[6] = f->_rev+'2';
83 return buf;
84 }
85 return value_string();
86 }
87
88 /* ====================================================== */
89
listing() const90 ID3v2::array ID3v2::listing() const
91 {
92 ID3FRAME f;
93
94 array vec;
95 if(tag) {
96 ID3VER version = ID3_start(f, tag);
97 char vstr[4] = { char(version+'0'), '.', '0', };
98 vec.push_back( array::value_type("ID3v2", vstr) );
99 while(ID3_frame(f)) {
100 charset::conv<local> desc;
101 value_string val = unbinarize(f, desc);
102 vec.push_back( array::value_type(f->ID+string(desc), val) );
103 }
104 }
105 return vec;
106 }
107
108 /* ====================================================== */
109
getframe(ID3FRAME f,const char * field,size_t n)110 static bool getframe(ID3FRAME f, const char* field, size_t n)
111 {
112 while(ID3_frame(f))
113 if(strncmp(field, f->ID, n) == 0) {
114 if(f->ID[0] == 'T') {
115 if(f->size > 1) return 1;
116 } else
117 return 1;
118 }
119 return 0;
120 }
121
122 // safely find a terminator in multi-string frames
123
membrk0(const char * buf,size_t size,bool wide)124 static const char *membrk0(const char* buf, size_t size, bool wide)
125 {
126 const char* const end = buf + size - wide;
127 const int step = 1+wide;
128 for( ; buf < end; buf += step) {
129 if(!buf[0] && !buf[wide])
130 return buf;
131 }
132 return 0;
133 }
134
unbinarize(ID3FRAME f,charset::conv<> & descriptor)135 extern ID3v2::value_string tag::unbinarize(ID3FRAME f, charset::conv<>& descriptor)
136 {
137 typedef conv<latin1> cs;
138
139 const string field = f->ID;
140 const char* p = f->data + 1;
141 bool wide = *f->data == 1 || *f->data == 2;
142
143 if(f->packed || f->encrypted || f->grouped)
144 return ID3v2::value_string(cs("<compressed or encrypted>"),0);
145
146 if(ID3v2::is_counter(field)) {
147 char buf[12]; // enough for 32bits
148 unsigned long t = 0;
149 for(size_t n = 0; n < f->size; ++n)
150 t = t << 8 | (f->data[n] & 0xFF);
151 sprintf(buf, "%lu", t & 0xFFFFFFFFul);
152 return conv<latin1>(buf);
153 }
154
155 const char* lang = 0;
156 if(ID3v2::has_lang(field)) {
157 lang = p;
158 if(memchr(lang,'\0',3)) // some programs use null-bytes
159 lang = "xxx";
160 p += 3; // skip-ignore language field
161 }
162 if(ID3v2::has_desc(field)) {
163 const char *q = membrk0(p, f->size - (p - f->data), wide);
164 if(!q) return conv<>(); // malformed frame
165 descriptor = conv<charset::latin1>(":");
166 switch(*f->data) {
167 case 0: descriptor += conv<charset::latin1> (p, q-p); break;
168 case 1: descriptor += conv<charset::utf16> (p, q-p); break;
169 case 2: descriptor += conv<charset::utf16be>(p, q-p); break;
170 case 3: descriptor += conv<charset::utf8> (p, q-p); break;
171 }
172 p = q+1+wide;
173 } else if(lang) {
174 descriptor += ':'; // only used by USER; enfore consistency in notation
175 }
176 if(lang)
177 (descriptor += ':') += conv<charset::latin1>(lang,3);
178
179 size_t hdrsiz = p - f->data;
180 if(hdrsiz > 1 || ID3v2::is_text(field)) {
181 size_t txtsiz = f->size - hdrsiz;
182 char encoding = *f->data;
183 if(ID3v2::is_url(field)) {
184 wide = encoding = 0; // the WXXX frame is so weird
185 }
186 if(const char *q = membrk0(p, txtsiz, wide)) {
187 txtsiz = q - p; // useless null-terminator
188 }
189 switch(encoding) {
190 case 0:
191 return conv<latin1> (p, txtsiz);
192 case 1:
193 return conv<utf16> (p, txtsiz);
194 case 2:
195 return conv<utf16be>(p, txtsiz);
196 case 3:
197 return conv<utf8> (p, txtsiz);
198 default:
199 return ID3v2::value_string(cs("<unsupported encoding>"),0);
200 };
201 } else if(ID3v2::is_url(field)) {
202 return conv<latin1>(f->data, f->size);
203 } else {
204 return ID3v2::value_string(cs("<binary data>"), 0);
205 }
206 }
207
208