Added way to handle disabled audio. Moved to channels/goroutines

This commit is contained in:
kacarmichael 2025-07-21 00:44:32 -05:00
parent b37a312b18
commit 354bbbf7df
6 changed files with 79 additions and 48 deletions

View File

@ -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
if !(audio == nil) {
audio_segments, err := service.ParseSegmentPlaylist(stream.BuildSegmentURL(audio.URL))
if err != nil {
panic(err)
}
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 i := 1; i <= 4; i++ {
wg.Add(1)
go service.DownloadWorker(i, videoChan, &wg)
}
if !(audio == nil) {
go func() {
for _, segment := range audio.Segments.SegmentList {
err := service.DownloadFile(stream.BuildSegmentURL(segment.URL))
if err != nil {
fmt.Println(err)
return
audioChan <- segment
}
close(audioChan)
}()
}
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
// }
//}
go func() {
for _, segment := range video.Segments.SegmentList {
videoChan <- segment
}
close(videoChan)
}()
wg.Wait()
}

View File

@ -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"
)

21
pkg/downloader/worker.go Normal file
View File

@ -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
}
}
}

View File

@ -60,5 +60,6 @@ var DefaultClient = &HTTPWrapper{
client: &http.Client{},
headers: map[string]string{
"User-Agent": constants.HTTPUserAgent,
"Referer": constants.REFERRER,
},
}

View File

@ -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,6 +37,7 @@ func NewVideoStream(streamInfo, url string) (*VideoPlaylist, error) {
return nil, err
}
if hasAudio {
return &VideoPlaylist{
URL: url,
Bandwidth: bandwidth,
@ -46,6 +48,16 @@ func NewVideoStream(streamInfo, url string) (*VideoPlaylist, error) {
}, 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)
}

View File

@ -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 {