140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
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) {
|
|
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 {
|
|
return nil, errors.New("insufficient stream attributes")
|
|
}
|
|
|
|
bandwidth, err := strconv.Atoi(matches[0][2])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|