Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

110 Zeilen
2.0KB

  1. package icecast
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "strconv"
  7. "strings"
  8. )
  9. type icyMetadata struct {
  10. StreamTitle string
  11. }
  12. type icyReader struct {
  13. r io.Reader
  14. metaInt int
  15. audioLeft int
  16. onMetadata func(icyMetadata)
  17. }
  18. func newICYReader(r io.Reader, metaInt int, onMetadata func(icyMetadata)) io.Reader {
  19. if r == nil || metaInt <= 0 {
  20. return r
  21. }
  22. return &icyReader{
  23. r: r,
  24. metaInt: metaInt,
  25. audioLeft: metaInt,
  26. onMetadata: onMetadata,
  27. }
  28. }
  29. func (r *icyReader) Read(p []byte) (int, error) {
  30. if len(p) == 0 {
  31. return 0, nil
  32. }
  33. for {
  34. if r.audioLeft == 0 {
  35. if err := r.readMetadataBlock(); err != nil {
  36. return 0, err
  37. }
  38. r.audioLeft = r.metaInt
  39. continue
  40. }
  41. want := len(p)
  42. if want > r.audioLeft {
  43. want = r.audioLeft
  44. }
  45. n, err := r.r.Read(p[:want])
  46. if n > 0 {
  47. r.audioLeft -= n
  48. return n, nil
  49. }
  50. if err != nil {
  51. return 0, err
  52. }
  53. }
  54. }
  55. func (r *icyReader) readMetadataBlock() error {
  56. var lenBuf [1]byte
  57. if _, err := io.ReadFull(r.r, lenBuf[:]); err != nil {
  58. return err
  59. }
  60. blockLen := int(lenBuf[0]) * 16
  61. if blockLen == 0 {
  62. return nil
  63. }
  64. block := make([]byte, blockLen)
  65. if _, err := io.ReadFull(r.r, block); err != nil {
  66. return err
  67. }
  68. if r.onMetadata != nil {
  69. r.onMetadata(parseICYMetadata(block))
  70. }
  71. return nil
  72. }
  73. func parseICYMetadata(block []byte) icyMetadata {
  74. raw := strings.TrimRight(string(bytes.Trim(block, "\x00")), "\x00")
  75. meta := icyMetadata{}
  76. for _, field := range strings.Split(raw, ";") {
  77. field = strings.TrimSpace(field)
  78. if !strings.HasPrefix(field, "StreamTitle=") {
  79. continue
  80. }
  81. v := strings.TrimPrefix(field, "StreamTitle=")
  82. v = strings.TrimSpace(v)
  83. if len(v) >= 2 && ((v[0] == '\'' && v[len(v)-1] == '\'') || (v[0] == '"' && v[len(v)-1] == '"')) {
  84. v = v[1 : len(v)-1]
  85. }
  86. meta.StreamTitle = v
  87. break
  88. }
  89. return meta
  90. }
  91. func parseICYMetaInt(raw string) (int, error) {
  92. raw = strings.TrimSpace(raw)
  93. if raw == "" {
  94. return 0, nil
  95. }
  96. n, err := strconv.Atoi(raw)
  97. if err != nil || n < 0 {
  98. return 0, fmt.Errorf("invalid icy-metaint: %q", raw)
  99. }
  100. return n, nil
  101. }