commit 001c761fe889345cc3ede4a4854a9b1d1aaa4e1a Author: kacarmichael Date: Thu Jul 17 01:14:45 2025 -0500 Initial Commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/m3u8-downloader.iml b/.idea/m3u8-downloader.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/m3u8-downloader.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d6a0bc3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f091169 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module m3u8-downloader diff --git a/main.go b/main.go new file mode 100644 index 0000000..27d4896 --- /dev/null +++ b/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "m3u8-downloader/media" +) + +func main() { + + master_url := "https://d17cyqyz9yhmep.cloudfront.net/streams/234945/playlist_1752291107574_1752292056713.m3u8" + + streams := media.GetStreamMetadata(master_url) + + fmt.Println(streams) +} diff --git a/media/types.go b/media/types.go new file mode 100644 index 0000000..3b512bd --- /dev/null +++ b/media/types.go @@ -0,0 +1,113 @@ +package media + +import ( + "io" + "net/http" + "regexp" + "strconv" + "strings" +) + +type StreamSet struct { + PlaylistURL string + VideoURLs []VideoURL + AudioURLs []AudioURL +} + +func GetStreamMetadata(master_url string) *StreamSet { + resp, err := http.Get(master_url) + if err != nil { + panic(err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + panic(resp.StatusCode) + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + content := string(data) + lines := strings.Split(content, "\n") + video_urls := []VideoURL{} + audio_urls := []AudioURL{} + for i, line := range lines { + if strings.HasPrefix(line, "#EXT-X-STREAM-INF") { + video := NewVideoURL(lines[i], lines[i+1]) + video_urls = append(video_urls, *video) + } else if strings.HasPrefix(line, "#EXT-X-MEDIA") { + audio := NewAudioURL(lines[i]) + audio_urls = append(audio_urls, *audio) + } + } + + return &StreamSet{ + PlaylistURL: master_url, + VideoURLs: video_urls, + AudioURLs: audio_urls, + } +} + +type VideoURL struct { + URL string + Bandwidth int + Codecs string + Resolution string + FrameRate string + Audio string +} + +func NewVideoURL(input string, url string) *VideoURL { + if strings.HasPrefix(input, "#EXT-X-STREAM-INF") { + reg := regexp.MustCompile(`([A-Z0-9-]+)=(".*?"|[^,]*)`) + split := reg.FindAllStringSubmatch(input, -1) + bandwidth := split[0][2] + bandwidth_int, err := strconv.Atoi(bandwidth) + if err != nil { + panic(err) + } + video := VideoURL{ + URL: url, + Bandwidth: bandwidth_int, + Codecs: split[1][2], + Resolution: split[2][2], + FrameRate: split[3][2], + Audio: split[4][2], + } + return &video + } + return nil +} + +type AudioURL struct { + URL string + MediaType string + GroupId string + Name string + Default string + Autoselect string +} + +func NewAudioURL(input string) *AudioURL { + if strings.HasPrefix(input, "#EXT-X-MEDIA") { + split := strings.Split(strings.Split(input, ":")[1], ",") + url := ParseAttribute(split[5]) + mediaType := ParseAttribute(split[0]) + groupId := ParseAttribute(split[1]) + name := ParseAttribute(split[2]) + default_ := ParseAttribute(split[3]) + autoselect := ParseAttribute(split[4]) + audio := AudioURL{ + URL: url, + MediaType: mediaType, + GroupId: groupId, + Name: name, + Default: default_, + Autoselect: autoselect, + } + return &audio + } + return nil +} diff --git a/media/utils.go b/media/utils.go new file mode 100644 index 0000000..7160206 --- /dev/null +++ b/media/utils.go @@ -0,0 +1,24 @@ +package media + +import ( + "strconv" + "strings" +) + +func ParseAttribute(attribute string) string { + split := strings.Split(attribute, "=") + return split[1] +} + +func ResolutionToPixels(resolution string) int { + split := strings.Split(resolution, "x") + width, err := strconv.Atoi(split[0]) + if err != nil { + panic(err) + } + height, err := strconv.Atoi(split[1]) + if err != nil { + panic(err) + } + return width * height +}