metadata: Normalize all metadata to Unicode NFC form

rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]> Sun, 3 May 2026 07:07:08 -0400
Newsgroups gmane.comp.systems.archos.rockbox.cvs
Message-ID <[email protected]>
commit 81fcb10f8fa92c5d01eda887e0ecde5682c240f9
Author: Solomon Peachy <[email protected]>
Date:   Sat Nov 8 18:51:43 2025 -0500

    metadata:  Normalize all metadata to Unicode NFC form
    
     * Standalone database tool
     * Simulator builds
     * Target firmware (Hosted and Native, for all >2MB targets)
    
    Change-Id: Ia7361affc2fc6a08e73c31ecc9ef3a4008c2415d

diff --git a/apps/features.txt b/apps/features.txt
index c9dea50abd..2d3ad26a73 100644
--- a/apps/features.txt
+++ b/apps/features.txt
@@ -316,3 +316,7 @@ lto
 #if defined(USB_ENABLE_AUDIO)
 usbdac
 #endif
+
+#if defined(UTF8PROC_EXPORTS)
+utf8proc
+#endif
diff --git a/firmware/common/unicode.c b/firmware/common/unicode.c
index c1e187a709..f7d44e1ee2 100644
--- a/firmware/common/unicode.c
+++ b/firmware/common/unicode.c
@@ -44,6 +44,10 @@
 #define O_NOISODECODE 0
 #endif
 
+#ifdef UTF8PROC_EXPORTS
+#include "utf8proc.h"
+#endif
+
 #define getle16(p) (p[0] | (p[1] << 8))
 #define getbe16(p) ((p[0] << 8) | p[1])
 
@@ -59,8 +63,9 @@
 #define open_noiso_internal open
 #endif /* !APPLICATION */
 
-#if 0 /* not needed just now (will probably end up a spinlock) */
 #include "mutex.h"
+
+#if 0 /* not needed just now (will probably end up a spinlock) */
 static struct mutex cp_mutex SHAREDBSS_ATTR;
 #define cp_lock_init()   mutex_init(&cp_mutex)
 #define cp_lock_enter()  mutex_lock(&cp_mutex)
@@ -651,9 +656,32 @@ const char * get_codepage_name(int cp)
     return cp_info[cp].name;
 }
 
-#if 0 /* not needed just now */
+#ifdef UTF8PROC_EXPORTS
+static utf8proc_int32_t normbuf[2048];
+static struct mutex norm_mutex SHAREDBSS_ATTR;
+
+void utf8_normalize(char *string)
+{
+    utf8proc_ssize_t result, orig;
+
+    if (!string || !*string)
+         return;
+
+    mutex_lock(&norm_mutex);
+    orig = strlen(string);
+    result = utf8proc_decompose(string, 0, normbuf, sizeof(normbuf)/4 -1, UTF8PROC_NULLTERM);
+    if (result > 0) {
+        result = utf8proc_reencode(normbuf, result, UTF8PROC_NULLTERM|UTF8PROC_COMPOSE|UTF8PROC_STABLE);
+        if (result > 0 && result <= orig && strcmp((char*)normbuf, string))
+            strcpy(string, (char*)normbuf);
+    }
+    mutex_unlock(&norm_mutex);
+}
+
 void unicode_init(void)
 {
     cp_lock_init();
+    mutex_init(&norm_mutex);
 }
+
 #endif
diff --git a/firmware/include/rbunicode.h b/firmware/include/rbunicode.h
index 48ca1b2583..d243c08596 100644
--- a/firmware/include/rbunicode.h
+++ b/firmware/include/rbunicode.h
@@ -76,8 +76,9 @@ const char *get_current_codepage_name_linux(void);
 #endif
 #endif /* APPLICATION */
 
-#if 0 /* not needed just now */
-void unicode_init(void);
+#ifdef UTF8PROC_EXPORTS
+void utf8_normalize(char *string);
+void unicode_init(void) INIT_ATTR;
 #else
 #define unicode_init() do {} while (0)
 #endif
diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c
index 00c6bfd581..16af58a291 100644
--- a/lib/rbcodec/metadata/metadata.c
+++ b/lib/rbcodec/metadata/metadata.c
@@ -27,6 +27,10 @@
 #include "logf.h"
 #include "metadata.h"
 
+#ifdef UTF8PROC_EXPORTS
+#include "rbunicode.h"
+#endif
+
 #include "metadata_parsers.h"
 
 /* For trailing tag stripping and base audio data types */
@@ -471,6 +475,22 @@ bool get_metadata_ex(struct mp3entry* id3, int fd, const char* trackname, int fl
         wipe_mp3entry(id3); /* ensure the mp3entry is clear */
     }
 
+#ifdef UTF8PROC_EXPORTS
+    if (success) {
+        utf8_normalize(id3->title);
+        utf8_normalize(id3->artist);
+        utf8_normalize(id3->album);
+        utf8_normalize(id3->genre_string);
+        utf8_normalize(id3->disc_string);
+        utf8_normalize(id3->track_string);
+        utf8_normalize(id3->year_string);
+        utf8_normalize(id3->composer);
+        utf8_normalize(id3->comment);
+        utf8_normalize(id3->albumartist);
+        utf8_normalize(id3->grouping);
+    }
+#endif
+
     if ((flags & METADATA_CLOSE_FD_ON_EXIT))
         close(fd);
     else
diff --git a/tools/database/database.make b/tools/database/database.make
index 79f84b4bee..c4b2d85f2a 100644
--- a/tools/database/database.make
+++ b/tools/database/database.make
@@ -9,6 +9,8 @@
 
 GCCOPTS += -g -DDEBUG -D__PCTOOL__ -DDBTOOL
 
+include $(ROOTDIR)/lib/utf8proc/utf8proc.make
+
 METADATAS := $(wildcard $(ROOTDIR)/lib/rbcodec/metadata/*.c)
 
 DATABASE_SRC = $(call preprocess, $(TOOLSDIR)/database/SOURCES) $(METADATAS)
@@ -28,7 +30,7 @@ INCLUDES += -I$(ROOTDIR)/apps/gui \
             -I$(APPSDIR) \
             -I$(BUILDDIR)
 
-OTHERLIBS := $(FIXEDPOINTLIB)
+OTHERLIBS := $(FIXEDPOINTLIB) $(LIBUTF8PROC)
 
 .SECONDEXPANSION: # $$(OBJ) is not populated until after this
 
diff --git a/tools/root.make b/tools/root.make
index 523eb5d180..b1659e7da4 100644
--- a/tools/root.make
+++ b/tools/root.make
@@ -128,6 +128,14 @@ else # core
   include $(APPSDIR)/apps.make
   include $(ROOTDIR)/lib/rbcodec/rbcodec.make
 
+  # bootloaders don't get utf8proc
+  ifeq (,$(findstring checkwps,$(APP_TYPE)))
+    IS_GREATER := $(shell [ $(MEMORYSIZE) -gt 2 ] && echo true || echo false)
+    ifeq ($(IS_GREATER),true)
+      include $(ROOTDIR)/lib/utf8proc/utf8proc.make
+    endif
+  endif
+
   ifeq ($(ENABLEDPLUGINS),yes)
     include $(APPSDIR)/plugins/bitmaps/pluginbitmaps.make
     include $(APPSDIR)/plugins/plugins.make
-- 
rockbox-cvs mailing list
[email protected]
https://lists.haxx.se/mailman/listinfo/rockbox-cvs