diff --git a/DEV.md b/DEV.md index 33aa01f..711289b 100644 --- a/DEV.md +++ b/DEV.md @@ -1,6 +1,6 @@ -GOOS=windows GOARCH=amd64 go build -o bin/windows && -GOOS=darwin GOARCH=amd64 go build -o bin/darwin && -GOOS=linux GOARCH=amd64 go build -o bin/linux +GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr && +GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr && +GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr && GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr && diff --git a/README-tw.md b/README-tw.md index 45eefdc..9fd3b88 100644 --- a/README-tw.md +++ b/README-tw.md @@ -37,8 +37,6 @@ Y8888D' YP 88 YD ## 說明 -影片畫質永遠是以最高為優先,目前沒辦法更改(懶得寫成一個選項)。 - ```bash NAME: chaturbate-dvr - watching a specified chaturbate channel and auto saves the stream as local file @@ -50,10 +48,14 @@ COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: - --username value, -u value channel username to watching - --interval value, -i value minutes to check if a channel goes online or not (default: 1) - --strip value, -s value MB sizes to split the video into chunks (default: 0) - --help, -h show help (default: false) + --username value, -u value channel username to watching + --interval value, -i value minutes to check if a channel goes online or not (default: 1) + --strip value, -s value MB sizes to split the video into chunks (default: 0) + --resolution 240, -r 240 Video resolution, could be 240, `480`, `540`, `720`, `1080` (default: "1080") + --resolution-fallback up, --rf up Looking for larger or smaller resolution (up for larger, `down` for smaller) if a specified resolution was not found (default: "down") + --fps value, -f value Preferred framerate, only works if steaming source supports it, otherwise it will always be 30 FPS (default: "60") + --help, -h show help (default: false) + --version, -v print the version (default: false) ``` ## 中文對應 diff --git a/README.md b/README.md index bbf100c..cc01e33 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,6 @@ Y8888D' YP 88 YD ## Help -The video quality is always the highest as possible, you cannot change it (too lazy to make it as an option). - ```bash NAME: chaturbate-dvr - watching a specified chaturbate channel and auto saves the stream as local file @@ -52,8 +50,12 @@ COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: - --username value, -u value channel username to watching - --interval value, -i value minutes to check if a channel goes online or not (default: 1) - --strip value, -s value MB sizes to split the video into chunks (default: 0) - --help, -h show help (default: false) + --username value, -u value channel username to watching + --interval value, -i value minutes to check if a channel goes online or not (default: 1) + --strip value, -s value MB sizes to split the video into chunks (default: 0) + --resolution 240, -r 240 Video resolution, could be 240, `480`, `540`, `720`, `1080` (default: "1080") + --resolution-fallback up, --rf up Looking for larger or smaller resolution (up for larger, `down` for smaller) if a specified resolution was not found (default: "down") + --fps value, -f value Preferred framerate, only works if steaming source supports it, otherwise it will always be 30 FPS (default: "60") + --help, -h show help (default: false) + --version, -v print the version (default: false) ``` diff --git a/bin/arm64/darwin/chatubrate-dvr b/bin/arm64/darwin/chatubrate-dvr index c266486..bc99259 100644 Binary files a/bin/arm64/darwin/chatubrate-dvr and b/bin/arm64/darwin/chatubrate-dvr differ diff --git a/bin/arm64/linux/chatubrate-dvr b/bin/arm64/linux/chatubrate-dvr index a634792..99aa629 100644 Binary files a/bin/arm64/linux/chatubrate-dvr and b/bin/arm64/linux/chatubrate-dvr differ diff --git a/bin/arm64/windows/chatubrate-dvr b/bin/arm64/windows/chatubrate-dvr index bfee31e..a083866 100644 Binary files a/bin/arm64/windows/chatubrate-dvr and b/bin/arm64/windows/chatubrate-dvr differ diff --git a/bin/windows/chaturbate-dvr.exe b/bin/darwin/chatubrate-dvr similarity index 57% rename from bin/windows/chaturbate-dvr.exe rename to bin/darwin/chatubrate-dvr index 6e720d2..afd7811 100644 Binary files a/bin/windows/chaturbate-dvr.exe and b/bin/darwin/chatubrate-dvr differ diff --git a/bin/linux/chaturbate-dvr b/bin/linux/chatubrate-dvr similarity index 57% rename from bin/linux/chaturbate-dvr rename to bin/linux/chatubrate-dvr index 988baf1..4619eec 100644 Binary files a/bin/linux/chaturbate-dvr and b/bin/linux/chatubrate-dvr differ diff --git a/bin/darwin/chaturbate-dvr b/bin/windows/chatubrate-dvr similarity index 55% rename from bin/darwin/chaturbate-dvr rename to bin/windows/chatubrate-dvr index 65df328..9f6a103 100644 Binary files a/bin/darwin/chaturbate-dvr and b/bin/windows/chatubrate-dvr differ diff --git a/chaturbate-dvr b/chaturbate-dvr index 4eedea8..4619eec 100644 Binary files a/chaturbate-dvr and b/chaturbate-dvr differ diff --git a/go.mod b/go.mod index 09b13dc..331c3a7 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,10 @@ require ( github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/samber/lo v1.38.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/smartystreets/goconvey v1.7.2 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 // indirect moul.io/http2curl v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 2ed24ec..1d0535b 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= @@ -30,6 +32,8 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3 github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 h1:v3NKo+t/Kc3EASxaKZ82lwK6mCf4ZeObQBduYFZHo7c= golang.org/x/net v0.0.0-20211109214657-ef0fda0de508/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/main.go b/main.go index 3b5a7d7..3d26484 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "time" "github.com/TwiN/go-color" + "github.com/samber/lo" "github.com/grafov/m3u8" "github.com/parnurzeal/gorequest" @@ -44,6 +45,15 @@ var stripLimit int // stripQuota represents how many Bytes left til the next video chunk stripping. var stripQuota int +// preferredFPS represents the preferred framerate. +var preferredFPS string + +// preferredResolution represents the preferred resolution, e.g. `240`, `480`, `540`, `720`, `1080`. +var preferredResolution string + +// preferredResolutionFallback represents the preferred resolution fallback, `up`, `down` or `no`. +var preferredResolutionFallback string + // path save video const savePath = "video" @@ -128,7 +138,83 @@ func parseHLSSource(url string, baseURL string) string { if !ok { return "" } - return fmt.Sprintf("%s%s", baseURL, master.Variants[len(master.Variants)-1].URI) + + resolutions := make(map[string][]string) + resolutionInts := []string{} + + for _, v := range master.Variants { + resStr := strings.Split(v.Resolution, "x") + resolutionInts = append(resolutionInts, resStr[1]) + // If the resolution exists in local, it might be a higher framerate source, store it for later use + if _, ok := resolutions[resStr[1]]; ok { + resolutions[resStr[1]] = append(resolutions[resStr[1]], v.URI) + continue + } + if strings.Contains(v.Name, "FPS:60.0") { + if _, ok := resolutions[resStr[1]]; !ok { + resolutions[resStr[1]] = []string{"", v.URI} // The video has no 30 FPS, we fill it with an empty URI + } else { + resolutions[resStr[1]] = []string{v.URI} + } + } else { + resolutions[resStr[1]] = []string{v.URI} + } + } + + log.Printf("Found available resolutions: %s", strings.TrimPrefix(lo.Reduce(resolutionInts, func(prev string, cur string, _ int) string { + return fmt.Sprintf("%s, %s", prev, cur) + }, ""), ", ")) + + pickedResolution, ok := resolutions[preferredResolution] + if !ok { + var comparison []string + if preferredResolutionFallback == "down" { + comparison = lo.Reverse(lo.Map(resolutionInts, func(v string, _ int) string { return v })) + } else { + comparison = resolutionInts + } + fallbackResolution, ok := lo.Find(comparison, func(v string) bool { + sizeInt, _ := strconv.Atoi(v) + prefInt, _ := strconv.Atoi(preferredResolution) + // + if preferredResolutionFallback == "down" { + return sizeInt < prefInt + } else { + return sizeInt > prefInt + } + }) + if ok { + pickedResolution = resolutions[fallbackResolution] + log.Printf("Preferred video resolution %sp not found, use %sp instead.", preferredResolution, fallbackResolution) + } else { + if preferredResolutionFallback == "down" { + pickedResolution = resolutions[resolutionInts[0]] + log.Printf("No fallback video resolution was found, use worse quality %sp instead.", resolutionInts[0]) + } else { + pickedResolution = resolutions[resolutionInts[len(resolutionInts)-1]] + log.Printf("No fallback video resolution was found, use best quality %sp instead.", resolutionInts[len(resolutionInts)-1]) + } + } + } else { + log.Printf("Fetching video resolution in %sp.", preferredResolution) + } + + var uri string + + if preferredFPS == "60" && len(pickedResolution) > 1 { + log.Printf("Fetching video in 60 FPS.") + uri = pickedResolution[1] + } else { + log.Printf("Fetching video in 30 FPS.") + uri = pickedResolution[0] + + if uri == "" { + log.Printf("The video has no 30 FPS, use 60 FPS instead.") + uri = pickedResolution[1] + } + } + + return fmt.Sprintf("%s%s", baseURL, uri) } // parseM3U8Source gets the current segment list, the channel might goes offline if 403 was returned. @@ -328,6 +414,10 @@ func endpoint(c *cli.Context) error { stripLimit = c.Int("strip") * 1024 * 1024 stripQuota = c.Int("strip") * 1024 * 1024 // + preferredFPS = c.String("fps") + preferredResolution = c.String("resolution") + preferredResolutionFallback = c.String("resolution-fallback") + // fmt.Println(" .o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b") fmt.Println("d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88'") @@ -370,6 +460,7 @@ func endpoint(c *cli.Context) error { func main() { app := &cli.App{ + Version: "0.94 Alpha", Flags: []cli.Flag{ &cli.StringFlag{ Name: "username", @@ -389,6 +480,24 @@ func main() { Value: 0, Usage: "MB sizes to split the video into chunks", }, + &cli.StringFlag{ + Name: "resolution", + Aliases: []string{"r"}, + Value: "1080", + Usage: "Video resolution, could be `240`, `480`, `540`, `720`, `1080`", + }, + &cli.StringFlag{ + Name: "resolution-fallback", + Aliases: []string{"rf"}, + Value: "down", + Usage: "Looking for larger or smaller resolution (`up` for larger, `down` for smaller) if a specified resolution was not found", + }, + &cli.StringFlag{ + Name: "fps", + Aliases: []string{"f"}, + Value: "60", + Usage: "Preferred framerate, only works if steaming source supports it, otherwise it will always be 30 FPS", + }, }, Name: "chaturbate-dvr", Usage: "watching a specified chaturbate channel and auto saves the stream as local file",