diff --git a/cmd/downloader/main.go b/cmd/downloader/main.go index ea1e000..f135530 100644 --- a/cmd/downloader/main.go +++ b/cmd/downloader/main.go @@ -1,14 +1,15 @@ package main import ( - "fmt" "m3u8-downloader/pkg/downloader" + "m3u8-downloader/pkg/media" + "sync" ) func main() { //Stream URL - masterUrl := "https://d17cyqyz9yhmep.cloudfront.net/streams/234945/playlist_1752291107574_1752292056713.m3u8" + masterUrl := "https://d17cyqyz9yhmep.cloudfront.net/streams/234951/playlist_vo_1752978025523_1752978954944.m3u8" //Download Service service := downloader.GetDownloadService() @@ -21,15 +22,17 @@ func main() { } //Select best quality streams - audio, video := stream.Master.SelectBestQualityStreams() + video, audio := stream.Master.SelectBestQualityStreams() - //Populate Segment Lists - audio_segments, err := service.ParseSegmentPlaylist(stream.BuildSegmentURL(audio.URL)) + if !(audio == nil) { + audio_segments, err := service.ParseSegmentPlaylist(stream.BuildSegmentURL(audio.URL)) - if err != nil { - panic(err) + if err != nil { + panic(err) + } + audio.Segments = *audio_segments } - audio.Segments = *audio_segments + //Populate Segment Lists video_segments, err := service.ParseSegmentPlaylist(stream.BuildSegmentURL(video.URL)) @@ -38,45 +41,38 @@ func main() { } video.Segments = *video_segments - //Download Segment Playlists - for _, segment := range video.Segments.SegmentList { - err := service.DownloadFile(stream.BuildSegmentURL(segment.URL)) - if err != nil { - fmt.Println(err) - return + audioChan := make(chan media.Segment, 10) + videoChan := make(chan media.Segment, 10) + + var wg sync.WaitGroup + + if !(audio == nil) { + for i := 1; i <= 2; i++ { + wg.Add(1) + go service.DownloadWorker(i, audioChan, &wg) } } - for _, segment := range audio.Segments.SegmentList { - err := service.DownloadFile(stream.BuildSegmentURL(segment.URL)) - if err != nil { - fmt.Println(err) - return - } + for i := 1; i <= 4; i++ { + wg.Add(1) + go service.DownloadWorker(i, videoChan, &wg) } - fmt.Println(stream) - //stream, err := media.ParseMasterPlaylist(masterUrl) - //if err != nil { - // panic(err) - //} - // - //audio, video, err := stream.FetchSegmentPlaylists() - //if err != nil { - // panic(err) - //} - // - //videoPlaylist := media.ParseMediaPlaylist(video) - //audioPlaylist := media.ParseMediaPlaylist(audio) - // - //for _, segment := range videoPlaylist.SegmentList { - // fmt.Println(segment.URL) - //} - // - //for _, segment := range audioPlaylist.SegmentList { - // err := http.DownloadFile(stream.BuildSegmentURL(segment.URL), media.OutputDirPath) - // if err != nil { - // return - // } - //} + if !(audio == nil) { + go func() { + for _, segment := range audio.Segments.SegmentList { + audioChan <- segment + } + close(audioChan) + }() + } + + go func() { + for _, segment := range video.Segments.SegmentList { + videoChan <- segment + } + close(videoChan) + }() + + wg.Wait() } diff --git a/pkg/constants/http.go b/pkg/constants/http.go index e0ac345..4f0f0b2 100644 --- a/pkg/constants/http.go +++ b/pkg/constants/http.go @@ -1,7 +1,8 @@ package constants const ( - HTTPUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0" + HTTPUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" HTTPPrefix = "http://" HTTPSPrefix = "https://" + REFERRER = "https://www.flomarching.com" ) diff --git a/pkg/downloader/worker.go b/pkg/downloader/worker.go new file mode 100644 index 0000000..aebd6c4 --- /dev/null +++ b/pkg/downloader/worker.go @@ -0,0 +1,21 @@ +package downloader + +import ( + "fmt" + "m3u8-downloader/pkg/media" + "sync" +) + +func (s *DownloadService) DownloadWorker(id int, segmentChan <-chan media.Segment, wg *sync.WaitGroup) { + defer wg.Done() + + for segment := range segmentChan { + + fmt.Printf("[Worker %d] Downloading: %s\n", id, segment.URL) + err := s.DownloadFile(segment.URL) + if err != nil { + fmt.Printf("[Worker %d] Error: %s\n", id, err) + return + } + } +} diff --git a/pkg/http/client.go b/pkg/http/client.go index d805762..b3a774d 100644 --- a/pkg/http/client.go +++ b/pkg/http/client.go @@ -60,5 +60,6 @@ var DefaultClient = &HTTPWrapper{ client: &http.Client{}, headers: map[string]string{ "User-Agent": constants.HTTPUserAgent, + "Referer": constants.REFERRER, }, } diff --git a/pkg/media/media_playlist.go b/pkg/media/media_playlist.go index 79e532e..8459a63 100644 --- a/pkg/media/media_playlist.go +++ b/pkg/media/media_playlist.go @@ -20,6 +20,7 @@ type VideoPlaylist struct { } 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") } @@ -28,7 +29,7 @@ func NewVideoStream(streamInfo, url string) (*VideoPlaylist, error) { matches := reg.FindAllStringSubmatch(streamInfo, -1) if len(matches) < 5 { - return nil, errors.New("insufficient stream attributes") + fmt.Println("Less than 5 stream attributes found - audio likely disabled") } bandwidth, err := strconv.Atoi(matches[0][2]) @@ -36,14 +37,25 @@ func NewVideoStream(streamInfo, url string) (*VideoPlaylist, error) { 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]), - AudioGroup: StripQuotes(matches[4][2]), }, nil + } func (v *VideoPlaylist) BuildPlaylistURL(filename string) string { diff --git a/pkg/media/segment.go b/pkg/media/segment.go index 4c6af2a..177b71c 100644 --- a/pkg/media/segment.go +++ b/pkg/media/segment.go @@ -46,7 +46,7 @@ type SegmentPlaylist struct { //} func (s *StreamSet) BuildSegmentURL(filename string) string { - return fmt.Sprintf("%s%s", constants.HTTPSPrefix, s.Metadata.Domain+"/streams/"+s.Metadata.StreamID+"/a/5000/"+filename) + return fmt.Sprintf("%s%s", constants.HTTPSPrefix, s.Metadata.Domain+"/streams/"+s.Metadata.StreamID+"/"+filename) } func ParseMediaPlaylist(content string) *SegmentPlaylist {