diff --git a/bin/darwin/chaturbate-dvr b/bin/darwin/chaturbate-dvr index 094c893..bc8744d 100644 Binary files a/bin/darwin/chaturbate-dvr and b/bin/darwin/chaturbate-dvr differ diff --git a/bin/linux/chaturbate-dvr b/bin/linux/chaturbate-dvr index e80ed0d..c2f59eb 100644 Binary files a/bin/linux/chaturbate-dvr and b/bin/linux/chaturbate-dvr differ diff --git a/bin/windows/chaturbate-dvr.exe b/bin/windows/chaturbate-dvr.exe index f94c6df..d5dbc3c 100644 Binary files a/bin/windows/chaturbate-dvr.exe and b/bin/windows/chaturbate-dvr.exe differ diff --git a/go.mod b/go.mod index a5a8be6..7ff6a16 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,9 @@ require ( github.com/parnurzeal/gorequest v0.2.16 github.com/teacat/pathx v0.0.0-20201109184104-55ec346a0c6d github.com/urfave/cli/v2 v2.3.0 +) + +require ( github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -17,4 +20,4 @@ require ( github.com/stretchr/testify v1.7.0 // indirect golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 // indirect moul.io/http2curl v1.0.0 // indirect -) \ No newline at end of file +) diff --git a/main.go b/main.go index a354b1e..2006f89 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "log" "net/http" "os" @@ -14,7 +13,6 @@ import ( "time" "github.com/TwiN/go-color" - "github.com/teacat/pathx" "github.com/grafov/m3u8" "github.com/parnurzeal/gorequest" @@ -33,6 +31,9 @@ var temp []string // segmentIndex is current stored segment index. var segmentIndex int +// segmentMap is the map stores temporary video segments, it will be merged into master video file then got deleted. +var segmentMap map[string][]byte = make(map[string][]byte) + // stripLimit reprsents the maximum Bytes sizes to split the video into chunks. var stripLimit int @@ -77,7 +78,10 @@ func getChannelURL(username string) string { // getBody gets the channel page content body. func getBody(username string) string { - _, body, _ := gorequest.New().Get(getChannelURL(username)).End() + resp, body, _ := gorequest.New().Get(getChannelURL(username)).End() + if resp.StatusCode != 200 { + return "" + } return body } @@ -105,13 +109,15 @@ func getHLSSource(body string) (string, string) { // parseHLSSource parses the HLS table and return the maximum resolution m3u8 source. func parseHLSSource(url string, baseURL string) string { - _, body, _ := gorequest.New().Get(url).End() - - <-time.After(time.Millisecond * 300) - - // Decode the HLS table. + resp, body, _ := gorequest.New().Get(url).End() + if resp.StatusCode == 403 { + return "" + } p, _, _ := m3u8.DecodeFrom(strings.NewReader(body), true) - master := p.(*m3u8.MasterPlaylist) + master, ok := p.(*m3u8.MasterPlaylist) + if !ok { + return "" + } return fmt.Sprintf("%s%s", baseURL, master.Variants[len(master.Variants)-1].URI) } @@ -144,12 +150,30 @@ func parseM3U8Source(url string) (chunks []*m3u8.MediaSegment, wait float64, err func capture(username string) { // Define the video filename by current time //04.09.22 added username into filename mK33y. filename := username + "_" + time.Now().Format("2006-01-02_15-04-05") - // Get the channel page content body. - body := getBody(username) - // Get the master playlist URL from extracting the channel body. - hlsSource, baseURL := getHLSSource(body) - // Get the best resolution m3u8 by parsing the HLS source table. - m3u8Source := parseHLSSource(hlsSource, baseURL) + var m3u8Source, baseURL, hlsSource string + var tried int + for { + tried++ + // + if tried > 10 { + panic(errors.New("cannot fetch the Playlist correctly after 10 tries")) + } + // Get the channel page content body. + body := getBody(username) + // + if body == "" { + continue + } + // Get the master playlist URL from extracting the channel body. + hlsSource, baseURL = getHLSSource(body) + // Get the best resolution m3u8 by parsing the HLS source table. + m3u8Source = parseHLSSource(hlsSource, baseURL) + // + if m3u8Source != "" { + break + } + <-time.After(time.Millisecond * 500) + } // Create the master video file. masterFile, _ := os.OpenFile("./"+savePath+"/"+filename+".ts", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) // @@ -210,7 +234,6 @@ func isDuplicateSegment(URI string) bool { // still needs some attention here func combineSegment(master *os.File, filename string) { index := 1 - delete := 1 stripIndex := 1 var retry int <-time.After(4 * time.Second) @@ -223,7 +246,7 @@ func combineSegment(master *os.File, filename string) { continue } - if !pathx.Exists(fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)) { + if _, ok := segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)]; !ok { if retry >= 5 { index++ retry = 0 @@ -240,7 +263,7 @@ func combineSegment(master *os.File, filename string) { retry = 0 } // - b, _ := ioutil.ReadFile(fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)) + b := segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)] // if stripLimit != 0 && stripQuota <= 0 { newMasterFilename := "./" + savePath + "/" + filename + "_" + strconv.Itoa(stripIndex) + ".ts" @@ -253,12 +276,7 @@ func combineSegment(master *os.File, filename string) { // log.Printf(infoMergeSegment, index, segmentIndex) - e := os.Remove(fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, delete)) - // - if e != nil { - delete-- - } - delete++ + delete(segmentMap, fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)) index++ } } @@ -272,14 +290,7 @@ func fetchSegment(master *os.File, segment *m3u8.MediaSegment, baseURL string, f return } stripQuota -= len(body) - // - f, err := os.OpenFile(fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) - if err != nil { - panic(err) - } - if _, err := f.Write(body); err != nil { - panic(err) - } + segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)] = body } // endpoint implements the application main function endpoint.