1 /*
2 Copyright (c) 2005-2012 Alon Bar-Lev <alon.barlev@gmail.com>
3 Copyright (c) 2005-2012 Andrey Dubovik <andu@inbox.ru>
4 All rights reserved.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License version 2
8 as published by the Free Software Foundation.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program (see the file COPYING.GPL included with this
17 distribution); if not, write to the Free Software Foundation, Inc.,
18 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include "config.h"
22 #include <stdio.h>
23 #include <getopt.h>
24 #include <fileref.h>
25 #include <mpegfile.h>
26 #include <tag.h>
27 #include <id3v1tag.h>
28 #include <id3v2tag.h>
29 #include <iconv.h>
30 #include <errno.h>
31 #include <cctype>
32
33 #include "messages.h"
34
tolower(std::string & s)35 void tolower(std::string& s) {
36 std::transform(s.begin(), s.end(), s.begin(), (int(*)(int))std::tolower);
37 }
38
39 class Converter {
40
41 protected:
42 bool m_id3v1;
43 bool m_id3v2;
44 bool m_src_utf8;
45 bool m_id3v2_utf8;
46 iconv_t m_cd_src;
47 iconv_t m_cd_id3v1;
48 iconv_t m_cd_id3v2;
49 TagLib::ID3v1::Tag *m_id3v1_tag;
50 TagLib::ID3v2::Tag *m_id3v2_tag;
51 bool m_preserve;
52
53 public:
Converter(const std::string & src_enc,const std::string & id3v1_enc,const std::string & id3v2_enc,TagLib::ID3v1::Tag * id3v1_tag,TagLib::ID3v2::Tag * id3v2_tag,bool preserve)54 Converter (
55 const std::string &src_enc,
56 const std::string &id3v1_enc,
57 const std::string &id3v2_enc,
58 TagLib::ID3v1::Tag *id3v1_tag,
59 TagLib::ID3v2::Tag *id3v2_tag,
60 bool preserve
61 ) {
62 m_id3v1 = false;
63 m_id3v2 = false;
64 m_src_utf8 = false;
65 m_id3v2_utf8 = false;
66 m_cd_src = (iconv_t)-1;
67 m_cd_id3v1 = (iconv_t)-1;
68 m_cd_id3v2 = (iconv_t)-1;
69 std::string _src_enc = src_enc;
70 std::string _id3v2_enc = id3v2_enc;
71
72 m_id3v1_tag = id3v1_tag;
73 m_id3v2_tag = id3v2_tag;
74 m_preserve = preserve;
75
76 if (_src_enc == "unicode") {
77 m_src_utf8 = true;
78 _src_enc = "UTF-8";
79 }
80 if ((m_cd_src = iconv_open ("UTF-8", _src_enc.c_str ())) == (iconv_t)-1) {
81 throw msg::wrong_senc;
82 }
83
84 if(id3v1_enc == "unicode") {
85 throw msg::v1unicode;
86 }
87
88 if(id3v1_enc != "none") {
89 if ((m_cd_id3v1 = iconv_open (id3v1_enc.c_str (), "UTF-8")) == (iconv_t)-1) {
90 throw msg::wrong_1enc;
91 }
92 m_id3v1 = true;
93 }
94 if(_id3v2_enc != "none") {
95 if (_id3v2_enc == "unicode") {
96 _id3v2_enc = "UTF-8";
97 m_id3v2_utf8 = true;
98 }
99
100 if ((m_cd_id3v2 = iconv_open (_id3v2_enc.c_str (), "UTF-8")) == (iconv_t)-1) {
101 throw msg::wrong_2enc;
102 }
103
104 m_id3v2 = true;
105 }
106 }
107
108
~Converter()109 ~Converter () {
110 if (m_cd_src != (iconv_t)-1) {
111 iconv_close (m_cd_src);
112 }
113 if (m_cd_id3v1 != (iconv_t)-1) {
114 iconv_close (m_cd_id3v1);
115 }
116 if (m_cd_id3v2 != (iconv_t)-1) {
117 iconv_close (m_cd_id3v2);
118 }
119 }
120
121 int
Tags()122 Tags() {
123 return (
124 (m_id3v1 ? TagLib::MPEG::File::ID3v1 : 0) |
125 (m_id3v2 ? TagLib::MPEG::File::ID3v2 : 0)
126 );
127 }
128
129 void
Convert(const TagLib::String & value,void (TagLib::Tag::* field)(const TagLib::String &))130 Convert (
131 const TagLib::String &value,
132 void (TagLib::Tag::*field)(const TagLib::String &)
133 ) {
134 std::string str;
135 if (!m_src_utf8 && m_preserve) {
136 if (heuristicIsUnicode (value)) {
137 str = value.to8Bit (true);
138 }
139 else {
140 str = ConvertString (m_cd_src, value.to8Bit (false));
141 }
142 }
143 else {
144 str = ConvertString (m_cd_src, value.to8Bit (m_src_utf8));
145 }
146
147 if (m_id3v1) {
148 (m_id3v1_tag->*field) (TagLib::String (ConvertString (m_cd_id3v1, str)));
149 }
150
151 if (m_id3v2) {
152 (m_id3v2_tag->*field) (
153 TagLib::String (
154 ConvertString (m_cd_id3v2, str),
155 m_id3v2_utf8 ? TagLib::String::UTF8 : TagLib::String::Latin1
156 )
157 );
158 }
159 }
160
161 void
printTags(const std::string & file)162 printTags (
163 const std::string &file
164 ) {
165 printf("%s\n", file.c_str());
166 printf("\tid3v1:\n");
167 if (m_id3v1) {
168 printf("\t\tTitle:\t\t%s\n", m_id3v1_tag->title ().to8Bit().c_str());
169 printf("\t\tArtist:\t\t%s\n", m_id3v1_tag->artist ().to8Bit().c_str());
170 printf("\t\tAlbum:\t\t%s\n", m_id3v1_tag->album ().to8Bit().c_str());
171 printf("\t\tComment:\t%s\n", m_id3v1_tag->comment ().to8Bit().c_str());
172 printf("\t\tGenre:\t\t%s\n", m_id3v1_tag->genre ().to8Bit().c_str());
173 }
174 else {
175 printf("\t\tempty.\n");
176 }
177 printf("\tid3v2:\n");
178 if (m_id3v2) {
179 printf("\t\tTitle:\t\t%s\n", m_id3v2_tag->title ().to8Bit(m_id3v2_utf8).c_str());
180 printf("\t\tArtist:\t\t%s\n", m_id3v2_tag->artist ().to8Bit(m_id3v2_utf8).c_str());
181 printf("\t\tAlbum:\t\t%s\n", m_id3v2_tag->album ().to8Bit(m_id3v2_utf8).c_str());
182 printf("\t\tComment:\t%s\n", m_id3v2_tag->comment ().to8Bit(m_id3v2_utf8).c_str());
183 printf("\t\tGenre:\t\t%s\n", m_id3v2_tag->genre ().to8Bit(m_id3v2_utf8).c_str());
184 }
185 else {
186 printf("\t\tempty.\n");
187 }
188 }
189
190 protected:
191
192 bool
heuristicIsUnicode(TagLib::String string)193 heuristicIsUnicode (TagLib::String string) {
194 unsigned u0080 = 0;
195 for(TagLib::uint i = 0; i < string.size(); i++) {
196 if(string[i] > 255) {
197 return true;
198 }
199 if(string[i] > 127) {
200 u0080++;
201 }
202 }
203 if(u0080 * 2 <= string.size()) {
204 return true;
205 }
206 else {
207 return false;
208 }
209 }
210
211 std::string
ConvertString(iconv_t cd,std::string src)212 ConvertString (
213 iconv_t cd,
214 std::string src
215 ) {
216 const char *from;
217 size_t from_size;
218 char to_buffer[1024];
219 char *to;
220 size_t to_size;
221
222 std::string dst;
223
224 from = &*src.begin ();
225 from_size = src.size ();
226 while (from_size > 0) {
227 to = to_buffer;
228 to_size = sizeof (to_buffer);
229 if (
230 iconv (
231 cd,
232 (char **)&from,
233 &from_size,
234 &to,
235 &to_size
236 ) == (size_t)-1 &&
237 errno != E2BIG
238 ) {
239 throw msg::enc_error;
240 }
241
242 dst.append (to_buffer, to);
243 }
244
245 to = to_buffer;
246 to_size = sizeof (to_buffer);
247 if (iconv (cd, NULL, NULL, &to, &to_size) == (size_t)-1) {
248 throw msg::enc_error;
249 }
250
251 dst.append (to_buffer, to);
252
253 return dst;
254 }
255 };
256
main(int argc,char * argv[])257 int main (int argc, char *argv[]) {
258 try {
259 static struct option long_options[] = {
260 { "source-encoding", required_argument, NULL, 's' },
261 { "id3v1-encoding", required_argument, NULL, '1' },
262 { "id3v2-encoding", required_argument, NULL, '2' },
263 { "preserve-unicode", no_argument, NULL, 'p' },
264 { "preview", no_argument, NULL, 'w' },
265 { "version", no_argument, NULL, 'v' },
266 { "help", no_argument, NULL, 'h' },
267 { "quiet", no_argument, NULL, 'q' },
268 { NULL, 0, NULL, 0 }
269 };
270 int long_options_ret;
271 std::string source_encoding;
272 std::string id3v1_encoding = "none";
273 std::string id3v2_encoding = "none";
274 bool preserve_unicode = false;
275 bool preview = false;
276 bool usage_ok = true;
277 bool verbose = true;
278
279 if(argc == 1) {
280 printf("%s", msg::usage);
281 exit(1);
282 }
283
284 while (
285 (long_options_ret = getopt_long (argc, argv, "s:1:2:pwdvhq", long_options, NULL)) != -1
286 ) {
287 switch (long_options_ret) {
288 case 's':
289 source_encoding = optarg;
290 tolower(source_encoding);
291 break;
292 case '1':
293 id3v1_encoding = optarg;
294 tolower(id3v1_encoding);
295 break;
296 case '2':
297 id3v2_encoding = optarg;
298 tolower(id3v2_encoding);
299 break;
300 case 'p':
301 preserve_unicode = true;
302 break;
303 case 'w':
304 preview = true;
305 break;
306 case 'v':
307 printf("%s %s\n", PACKAGE, PACKAGE_VERSION);
308 printf("%s", msg::copyright);
309 exit (1);
310 break;
311 case 'h':
312 printf("%s", msg::usage);
313 exit(1);
314 case 'q':
315 verbose = false;
316 break;
317 default:
318 usage_ok = false;
319 break;
320 }
321 }
322
323 if (usage_ok && source_encoding.empty ()) {
324 printf("%s", msg::nosenc);
325 usage_ok = false;
326 }
327
328 if(optind == argc) {
329 printf("%s", msg::nofiles);
330 usage_ok = false;
331 }
332
333 if (!usage_ok) {
334 printf("%s", msg::seehelp);
335 exit (1);
336 }
337
338 TagLib::ID3v2::FrameFactory::instance()->setDefaultTextEncoding (TagLib::String::UTF8);
339
340 for (int i=optind;i<argc;i++) {
341 TagLib::MPEG::File mp3file(argv[i]);
342
343 if (!mp3file.isOpen ()) {
344 throw msg::nofile(argv[i]);
345 }
346
347 TagLib::Tag *tag = mp3file.tag();
348
349 if(tag->isEmpty()) {
350 printf("%s", msg::emptyfile(argv[i]).c_str());
351 }
352 else {
353 Converter converter (
354 source_encoding,
355 id3v1_encoding,
356 id3v2_encoding,
357 mp3file.ID3v1Tag (true),
358 mp3file.ID3v2Tag (true),
359 preserve_unicode
360 );
361
362 converter.Convert (tag->title (), &TagLib::Tag::setTitle);
363 converter.Convert (tag->artist (), &TagLib::Tag::setArtist);
364 converter.Convert (tag->album (), &TagLib::Tag::setAlbum);
365 converter.Convert (tag->comment (), &TagLib::Tag::setComment);
366 converter.Convert (tag->genre (), &TagLib::Tag::setGenre);
367
368 if (preview) {
369 converter.printTags(argv[i]);
370 }
371 else {
372 mp3file.strip(~converter.Tags());
373 if (!mp3file.save (converter.Tags ())) {
374 printf("%s", msg::writefail(argv[i]).c_str());
375 }
376 else if(verbose) {
377 printf("%s", msg::filedone(argv[i]).c_str());
378 }
379 }
380 }
381 }
382
383 exit (0);
384 }
385 catch (const std::string &e) {
386 printf ("%s", msg::error(e).c_str());
387 exit (1);
388 }
389 catch (const char *e) {
390 printf ("%s", msg::error(e).c_str());
391 exit (1);
392 }
393
394 return 1;
395 }
396