package media import ( "errors" "fmt" "m3u8-downloader/pkg/constants" "regexp" "strconv" "strings" ) type VideoPlaylist struct { URL string Bandwidth int Codecs string Resolution string FrameRate string AudioGroup string Segments SegmentPlaylist } func NewVideoStream(streamInfo, url string) (*VideoPlaylist, error) { hasAudio := strings.Contains(strings.ToLower(streamInfo), "audio") if !strings.HasPrefix(streamInfo, constants.ExtXStreamInf) { return nil, errors.New("invalid stream info line") } reg := regexp.MustCompile(`([A-Z0-9-]+)=(".*?"|[^,]*)`) matches := reg.FindAllStringSubmatch(streamInfo, -1) if len(matches) < 5 { fmt.Println("Less than 5 stream attributes found - audio likely disabled") } bandwidth, err := strconv.Atoi(matches[0][2]) if err != nil { return nil, err } if hasAudio { return &VideoPlaylist{ URL: url, Bandwidth: bandwidth, Codecs: StripQuotes(matches[1][2]), Resolution: StripQuotes(matches[2][2]), FrameRate: StripQuotes(matches[3][2]), AudioGroup: StripQuotes(matches[4][2]), }, nil } return &VideoPlaylist{ URL: url, Bandwidth: bandwidth, Codecs: StripQuotes(matches[1][2]), Resolution: StripQuotes(matches[2][2]), FrameRate: StripQuotes(matches[3][2]), }, nil } func (v *VideoPlaylist) BuildPlaylistURL(filename string) string { return fmt.Sprintf("%s%s", constants.HTTPSPrefix, v.URL+"/a/5000/"+filename) } type AudioPlaylist struct { URL string MediaType string GroupID string Name string IsDefault bool AutoSelect bool Segments SegmentPlaylist } func (a *AudioPlaylist) BuildPlaylistURL(filename string) string { return fmt.Sprintf("%s%s", constants.HTTPSPrefix, a.URL+"/a/5000/"+filename) } func NewAudioStream(mediaInfo string) (*AudioPlaylist, error) { if !strings.HasPrefix(mediaInfo, constants.ExtXMedia) { return nil, errors.New("invalid downloader info line") } attributes := ParseMediaAttributes(mediaInfo) return &AudioPlaylist{ URL: StripQuotes(attributes["URI"]), MediaType: StripQuotes(attributes["TYPE"]), GroupID: StripQuotes(attributes["GROUP-ID"]), Name: StripQuotes(attributes["NAME"]), IsDefault: attributes["DEFAULT"] == "YES", AutoSelect: attributes["AUTOSELECT"] == "YES", }, nil } type PlaylistMetadata struct { URL string Domain string StreamID string } func GetPlaylistMetadata(masterURL string) *PlaylistMetadata { strippedURL := strings.Replace(masterURL, constants.HTTPSPrefix, "", 1) strippedURL = strings.Replace(strippedURL, constants.HTTPPrefix, "", 1) urlPrefix := strings.Split(strippedURL, "/")[0] return &PlaylistMetadata{ URL: masterURL, Domain: urlPrefix, StreamID: strings.Split(strippedURL, "/")[2], } } func ParsePlaylistLines(content string) ([]VideoPlaylist, []AudioPlaylist) { lines := strings.Split(content, "\n") var videoStreams []VideoPlaylist var audioStreams []AudioPlaylist for i, line := range lines { if strings.HasPrefix(line, constants.ExtXStreamInf) && i+1 < len(lines) { if video, err := NewVideoStream(line, lines[i+1]); err == nil { videoStreams = append(videoStreams, *video) } } else if strings.HasPrefix(line, constants.ExtXMedia) { if audio, err := NewAudioStream(line); err == nil { audioStreams = append(audioStreams, *audio) } } } return videoStreams, audioStreams } func ResolutionToPixels(resolution string) int { parts := strings.Split(resolution, "x") if len(parts) != 2 { return 0 } width, err := strconv.Atoi(parts[0]) if err != nil { return 0 } height, err := strconv.Atoi(parts[1]) if err != nil { return 0 } return width * height }