feat: idnorm and playlist support
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
tvgIDRe = regexp.MustCompile(`tvg-id="([^"]*)"`)
|
||||
urlTvgRe = regexp.MustCompile(`url-tvg="([^"]*)"`)
|
||||
)
|
||||
|
||||
// processPlaylist streams in→out, rewriting the #EXTM3U header so url-tvg
|
||||
// points at epgURL. Returns the playlist's tvg-ids (in order, with duplicates)
|
||||
// and the URL the playlist already declared in url-tvg (empty if absent).
|
||||
//
|
||||
// If epgURL is empty the header is passed through unchanged. The playlist
|
||||
// must start with #EXTM3U after any leading blank lines, otherwise an error
|
||||
// is returned without writing the header.
|
||||
func processPlaylist(in io.Reader, out io.Writer, epgURL string) (tvgIDs []string, originalEPG string, err error) {
|
||||
r := bufio.NewReader(in)
|
||||
headerDone := false
|
||||
|
||||
for {
|
||||
line, readErr := r.ReadString('\n')
|
||||
|
||||
if line != "" {
|
||||
switch {
|
||||
case !headerDone && strings.TrimSpace(line) == "":
|
||||
// leading blank line — write through, keep looking for header
|
||||
|
||||
case !headerDone:
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "#EXTM3U") {
|
||||
return nil, "", errors.New("playlist: missing #EXTM3U header")
|
||||
}
|
||||
line, originalEPG = rewriteHeader(line, epgURL)
|
||||
headerDone = true
|
||||
|
||||
default:
|
||||
if strings.HasPrefix(line, "#EXTINF:") {
|
||||
if m := tvgIDRe.FindStringSubmatch(line); m != nil {
|
||||
if id := strings.TrimSpace(m[1]); id != "" {
|
||||
tvgIDs = append(tvgIDs, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, werr := out.Write([]byte(line)); werr != nil {
|
||||
return nil, "", werr
|
||||
}
|
||||
}
|
||||
|
||||
if readErr == io.EOF {
|
||||
if !headerDone {
|
||||
return nil, "", errors.New("playlist: missing #EXTM3U header")
|
||||
}
|
||||
return tvgIDs, originalEPG, nil
|
||||
}
|
||||
if readErr != nil {
|
||||
return nil, "", readErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteHeader(line, epgURL string) (rewritten, original string) {
|
||||
if m := urlTvgRe.FindStringSubmatch(line); m != nil {
|
||||
original = m[1]
|
||||
if epgURL != "" {
|
||||
line = urlTvgRe.ReplaceAllLiteralString(line, `url-tvg="`+epgURL+`"`)
|
||||
}
|
||||
return line, original
|
||||
}
|
||||
if epgURL == "" {
|
||||
return line, ""
|
||||
}
|
||||
body, eol := splitEOL(line)
|
||||
return body + ` url-tvg="` + epgURL + `"` + eol, ""
|
||||
}
|
||||
|
||||
func splitEOL(s string) (body, eol string) {
|
||||
n := len(s)
|
||||
for n > 0 && (s[n-1] == '\n' || s[n-1] == '\r') {
|
||||
n--
|
||||
}
|
||||
return s[:n], s[n:]
|
||||
}
|
||||
Reference in New Issue
Block a user