1 #include <cstdio>
2 #include <cstring>
3 #include <cstdlib>
4 #include <cctype>
5 #include <algorithm>
6 #include <map>
7 #include <string>
8 #include "id3v1.h"
9 #include "getid3.h"
10 #include "setid3.h"
11
12 /*
13
14 copyright (c) 2004, 2005 squell <squell@alumina.nl>
15
16 use, modification, copying and distribution of this software is permitted
17 under the conditions described in the file 'COPYING'.
18
19 */
20
21 #if defined(_WIN32)
22 # include <io.h>
23 # define ftrunc(f) chsize(fileno(f), ftell(f))
24 #else
25 # include <unistd.h>
26 # define ftrunc(f) ftruncate(fileno(f), ftell(f))
27 #endif
28
29 using namespace std;
30
31 using tag::write::ID3;
32 using tag::ID3field;
33
34 typedef int concreteness_check[ sizeof ID3() ];
35
36 static ID3v1 const zero_tag = {
37 { 'T', 'A', 'G' },
38 "", // title
39 "", // artist
40 "", // album
41 "", // year
42 "", // cmnt
43 0,
44 0, // track
45 255 // genre
46 };
47
48 /* ====================================================== */
49
str_upper(string s)50 static string str_upper(string s)
51 {
52 for(string::iterator p = s.begin(); p != s.end(); ++p)
53 *p = toupper(*p);
54 return s;
55 }
56
57 /* ====================================================== */
58
59 /*
60 This is basically an enhanced lexicographical_compare which ignores
61 certain parts of a string during comparison:
62
63 Any plain character compared against a seperator gets 'eaten'
64 e.g. "Alt Rock" will match "Alternative Rock" exactly.
65
66 Every seperator matches every other seperator.
67 e.g. "Fast Fusion" matches "Fast-Fusion"
68 */
69
issep(int c)70 static inline bool issep(int c)
71 {
72 return !isalnum(c);
73 }
74
clipped_compare(const string & is,const string & js)75 static bool clipped_compare(const string& is, const string& js)
76 {
77 string::const_iterator i = is.begin();
78 string::const_iterator j = js.begin();
79
80 for( ; i != is.end() && j != js.end(); ++i, ++j) {
81 if(issep(*i)) {
82 if(!issep(*j)) --i;
83 } else {
84 if( issep(*j)) --j; else {
85 if(*i < *j)
86 return true;
87 if(*i > *j)
88 return false;
89 }
90 }
91 }
92 return find_if(i, is.end(), issep) == is.end() &&
93 find_if(j, js.end(), issep) != js.end();
94 }
95
96 /* ====================================================== */
97
98 struct genre_map : map<string,int,bool (*)(const string&,const string&)> {
99 typedef const_iterator iter; // shorthand
100
genre_mapgenre_map101 genre_map() // initialize associative map
102 : map<string,int,key_compare>( clipped_compare )
103 {
104 (*this)[ "Psych" ] = 67; // small kludges
105 (*this)[ "Folk0" ] = 80;
106 (*this)[ "Humo" ] = 100;
107 for(int i=0; i < ID3v1_numgenres; i++) {
108 (*this)[ str_upper(ID3v1_genre[i]) ] = i;
109 }
110 }
111 } const ID3_genre;
112
113 /* ====================================================== */
114
from(const char * fn)115 bool ID3::from(const char* fn)
116 {
117 delete null_tag, null_tag = 0;
118 if(fn) {
119 read::ID3 src(fn);
120 if(src) null_tag = new ID3v1(src.tag);
121 }
122 return null_tag;
123 }
124
125 // note: Intel C++ produces an internal error here if the base class has
126 // the return type as 'const reader*' and this class doesn't. apparently
127 // caused by some compiler bug in relation to multiple inheritance.
128
read(const char * fn) const129 tag::metadata* ID3::read(const char* fn) const
130 {
131 return new read::ID3(fn);
132 }
133
134 // note: Borland doesn't suppress array-to-pointer conversion when deducing
135 // reference type parameters (in this case), so use pointer instead
136
137 template<size_t N>
setfield(char (* dest)[N],const charset::conv<> * src,size_t maxlen=N)138 static inline bool setfield(char (*dest)[N], const charset::conv<>* src, size_t maxlen = N)
139 {
140 if(src)
141 return strncpy(*dest, src->template str<charset::latin1>().c_str(), maxlen);
142 else
143 return false;
144 }
145
vmodify(const char * fn,const function & edit) const146 bool ID3::vmodify(const char* fn, const function& edit) const
147 {
148 const ID3v1& synth_tag = null_tag? *null_tag : zero_tag;
149
150 ID3v1 tag;
151
152 if( FILE* f = fopen(fn, "rb+") ) {
153 fseek(f, -128, SEEK_END) == 0 &&
154 fread(&tag, 1, 128, f) == 128 || (tag.TAG[0] = 0);
155
156 int err = 1;
157 if( memcmp(tag.TAG, "TAG", 3) == 0 ) {
158 err = fseek(f, -128, SEEK_END); // overwrite existing tag
159 } else if( generate ) {
160 tag = synth_tag; // create new tag
161 clearerr(f);
162 err = fseek(f, 0, ftell(f)<128? SEEK_END : SEEK_CUR);
163 } else {
164 fclose(f);
165 return true;
166 }
167
168 if( err != 0 ) {
169 fclose(f);
170 return false;
171 }
172
173 if( cleared ) tag = synth_tag;
174
175 using namespace charset;
176 const string* field; // reading aid
177 int n = bool(null_tag); // count number of set fields
178
179 if(field = update[title])
180 n += setfield(&tag.title, edit(*field));
181
182 if(field = update[artist])
183 n += setfield(&tag.artist, edit(*field));
184
185 if(field = update[album])
186 n += setfield(&tag.album, edit(*field));
187
188 if(field = update[year])
189 n += setfield(&tag.year, edit(*field));
190
191 if(field = update[track])
192 if(function::result rs = edit(*field)) {
193 ++n, tag.track = atoi( rs.str<latin1>().c_str() );
194 tag.zero = '\0';
195 }
196
197 if(field = update[cmnt])
198 n += setfield(&tag.cmnt, edit(*field), sizeof tag.cmnt + 2*(tag.zero||!tag.track));
199
200 if(field = update[genre]) {
201 if(function::result rs = edit(*field)) {
202 string s = str_upper( rs.str<latin1>() );
203 unsigned int x = atoi(s.c_str()) - 1;
204 genre_map::iter g = ID3_genre.find(s);
205 tag.genre = (s.empty() || g==ID3_genre.end()? x : g->second);
206 ++n;
207 }
208 }
209
210 if( cleared && n == 0 ) {
211 err = ftrunc(f) != 0;
212 } else {
213 err = fwrite(&tag, 1, 128, f) != 128;
214 }
215
216 err |= fclose(f) != 0;
217
218 if(err)
219 throw failure("error writing TAG to ", fn);
220
221 return true;
222 };
223
224 return false;
225 }
226
~ID3()227 ID3::~ID3()
228 {
229 delete null_tag;
230 }
231
232 /*
233
234 annotated bug:
235
236 Under MS-DOS with DJGPP, if an fwrite() crossed the end of the file, and no
237 fseek() has occured, the buffer space(??) of the last fread() will be
238 appended to the file before the data actually written by fwrite(). (?)
239
240 ARGH!
241
242 Actually, this is C conformant behaviour - no write shall follow a read
243 without an intervening fflush or fseek.
244
245 */
246
247