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:] }