mirror of
https://git.code.sf.net/p/minidlna/git
synced 2025-01-25 01:26:48 +00:00
a8325705ef
Some extended and commonly used tag names may store metadata useful to minidlna, so read these where allowance is already made for their storage and use.
323 lines
8.3 KiB
C
323 lines
8.3 KiB
C
//=========================================================================
|
|
// FILENAME : tagutils-misc.c
|
|
// DESCRIPTION : Misc routines for supporting tagutils
|
|
//=========================================================================
|
|
// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
|
|
//=========================================================================
|
|
|
|
/* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**************************************************************************
|
|
* Language
|
|
**************************************************************************/
|
|
|
|
#define MAX_ICONV_BUF 1024
|
|
|
|
typedef enum {
|
|
ICONV_OK,
|
|
ICONV_TRYNEXT,
|
|
ICONV_FATAL
|
|
} iconv_result;
|
|
|
|
#ifdef HAVE_ICONV
|
|
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)
|
|
{
|
|
size_t rc;
|
|
iconv_result ret = ICONV_OK;
|
|
|
|
size_t outbytesleft = outbytesleft_orig - 1;
|
|
char* outbuf = outbuf_orig;
|
|
|
|
iconv_t cd = iconv_open(to_ces, from_ces);
|
|
|
|
if(cd == (iconv_t)-1)
|
|
{
|
|
return ICONV_FATAL;
|
|
}
|
|
rc = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
|
|
if(rc == (size_t)-1)
|
|
{
|
|
if(errno == E2BIG)
|
|
{
|
|
ret = ICONV_FATAL;
|
|
}
|
|
else
|
|
{
|
|
ret = ICONV_TRYNEXT;
|
|
memset(outbuf_orig, '\0', outbytesleft_orig);
|
|
}
|
|
}
|
|
iconv_close(cd);
|
|
|
|
return ret;
|
|
}
|
|
#else // HAVE_ICONV
|
|
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)
|
|
{
|
|
return ICONV_FATAL;
|
|
}
|
|
#endif // HAVE_ICONV
|
|
|
|
#define N_LANG_ALT 8
|
|
struct {
|
|
char *lang;
|
|
char *cpnames[N_LANG_ALT];
|
|
} iconv_map[] = {
|
|
{ "ja_JP", { "CP932", "CP950", "CP936", "ISO-8859-1", 0 } },
|
|
{ "zh_CN", { "CP936", "CP950", "CP932", "ISO-8859-1", 0 } },
|
|
{ "zh_TW", { "CP950", "CP936", "CP932", "ISO-8859-1", 0 } },
|
|
{ "ko_KR", { "CP949", "ISO-8859-1", 0 } },
|
|
{ 0, { 0 } }
|
|
};
|
|
static int lang_index = -1;
|
|
|
|
static int
|
|
_lang2cp(char *lang)
|
|
{
|
|
int cp;
|
|
|
|
if(!lang || lang[0] == '\0')
|
|
return -1;
|
|
for(cp = 0; iconv_map[cp].lang; cp++)
|
|
{
|
|
if(!strcasecmp(iconv_map[cp].lang, lang))
|
|
return cp;
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
static unsigned char*
|
|
_get_utf8_text(const id3_ucs4_t* native_text)
|
|
{
|
|
unsigned char *utf8_text = NULL;
|
|
char *in, *in8, *iconv_buf;
|
|
iconv_result rc;
|
|
int i, n;
|
|
|
|
in = (char*)id3_ucs4_latin1duplicate(native_text);
|
|
if(!in)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
in8 = (char*)id3_ucs4_utf8duplicate(native_text);
|
|
if(!in8)
|
|
{
|
|
free(in);
|
|
goto out;
|
|
}
|
|
|
|
iconv_buf = (char*)calloc(MAX_ICONV_BUF, sizeof(char));
|
|
if(!iconv_buf)
|
|
{
|
|
free(in); free(in8);
|
|
goto out;
|
|
}
|
|
|
|
i = lang_index;
|
|
// (1) try utf8 -> default
|
|
rc = do_iconv(iconv_map[i].cpnames[0], "UTF-8", in8, strlen(in8), iconv_buf, MAX_ICONV_BUF);
|
|
if(rc == ICONV_OK)
|
|
{
|
|
utf8_text = (unsigned char*)in8;
|
|
free(iconv_buf);
|
|
}
|
|
else if(rc == ICONV_TRYNEXT)
|
|
{
|
|
// (2) try default -> utf8
|
|
rc = do_iconv("UTF-8", iconv_map[i].cpnames[0], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
|
|
if(rc == ICONV_OK)
|
|
{
|
|
utf8_text = (unsigned char*)iconv_buf;
|
|
}
|
|
else if(rc == ICONV_TRYNEXT)
|
|
{
|
|
// (3) try other encodes
|
|
for(n = 1; n < N_LANG_ALT && iconv_map[i].cpnames[n]; n++)
|
|
{
|
|
rc = do_iconv("UTF-8", iconv_map[i].cpnames[n], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
|
|
if(rc == ICONV_OK)
|
|
{
|
|
utf8_text = (unsigned char*)iconv_buf;
|
|
break;
|
|
}
|
|
}
|
|
if(!utf8_text)
|
|
{
|
|
// cannot iconv
|
|
utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
|
|
free(iconv_buf);
|
|
}
|
|
}
|
|
free(in8);
|
|
}
|
|
free(in);
|
|
|
|
out:
|
|
if(!utf8_text)
|
|
{
|
|
utf8_text = (unsigned char*)strdup("UNKNOWN");
|
|
}
|
|
|
|
return utf8_text;
|
|
}
|
|
|
|
|
|
static void
|
|
vc_scan(struct song_metadata *psong, const char *comment, const size_t length)
|
|
{
|
|
char strbuf[1024];
|
|
|
|
if(length > (sizeof(strbuf) - 1))
|
|
{
|
|
if( strncasecmp(comment, "LYRICS=", 7) != 0 &&
|
|
strncasecmp(comment, "coverart=", 9) != 0 &&
|
|
strncasecmp(comment, "METADATA_BLOCK_PICTURE=", 23) != 0 )
|
|
{
|
|
const char *eq = strchr(comment, '=');
|
|
int len = 8;
|
|
if (eq)
|
|
len = eq - comment;
|
|
DPRINTF(E_WARN, L_SCANNER, "Vorbis %.*s too long [%s]\n",
|
|
len, comment, psong->path);
|
|
}
|
|
return;
|
|
}
|
|
strncpy(strbuf, comment, length);
|
|
strbuf[length] = '\0';
|
|
|
|
// Xiph.org lists recommended field names for interoperability between programs.
|
|
// Beyond these software may use other tag names, and because the files we are
|
|
// tasked with reading may come from a variety of sources we include other commonly
|
|
// used tags where we can.
|
|
// https://xiph.org/vorbis/doc/v-comment.html
|
|
// ALBUM, ARTIST, PUBLISHER, COPYRIGHT, DISCNUMBER, ISRC, EAN/UPN, LABEL, LABELNO,
|
|
// LICENSE, OPUS, SOURCEMEDIA, TITLE, TRACKNUMBER, VERSION, ENCODED-BY, ENCODING,
|
|
// -- following tags are muliples
|
|
// COMPOSER, ARRANGER, LYRICIST, AUTHOR, CONDUCTOR, PERFORMER, ENSEMBLE, PART
|
|
// PARTNUMBER, GENRE, DATE, LOCATION, COMMENT
|
|
// -- In addition, some software (e.g. Windows Media Player) insists on using YEAR
|
|
// -- rather than DATE, so support this where DATE is not available for reasons of usefulness.
|
|
if(!strncasecmp(strbuf, "ALBUM=", 6))
|
|
{
|
|
if( *(strbuf+6) )
|
|
psong->album = strdup(strbuf + 6);
|
|
}
|
|
else if(!strncasecmp(strbuf, "ARTIST=", 7))
|
|
{
|
|
if( *(strbuf+7) )
|
|
psong->contributor[ROLE_ARTIST] = strdup(strbuf + 7);
|
|
}
|
|
else if(!strncasecmp(strbuf, "ARTISTSORT=", 11))
|
|
{
|
|
psong->contributor_sort[ROLE_ARTIST] = strdup(strbuf + 11);
|
|
}
|
|
else if(!strncasecmp(strbuf, "ALBUMARTIST=", 12))
|
|
{
|
|
if( *(strbuf+12) )
|
|
psong->contributor[ROLE_BAND] = strdup(strbuf + 12);
|
|
}
|
|
else if(!strncasecmp(strbuf, "ALBUMARTISTSORT=", 16))
|
|
{
|
|
psong->contributor_sort[ROLE_BAND] = strdup(strbuf + 16);
|
|
}
|
|
else if(!strncasecmp(strbuf, "COMPOSER=", 9))
|
|
{
|
|
if( *(strbuf+9) )
|
|
psong->contributor[ROLE_COMPOSER] = strdup(strbuf + 9);
|
|
}
|
|
else if(!strncasecmp(strbuf, "CONDUCTOR=", 10))
|
|
{
|
|
if( *(strbuf+10) )
|
|
psong->contributor[ROLE_CONDUCTOR] = strdup(strbuf + 10);
|
|
}
|
|
else if(!strncasecmp(strbuf, "TITLE=", 6))
|
|
{
|
|
if( *(strbuf+6) )
|
|
psong->title = strdup(strbuf + 6);
|
|
}
|
|
else if(!strncasecmp(strbuf, "TRACKNUMBER=", 12))
|
|
{
|
|
psong->track = atoi(strbuf + 12);
|
|
}
|
|
else if(!strncasecmp(strbuf, "TRACKTOTAL=", 11))
|
|
{
|
|
psong->total_tracks = atoi(strbuf + 11);
|
|
}
|
|
else if(!strncasecmp(strbuf, "DISCNUMBER=", 11))
|
|
{
|
|
psong->disc = atoi(strbuf + 11);
|
|
}
|
|
else if(!strncasecmp(strbuf, "DISCTOTAL=", 10))
|
|
{
|
|
psong->total_discs = atoi(strbuf + 10);
|
|
}
|
|
else if(!strncasecmp(strbuf, "GENRE=", 6))
|
|
{
|
|
if( *(strbuf+6) )
|
|
psong->genre = strdup(strbuf + 6);
|
|
}
|
|
else if(!strncasecmp(strbuf, "DATE=", 5) ||
|
|
(!strncasecmp(strbuf, "YEAR=", 5) && psong->year == 0))
|
|
{
|
|
if(length >= (5 + 10) &&
|
|
isdigit(strbuf[5 + 0]) && isdigit(strbuf[5 + 1]) && ispunct(strbuf[5 + 2]) &&
|
|
isdigit(strbuf[5 + 3]) && isdigit(strbuf[5 + 4]) && ispunct(strbuf[5 + 5]) &&
|
|
isdigit(strbuf[5 + 6]) && isdigit(strbuf[5 + 7]) && isdigit(strbuf[5 + 8]) && isdigit(strbuf[5 + 9]))
|
|
{
|
|
// nn-nn-yyyy
|
|
strbuf[5 + 10] = '\0';
|
|
psong->year = atoi(strbuf + 5 + 6);
|
|
}
|
|
else
|
|
{
|
|
// year first. year is at most 4 digit.
|
|
strbuf[5 + 4] = '\0';
|
|
psong->year = atoi(strbuf + 5);
|
|
}
|
|
}
|
|
else if(!strncasecmp(strbuf, "COMMENT=", 8))
|
|
{
|
|
if( *(strbuf+8) )
|
|
psong->comment = strdup(strbuf + 8);
|
|
}
|
|
else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMID=", 20))
|
|
{
|
|
psong->musicbrainz_albumid = strdup(strbuf + 20);
|
|
}
|
|
else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
|
|
{
|
|
psong->musicbrainz_trackid = strdup(strbuf + 20);
|
|
}
|
|
else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
|
|
{
|
|
psong->musicbrainz_trackid = strdup(strbuf + 20);
|
|
}
|
|
else if(!strncasecmp(strbuf, "MUSICBRAINZ_ARTISTID=", 21))
|
|
{
|
|
psong->musicbrainz_artistid = strdup(strbuf + 21);
|
|
}
|
|
else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMARTISTID=", 26))
|
|
{
|
|
psong->musicbrainz_albumartistid = strdup(strbuf + 26);
|
|
}
|
|
}
|