Decode the UCS4 to the current locale instead of latin1.

Kurt Roeckx <[email protected]> Sat, 17 Jun 2006 19:55:42 +0200
Newsgroups gmane.comp.audio.mad.devel
Message-ID <[email protected]>
Hi,

madplay currently always converts the id3 tag information to
latin1.  The attached patch converts it to the currently codeset
based on nl_langinfo(CODESET).

It seems the configure script already checks for nl_langinfo and
iconv with the automake macro's, so I didn't have to patch
configure for this.

Maybe those function belong in libid3tag instead, it would be
more logical.  It would also not require us to use non-exported
functions.  The reason I just put it in madplay is that this only
require me to change one source package.

There is one problem with this patch that I know of, and that is
that it doesn't properly deal with (argualby broken) iconv()
implementations saying the input sequence is invalid when it
can't map a valid input character to the output encoding, and I'm
not sure how to deal with it, see the comment.


Kurt
madplay-0.15.2b+codeset.diff (text/plain, 3.7 KB)
--- madplay-0.15.2b/player.c	2004-02-23 21:34:53.000000000 +0000
+++ madplay-0.15.2b+codeset/player.c	2006-06-17 17:38:33.000000000 +0000
@@ -71,6 +71,14 @@
 #  include <sys/mman.h>
 # endif
 
+# if defined(HAVE_ICONV)
+#  include <iconv.h>
+# endif
+
+# if defined(HAVE_LANGINFO_CODESET)
+#  include <langinfo.h>
+# endif
+
 # if !defined(O_BINARY)
 #  define O_BINARY  0
 # endif
@@ -598,6 +606,103 @@
   return 0;
 }
 
+/* Note: id3_ucs4_length and id3_utf8_size are part of libid3tag,
+ * but they're currently not exported it in a public header.
+ */
+id3_length_t id3_ucs4_length(id3_ucs4_t const *ucs4);
+id3_length_t id3_utf8_length(id3_utf8_t const *);
+
+/*
+ * NAME:	ucs4_to_locale_strdup()
+ * DESCRIPTION:	convert ucs4 string to locale encoding
+ */
+static
+char *ucs4_to_locale_strdup(const id3_ucs4_t *ucs4)
+#if defined(HAVE_ICONV) && defined(HAVE_LANGINFO_CODESET)
+{
+	size_t	len;
+	id3_utf8_t *utf8 = NULL;
+	char	*outbuf = NULL;
+	size_t	inlen, outlen, retlen;
+	char	*in_p, *out_p;
+	iconv_t	cd;
+
+	/*
+	 * First convert from ucs4 to utf8, so we have a charset
+	 * that isn't endian dependant, and then can
+	 * covert it using iconv() to the current codeset.
+	 */
+	len = id3_ucs4_length(ucs4) + 1;
+	utf8 = id3_ucs4_utf8duplicate(ucs4);
+	if (!utf8)
+	{
+		error("ucs4_to_locale_strdup", 
+			_("id3_ucs4_utf8duplicate() failed."));
+		goto fail;
+	}
+
+	cd = iconv_open(nl_langinfo(CODESET), "UTF-8");
+	if (cd == (iconv_t)(-1))
+	{
+		error("ucs4_to_locale_strdup", 
+			_("Can't convert characters to current codeset"));
+		free(utf8);
+		goto fail;
+	}
+
+	/*
+	 * UTF8 is at most 5 bytes per character and should be the worst
+	 * case encoding, so we use this here.
+	 */
+	outbuf = malloc(len*5);
+	if (!outbuf)
+	{
+		error("ucs4_to_locale_strdup", 
+			_("not enough memory to allocate buffer"));
+		free(utf8);
+		goto fail;
+	}
+	memset(outbuf, 0, len*5);
+
+	in_p = (char *)utf8;
+	out_p = outbuf;
+	inlen = id3_utf8_length(utf8);
+	outlen = len*5;
+
+	/* Some implementations of iconv() return -1 and 
+	 * set errno to EILSEQ on valid input for which
+	 * the output encoding doesn't have a correspondending 
+	 * character.  The standard says they should do an
+	 * implementation defined conversion in this case.
+	 *
+	 * Currently we print an error and just return the first
+	 * few chars it could convert, if any.  To be the same
+	 * as other code, it should probably skip the input
+	 * character and set the output character to 
+	 * ID3_UCS4_REPLACEMENTCHAR.  It's just a little hard
+	 * to do so in case you don't know the output encoding.
+	 *
+	 * Is there a better solution to this?
+	 */
+	retlen = iconv(cd, &in_p, &inlen, &out_p, &outlen);
+	if (retlen == (size_t)(-1) || inlen != 0)
+	{
+		error("ucs4_to_locale_strdup", 
+			_("Conversion failed"));
+	}
+	free(utf8);
+	iconv_close(cd);
+	return outbuf;
+fail:
+	/* This is probably going to break something if this ever happens. */
+	return NULL;
+}
+#else
+{
+  return id3_ucs4_latin1duplicate(ucs4);
+}
+#endif
+
 /*
  * NAME:	show_id3()
  * DESCRIPTION:	display ID3 tag information
@@ -652,7 +757,7 @@
       if (strcmp(info[i].id, ID3_FRAME_GENRE) == 0)
 	ucs4 = id3_genre_name(ucs4);
 
-      latin1 = id3_ucs4_latin1duplicate(ucs4);
+      latin1 = ucs4_to_locale_strdup(ucs4);
       if (latin1 == 0)
 	goto fail;
 
@@ -685,7 +790,7 @@
     ucs4 = id3_field_getfullstring(id3_frame_field(frame, 3));
     assert(ucs4);
 
-    latin1 = id3_ucs4_latin1duplicate(ucs4);
+    latin1 = ucs4_to_locale_strdup(ucs4);
     if (latin1 == 0)
       goto fail;
 
@@ -1211,7 +1316,7 @@
     if (nstrings > 0) {
       id3_latin1_t *latin1;
 
-      latin1 = id3_ucs4_latin1duplicate(id3_field_getstrings(field, 0));
+      latin1 = ucs4_to_locale_strdup(id3_field_getstrings(field, 0));
       if (latin1) {
 	signed long ms;