1//=========================================================================
2// FILENAME	: tagutils-misc.c
3// DESCRIPTION	: Misc routines for supporting tagutils
4//=========================================================================
5// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
6//=========================================================================
7
8/* This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 */
22
23/**************************************************************************
24* Language
25**************************************************************************/
26
27#define MAX_ICONV_BUF 1024
28
29typedef enum {
30	ICONV_OK,
31	ICONV_TRYNEXT,
32	ICONV_FATAL
33} iconv_result;
34
35static iconv_result
36do_iconv(const char* to_ces, const char* from_ces,
37	 char *inbuf,  size_t inbytesleft,
38	 char *outbuf_orig, size_t outbytesleft_orig)
39{
40#ifdef HAVE_ICONV_H
41	size_t rc;
42	iconv_result ret = ICONV_OK;
43
44	size_t outbytesleft = outbytesleft_orig - 1;
45	char* outbuf = outbuf_orig;
46
47	iconv_t cd  = iconv_open(to_ces, from_ces);
48
49	if(cd == (iconv_t)-1)
50	{
51		return ICONV_FATAL;
52	}
53	rc = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
54	if(rc == (size_t)-1)
55	{
56		if(errno == E2BIG)
57		{
58			ret = ICONV_FATAL;
59		}
60		else
61		{
62			ret = ICONV_TRYNEXT;
63			memset(outbuf_orig, '\0', outbytesleft_orig);
64		}
65	}
66	iconv_close(cd);
67
68	return ret;
69#else // HAVE_ICONV_H
70	return ICONV_FATAL;
71#endif // HAVE_ICONV_H
72}
73
74#define N_LANG_ALT 8
75struct {
76	char *lang;
77	char *cpnames[N_LANG_ALT];
78} iconv_map[] = {
79	{ "ja_JP",     { "CP932", "CP950", "CP936", "ISO-8859-1", 0 } },
80	{ "zh_CN",  { "CP936", "CP950", "CP932", "ISO-8859-1", 0 } },
81	{ "zh_TW",  { "CP950", "CP936", "CP932", "ISO-8859-1", 0 } },
82	{ "ko_KR",  { "CP949", "ISO-8859-1", 0 } },
83	{ 0,        { 0 } }
84};
85static int lang_index = -1;
86
87static int
88_lang2cp(char *lang)
89{
90	int cp;
91
92	if(!lang || lang[0] == '\0')
93		return -1;
94	for(cp = 0; iconv_map[cp].lang; cp++)
95	{
96		if(!strcasecmp(iconv_map[cp].lang, lang))
97			return cp;
98	}
99	return -2;
100}
101
102static unsigned char*
103_get_utf8_text(const id3_ucs4_t* native_text)
104{
105	unsigned char *utf8_text = NULL;
106	char *in, *in8, *iconv_buf;
107	iconv_result rc;
108	int i, n;
109
110	in = (char*)id3_ucs4_latin1duplicate(native_text);
111	if(!in)
112	{
113		goto out;
114	}
115
116	in8 = (char*)id3_ucs4_utf8duplicate(native_text);
117	if(!in8)
118	{
119		free(in);
120		goto out;
121	}
122
123	iconv_buf = (char*)calloc(MAX_ICONV_BUF, sizeof(char));
124	if(!iconv_buf)
125	{
126		free(in); free(in8);
127		goto out;
128	}
129
130	i = lang_index;
131	// (1) try utf8 -> default
132	rc = do_iconv(iconv_map[i].cpnames[0], "UTF-8", in8, strlen(in8), iconv_buf, MAX_ICONV_BUF);
133	if(rc == ICONV_OK)
134	{
135		utf8_text = (unsigned char*)in8;
136		free(iconv_buf);
137	}
138	else if(rc == ICONV_TRYNEXT)
139	{
140		// (2) try default -> utf8
141		rc = do_iconv("UTF-8", iconv_map[i].cpnames[0], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
142		if(rc == ICONV_OK)
143		{
144			utf8_text = (unsigned char*)iconv_buf;
145		}
146		else if(rc == ICONV_TRYNEXT)
147		{
148			// (3) try other encodes
149			for(n = 1; n < N_LANG_ALT && iconv_map[i].cpnames[n]; n++)
150			{
151				rc = do_iconv("UTF-8", iconv_map[i].cpnames[n], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
152				if(rc == ICONV_OK)
153				{
154					utf8_text = (unsigned char*)iconv_buf;
155					break;
156				}
157			}
158			if(!utf8_text)
159			{
160				// cannot iconv
161				utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
162				free(iconv_buf);
163			}
164		}
165		free(in8);
166	}
167	free(in);
168
169 out:
170	if(!utf8_text)
171	{
172		utf8_text = (unsigned char*)strdup("UNKNOWN");
173	}
174
175	return utf8_text;
176}
177
178
179static void
180vc_scan(struct song_metadata *psong, const char *comment, const size_t length)
181{
182	char strbuf[1024];
183
184	if(length > (sizeof(strbuf) - 1))
185	{
186		if( strncasecmp(comment, "LYRICS=", 7) != 0 )
187		{
188			DPRINTF(E_WARN, L_SCANNER, "Vorbis %.*s too long [%s]\n", (index(comment, '=')-comment), comment, psong->path);
189		}
190		return;
191	}
192	strncpy(strbuf, comment, length);
193	strbuf[length] = '\0';
194
195	// ALBUM, ARTIST, PUBLISHER, COPYRIGHT, DISCNUMBER, ISRC, EAN/UPN, LABEL, LABELNO,
196	// LICENSE, OPUS, SOURCEMEDIA, TITLE, TRACKNUMBER, VERSION, ENCODED-BY, ENCODING,
197	// -- foollowing tags are muliples
198	// COMPOSER, ARRANGER, LYRICIST, AUTHOR, CONDUCTOR, PERFORMER, ENSEMBLE, PART
199	// PARTNUMBER, GENRE, DATE, LOCATION, COMMENT
200	if(!strncasecmp(strbuf, "ALBUM=", 6))
201	{
202		if( *(strbuf+6) )
203			psong->album = strdup(strbuf + 6);
204	}
205	else if(!strncasecmp(strbuf, "ARTIST=", 7))
206	{
207		if( *(strbuf+7) )
208			psong->contributor[ROLE_ARTIST] = strdup(strbuf + 7);
209	}
210	else if(!strncasecmp(strbuf, "ARTISTSORT=", 11))
211	{
212		psong->contributor_sort[ROLE_ARTIST] = strdup(strbuf + 11);
213	}
214	else if(!strncasecmp(strbuf, "TITLE=", 6))
215	{
216		if( *(strbuf+6) )
217			psong->title = strdup(strbuf + 6);
218	}
219	else if(!strncasecmp(strbuf, "TRACKNUMBER=", 12))
220	{
221		psong->track = atoi(strbuf + 12);
222	}
223	else if(!strncasecmp(strbuf, "DISCNUMBER=", 11))
224	{
225		psong->disc = atoi(strbuf + 11);
226	}
227	else if(!strncasecmp(strbuf, "GENRE=", 6))
228	{
229		if( *(strbuf+6) )
230			psong->genre = strdup(strbuf + 6);
231	}
232	else if(!strncasecmp(strbuf, "DATE=", 5))
233	{
234		if(length >= (5 + 10) &&
235		   isdigit(strbuf[5 + 0]) && isdigit(strbuf[5 + 1]) && ispunct(strbuf[5 + 2]) &&
236		   isdigit(strbuf[5 + 3]) && isdigit(strbuf[5 + 4]) && ispunct(strbuf[5 + 5]) &&
237		   isdigit(strbuf[5 + 6]) && isdigit(strbuf[5 + 7]) && isdigit(strbuf[5 + 8]) && isdigit(strbuf[5 + 9]))
238		{
239			// nn-nn-yyyy
240			strbuf[5 + 10] = '\0';
241			psong->year = atoi(strbuf + 5 + 6);
242		}
243		else
244		{
245			// year first. year is at most 4 digit.
246			strbuf[5 + 4] = '\0';
247			psong->year = atoi(strbuf + 5);
248		}
249	}
250	else if(!strncasecmp(strbuf, "COMMENT=", 8))
251	{
252		if( *(strbuf+8) )
253			psong->comment = strdup(strbuf + 8);
254	}
255	else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMID=", 20))
256	{
257		psong->musicbrainz_albumid = strdup(strbuf + 20);
258	}
259	else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
260	{
261		psong->musicbrainz_trackid = strdup(strbuf + 20);
262	}
263	else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
264	{
265		psong->musicbrainz_trackid = strdup(strbuf + 20);
266	}
267	else if(!strncasecmp(strbuf, "MUSICBRAINZ_ARTISTID=", 21))
268	{
269		psong->musicbrainz_artistid = strdup(strbuf + 21);
270	}
271	else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMARTISTID=", 26))
272	{
273		psong->musicbrainz_albumartistid = strdup(strbuf + 26);
274	}
275}
276