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