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, see <http://www.gnu.org/licenses/>.
20  */
21 
22 /**************************************************************************
23 * Language
24 **************************************************************************/
25 
26 #define MAX_ICONV_BUF 1024
27 
28 typedef enum {
29 	ICONV_OK,
30 	ICONV_TRYNEXT,
31 	ICONV_FATAL
32 } iconv_result;
33 
34 #ifdef HAVE_ICONV
35 static iconv_result
do_iconv(const char * to_ces,const char * from_ces,ICONV_CONST char * inbuf,size_t inbytesleft,char * outbuf_orig,size_t outbytesleft_orig)36 do_iconv(const char* to_ces, const char* from_ces,
37 	 ICONV_CONST char *inbuf,  size_t inbytesleft,
38 	 char *outbuf_orig, size_t outbytesleft_orig)
39 {
40 	size_t rc;
41 	iconv_result ret = ICONV_OK;
42 
43 	size_t outbytesleft = outbytesleft_orig - 1;
44 	char* outbuf = outbuf_orig;
45 
46 	iconv_t cd  = iconv_open(to_ces, from_ces);
47 
48 	if(cd == (iconv_t)-1)
49 	{
50 		return ICONV_FATAL;
51 	}
52 	rc = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
53 	if(rc == (size_t)-1)
54 	{
55 		if(errno == E2BIG)
56 		{
57 			ret = ICONV_FATAL;
58 		}
59 		else
60 		{
61 			ret = ICONV_TRYNEXT;
62 			memset(outbuf_orig, '\0', outbytesleft_orig);
63 		}
64 	}
65 	iconv_close(cd);
66 
67 	return ret;
68 }
69 #else // HAVE_ICONV
70 static iconv_result
do_iconv(const char * to_ces,const char * from_ces,char * inbuf,size_t inbytesleft,char * outbuf_orig,size_t outbytesleft_orig)71 do_iconv(const char* to_ces, const char* from_ces,
72 	 char *inbuf,  size_t inbytesleft,
73 	 char *outbuf_orig, size_t outbytesleft_orig)
74 {
75 	return ICONV_FATAL;
76 }
77 #endif // HAVE_ICONV
78 
79 #define N_LANG_ALT 8
80 struct {
81 	char *lang;
82 	char *cpnames[N_LANG_ALT];
83 } iconv_map[] = {
84 	{ "ja_JP",     { "CP932", "CP950", "CP936", "ISO-8859-1", 0 } },
85 	{ "zh_CN",  { "CP936", "CP950", "CP932", "ISO-8859-1", 0 } },
86 	{ "zh_TW",  { "CP950", "CP936", "CP932", "ISO-8859-1", 0 } },
87 	{ "ko_KR",  { "CP949", "ISO-8859-1", 0 } },
88 	{ 0,        { 0 } }
89 };
90 static int lang_index = -1;
91 
92 static int
_lang2cp(char * lang)93 _lang2cp(char *lang)
94 {
95 	int cp;
96 
97 	if(!lang || lang[0] == '\0')
98 		return -1;
99 	for(cp = 0; iconv_map[cp].lang; cp++)
100 	{
101 		if(!strcasecmp(iconv_map[cp].lang, lang))
102 			return cp;
103 	}
104 	return -2;
105 }
106 
107 static unsigned char*
_get_utf8_text(const id3_ucs4_t * native_text)108 _get_utf8_text(const id3_ucs4_t* native_text)
109 {
110 	unsigned char *utf8_text = NULL;
111 	char *in, *in8, *iconv_buf;
112 	iconv_result rc;
113 	int i, n;
114 
115 	in = (char*)id3_ucs4_latin1duplicate(native_text);
116 	if(!in)
117 	{
118 		goto out;
119 	}
120 
121 	in8 = (char*)id3_ucs4_utf8duplicate(native_text);
122 	if(!in8)
123 	{
124 		free(in);
125 		goto out;
126 	}
127 
128 	iconv_buf = (char*)calloc(MAX_ICONV_BUF, sizeof(char));
129 	if(!iconv_buf)
130 	{
131 		free(in); free(in8);
132 		goto out;
133 	}
134 
135 	i = lang_index;
136 	// (1) try utf8 -> default
137 	rc = do_iconv(iconv_map[i].cpnames[0], "UTF-8", in8, strlen(in8), iconv_buf, MAX_ICONV_BUF);
138 	if(rc == ICONV_OK)
139 	{
140 		utf8_text = (unsigned char*)in8;
141 		free(iconv_buf);
142 	}
143 	else if(rc == ICONV_TRYNEXT)
144 	{
145 		// (2) try default -> utf8
146 		rc = do_iconv("UTF-8", iconv_map[i].cpnames[0], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
147 		if(rc == ICONV_OK)
148 		{
149 			utf8_text = (unsigned char*)iconv_buf;
150 		}
151 		else if(rc == ICONV_TRYNEXT)
152 		{
153 			// (3) try other encodes
154 			for(n = 1; n < N_LANG_ALT && iconv_map[i].cpnames[n]; n++)
155 			{
156 				rc = do_iconv("UTF-8", iconv_map[i].cpnames[n], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
157 				if(rc == ICONV_OK)
158 				{
159 					utf8_text = (unsigned char*)iconv_buf;
160 					break;
161 				}
162 			}
163 			if(!utf8_text)
164 			{
165 				// cannot iconv
166 				utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
167 				free(iconv_buf);
168 			}
169 		}
170 		free(in8);
171 	}
172 	free(in);
173 
174  out:
175 	if(!utf8_text)
176 	{
177 		utf8_text = (unsigned char*)strdup("UNKNOWN");
178 	}
179 
180 	return utf8_text;
181 }
182 
183 
184 static void
vc_scan(struct song_metadata * psong,const char * comment,const size_t length)185 vc_scan(struct song_metadata *psong, const char *comment, const size_t length)
186 {
187 	char strbuf[1024];
188 
189 	if(length > (sizeof(strbuf) - 1))
190 	{
191 		if( strncasecmp(comment, "LYRICS=", 7) != 0 &&
192 		    strncasecmp(comment, "coverart=", 9) != 0 &&
193 		    strncasecmp(comment, "METADATA_BLOCK_PICTURE=", 23) != 0 )
194 		{
195 			const char *eq = strchr(comment, '=');
196 			int len = 8;
197 			if (eq)
198 				len = eq - comment;
199 			DPRINTF(E_WARN, L_SCANNER, "Vorbis %.*s too long [%s]\n",
200 				len, comment, psong->path);
201 		}
202 		return;
203 	}
204 	strncpy(strbuf, comment, length);
205 	strbuf[length] = '\0';
206 
207 	// ALBUM, ARTIST, PUBLISHER, COPYRIGHT, DISCNUMBER, ISRC, EAN/UPN, LABEL, LABELNO,
208 	// LICENSE, OPUS, SOURCEMEDIA, TITLE, TRACKNUMBER, VERSION, ENCODED-BY, ENCODING,
209 	// -- following tags are muliples
210 	// COMPOSER, ARRANGER, LYRICIST, AUTHOR, CONDUCTOR, PERFORMER, ENSEMBLE, PART
211 	// PARTNUMBER, GENRE, DATE, LOCATION, COMMENT
212 	if(!strncasecmp(strbuf, "ALBUM=", 6))
213 	{
214 		if( *(strbuf+6) )
215 			psong->album = strdup(strbuf + 6);
216 	}
217 	else if(!strncasecmp(strbuf, "ARTIST=", 7))
218 	{
219 		if( *(strbuf+7) )
220 			psong->contributor[ROLE_ARTIST] = strdup(strbuf + 7);
221 	}
222 	else if(!strncasecmp(strbuf, "ARTISTSORT=", 11))
223 	{
224 		psong->contributor_sort[ROLE_ARTIST] = strdup(strbuf + 11);
225 	}
226 	else if(!strncasecmp(strbuf, "ALBUMARTIST=", 12))
227 	{
228 		if( *(strbuf+12) )
229 			psong->contributor[ROLE_BAND] = strdup(strbuf + 12);
230 	}
231 	else if(!strncasecmp(strbuf, "ALBUMARTISTSORT=", 16))
232 	{
233 		psong->contributor_sort[ROLE_BAND] = strdup(strbuf + 16);
234 	}
235 	else if(!strncasecmp(strbuf, "TITLE=", 6))
236 	{
237 		if( *(strbuf+6) )
238 			psong->title = strdup(strbuf + 6);
239 	}
240 	else if(!strncasecmp(strbuf, "TRACKNUMBER=", 12))
241 	{
242 		psong->track = atoi(strbuf + 12);
243 	}
244 	else if(!strncasecmp(strbuf, "DISCNUMBER=", 11))
245 	{
246 		psong->disc = atoi(strbuf + 11);
247 	}
248 	else if(!strncasecmp(strbuf, "GENRE=", 6))
249 	{
250 		if( *(strbuf+6) )
251 			psong->genre = strdup(strbuf + 6);
252 	}
253 	else if(!strncasecmp(strbuf, "DATE=", 5))
254 	{
255 		if(length >= (5 + 10) &&
256 		   isdigit(strbuf[5 + 0]) && isdigit(strbuf[5 + 1]) && ispunct(strbuf[5 + 2]) &&
257 		   isdigit(strbuf[5 + 3]) && isdigit(strbuf[5 + 4]) && ispunct(strbuf[5 + 5]) &&
258 		   isdigit(strbuf[5 + 6]) && isdigit(strbuf[5 + 7]) && isdigit(strbuf[5 + 8]) && isdigit(strbuf[5 + 9]))
259 		{
260 			// nn-nn-yyyy
261 			strbuf[5 + 10] = '\0';
262 			psong->year = atoi(strbuf + 5 + 6);
263 		}
264 		else
265 		{
266 			// year first. year is at most 4 digit.
267 			strbuf[5 + 4] = '\0';
268 			psong->year = atoi(strbuf + 5);
269 		}
270 	}
271 	else if(!strncasecmp(strbuf, "COMMENT=", 8))
272 	{
273 		if( *(strbuf+8) )
274 			psong->comment = strdup(strbuf + 8);
275 	}
276 	else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMID=", 20))
277 	{
278 		psong->musicbrainz_albumid = strdup(strbuf + 20);
279 	}
280 	else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
281 	{
282 		psong->musicbrainz_trackid = strdup(strbuf + 20);
283 	}
284 	else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
285 	{
286 		psong->musicbrainz_trackid = strdup(strbuf + 20);
287 	}
288 	else if(!strncasecmp(strbuf, "MUSICBRAINZ_ARTISTID=", 21))
289 	{
290 		psong->musicbrainz_artistid = strdup(strbuf + 21);
291 	}
292 	else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMARTISTID=", 26))
293 	{
294 		psong->musicbrainz_albumartistid = strdup(strbuf + 26);
295 	}
296 }
297