Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

92 lines
2.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package playlist
import (
"unicode"
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/charmap"
)
// legacyEncodings lists codepages commonly used by music taggers that wrote
// non-Latin text but marked the encoding as Latin-1.
var legacyEncodings = []encoding.Encoding{
charmap.Windows1255, // Hebrew
charmap.Windows1256, // Arabic
charmap.Windows1251, // Cyrillic
charmap.Windows1253, // Greek
charmap.Windows874, // Thai
}
// sanitizeTag detects mojibake from legacy codepages and re-decodes to UTF-8.
//
// Many old ID3 taggers write non-Latin text using a Windows codepage but mark
// the encoding as Latin-1. The tag library faithfully decodes those bytes as
// Latin-1, producing garbled text. We detect this by checking for a high
// density of Latin-1 supplement characters (U+0080U+00FF), reverse the
// Latin-1 decode to recover the original bytes, then try common codepages
// and pick the one that produces the most non-Latin script characters.
func sanitizeTag(s string) string {
if s == "" {
return s
}
// Count runes in the Latin-1 supplement range (U+0080U+00FF).
// Real Latin text (French, German) rarely exceeds ~10% accented chars.
// Mojibake from non-Latin scripts is almost entirely in this range.
var total, highCount int
for _, r := range s {
total++
if r >= 0x80 && r <= 0xFF {
highCount++
}
}
if total == 0 || highCount*3 < total {
return s
}
// Reverse the Latin-1 decode to recover the original tag bytes.
raw := make([]byte, 0, total)
for _, r := range s {
if r > 0xFF {
return s // Contains runes outside Latin-1 — not simple mojibake
}
raw = append(raw, byte(r))
}
// The original bytes might be valid UTF-8 that was double-decoded.
if utf8.Valid(raw) {
return string(raw)
}
// Try legacy codepages; pick the one that produces the most
// non-Latin letters (Hebrew, Cyrillic, Arabic, Greek, Thai, etc.).
var bestText string
var bestScore int
for _, enc := range legacyEncodings {
decoded, err := enc.NewDecoder().Bytes(raw)
if err != nil {
continue
}
text := string(decoded)
if !utf8.ValidString(text) {
continue
}
score := 0
for _, r := range text {
if unicode.IsLetter(r) && r > 0x024F {
score++
}
}
if score > bestScore {
bestScore = score
bestText = text
}
}
if bestText != "" {
return bestText
}
return s
}