|
|
|
@@ -77,18 +77,31 @@ func (r *icyReader) readMetadataBlock() error { |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
// parseICYMetadata parses the ICY inline metadata block. |
|
|
|
// |
|
|
|
// ICY metadata is a semicolon-delimited key=value format where values are |
|
|
|
// single-quoted strings. A naive strings.Split(raw, ";") breaks when the |
|
|
|
// StreamTitle itself contains semicolons (e.g. "Artist - Title; Live Edit"). |
|
|
|
// This parser is quote-aware: it only splits on semicolons that appear |
|
|
|
// outside of single-quoted value strings. |
|
|
|
func parseICYMetadata(block []byte) icyMetadata { |
|
|
|
raw := strings.TrimRight(string(bytes.Trim(block, "\x00")), "\x00") |
|
|
|
meta := icyMetadata{} |
|
|
|
for _, field := range strings.Split(raw, ";") { |
|
|
|
|
|
|
|
fields := splitICYFields(raw) |
|
|
|
for _, field := range fields { |
|
|
|
field = strings.TrimSpace(field) |
|
|
|
if !strings.HasPrefix(field, "StreamTitle=") { |
|
|
|
continue |
|
|
|
} |
|
|
|
v := strings.TrimPrefix(field, "StreamTitle=") |
|
|
|
v = strings.TrimSpace(v) |
|
|
|
if len(v) >= 2 && ((v[0] == '\'' && v[len(v)-1] == '\'') || (v[0] == '"' && v[len(v)-1] == '"')) { |
|
|
|
v = v[1 : len(v)-1] |
|
|
|
// Strip enclosing single or double quotes. |
|
|
|
if len(v) >= 2 { |
|
|
|
if (v[0] == '\'' && v[len(v)-1] == '\'') || |
|
|
|
(v[0] == '"' && v[len(v)-1] == '"') { |
|
|
|
v = v[1 : len(v)-1] |
|
|
|
} |
|
|
|
} |
|
|
|
meta.StreamTitle = v |
|
|
|
break |
|
|
|
@@ -96,6 +109,29 @@ func parseICYMetadata(block []byte) icyMetadata { |
|
|
|
return meta |
|
|
|
} |
|
|
|
|
|
|
|
// splitICYFields splits an ICY metadata string on semicolons that appear |
|
|
|
// outside of single-quoted value strings. Semicolons inside quotes (e.g. |
|
|
|
// StreamTitle='Artist - Song; Live';) are preserved as part of the value. |
|
|
|
func splitICYFields(s string) []string { |
|
|
|
var fields []string |
|
|
|
inQuote := false |
|
|
|
start := 0 |
|
|
|
for i := 0; i < len(s); i++ { |
|
|
|
c := s[i] |
|
|
|
if c == '\'' { |
|
|
|
inQuote = !inQuote |
|
|
|
} |
|
|
|
if c == ';' && !inQuote { |
|
|
|
fields = append(fields, s[start:i]) |
|
|
|
start = i + 1 |
|
|
|
} |
|
|
|
} |
|
|
|
if start < len(s) { |
|
|
|
fields = append(fields, s[start:]) |
|
|
|
} |
|
|
|
return fields |
|
|
|
} |
|
|
|
|
|
|
|
func parseICYMetaInt(raw string) (int, error) { |
|
|
|
raw = strings.TrimSpace(raw) |
|
|
|
if raw == "" { |
|
|
|
|