Skip to content

Commit d4556e7

Browse files
committed
Support unicode when writing id3 tags
Falling back to latin1 for commands which are not valid utf8 LAME expects utf16. On Windows, we use the built-in MultiByteToWideChar. On other platforms, we try to link to iconv.
1 parent cddfb07 commit d4556e7

File tree

3 files changed

+96
-55
lines changed

3 files changed

+96
-55
lines changed

configure.ac

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,14 @@ if test "$with_lame" != "no"; then
442442
AC_CHECK_LIB(mp3lame, id3tag_set_fieldvalue, using_lame=$using_lame)
443443
if test "$ac_cv_lib_mp3lame_id3tag_set_fieldvalue" = yes; then
444444
AC_DEFINE(HAVE_LAME_ID3TAG, 1, [Define to 1 if lame supports optional ID3 tags.])
445+
using_iconv=yes
446+
AC_CHECK_HEADERS(windows.h, using_iconv=no)
447+
if test "$using_iconv" = yes; then
448+
AC_CHECK_LIB(iconv, iconv, MP3_LIBS="$MP3_LIBS -liconv", using_iconv=no)
449+
if test "$ac_cv_lib_mp3lame_id3tag_set_fieldvalue" = yes; then
450+
AC_DEFINE(HAVE_ICONV, 1, [Define to 1 if lame should use iconv to write unicode ID3 tags.])
451+
fi
452+
fi
445453
fi
446454
if test "$with_lame" = "yes" -a "$using_lame" = "no"; then
447455
AC_MSG_FAILURE([cannot find LAME])

src/mp3-util.h

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
#include <sys/stat.h>
1919

20-
#ifdef USING_ID3TAG
20+
#if defined(USING_ID3TAG) || defined(HAVE_LAME_ID3TAG)
2121

2222
static char const * id3tagmap[][2] =
2323
{
@@ -32,49 +32,99 @@ static char const * id3tagmap[][2] =
3232
{NULL, NULL}
3333
};
3434

35-
#endif /* USING_ID3TAG */
35+
#endif /* USING_ID3TAG || HAVE_LAME_ID3TAG */
3636

3737
#if defined(HAVE_LAME)
3838

