-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathm3uplus.go
118 lines (97 loc) · 2.43 KB
/
m3uplus.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package proxytv
import (
"bufio"
"errors"
"fmt"
"io"
"net/url"
"regexp"
"strconv"
"strings"
)
type m3uHandler interface {
OnPlaylistStart()
OnTrack(track *Track)
OnPlaylistEnd()
}
type Track struct {
Name string
Length float64
URI *url.URL
Tags map[string]string
Raw string
LineNumber int
}
var errMalformedM3U = errors.New("malformed M3U provided")
var errMissingExtinf = errors.New("URL found without preceding EXTINF")
func loadM3u(r io.Reader, handler m3uHandler) error {
scanner := bufio.NewScanner(r)
lineNum := 0
var currentTrack *Track
handler.OnPlaylistStart()
for scanner.Scan() {
lineNum++
line := strings.TrimSpace(scanner.Text())
if lineNum == 1 && !strings.HasPrefix(line, "#EXTM3U") {
return errMalformedM3U
}
switch {
case strings.HasPrefix(line, "#EXTINF:"):
if currentTrack != nil {
handler.OnTrack(currentTrack)
}
currentTrack = &Track{
Raw: line,
LineNumber: lineNum,
}
var err error
currentTrack.Length, currentTrack.Name, currentTrack.Tags, err = decodeInfoLine(line)
if err != nil {
return err
}
case isURL(line):
if currentTrack == nil {
return errMissingExtinf
}
uri, _ := url.Parse(line)
currentTrack.URI = uri
handler.OnTrack(currentTrack)
currentTrack = nil
}
}
if currentTrack != nil {
handler.OnTrack(currentTrack)
}
if err := scanner.Err(); err != nil {
return err
}
handler.OnPlaylistEnd()
return nil
}
func isURL(str string) bool {
u, err := url.Parse(str)
return err == nil && u.Scheme != "" && u.Host != ""
}
var infoRegex = regexp.MustCompile(`([^\s="]+)=(?:"(.*?)"|(\d+))(?:,([.*^,]))?|#EXTINF:(-?\d*\s*)|,(.*)`)
func decodeInfoLine(line string) (float64, string, map[string]string, error) {
matches := infoRegex.FindAllStringSubmatch(line, -1)
var err error
durationFloat := 0.0
durationStr := strings.TrimSpace(matches[0][len(matches[0])-2])
if durationStr != "-1" && len(durationStr) > 0 {
if durationFloat, err = strconv.ParseFloat(durationStr, 64); err != nil {
return 0, "", nil, fmt.Errorf("duration parsing error: %s", err)
}
}
titleIndex := len(matches) - 1
title := matches[titleIndex][len(matches[titleIndex])-1]
keyMap := make(map[string]string)
for _, match := range matches[1 : len(matches)-1] {
val := match[2]
if val == "" {
val = match[3]
}
keyMap[strings.ToLower(match[1])] = val
}
return durationFloat, title, keyMap, nil
}