diff --git a/main.go b/main.go index 0d9e432..b485042 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "log" "net/http" "os" @@ -12,6 +13,8 @@ import ( "strings" "time" + "github.com/teacat/pathx" + "github.com/grafov/m3u8" "github.com/parnurzeal/gorequest" "github.com/urfave/cli" @@ -27,11 +30,14 @@ var retriesAfterOnlined = 0 var lastCheckOnline = time.Now() // buffer stores the media segments and wait for comsume. -var buffer = make(chan *m3u8.MediaSegment, 999999) +var buffer = make(chan *m3u8.MediaSegment) // var bucket []string +// +var segmentIndex int + // var ( errInternal = errors.New("err") @@ -131,7 +137,7 @@ func parseM3U8Source(url string) (chunks []*m3u8.MediaSegment, wait float64, err // capture captures the specified channel streaming. func capture(username string) { // Define the video filename by current time. - filename := time.Now().String() + ".ts" + filename := time.Now().String() // Get the channel page content body. body := getBody(username) // Get the master playlist URL from extracting the channel body. @@ -139,22 +145,12 @@ func capture(username string) { // Get the best resolution m3u8 by parsing the HLS source table. m3u8Source := parseHLSSource(hlsSource, baseURL) // - f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) + f, err := os.OpenFile(filename+".ts", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) if err != nil { panic(err) } - /*defer func() { - for { - if len(buffer) == 0 { - f.Close() - return - } - log.Printf("Waiting for the buffer to be purged...") - <-time.After(2 * time.Second) - } - }()*/ - go comsumer(f, baseURL) + go comsumer(f, filename, baseURL) // Keep fetching the stream chunks until the playlist cannot be accessed after retried x times (which means the channel is offlined). for { @@ -191,41 +187,44 @@ func capture(username string) { continue } bucket = append(bucket, v.URI[len(v.URI)-10:]) - log.Printf("%s (%d in buffer)", v.URI, len(buffer)) - buffer <- v + segmentIndex++ + go fetchSegment(f, v, baseURL, filename, segmentIndex) } <-time.After(time.Duration(wait*1000) * time.Millisecond) } } -// comsumer -func comsumer(file *os.File, baseURL string) { - for { - v := <-buffer - var retry int - var body []byte - var errs []error - for { - if retry > 5 { - break - } - _, body, errs = gorequest.New().Get(fmt.Sprintf("%s%s", baseURL, v.URI)).EndBytes() - if len(errs) > 0 { - log.Printf("segment fetch failed", v.URI, len(buffer)) - retry++ - continue - } - break - } - fmt.Printf("GET %s, SIZE: %d\n", v.URI, len(body)) - if len(body) == 0 { - continue - } - - if _, err := file.Write(body); err != nil { - panic(err) - } +// +func fetchSegment(master *os.File, segment *m3u8.MediaSegment, baseURL string, filename string, index int) { + _, body, _ := gorequest.New().Get(fmt.Sprintf("%s%s", baseURL, segment.URI)).EndBytes() + fmt.Printf("GET %s, SIZE: %d\n", segment.URI, len(body)) + if len(body) == 0 { + return } + // + f, err := os.OpenFile(fmt.Sprintf("%s~%d.ts", 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) + } + // + var retry int + for { + if !pathx.Exists(fmt.Sprintf("%s~%d.ts", filename, index-1)) && retry < 3 { + retry++ + <-time.After(1 * time.Second) + } + break + } + // + b, _ := ioutil.ReadFile(fmt.Sprintf("%s~%d.ts", filename, index-1)) + if _, err := master.Write(b); err != nil { + panic(err) + } + // + os.Remove(fmt.Sprintf("%s~%d.ts", filename, index-1)) } // endpoint implements the application main function endpoint. diff --git a/xx/main.go b/xx/main.go deleted file mode 100644 index 43943c7..0000000 --- a/xx/main.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "regexp" - "strconv" - "strings" - "time" - - "github.com/grafov/m3u8" - "github.com/parnurzeal/gorequest" -) - -// -const chaturbateURL = "https://chaturbate.com/" - -// -var retriesAfterOnlined = 0 - -// -var lastCheckOnline = time.Now() - -// -type roomDossier struct { - HLSSource string `json:"hls_source"` -} - -// -func unescapeUnicode(raw string) string { - str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(raw)), `\\u`, `\u`, -1)) - if err != nil { - panic(err) - } - return str -} - -// -func getChannelURL(username string) string { - return fmt.Sprintf("%s%s", chaturbateURL, username) -} - -// -func getPlaylistURL(body string) string { - // - r := regexp.MustCompile(`window\.initialRoomDossier = "(.*?)"`) - matches := r.FindAllStringSubmatch(body, -1) - - // - var roomData roomDossier - data := unescapeUnicode(matches[0][1]) - err := json.Unmarshal([]byte(data), &roomData) - if err != nil { - panic(err) - } - - return roomData.HLSSource -} - -// -func getBody(username string) string { - // - resp, body, errs := gorequest.New().Get(getChannelURL(username)).End() - return body -} - -// -func getOnlineStatus(username string) bool { - // - body := getBody(username) - return strings.Contains(body, "playlist.m3u8") - - // - // resp, body, errs = gorequest.New().Get(url).End() - // if resp.StatusCode == http.StatusForbidden { - // return false - // } -} - -// -func getChunklistURL(playlistURL string) string { - _, body, _ := gorequest.New().Get(playlistURL).End() - - p, listType, err := m3u8.DecodeFrom(strings.NewReader(body), true) - if err != nil { - panic(err) - } - if listType != m3u8.MEDIA { - return - } - switch listType { - case m3u8.MEDIA: - mediapl := p.(*m3u8.MediaPlaylist) - for _, v := range mediapl.Segments.URI { - fmt.Printf("%s\n", v) - } - } - - // - /*resp, body, errs := gorequest.New().Get(playlistURL).End() - - // - var lines []string - reader := bufio.NewReader(strings.NewReader(body)) - for { - line, err := reader.ReadString('\n') - lines = append(lines, line) - if err != nil { - break - } - } - - // - baseURL := strings.TrimRight(playlistURL, "playlist.m3u8") - // - return fmt.Sprintf("%s%s", baseURL, lines[len(lines)-1])*/ -} - -func main() { - username := "sexykiska" - - // - for { - // - if !getOnlineStatus(username) { - <-time.After(time.Minute * 3) - } - - // - body := getBody(username) - // - playlistURL := getPlaylistURL(body) - // - chunklistURL := getChunklistURL(playlistURL) - } -}