39+
40+
#if defined _WIN32 || defined _WIN64
41+
42+
#include <wchar.h>
43+
#include <windows.h>
44+
45+
#define UTF16_ID3 1
46+
47+
static unsigned short * utf8_to_utf16 (const char * utf8)
48+
{
49+
int len = strlen(utf8);
50+
int wlength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, len, 0, 0);
51+
if (wlength == 0) return NULL;
52+
LPWSTR wstr = (LPWSTR)calloc((size_t)(wlength+2), sizeof(wchar_t));
53+
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, len, wstr+1, wlength);
54+
wstr[0] = 0xFEFF; /* add BOM, it is required by LAME */
55+
return (unsigned short *) wstr;
56+
}
57+
58+
#elif defined(HAVE_ICONV)
59+
60+
#include <iconv.h>
61+
62+
#define UTF16_ID3 1
63+
64+
static unsigned short * utf8_to_utf16 (const char * utf8) {
65+
size_t inSize, outSize, cstrSize;
66+
char *in, *out, *cstr;
67+
68+
inSize = strlen(utf8);
69+
outSize = inSize * 2 + 2; /* worst case, UTF-16 can take max twice as much space as UTF-8 */
70+
in = (char *) utf8;
71+
out = lsx_malloc(outSize);
72+
cstr = out;
73+
74+
iconv_t conv = iconv_open("UTF-16", "UTF-8");
75+
size_t status = iconv(conv, &in, &inSize, &out, &outSize);
76+
iconv_close(conv);
77+
78+
if (status == ((size_t) -1)) {
79+
free(cstr);
80+
return NULL;
81+
}
82+
83+
cstrSize = out - cstr;
84+
cstr[cstrSize] = 0; /* add null-terminator, first byte */
85+
cstr[cstrSize+1] = 0; /* second byte */
86+
return (unsigned short *) cstr;
87+
}
88+
89+
#endif /* HAVE_ICONV */
90+
91+
92+
93+
static void set_id3_field (priv_t * p, const char * field, const char * value)
94+
{
95+
char* buf = lsx_malloc(strlen(field) + strlen(value) + 2);
96+
if (!buf) return;
97+
sprintf(buf, "%s=%s", field, value);
98+
99+
#if defined(UTF16_ID3)
100+
unsigned short * utf16 = utf8_to_utf16(buf);
101+
if (utf16) {
102+
p->id3tag_set_fieldvalue_utf16(p->gfp, utf16);
103+
free(utf16);
104+
} else {
105+
/* the value wasn't valid utf8, fall back to latin1 */
106+
p->id3tag_set_fieldvalue(p->gfp, buf);
107+
}
108+
#else
109+
p->id3tag_set_fieldvalue(p->gfp, buf);
110+
#endif
111+
112+
free(buf);
113+
}
114+
115+
39116
static void write_comments(sox_format_t * ft)
40117
{
41118
priv_t *p = (priv_t *) ft->priv;
42119
const char* comment;
120+
size_t i;
43121

44122
p->id3tag_init(p->gfp);
45123
p->id3tag_set_pad(p->gfp, (size_t)ID3PADDING);
46124

47-
/* Note: id3tag_set_fieldvalue is not present in LAME 3.97, so we're using
48-
the 3.97-compatible methods for all of the tags that 3.97 supported. */
49-
/* FIXME: This is no more necessary, since support for LAME 3.97 has ended. */
50-
if ((comment = sox_find_comment(ft->oob.comments, "Title")))
51-
p->id3tag_set_title(p->gfp, comment);
52-
if ((comment = sox_find_comment(ft->oob.comments, "Artist")))
53-
p->id3tag_set_artist(p->gfp, comment);
54-
if ((comment = sox_find_comment(ft->oob.comments, "Album")))
55-
p->id3tag_set_album(p->gfp, comment);
56-
if ((comment = sox_find_comment(ft->oob.comments, "Tracknumber")))
57-
p->id3tag_set_track(p->gfp, comment);
58-
if ((comment = sox_find_comment(ft->oob.comments, "Year")))
59-
p->id3tag_set_year(p->gfp, comment);
60-
if ((comment = sox_find_comment(ft->oob.comments, "Comment")))
61-
p->id3tag_set_comment(p->gfp, comment);
62-
if ((comment = sox_find_comment(ft->oob.comments, "Genre")))
63-
{
64-
if (p->id3tag_set_genre(p->gfp, comment))
65-
lsx_warn("\"%s\" is not a recognized ID3v1 genre.", comment);
66-
}
67-
68-
if ((comment = sox_find_comment(ft->oob.comments, "Discnumber")))
69-
{
70-
char* id3tag_buf = lsx_malloc(strlen(comment) + 6);
71-
if (id3tag_buf)
72-
{
73-
sprintf(id3tag_buf, "TPOS=%s", comment);
74-
p->id3tag_set_fieldvalue(p->gfp, id3tag_buf);
75-
free(id3tag_buf);
76-
}
77-
}
125+
for (i = 0; id3tagmap[i][0]; ++i)
126+
if ((comment = sox_find_comment(ft->oob.comments, id3tagmap[i][1])))
127+
set_id3_field(p, id3tagmap[i][0], comment);
78128
}
79129

80130
#endif /* HAVE_LAME */

