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