From 54f98497c2d4572c50f54c9b9a361671ef325d67 Mon Sep 17 00:00:00 2001 From: caiocotts <31974888+caiocotts@users.noreply.github.com> Date: Sat, 28 Aug 2021 19:35:54 -0400 Subject: [PATCH] Use wchar_t for TagLib filenames on Windows (#1310) * Use wchar_t for tagLib filenames on Windows * Make TagLib default extractor for all platforms. * Organize imports Co-authored-by: Deluan --- conf/configuration.go | 2 +- conf/defaults.go | 5 -- conf/defaults_win.go | 5 -- consts/consts.go | 2 + scanner/metadata/metadata.go | 11 ++- scanner/metadata/taglib/get_filename.go | 9 ++ scanner/metadata/taglib/get_filename_win.go | 96 +++++++++++++++++++++ scanner/metadata/taglib/taglib_wrapper.cpp | 4 + scanner/metadata/taglib/taglib_wrapper.go | 2 +- scanner/metadata/taglib/taglib_wrapper.h | 4 + 10 files changed, 122 insertions(+), 18 deletions(-) delete mode 100644 conf/defaults.go delete mode 100644 conf/defaults_win.go create mode 100644 scanner/metadata/taglib/get_filename.go create mode 100644 scanner/metadata/taglib/get_filename_win.go diff --git a/conf/configuration.go b/conf/configuration.go index 23c1be871..83b3dd397 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -214,7 +214,7 @@ func init() { viper.SetDefault("reverseproxyuserheader", "Remote-User") viper.SetDefault("reverseproxywhitelist", "") - viper.SetDefault("scanner.extractor", DefaultScannerExtractor) + viper.SetDefault("scanner.extractor", consts.DefaultScannerExtractor) viper.SetDefault("scanner.genreseparators", ";/,") viper.SetDefault("agents", "lastfm,spotify") diff --git a/conf/defaults.go b/conf/defaults.go deleted file mode 100644 index 283449bf8..000000000 --- a/conf/defaults.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build !windows - -package conf - -const DefaultScannerExtractor = "taglib" diff --git a/conf/defaults_win.go b/conf/defaults_win.go deleted file mode 100644 index 53091285e..000000000 --- a/conf/defaults_win.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build windows - -package conf - -const DefaultScannerExtractor = "ffmpeg" diff --git a/consts/consts.go b/consts/consts.go index 04ce5e897..4a64715bc 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -47,6 +47,8 @@ const ( PlaceholderAvatar = "logo-192x192.png" DefaultHttpClientTimeOut = 10 * time.Second + + DefaultScannerExtractor = "taglib" ) // Cache options diff --git a/scanner/metadata/metadata.go b/scanner/metadata/metadata.go index bac50b7d2..a9a84f1c1 100644 --- a/scanner/metadata/metadata.go +++ b/scanner/metadata/metadata.go @@ -10,13 +10,12 @@ import ( "strings" "time" - "github.com/navidrome/navidrome/scanner/metadata/ffmpeg" - - "github.com/navidrome/navidrome/scanner/metadata/taglib" - "github.com/google/uuid" "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/scanner/metadata/ffmpeg" + "github.com/navidrome/navidrome/scanner/metadata/taglib" ) type Parser interface { @@ -32,8 +31,8 @@ func Extract(files ...string) (map[string]Tags, error) { p, ok := parsers[conf.Server.Scanner.Extractor] if !ok { log.Warn("Invalid 'Scanner.Extractor' option. Using default", "requested", conf.Server.Scanner.Extractor, - "validOptions", "ffmpeg,taglib", "default", conf.DefaultScannerExtractor) - p = parsers[conf.DefaultScannerExtractor] + "validOptions", "ffmpeg,taglib", "default", consts.DefaultScannerExtractor) + p = parsers[consts.DefaultScannerExtractor] } extractedTags, err := p.Parse(files...) diff --git a/scanner/metadata/taglib/get_filename.go b/scanner/metadata/taglib/get_filename.go new file mode 100644 index 000000000..f4c2307b5 --- /dev/null +++ b/scanner/metadata/taglib/get_filename.go @@ -0,0 +1,9 @@ +// +build !windows + +package taglib + +import "C" + +func getFilename(s string) *C.char { + return C.CString(s) +} diff --git a/scanner/metadata/taglib/get_filename_win.go b/scanner/metadata/taglib/get_filename_win.go new file mode 100644 index 000000000..90bafec5e --- /dev/null +++ b/scanner/metadata/taglib/get_filename_win.go @@ -0,0 +1,96 @@ +// +build windows + +package taglib + +// From https://github.com/orofarne/gowchar + +/* +#include + +const size_t SIZEOF_WCHAR_T = sizeof(wchar_t); + +void gowchar_set (wchar_t *arr, int pos, wchar_t val) +{ + arr[pos] = val; +} + +wchar_t gowchar_get (wchar_t *arr, int pos) +{ + return arr[pos]; +} +*/ +import "C" + +import ( + "fmt" + "unicode/utf16" + "unicode/utf8" +) + +var SIZEOF_WCHAR_T C.size_t = C.size_t(C.SIZEOF_WCHAR_T) + +func getFilename(s string) *C.wchar_t { + wstr, _ := StringToWcharT(s) + return wstr +} + +func StringToWcharT(s string) (*C.wchar_t, C.size_t) { + switch SIZEOF_WCHAR_T { + case 2: + return stringToWchar2(s) // Windows + case 4: + return stringToWchar4(s) // Unix + default: + panic(fmt.Sprintf("Invalid sizeof(wchar_t) = %v", SIZEOF_WCHAR_T)) + } + panic("?!!") +} + +// Windows +func stringToWchar2(s string) (*C.wchar_t, C.size_t) { + var slen int + s1 := s + for len(s1) > 0 { + r, size := utf8.DecodeRuneInString(s1) + if er, _ := utf16.EncodeRune(r); er == '\uFFFD' { + slen += 1 + } else { + slen += 2 + } + s1 = s1[size:] + } + slen++ // \0 + res := C.malloc(C.size_t(slen) * SIZEOF_WCHAR_T) + var i int + for len(s) > 0 { + r, size := utf8.DecodeRuneInString(s) + if r1, r2 := utf16.EncodeRune(r); r1 != '\uFFFD' { + C.gowchar_set((*C.wchar_t)(res), C.int(i), C.wchar_t(r1)) + i++ + C.gowchar_set((*C.wchar_t)(res), C.int(i), C.wchar_t(r2)) + i++ + } else { + C.gowchar_set((*C.wchar_t)(res), C.int(i), C.wchar_t(r)) + i++ + } + s = s[size:] + } + C.gowchar_set((*C.wchar_t)(res), C.int(slen-1), C.wchar_t(0)) // \0 + return (*C.wchar_t)(res), C.size_t(slen) +} + +// Unix +func stringToWchar4(s string) (*C.wchar_t, C.size_t) { + slen := utf8.RuneCountInString(s) + slen++ // \0 + res := C.malloc(C.size_t(slen) * SIZEOF_WCHAR_T) + var i int + for len(s) > 0 { + r, size := utf8.DecodeRuneInString(s) + C.gowchar_set((*C.wchar_t)(res), C.int(i), C.wchar_t(r)) + s = s[size:] + i++ + } + C.gowchar_set((*C.wchar_t)(res), C.int(slen-1), C.wchar_t(0)) // \0 + return (*C.wchar_t)(res), C.size_t(slen) +} diff --git a/scanner/metadata/taglib/taglib_wrapper.cpp b/scanner/metadata/taglib/taglib_wrapper.cpp index 9f77748a6..6d6674670 100644 --- a/scanner/metadata/taglib/taglib_wrapper.cpp +++ b/scanner/metadata/taglib/taglib_wrapper.cpp @@ -17,7 +17,11 @@ char has_cover(const TagLib::FileRef f); +#ifdef WIN32 +int taglib_read(const wchar_t *filename, unsigned long id) { +#else int taglib_read(const char *filename, unsigned long id) { +#endif TagLib::FileRef f(filename, true, TagLib::AudioProperties::Fast); if (f.isNull()) { diff --git a/scanner/metadata/taglib/taglib_wrapper.go b/scanner/metadata/taglib/taglib_wrapper.go index 900a0af9b..899a95a60 100644 --- a/scanner/metadata/taglib/taglib_wrapper.go +++ b/scanner/metadata/taglib/taglib_wrapper.go @@ -21,7 +21,7 @@ import ( ) func Read(filename string) (map[string][]string, error) { - fp := C.CString(filename) + fp := getFilename(filename) defer C.free(unsafe.Pointer(fp)) id, m := newMap() defer deleteMap(id) diff --git a/scanner/metadata/taglib/taglib_wrapper.h b/scanner/metadata/taglib/taglib_wrapper.h index 95edac884..7be62e2cd 100644 --- a/scanner/metadata/taglib/taglib_wrapper.h +++ b/scanner/metadata/taglib/taglib_wrapper.h @@ -8,7 +8,11 @@ extern "C" { extern void go_map_put_str(unsigned long id, char *key, char *val); extern void go_map_put_int(unsigned long id, char *key, int val); +#ifdef WIN32 +int taglib_read(const wchar_t *filename, unsigned long id); +#else int taglib_read(const char *filename, unsigned long id); +#endif #ifdef __cplusplus }