src/mp3.c

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,10 @@ static const char* const lame_library_names[] =
243243
LAME_FUNC(f,x, int, lame_close, (lame_global_flags *)) \
244244
LAME_FUNC(f,x, size_t, lame_get_lametag_frame, (const lame_global_flags *, unsigned char*, size_t)) \
245245
LAME_FUNC_ID3(f,x, void, id3tag_init, (lame_global_flags *)) \
246-
LAME_FUNC_ID3(f,x, void, id3tag_set_title, (lame_global_flags *, const char* title)) \
247-
LAME_FUNC_ID3(f,x, void, id3tag_set_artist, (lame_global_flags *, const char* artist)) \
248-
LAME_FUNC_ID3(f,x, void, id3tag_set_album, (lame_global_flags *, const char* album)) \
249-
LAME_FUNC_ID3(f,x, void, id3tag_set_year, (lame_global_flags *, const char* year)) \
250-
LAME_FUNC_ID3(f,x, void, id3tag_set_comment, (lame_global_flags *, const char* comment)) \
251-
LAME_FUNC_ID3(f,x, int, id3tag_set_track, (lame_global_flags *, const char* track)) \
252-
LAME_FUNC_ID3(f,x, int, id3tag_set_genre, (lame_global_flags *, const char* genre)) \
253246
LAME_FUNC_ID3(f,x, size_t, id3tag_set_pad, (lame_global_flags *, size_t)) \
254247
LAME_FUNC_ID3(f,x, size_t, lame_get_id3v2_tag, (lame_global_flags *, unsigned char*, size_t)) \
255-
LAME_FUNC_ID3(f,x, int, id3tag_set_fieldvalue, (lame_global_flags *, const char *))
248+
LAME_FUNC_ID3(f,x, int, id3tag_set_fieldvalue, (lame_global_flags *, const char *)) \
249+
LAME_FUNC_ID3(f,x, int, id3tag_set_fieldvalue_utf16, (lame_global_flags *, const unsigned short *))
256250

257251
static const char* const twolame_library_names[] =
258252
{
@@ -1015,26 +1009,15 @@ static void msgf(const char* fmt, va_list va)
10151009

10161010
UNUSED static void id3tag_init_stub(lame_global_flags * gfp UNUSED)
10171011
{ return; }
1018-
UNUSED static void id3tag_set_title_stub(lame_global_flags * gfp UNUSED, const char* title UNUSED)
1019-
{ return; }
1020-
UNUSED static void id3tag_set_artist_stub(lame_global_flags * gfp UNUSED, const char* artist UNUSED)
1021-
{ return; }
1022-
UNUSED static void id3tag_set_album_stub(lame_global_flags * gfp UNUSED, const char* album UNUSED)
1023-
{ return; }
1024-
UNUSED static void id3tag_set_year_stub(lame_global_flags * gfp UNUSED, const char* year UNUSED)
1025-
{ return; }
1026-
UNUSED static void id3tag_set_comment_stub(lame_global_flags * gfp UNUSED, const char* comment UNUSED)
1027-
{ return; }
1028-
UNUSED static void id3tag_set_track_stub(lame_global_flags * gfp UNUSED, const char* track UNUSED)
1029-
{ return; }
1030-
UNUSED static int id3tag_set_genre_stub(lame_global_flags * gfp UNUSED, const char* genre UNUSED)
1031-
{ return 0; }
10321012
UNUSED static size_t id3tag_set_pad_stub(lame_global_flags * gfp UNUSED, size_t n UNUSED)
10331013
{ return 0; }
10341014
UNUSED static size_t lame_get_id3v2_tag_stub(lame_global_flags * gfp UNUSED, unsigned char * buffer UNUSED, size_t size UNUSED)
10351015
{ return 0; }
10361016
UNUSED static int id3tag_set_fieldvalue_stub(lame_global_flags * gfp UNUSED, const char *fieldvalue UNUSED)
10371017
{ return 0; }
1018+
UNUSED static int id3tag_set_fieldvalue_utf16_stub(lame_global_flags * gfp UNUSED, const unsigned short *fieldvalue UNUSED)
1019+
{ return 0; }
1020+
10381021

10391022
static int get_id3v2_tag_size(sox_format_t * ft)
10401023
{

0 commit comments

Comments
 (0)