From 6b3635aef3c997dab4bdc495dcdc36bd4b734d57 Mon Sep 17 00:00:00 2001 From: bytedream Date: Sat, 26 Mar 2022 20:30:02 +0100 Subject: [PATCH 001/630] Made symbolic link to second binary in install target relative --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5ba784c..7991824 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ clean: install: install -Dm755 $(BINARY_NAME) $(DESTDIR)$(PREFIX)/bin/crunchyroll-go - ln -sf $(DESTDIR)$(PREFIX)/bin/crunchyroll-go $(DESTDIR)$(PREFIX)/bin/crunchy + ln -sf ./crunchyroll-go $(DESTDIR)$(PREFIX)/bin/crunchy install -Dm644 crunchyroll-go.1 $(DESTDIR)$(PREFIX)/share/man/man1/crunchyroll-go.1 install -Dm644 LICENSE $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE From 051cad45370a6de16759bbe45f6f4eea740fd0ee Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 28 Mar 2022 19:48:19 +0200 Subject: [PATCH 002/630] Fix download and goroutines flag not working with archive (#19) --- cmd/crunchyroll-go/cmd/archive.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index a36f806..cd18349 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -229,7 +229,7 @@ func archive(urls []string) error { return fmt.Errorf("failed to pre generate new archive file: %v", err) } } else { - dir := info.Format(downloadDirectoryFlag) + dir := info.Format(archiveDirectoryFlag) if _, err = os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0777); err != nil { return fmt.Errorf("error while creating directory: %v", err) @@ -289,7 +289,7 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st ctx, cancel := context.WithCancel(context.Background()) defer cancel() - downloader := crunchyroll.NewDownloader(ctx, rootFile, downloadGoroutinesFlag, func(segment *m3u8.MediaSegment, current, total int, file *os.File) error { + downloader := crunchyroll.NewDownloader(ctx, rootFile, archiveGoroutinesFlag, func(segment *m3u8.MediaSegment, current, total int, file *os.File) error { // check if the context was cancelled. // must be done in to not print any progress messages if ctrl+c was pressed if ctx.Err() != nil { From e4d075c8557e9024a597eeab0e86f6bf47297475 Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 28 Mar 2022 19:56:09 +0200 Subject: [PATCH 003/630] Fix download not converting into other media formats if specified --- cmd/crunchyroll-go/cmd/download.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index bbf9a3e..4832e68 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -216,6 +216,9 @@ func downloadInfo(info formatInformation, file *os.File) error { } return nil }) + if hasFFmpeg() { + downloader.FFmpegOpts = make([]string, 0) + } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) From e0069a10e09287ba553846b950c5f7d6b3a73b34 Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 28 Mar 2022 19:57:19 +0200 Subject: [PATCH 004/630] Rename variables --- cmd/crunchyroll-go/cmd/archive.go | 18 +++++++++--------- cmd/crunchyroll-go/cmd/download.go | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index cd18349..aefc85c 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -270,12 +270,12 @@ func archive(urls []string) error { func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename string) error { out.Debug("Entering season %d, episode %d with %d additional formats", info.SeasonNumber, info.EpisodeNumber, len(info.additionalFormats)) - downloadProgress, err := createArchiveProgress(info) + dp, err := createArchiveProgress(info) if err != nil { return fmt.Errorf("error while setting up downloader: %v", err) } defer func() { - if downloadProgress.Total != downloadProgress.Current { + if dp.Total != dp.Current { fmt.Println() } }() @@ -297,13 +297,13 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st } if out.IsDev() { - downloadProgress.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) + dp.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) } else { - downloadProgress.Update() + dp.Update() } if current == total { - downloadProgress.UpdateMessage("Merging segments", false) + dp.UpdateMessage("Merging segments", false) } return nil }) @@ -415,7 +415,7 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st return fmt.Errorf("failed to merge files: %v", err) } - downloadProgress.UpdateMessage("Download finished", false) + dp.UpdateMessage("Download finished", false) signal.Stop(sig) out.Debug("Stopped signal catcher") @@ -445,7 +445,7 @@ func createArchiveProgress(info formatInformation) (*downloadProgress, error) { progressCount += int(f.Video.Chunklist.Count()) + 1 } - downloadProgress := &downloadProgress{ + dp := &downloadProgress{ Prefix: out.InfoLog.Prefix(), Message: "Downloading video", // number of segments a video +1 is for the success message @@ -454,10 +454,10 @@ func createArchiveProgress(info formatInformation) (*downloadProgress, error) { Quiet: out.IsQuiet(), } if out.IsDev() { - downloadProgress.Prefix = out.DebugLog.Prefix() + dp.Prefix = out.DebugLog.Prefix() } - return downloadProgress, nil + return dp, nil } func archiveDownloadVideos(downloader crunchyroll.Downloader, filename string, video bool, formats ...*crunchyroll.Format) ([]string, error) { diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 4832e68..30d764a 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -179,7 +179,7 @@ func downloadInfo(info formatInformation, file *os.File) error { return fmt.Errorf("error while initializing the video: %v", err) } - downloadProgress := &downloadProgress{ + dp := &downloadProgress{ Prefix: out.InfoLog.Prefix(), Message: "Downloading video", // number of segments a video has +2 is for merging and the success message @@ -188,10 +188,10 @@ func downloadInfo(info formatInformation, file *os.File) error { Quiet: out.IsQuiet(), } if out.IsDev() { - downloadProgress.Prefix = out.DebugLog.Prefix() + dp.Prefix = out.DebugLog.Prefix() } defer func() { - if downloadProgress.Total != downloadProgress.Current { + if dp.Total != dp.Current { fmt.Println() } }() @@ -206,13 +206,13 @@ func downloadInfo(info formatInformation, file *os.File) error { } if out.IsDev() { - downloadProgress.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) + dp.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) } else { - downloadProgress.Update() + dp.Update() } if current == total { - downloadProgress.UpdateMessage("Merging segments", false) + dp.UpdateMessage("Merging segments", false) } return nil }) @@ -248,7 +248,7 @@ func downloadInfo(info formatInformation, file *os.File) error { return fmt.Errorf("error while downloading: %v", err) } - downloadProgress.UpdateMessage("Download finished", false) + dp.UpdateMessage("Download finished", false) signal.Stop(sig) out.Debug("Stopped signal catcher") From d27fc672889f97b245a953597017e890dc1025dd Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 28 Mar 2022 20:02:51 +0200 Subject: [PATCH 005/630] Remove empty output on last download --- cmd/crunchyroll-go/cmd/archive.go | 10 ++++++---- cmd/crunchyroll-go/cmd/download.go | 9 ++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index aefc85c..95091d0 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -218,8 +218,8 @@ func archive(urls []string) error { } out.Empty() - for _, season := range episodes { - for _, info := range season { + for j, season := range episodes { + for k, info := range season { var filename string var writeCloser io.WriteCloser if c != nil { @@ -253,8 +253,11 @@ func archive(urls []string) error { } return err } - writeCloser.Close() + + if i != len(urls)-1 || j != len(episodes)-1 || k != len(season)-1 { + out.Empty() + } } } if c != nil { @@ -420,7 +423,6 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st signal.Stop(sig) out.Debug("Stopped signal catcher") - out.Empty() out.Empty() return nil diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 30d764a..4fbac96 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -147,8 +147,8 @@ func download(urls []string) error { } out.Empty() - for _, season := range episodes { - for _, info := range season { + for j, season := range episodes { + for k, info := range season { dir := info.Format(downloadDirectoryFlag) if _, err = os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0777); err != nil { @@ -166,6 +166,10 @@ func download(urls []string) error { return err } file.Close() + + if i != len(urls)-1 || j != len(episodes)-1 || k != len(season)-1 { + out.Empty() + } } } } @@ -253,7 +257,6 @@ func downloadInfo(info formatInformation, file *os.File) error { signal.Stop(sig) out.Debug("Stopped signal catcher") - out.Empty() out.Empty() return nil From d34fd10516f0834f672bb509af1eff49b544dc83 Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 28 Mar 2022 20:05:40 +0200 Subject: [PATCH 006/630] Refactoring --- README.md | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 709c3ae..64c7cd9 100644 --- a/README.md +++ b/README.md @@ -46,21 +46,21 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http ## ๐Ÿ’พ Get the executable - ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchyroll-go/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_darwin) + - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ``` $ yay -S crunchyroll-go ``` - ๐Ÿ›  Build it yourself - - use `make` (requires `go` to be installed): + - use `make` (requires `go` to be installed): ``` $ git clone https://github.com/ByteDream/crunchyroll-go $ cd crunchyroll-go $ make && sudo make install ``` - - use `go`: + - use `go`: ``` $ git clone https://github.com/ByteDream/crunchyroll-go $ cd crunchyroll-go/cmd/crunchyroll-go @@ -69,27 +69,27 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http ## ๐Ÿ“ Examples -_Before reading_: Because of the huge functionality not all cases can be covered in the README. -Make sure to check the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Cli), further usages and options are described there. +_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Cli), further usages and options are described there. ### Login Before you can do something, you have to login first. This can be performed via crunchyroll account email and password. + ``` $ crunchy login user@example.com password ``` or via session id + ``` $ crunchy login --session-id 8e9gs135defhga790dvrf2i0eris8gts ``` ### Download -By default, the cli tries to download the episode with your system language as audio. -If no streams with your system language are available, the video will be downloaded with japanese audio and hardsubbed subtitles in your system language. +By default, the cli tries to download the episode with your system language as audio. If no streams with your system language are available, the video will be downloaded with japanese audio and hardsubbed subtitles in your system language. **If your system language is not supported, an error message will be displayed and en-US (american english) will be chosen as language.** ``` @@ -97,21 +97,22 @@ $ crunchy download https://www.crunchyroll.com/darling-in-the-franxx/episode-1-a ``` With `-r best` the video(s) will have the best available resolution (mostly 1920x1080 / Full HD). + ``` $ crunchy download -r best https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 ``` The file is by default saved as a `.ts` (mpeg transport stream) file. -`.ts` files may can't be played or are looking very weird (it depends on the video player you are using). -With the `-o` flag, you can change the name (and file ending) of the output file. -So if you want to save it as, for example, `mp4` file, just name it `whatever.mp4`. +`.ts` files may can't be played or are looking very weird (it depends on the video player you are using). With the `-o` flag, you can change the name (and file ending) of the output file. So if you want to save it as, for example, `mp4` +file, just name it `whatever.mp4`. **You need [ffmpeg](https://ffmpeg.org) to store the video in other file formats.** + ``` $ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 ``` -With the `--audio` flag you can specify which audio the video should have and with `--subtitle` which subtitle it should have. -Type `crunchy help download` to see all available locales. +With the `--audio` flag you can specify which audio the video should have and with `--subtitle` which subtitle it should have. Type `crunchy help download` to see all available locales. + ``` $ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/darling-in-the-franxx ``` @@ -131,15 +132,17 @@ The following flags can be (optional) passed to modify the [download](#download) ### Archive -Archive works just like [download](#download). It downloads the given videos as `.mkv` files and stores all (soft) subtitles in it. -Default audio locales are japanese and your system language (if available) but you can set more or less with the `--language` flag. +Archive works just like [download](#download). It downloads the given videos as `.mkv` files and stores all (soft) subtitles in it. Default audio locales are japanese and your system language (if available) but you can set more or less with +the `--language` flag. Archive a file + ```shell $ crunchy archive https://www.crunchyroll.com/darling-in-the-franxx/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 ``` Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. + ```shell $ crunchy archive -c "ditf.tar.gz" https://www.crunchyroll.com/darling-in-the-franxx/darling-in-the-franxx ``` @@ -191,6 +194,7 @@ These flags you can use across every sub-command # ๐Ÿ“š Library Download the library via `go get` + ```shell $ go get github.com/ByteDream/crunchyroll-go ``` @@ -201,8 +205,7 @@ Examples how to use the library and some features of it are described in the [wi # โ˜๏ธ Disclaimer -This tool is **ONLY** meant to be used for private purposes. -To use this tool you need crunchyroll premium anyway, so there is no reason why rip and share the episodes. +This tool is **ONLY** meant to be used for private purposes. To use this tool you need crunchyroll premium anyway, so there is no reason why rip and share the episodes. **The responsibility for what happens to the downloaded videos lies entirely with the user who downloaded them.** From 8dcbced9c78068e937e06573daa87c5797006fbe Mon Sep 17 00:00:00 2001 From: bytedream Date: Sat, 2 Apr 2022 20:16:45 +0200 Subject: [PATCH 007/630] Version 2.0.2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7991824..8a21b56 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=2.0.1 +VERSION=2.0.2 BINARY_NAME=crunchy VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) From cda7bc9d35ad79f36f2fe75637a73ca49f8b1e8d Mon Sep 17 00:00:00 2001 From: bytedream Date: Fri, 15 Apr 2022 22:01:10 +0200 Subject: [PATCH 008/630] Add login file fallback and session id caching if logging in with credentials --- cmd/crunchyroll-go/cmd/utils.go | 58 ++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index b4afb73..f7a95ad 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -119,39 +119,51 @@ func loadCrunchy() { files = append(files, filepath.Join(usr.HomeDir, ".config/crunchy")) } - var body []byte var err error for _, file := range files { if _, err = os.Stat(file); os.IsNotExist(err) { + err = nil continue } - body, err = os.ReadFile(file) - break - } - if body == nil { - out.StopProgress("To use this command, login first. Type `%s login -h` to get help", os.Args[0]) - os.Exit(1) - } else if err != nil { - out.StopProgress("Failed to read login information: %v", err) - os.Exit(1) - } - - split := strings.SplitN(string(body), "\n", 2) - if len(split) == 1 || split[1] == "" { - if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) + var body []byte + if body, err = os.ReadFile(file); err != nil { + out.StopProgress("Failed to read login information: %v", err) os.Exit(1) + } else if body == nil { + continue } - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + + split := strings.SplitN(string(body), "\n", 2) + if len(split) == 1 || split[1] == "" { + if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err == nil { + out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + } + } else { + if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { + continue + } + out.Debug("Logged in with username '%s' and password '%s'. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0], split[1]) + if runtime.GOOS != "windows" { + // the session id is written to a temp file to reduce the amount of re-logging in. + // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600); err != nil { + out.StopProgress("Failed to write session id to temp file") + os.Exit(1) + } + out.Debug("Wrote session id to temp file") + } + } + + out.StopProgress("Logged in") + return + } + if err != nil { + out.StopProgress(err.Error()) } else { - if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) - } - out.Debug("Logged in with username '%s' and password '%s'. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0], split[1]) + out.StopProgress("To use this command, login first. Type `%s login -h` to get help", os.Args[0]) } - out.StopProgress("Logged in") + os.Exit(1) } func hasFFmpeg() bool { From 02bd33ef596d3e818ddc8dfdfee855ab2824f9a9 Mon Sep 17 00:00:00 2001 From: bytedream Date: Fri, 15 Apr 2022 22:14:11 +0200 Subject: [PATCH 009/630] Deprecate pure crunchyroll classic url functions --- crunchyroll.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 82cd118..3717eb3 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -348,7 +348,11 @@ func (c *Crunchyroll) FindEpisodeByName(seriesName, episodeTitle string) ([]*Epi return matchingEpisodes, nil } -// ParseVideoURL tries to extract the crunchyroll series / movie name out of the given url +// ParseVideoURL tries to extract the crunchyroll series / movie name out of the given url. +// +// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaSeriesURL +// if possible since beta url are always safe to use. +// The method will stay in the library until only beta urls are supported by crunchyroll itself. func ParseVideoURL(url string) (seriesName string, ok bool) { pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P[^/]+)/?$`) if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { @@ -364,7 +368,11 @@ func ParseVideoURL(url string) (seriesName string, ok bool) { // ParseEpisodeURL tries to extract the crunchyroll series name, title, episode number and web id out of the given crunchyroll url // Note that the episode number can be misleading. For example if an episode has the episode number 23.5 (slime isekai) -// the episode number will be 235 +// the episode number will be 235. +// +// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaEpisodeURL +// if possible since beta url are always safe to use. +// The method will stay in the library until only beta urls are supported by crunchyroll itself. func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, webId int, ok bool) { pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P[^/]+)/episode-(?P\d+)-(?P.+)-(?P<webId>\d+).*`) if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { From db30b9eadcab09b484b5c3379723cd75a0e6bea5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 15 Apr 2022 22:16:45 +0200 Subject: [PATCH 010/630] Add notice when downloading via cli and no episodes could be found --- cmd/crunchyroll-go/cmd/archive.go | 2 ++ cmd/crunchyroll-go/cmd/download.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 95091d0..9f5df94 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -182,6 +182,8 @@ func archive(urls []string) error { episodes, err := archiveExtractEpisodes(url) if err != nil { out.StopProgress("Failed to parse url %d", i+1) + out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") return err } out.StopProgress("Parsed url %d", i+1) diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 4fbac96..f53b3be 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -128,6 +128,8 @@ func download(urls []string) error { episodes, err := downloadExtractEpisodes(url) if err != nil { out.StopProgress("Failed to parse url %d", i+1) + out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") return err } out.StopProgress("Parsed url %d", i+1) From 253d8712c81b3389aa2aafac14334455c9a7e708 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 15 Apr 2022 22:34:45 +0200 Subject: [PATCH 011/630] Add /videos suffix support for classic series url --- crunchyroll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchyroll.go b/crunchyroll.go index 3717eb3..d34ba81 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -354,7 +354,7 @@ func (c *Crunchyroll) FindEpisodeByName(seriesName, episodeTitle string) ([]*Epi // if possible since beta url are always safe to use. // The method will stay in the library until only beta urls are supported by crunchyroll itself. func ParseVideoURL(url string) (seriesName string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)/?$`) + pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)(/videos)?/?$`) if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { groups := regexGroups(urlMatch, pattern.SubexpNames()...) seriesName = groups["series"] From e2f42493ac23e90334d5ce164f3bb2ddf6dfd020 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 15 Apr 2022 22:38:06 +0200 Subject: [PATCH 012/630] Fix credential file overwrite with session id --- cmd/crunchyroll-go/cmd/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index f7a95ad..4d3faad 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -143,7 +143,7 @@ func loadCrunchy() { continue } out.Debug("Logged in with username '%s' and password '%s'. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0], split[1]) - if runtime.GOOS != "windows" { + if file != filepath.Join(os.TempDir(), ".crunchy") { // the session id is written to a temp file to reduce the amount of re-logging in. // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600); err != nil { From 2e9ce3cf52628b49171da4c36754aa340867a295 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 15 Apr 2022 23:45:09 +0200 Subject: [PATCH 013/630] Deprecated find video and optimized find episode (as result of #22) --- crunchyroll.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index d34ba81..cc21b8b 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -306,6 +306,10 @@ func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, // FindVideoByName finds a Video (Season or Movie) by its name. // Use this in combination with ParseVideoURL and hand over the corresponding results // to this function. +// +// Deprecated: Use Search instead. The first result sometimes isn't the correct one +// so this function is inaccurate in some cases. +// See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information. func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { s, m, err := c.Search(seriesName, 1) if err != nil { @@ -324,27 +328,31 @@ func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { // Use this in combination with ParseEpisodeURL and hand over the corresponding results // to this function. func (c *Crunchyroll) FindEpisodeByName(seriesName, episodeTitle string) ([]*Episode, error) { - video, err := c.FindVideoByName(seriesName) - if err != nil { - return nil, err - } - seasons, err := video.(*Series).Seasons() + series, _, err := c.Search(seriesName, 5) if err != nil { return nil, err } var matchingEpisodes []*Episode - for _, season := range seasons { - episodes, err := season.Episodes() + for _, s := range series { + seasons, err := s.Seasons() if err != nil { return nil, err } - for _, episode := range episodes { - if episode.SlugTitle == episodeTitle { - matchingEpisodes = append(matchingEpisodes, episode) + + for _, season := range seasons { + episodes, err := season.Episodes() + if err != nil { + return nil, err + } + for _, episode := range episodes { + if episode.SlugTitle == episodeTitle { + matchingEpisodes = append(matchingEpisodes, episode) + } } } } + return matchingEpisodes, nil } From 3617955bc5e83293ec9a5217a5d544b74e666455 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 16 Apr 2022 00:17:36 +0200 Subject: [PATCH 014/630] Fix typos & add more comments --- README.md | 2 +- cmd/crunchyroll-go/cmd/archive.go | 8 ++++---- cmd/crunchyroll-go/cmd/utils.go | 2 +- crunchyroll.go | 30 +++++++++++++++--------------- downloader.go | 30 +++++++++++++++--------------- episode.go | 10 ++++++---- error.go | 2 +- format.go | 7 ++++--- movie_listing.go | 8 +++++--- season.go | 6 ++++-- stream.go | 7 ++++--- subtitle.go | 2 ++ url.go | 4 ++-- utils/locale.go | 5 +++-- utils/sort.go | 12 +++++++----- video.go | 15 ++++++++------- 16 files changed, 82 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 64c7cd9..8bfa193 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ _Before reading_: Because of the huge functionality not all cases can be covered ### Login -Before you can do something, you have to login first. +Before you can do something, you have to log in first. This can be performed via crunchyroll account email and password. diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 9f5df94..23c2c68 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -728,7 +728,7 @@ func (tc *tarCompress) Close() error { err = tc.dst.Close() if err != nil && err2 != nil { - // best way to show double errors at once that i've found + // best way to show double errors at once that I've found return fmt.Errorf("%v\n%v", err, err2) } else if err == nil && err2 != nil { err = err2 @@ -750,11 +750,11 @@ func (tc *tarCompress) NewFile(information formatInformation) (io.WriteCloser, e ModTime: time.Now(), Mode: 0644, Typeflag: tar.TypeReg, - // fun fact: i did not set the size for quiet some time because i thought that it isn't - // required. well because of this i debugged this part for multiple hours because without + // fun fact: I did not set the size for quiet some time because I thought that it isn't + // required. well because of this I debugged this part for multiple hours because without // proper size information only a tiny amount gets copied into the tar (or zip) writer. // this is also the reason why the file content is completely copied into a buffer before - // writing it to the writer. i could bypass this and save some memory but this requires + // writing it to the writer. I could bypass this and save some memory but this requires // some rewriting and im nearly at the (planned) finish for version 2 so nah in the future // maybe Size: int64(buf.Len()), diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 4d3faad..42fe7de 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -403,7 +403,7 @@ func (dp *downloadProgress) update(msg string, permanent bool) { pre := fmt.Sprintf("%s%s [", dp.Prefix, msg) post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total) - // i don't really know why +2 is needed here but without it the Printf below would not print to the line end + // I don't really know why +2 is needed here but without it the Printf below would not print to the line end progressWidth := terminalWidth() - len(pre) - len(post) + 2 repeatCount := int(percentage / float32(100) * float32(progressWidth)) // it can be lower than zero when the terminal is very tiny diff --git a/crunchyroll.go b/crunchyroll.go index cc21b8b..d6d2744 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -13,7 +13,7 @@ import ( "strconv" ) -// LOCALE represents a locale / language +// LOCALE represents a locale / language. type LOCALE string const ( @@ -31,16 +31,16 @@ const ( ) type Crunchyroll struct { - // Client is the http.Client to perform all requests over + // Client is the http.Client to perform all requests over. Client *http.Client - // Context can be used to stop requests with Client and is context.Background by default + // Context can be used to stop requests with Client and is context.Background by default. Context context.Context - // Locale specifies in which language all results should be returned / requested + // Locale specifies in which language all results should be returned / requested. Locale LOCALE - // SessionID is the crunchyroll session id which was used for authentication + // SessionID is the crunchyroll session id which was used for authentication. SessionID string - // Config stores parameters which are needed by some api calls + // Config stores parameters which are needed by some api calls. Config struct { TokenType string AccessToken string @@ -56,11 +56,11 @@ type Crunchyroll struct { MaturityRating string } - // If cache is true, internal caching is enabled + // If cache is true, internal caching is enabled. cache bool } -// LoginWithCredentials logs in via crunchyroll username or email and password +// LoginWithCredentials logs in via crunchyroll username or email and password. func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { sessionIDEndpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?version=1.0&access_token=%s&device_type=%s&device_id=%s", "LNDJgOit5yaRIWN", "com.crunchyroll.windows.desktop", "Az2srGnChW65fuxYz2Xxl1GcZQgtGgI") @@ -87,7 +87,7 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h } // LoginWithSessionID logs in via a crunchyroll session id. -// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com +// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com. func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { crunchy := &Crunchyroll{ Client: client, @@ -205,7 +205,7 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return crunchy, nil } -// request is a base function which handles api requests +// request is a base function which handles api requests. func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { @@ -241,7 +241,7 @@ func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { } // IsCaching returns if data gets cached or not. -// See SetCaching for more information +// See SetCaching for more information. func (c *Crunchyroll) IsCaching() bool { return c.cache } @@ -249,12 +249,12 @@ func (c *Crunchyroll) IsCaching() bool { // SetCaching enables or disables internal caching of requests made. // Caching is enabled by default. // If it is disabled the already cached data still gets called. -// The best way to prevent this is to create a complete new Crunchyroll struct +// The best way to prevent this is to create a complete new Crunchyroll struct. func (c *Crunchyroll) SetCaching(caching bool) { c.cache = caching } -// Search searches a query and returns all found series and movies within the given limit +// Search searches a query and returns all found series and movies within the given limit. func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) { searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s", query, limit, c.Locale) @@ -397,7 +397,7 @@ func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, w return } -// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season +// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season. func ParseBetaSeriesURL(url string) (seasonId string, ok bool) { pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`) if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { @@ -408,7 +408,7 @@ func ParseBetaSeriesURL(url string) (seasonId string, ok bool) { return } -// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode +// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode. func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) { pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`) if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { diff --git a/downloader.go b/downloader.go index be625b3..74546da 100644 --- a/downloader.go +++ b/downloader.go @@ -20,7 +20,7 @@ import ( ) // NewDownloader creates a downloader with default settings which should -// fit the most needs +// fit the most needs. func NewDownloader(context context.Context, writer io.Writer, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) Downloader { tmp, _ := os.MkdirTemp("", "crunchy_") @@ -43,12 +43,12 @@ type Downloader struct { // The files will be placed directly into the root of the directory. // If empty a random temporary directory on the system's default tempdir // will be created. - // If the directory does not exist, it will be created + // If the directory does not exist, it will be created. TempDir string // If DeleteTempAfter is true, the temp directory gets deleted afterwards. // Note that in case of a hard signal exit (os.Interrupt, ...) the directory // will NOT be deleted. In such situations try to catch the signal and - // cancel Context + // cancel Context. DeleteTempAfter bool // Context to control the download process with. @@ -56,7 +56,7 @@ type Downloader struct { // process. So it is not recommend stopping the program immediately after calling // the cancel function. It's better when canceling it and then exit the program // when Format.Download throws an error. See the signal handling section in - // cmd/crunchyroll-go/cmd/download.go for an example + // cmd/crunchyroll-go/cmd/download.go for an example. Context context.Context // Goroutines is the number of goroutines to download segments with @@ -65,11 +65,11 @@ type Downloader struct { // A method to call when a segment was downloaded. // Note that the segments are downloaded asynchronously (depending on the count of // Goroutines) and the function gets called asynchronously too, so for example it is - // first called on segment 1, then segment 254, then segment 3 and so on + // first called on segment 1, then segment 254, then segment 3 and so on. OnSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error // If LockOnSegmentDownload is true, only one OnSegmentDownload function can be called at // once. Normally (because of the use of goroutines while downloading) multiple could get - // called simultaneously + // called simultaneously. LockOnSegmentDownload bool // If FFmpegOpts is not nil, ffmpeg will be used to merge and convert files. @@ -82,7 +82,7 @@ type Downloader struct { FFmpegOpts []string } -// download's the given format +// download's the given format. func (d Downloader) download(format *Format) error { if err := format.InitVideo(); err != nil { return err @@ -109,7 +109,7 @@ func (d Downloader) download(format *Format) error { } // mergeSegments reads every file in tempDir and writes their content to Downloader.Writer. -// The given output file gets created or overwritten if already existing +// The given output file gets created or overwritten if already existing. func (d Downloader) mergeSegments(files []string) error { for _, file := range files { select { @@ -132,7 +132,7 @@ func (d Downloader) mergeSegments(files []string) error { // mergeSegmentsFFmpeg reads every file in tempDir and merges their content to the outputFile // with ffmpeg (https://ffmpeg.org/). -// The given output file gets created or overwritten if already existing +// The given output file gets created or overwritten if already existing. func (d Downloader) mergeSegmentsFFmpeg(files []string) error { list, err := os.Create(filepath.Join(d.TempDir, "list.txt")) if err != nil { @@ -214,13 +214,13 @@ func (d Downloader) mergeSegmentsFFmpeg(files []string) error { // After every segment download onSegmentDownload will be called with: // the downloaded segment, the current position, the total size of segments to download, // the file where the segment content was written to an error (if occurred). -// The filename is always <number of downloaded segment>.ts +// The filename is always <number of downloaded segment>.ts. // // Short explanation: // The actual crunchyroll video is split up in multiple segments (or video files) which // have to be downloaded and merged after to generate a single video file. // And this function just downloads each of this segment into the given directory. -// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information +// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information. func (d Downloader) downloadSegments(format *Format) ([]string, error) { if err := format.InitVideo(); err != nil { return nil, err @@ -318,7 +318,7 @@ func (d Downloader) downloadSegments(format *Format) ([]string, error) { } } -// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence +// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence. func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, iv []byte, err error) { var resp *http.Response @@ -341,7 +341,7 @@ func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, i return block, iv, nil } -// downloadSegment downloads a segment, decrypts it and names it after the given index +// downloadSegment downloads a segment, decrypts it and names it after the given index. func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment, filename string, block cipher.Block, iv []byte) (*os.File, error) { // every segment is aes-128 encrypted and has to be decrypted when downloaded content, err := d.decryptSegment(format.crunchy.Client, segment, block, iv) @@ -361,7 +361,7 @@ func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment, return file, nil } -// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25 +// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25. func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegment, block cipher.Block, iv []byte) ([]byte, error) { req, err := http.NewRequestWithContext(d.Context, http.MethodGet, segment.URI, nil) if err != nil { @@ -387,7 +387,7 @@ func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegme return raw, nil } -// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47 +// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47. func (d Downloader) pkcs5UnPadding(origData []byte) []byte { length := len(origData) unPadding := int(origData[length-1]) diff --git a/episode.go b/episode.go index d9814cb..a25844c 100644 --- a/episode.go +++ b/episode.go @@ -9,6 +9,7 @@ import ( "time" ) +// Episode contains all information about an episode. type Episode struct { crunchy *Crunchyroll @@ -74,7 +75,7 @@ type Episode struct { StreamID string } -// EpisodeFromID returns an episode by its api id +// EpisodeFromID returns an episode by its api id. func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", crunchy.Config.CountryCode, @@ -111,7 +112,8 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { // AudioLocale returns the audio locale of the episode. // Every episode in a season (should) have the same audio locale, -// so if you want to get the audio locale of a season, just call this method on the first episode of the season +// so if you want to get the audio locale of a season, just call +// this method on the first episode of the season. func (e *Episode) AudioLocale() (LOCALE, error) { streams, err := e.Streams() if err != nil { @@ -120,7 +122,7 @@ func (e *Episode) AudioLocale() (LOCALE, error) { return streams[0].AudioLocale, nil } -// GetFormat returns the format which matches the given resolution and subtitle locale +// GetFormat returns the format which matches the given resolution and subtitle locale. func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) { streams, err := e.Streams() if err != nil { @@ -186,7 +188,7 @@ func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (* return nil, fmt.Errorf("no matching resolution found") } -// Streams returns all streams which are available for the episode +// Streams returns all streams which are available for the episode. func (e *Episode) Streams() ([]*Stream, error) { if e.children != nil { return e.children, nil diff --git a/error.go b/error.go index 79ce1c0..b3e887f 100644 --- a/error.go +++ b/error.go @@ -3,7 +3,7 @@ package crunchyroll import "fmt" // AccessError is an error which will be returned when some special sort of api request fails. -// See Crunchyroll.request when the error gets used +// See Crunchyroll.request when the error gets used. type AccessError struct { error diff --git a/format.go b/format.go index ec94c16..6f17bf7 100644 --- a/format.go +++ b/format.go @@ -11,11 +11,12 @@ const ( MOVIE = "movies" ) +// Format contains detailed information about an episode video stream. type Format struct { crunchy *Crunchyroll ID string - // FormatType represents if the format parent is an episode or a movie + // FormatType represents if the format parent is an episode or a movie. FormatType FormatType Video *m3u8.Variant AudioLocale LOCALE @@ -27,7 +28,7 @@ type Format struct { // The Format.Video.Chunklist pointer is, by default, nil because an additional // request must be made to receive its content. The request is not made when // initializing a Format struct because it would probably cause an intense overhead -// since Format.Video.Chunklist is only used sometimes +// since Format.Video.Chunklist is only used sometimes. func (f *Format) InitVideo() error { if f.Video.Chunklist == nil { resp, err := f.crunchy.Client.Get(f.Video.URI) @@ -45,7 +46,7 @@ func (f *Format) InitVideo() error { return nil } -// Download downloads the Format with the via Downloader specified options +// Download downloads the Format with the via Downloader specified options. func (f *Format) Download(downloader Downloader) error { return downloader.download(f) } diff --git a/movie_listing.go b/movie_listing.go index 9646c18..63d7fab 100644 --- a/movie_listing.go +++ b/movie_listing.go @@ -5,6 +5,8 @@ import ( "fmt" ) +// MovieListing contains information about something which is called +// movie listing. I don't know what this means thb. type MovieListing struct { crunchy *Crunchyroll @@ -36,7 +38,7 @@ type MovieListing struct { AvailabilityNotes string `json:"availability_notes"` } -// MovieListingFromID returns a movie listing by its api id +// MovieListingFromID returns a movie listing by its api id. func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", crunchy.Config.CountryCode, @@ -65,7 +67,7 @@ func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) return movieListing, nil } -// AudioLocale is same as Episode.AudioLocale +// AudioLocale is same as Episode.AudioLocale. func (ml *MovieListing) AudioLocale() (LOCALE, error) { resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", ml.crunchy.Config.CountryCode, @@ -86,7 +88,7 @@ func (ml *MovieListing) AudioLocale() (LOCALE, error) { return LOCALE(jsonBody["audio_locale"].(string)), nil } -// Streams returns all streams which are available for the movie listing +// Streams returns all streams which are available for the movie listing. func (ml *MovieListing) Streams() ([]*Stream, error) { return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", ml.crunchy.Config.CountryCode, diff --git a/season.go b/season.go index c9a17da..825a816 100644 --- a/season.go +++ b/season.go @@ -6,6 +6,7 @@ import ( "regexp" ) +// Season contains information about an anime season. type Season struct { crunchy *Crunchyroll @@ -41,7 +42,7 @@ type Season struct { SubtitleLocales []LOCALE } -// SeasonFromID returns a season by its api id +// SeasonFromID returns a season by its api id. func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", crunchy.Config.CountryCode, @@ -70,6 +71,7 @@ func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { return season, nil } +// AudioLocale returns the audio locale of the season. func (s *Season) AudioLocale() (LOCALE, error) { episodes, err := s.Episodes() if err != nil { @@ -78,7 +80,7 @@ func (s *Season) AudioLocale() (LOCALE, error) { return episodes[0].AudioLocale() } -// Episodes returns all episodes which are available for the season +// Episodes returns all episodes which are available for the season. func (s *Season) Episodes() (episodes []*Episode, err error) { if s.children != nil { return s.children, nil diff --git a/stream.go b/stream.go index 5fe0f96..d8957e6 100644 --- a/stream.go +++ b/stream.go @@ -8,6 +8,7 @@ import ( "regexp" ) +// Stream contains information about all available video stream of an episode. type Stream struct { crunchy *Crunchyroll @@ -22,7 +23,7 @@ type Stream struct { streamURL string } -// StreamsFromID returns a stream by its api id +// StreamsFromID returns a stream by its api id. func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) { return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", crunchy.Config.CountryCode, @@ -35,7 +36,7 @@ func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) { crunchy.Config.KeyPairID)) } -// Formats returns all formats which are available for the stream +// Formats returns all formats which are available for the stream. func (s *Stream) Formats() ([]*Format, error) { if s.children != nil { return s.children, nil @@ -70,7 +71,7 @@ func (s *Stream) Formats() ([]*Format, error) { return formats, nil } -// fromVideoStreams returns all streams which are accessible via the endpoint +// fromVideoStreams returns all streams which are accessible via the endpoint. func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, err error) { resp, err := crunchy.request(endpoint) if err != nil { diff --git a/subtitle.go b/subtitle.go index 6a33e14..164b56e 100644 --- a/subtitle.go +++ b/subtitle.go @@ -5,6 +5,7 @@ import ( "net/http" ) +// Subtitle contains the information about a video subtitle. type Subtitle struct { crunchy *Crunchyroll @@ -13,6 +14,7 @@ type Subtitle struct { Format string `json:"format"` } +// Save writes the subtitle to the given io.Writer. func (s Subtitle) Save(writer io.Writer) error { req, err := http.NewRequestWithContext(s.crunchy.Context, http.MethodGet, s.URL, nil) if err != nil { diff --git a/url.go b/url.go index 5abf370..32603fc 100644 --- a/url.go +++ b/url.go @@ -5,7 +5,7 @@ import ( ) // ExtractEpisodesFromUrl extracts all episodes from an url. -// If audio is not empty, the episodes gets filtered after the given locale +// If audio is not empty, the episodes gets filtered after the given locale. func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Episode, error) { series, episodes, err := c.ParseUrl(url) if err != nil { @@ -78,7 +78,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep } // ParseUrl parses the given url into a series or episode. -// The returning episode is a slice because non-beta urls have the same episode with different languages +// The returning episode is a slice because non-beta urls have the same episode with different languages. func (c *Crunchyroll) ParseUrl(url string) (*Series, []*Episode, error) { if seriesId, ok := ParseBetaSeriesURL(url); ok { series, err := SeriesFromID(c, seriesId) diff --git a/utils/locale.go b/utils/locale.go index 8d78912..111496b 100644 --- a/utils/locale.go +++ b/utils/locale.go @@ -4,6 +4,7 @@ import ( "github.com/ByteDream/crunchyroll-go" ) +// AllLocales is an array of all available locales. var AllLocales = []crunchyroll.LOCALE{ crunchyroll.JP, crunchyroll.US, @@ -18,7 +19,7 @@ var AllLocales = []crunchyroll.LOCALE{ crunchyroll.AR, } -// ValidateLocale validates if the given locale actually exist +// ValidateLocale validates if the given locale actually exist. func ValidateLocale(locale crunchyroll.LOCALE) bool { for _, l := range AllLocales { if l == locale { @@ -28,7 +29,7 @@ func ValidateLocale(locale crunchyroll.LOCALE) bool { return false } -// LocaleLanguage returns the country by its locale +// LocaleLanguage returns the country by its locale. func LocaleLanguage(locale crunchyroll.LOCALE) string { switch locale { case crunchyroll.JP: diff --git a/utils/sort.go b/utils/sort.go index 82eb2b4..d4c7925 100644 --- a/utils/sort.go +++ b/utils/sort.go @@ -9,7 +9,7 @@ import ( ) // SortEpisodesBySeason sorts the given episodes by their seasons. -// Note that the same episodes just with different audio locales will cause problems +// Note that the same episodes just with different audio locales will cause problems. func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Episode { sortMap := map[string]map[int][]*crunchyroll.Episode{} @@ -43,7 +43,7 @@ func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Epis return eps } -// SortEpisodesByAudio sort the given episodes by their audio locale +// SortEpisodesByAudio sort the given episodes by their audio locale. func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCALE][]*crunchyroll.Episode, error) { eps := map[crunchyroll.LOCALE][]*crunchyroll.Episode{} @@ -81,7 +81,7 @@ func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCAL return eps, nil } -// MovieListingsByDuration sorts movie listings by their duration +// MovieListingsByDuration sorts movie listings by their duration. type MovieListingsByDuration []*crunchyroll.MovieListing func (mlbd MovieListingsByDuration) Len() int { @@ -94,7 +94,7 @@ func (mlbd MovieListingsByDuration) Less(i, j int) bool { return mlbd[i].DurationMS < mlbd[j].DurationMS } -// EpisodesByDuration sorts episodes by their duration +// EpisodesByDuration sorts episodes by their duration. type EpisodesByDuration []*crunchyroll.Episode func (ebd EpisodesByDuration) Len() int { @@ -107,6 +107,7 @@ func (ebd EpisodesByDuration) Less(i, j int) bool { return ebd[i].DurationMS < ebd[j].DurationMS } +// EpisodesByNumber sorts episodes after their episode number. type EpisodesByNumber []*crunchyroll.Episode func (ebn EpisodesByNumber) Len() int { @@ -119,7 +120,7 @@ func (ebn EpisodesByNumber) Less(i, j int) bool { return ebn[i].EpisodeNumber < ebn[j].EpisodeNumber } -// FormatsByResolution sorts formats after their resolution +// FormatsByResolution sorts formats after their resolution. type FormatsByResolution []*crunchyroll.Format func (fbr FormatsByResolution) Len() int { @@ -140,6 +141,7 @@ func (fbr FormatsByResolution) Less(i, j int) bool { return iResX+iResY < jResX+jResY } +// SubtitlesByLocale sorts subtitles after their locale. type SubtitlesByLocale []*crunchyroll.Subtitle func (sbl SubtitlesByLocale) Len() int { diff --git a/video.go b/video.go index f543df1..00b7734 100644 --- a/video.go +++ b/video.go @@ -30,8 +30,10 @@ type video struct { } `json:"images"` } +// Video is the base for Movie and Season. type Video interface{} +// Movie contains information about a movie. type Movie struct { video Video @@ -40,7 +42,7 @@ type Movie struct { children []*MovieListing - // not generated when calling MovieFromID + // not generated when calling MovieFromID. MovieListingMetadata struct { AvailabilityNotes string `json:"availability_notes"` AvailableOffline bool `json:"available_offline"` @@ -65,7 +67,7 @@ type Movie struct { } } -// MovieFromID returns a movie by its api id +// MovieFromID returns a movie by its api id. func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", crunchy.Config.CountryCode, @@ -95,8 +97,6 @@ func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) { } // MovieListing returns all videos corresponding with the movie. -// Beside the normal movie, sometimes movie previews are returned too, but you can try to get the actual movie -// by sorting the returning MovieListing slice with the utils.MovieListingByDuration interface func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) { if m.children != nil { return m.children, nil @@ -134,6 +134,7 @@ func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) { return movieListings, nil } +// Series contains information about an anime series. type Series struct { video Video @@ -156,13 +157,13 @@ type Series struct { MatureRatings []string `json:"mature_ratings"` SeasonCount int `json:"season_count"` - // not generated when calling SeriesFromID + // not generated when calling SeriesFromID. SearchMetadata struct { Score float64 `json:"score"` } } -// SeriesFromID returns a series by its api id +// SeriesFromID returns a series by its api id. func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", crunchy.Config.CountryCode, @@ -191,7 +192,7 @@ func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { return series, nil } -// Seasons returns all seasons of a series +// Seasons returns all seasons of a series. func (s *Series) Seasons() (seasons []*Season, err error) { if s.children != nil { return s.children, nil From 598e460e6cd14dee602300e778f96f6a558ddfb7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 16 Apr 2022 01:07:56 +0200 Subject: [PATCH 015/630] Add custom useragent for cli request --- Makefile | 13 +++++------- cmd/crunchyroll-go/cmd/root.go | 13 ++++++++++-- cmd/crunchyroll-go/cmd/utils.go | 37 +++++++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 8a21b56..8f42f36 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,7 @@ DESTDIR= PREFIX=/usr build: - cd cmd/crunchyroll-go && go build -o $(BINARY_NAME) - mv cmd/crunchyroll-go/$(BINARY_NAME) . + go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* @@ -25,10 +24,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE release: - cd cmd/crunchyroll-go && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $(VERSION_BINARY_NAME)_linux - cd cmd/crunchyroll-go && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o $(VERSION_BINARY_NAME)_windows.exe - cd cmd/crunchyroll-go && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o $(VERSION_BINARY_NAME)_darwin + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go - strip cmd/crunchyroll-go/$(VERSION_BINARY_NAME)_linux - - mv cmd/crunchyroll-go/$(VERSION_BINARY_NAME)_* . + strip $(VERSION_BINARY_NAME)_linux diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index 25c8270..c06df04 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "fmt" "github.com/ByteDream/crunchyroll-go" "github.com/spf13/cobra" "net/http" @@ -10,6 +11,8 @@ import ( "strings" ) +var Version = "development" + var ( client *http.Client crunchy *crunchyroll.Crunchyroll @@ -17,7 +20,10 @@ var ( quietFlag bool verboseFlag bool - proxyFlag string + + proxyFlag string + + useragentFlag string ) var rootCmd = &cobra.Command{ @@ -36,7 +42,7 @@ var rootCmd = &cobra.Command{ out.DebugLog.Printf("Executing `%s` command with %d arg(s)\n", cmd.Name(), len(args)) - client, err = createOrDefaultClient(proxyFlag) + client, err = createOrDefaultClient(proxyFlag, useragentFlag) return }, } @@ -44,7 +50,10 @@ var rootCmd = &cobra.Command{ func init() { rootCmd.PersistentFlags().BoolVarP(&quietFlag, "quiet", "q", false, "Disable all output") rootCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Adds debug messages to the normal output") + rootCmd.PersistentFlags().StringVarP(&proxyFlag, "proxy", "p", "", "Proxy to use") + + rootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchyroll-go/%s", Version), "Useragent to do all request with") } func Execute() { diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 42fe7de..7589010 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -71,7 +71,23 @@ func allLocalesAsStrings() (locales []string) { return } -func createOrDefaultClient(proxy string) (*http.Client, error) { +type headerRoundTripper struct { + http.RoundTripper + header map[string]string +} + +func (rht headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + resp, err := rht.RoundTripper.RoundTrip(r) + if err != nil { + return nil, err + } + for k, v := range rht.header { + resp.Header.Set(k, v) + } + return resp, nil +} + +func createOrDefaultClient(proxy, useragent string) (*http.Client, error) { if proxy == "" { return http.DefaultClient, nil } else { @@ -80,12 +96,21 @@ func createOrDefaultClient(proxy string) (*http.Client, error) { if err != nil { return nil, err } + + var rt http.RoundTripper = &http.Transport{ + DisableCompression: true, + Proxy: http.ProxyURL(proxyURL), + } + if useragent != "" { + rt = headerRoundTripper{ + RoundTripper: rt, + header: map[string]string{"User-Agent": useragent}, + } + } + client := &http.Client{ - Transport: &http.Transport{ - DisableCompression: true, - Proxy: http.ProxyURL(proxyURL), - }, - Timeout: 30 * time.Second, + Transport: rt, + Timeout: 30 * time.Second, } return client, nil } From b524a1a7dd99071b01fced9b8c4dbab5ede1852c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 16 Apr 2022 01:08:18 +0200 Subject: [PATCH 016/630] Version 2.1.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8f42f36..bbff663 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=2.0.2 +VERSION=2.1.0 BINARY_NAME=crunchy VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) From 9ccd1ed93cd8bae97ae95594eff406aab68990e3 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 16 Apr 2022 01:27:59 +0200 Subject: [PATCH 017/630] Create dependabot.yml --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3938344 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" From a98eb56fbad77f4c666f9270282dc004cda07f9a Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 16 Apr 2022 01:59:34 +0200 Subject: [PATCH 018/630] Create ci.yml --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0ae3bc7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.16 + + - name: Build + run: go build -v cmd/crunchyroll-go/main.go + + - name: Test + run: go test -v cmd/crunchyroll-go/main.go From 543b9c36681a1b6a4d9a1065c6db658e7e535118 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 16 Apr 2022 02:02:10 +0200 Subject: [PATCH 019/630] Update ci.yml --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ae3bc7..7887e0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,5 @@ +name: CI + on: push: branches: [ master ] @@ -5,7 +7,7 @@ on: branches: [ master ] jobs: - build: + test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 4b5b18773093f7a17262316f1a32a8c365b09720 Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Sun, 17 Apr 2022 18:57:42 +0200 Subject: [PATCH 020/630] Add version to cli root command --- cmd/crunchyroll-go/cmd/root.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index c06df04..3a8a6b6 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -27,8 +27,9 @@ var ( ) var rootCmd = &cobra.Command{ - Use: "crunchyroll", - Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchyroll-go/wiki", + Use: "crunchyroll", + Version: Version, + Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchyroll-go/wiki", SilenceErrors: true, SilenceUsage: true, From 6385457c10110f1f006f6ea2c0c5a32258d56341 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 17 Apr 2022 21:28:53 +0200 Subject: [PATCH 021/630] Add --version command description (#24) --- crunchyroll-go.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crunchyroll-go.1 b/crunchyroll-go.1 index 4054554..2fd5437 100644 --- a/crunchyroll-go.1 +++ b/crunchyroll-go.1 @@ -40,6 +40,10 @@ Disables all output. \fB-v, --verbose\fR Shows verbose output. +.TP + +\fB--version\fR +Shows the current cli version. .SH LOGIN COMMAND This command logs in to crunchyroll and stores the session id or credentials on the drive. This needs to be done before calling other commands since they need a valid login to operate. From 2d08e985383ad0a61db5b60ec7b74dfc5fb0008d Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sun, 17 Apr 2022 21:33:20 +0200 Subject: [PATCH 022/630] Add ci badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8bfa193..d6d8b6e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> + <a href="https://github.com/ByteDream/crunchyroll-go/actions/workflows/ci.yml"> + <img src="https://github.com/ByteDream/crunchyroll-go/actions/workflows/ci.yml/badge.svg" alt="CI"> + </a> </p> <p align="center"> From 580ea74902baecf126402ae10765ff0f8cf7e8fb Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 18 Apr 2022 10:24:56 +0200 Subject: [PATCH 023/630] Update ci badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d8b6e..b56657b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> <a href="https://github.com/ByteDream/crunchyroll-go/actions/workflows/ci.yml"> - <img src="https://github.com/ByteDream/crunchyroll-go/actions/workflows/ci.yml/badge.svg" alt="CI"> + <img src="https://github.com/ByteDream/crunchyroll-go/workflows/CI/badge.svg?style=flat" alt="CI"> </a> </p> From fd3d945c3afee236a17cd9e218d83c940be18fb8 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 18 Apr 2022 15:43:50 +0200 Subject: [PATCH 024/630] Create codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..b7d4dfe --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '40 3 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # โ„น๏ธ Command-line programs to run using the OS shell. + # ๐Ÿ“š https://git.io/JvXDl + + # โœ๏ธ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 From be3d33744fafd6b195008cc8b6d2a5a00a7f166a Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:56:27 +0200 Subject: [PATCH 025/630] Rename getCmd variable to downloadCmd --- cmd/crunchyroll-go/cmd/download.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index f53b3be..55b4b1f 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -28,7 +28,7 @@ var ( downloadGoroutinesFlag int ) -var getCmd = &cobra.Command{ +var downloadCmd = &cobra.Command{ Use: "download", Short: "Download a video", Args: cobra.MinimumNArgs(1), @@ -72,23 +72,23 @@ var getCmd = &cobra.Command{ } func init() { - getCmd.Flags().StringVarP(&downloadAudioFlag, "audio", + downloadCmd.Flags().StringVarP(&downloadAudioFlag, "audio", "a", string(systemLocale(false)), "The locale of the audio. Available locales: "+strings.Join(allLocalesAsStrings(), ", ")) - getCmd.Flags().StringVarP(&downloadSubtitleFlag, + downloadCmd.Flags().StringVarP(&downloadSubtitleFlag, "subtitle", "s", "", "The locale of the subtitle. Available locales: "+strings.Join(allLocalesAsStrings(), ", ")) cwd, _ := os.Getwd() - getCmd.Flags().StringVarP(&downloadDirectoryFlag, + downloadCmd.Flags().StringVarP(&downloadDirectoryFlag, "directory", "d", cwd, "The directory to download the file(s) into") - getCmd.Flags().StringVarP(&downloadOutputFlag, + downloadCmd.Flags().StringVarP(&downloadOutputFlag, "output", "o", "{title}.ts", @@ -104,7 +104,7 @@ func init() { "\t{audio} ยป Audio locale of the video\n"+ "\t{subtitle} ยป Subtitle locale of the video") - getCmd.Flags().StringVarP(&downloadResolutionFlag, + downloadCmd.Flags().StringVarP(&downloadResolutionFlag, "resolution", "r", "best", @@ -113,13 +113,13 @@ func init() { "\tAvailable abbreviations: 1080p, 720p, 480p, 360p, 240p\n"+ "\tAvailable common-use words: best (best available resolution), worst (worst available resolution)") - getCmd.Flags().IntVarP(&downloadGoroutinesFlag, + downloadCmd.Flags().IntVarP(&downloadGoroutinesFlag, "goroutines", "g", runtime.NumCPU(), "Sets how many parallel segment downloads should be used") - rootCmd.AddCommand(getCmd) + rootCmd.AddCommand(downloadCmd) } func download(urls []string) error { From 58bf54996496a6e98b241967ba50222c3a2ed595 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Tue, 19 Apr 2022 21:32:19 +0200 Subject: [PATCH 026/630] Update README.md --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b56657b..6e205c4 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,19 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_windows.exe) - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): - ``` + ```shell $ yay -S crunchyroll-go ``` -- ๐Ÿ›  Build it yourself +- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 mac): - use `make` (requires `go` to be installed): - ``` + ```shell $ git clone https://github.com/ByteDream/crunchyroll-go $ cd crunchyroll-go - $ make && sudo make install + $ make + $ sudo make install # <- only if you want to install it on your system ``` - use `go`: - ``` + ```shell $ git clone https://github.com/ByteDream/crunchyroll-go $ cd crunchyroll-go/cmd/crunchyroll-go $ go build -o crunchy @@ -80,13 +81,13 @@ Before you can do something, you have to log in first. This can be performed via crunchyroll account email and password. -``` +```shell $ crunchy login user@example.com password ``` or via session id -``` +```shell $ crunchy login --session-id 8e9gs135defhga790dvrf2i0eris8gts ``` @@ -95,13 +96,13 @@ $ crunchy login --session-id 8e9gs135defhga790dvrf2i0eris8gts By default, the cli tries to download the episode with your system language as audio. If no streams with your system language are available, the video will be downloaded with japanese audio and hardsubbed subtitles in your system language. **If your system language is not supported, an error message will be displayed and en-US (american english) will be chosen as language.** -``` +```shell $ crunchy download https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 ``` With `-r best` the video(s) will have the best available resolution (mostly 1920x1080 / Full HD). -``` +```shell $ crunchy download -r best https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 ``` @@ -110,13 +111,13 @@ The file is by default saved as a `.ts` (mpeg transport stream) file. file, just name it `whatever.mp4`. **You need [ffmpeg](https://ffmpeg.org) to store the video in other file formats.** -``` +```shell $ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 ``` With the `--audio` flag you can specify which audio the video should have and with `--subtitle` which subtitle it should have. Type `crunchy help download` to see all available locales. -``` +```shell $ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/darling-in-the-franxx ``` From 68aa7e903fee57e1d9ba572a77e7f983de99c791 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Fri, 22 Apr 2022 12:30:33 +0200 Subject: [PATCH 027/630] Update go build instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e205c4..8ffc2b5 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http - use `go`: ```shell $ git clone https://github.com/ByteDream/crunchyroll-go - $ cd crunchyroll-go/cmd/crunchyroll-go - $ go build -o crunchy + $ cd crunchyroll-go + $ go build -o crunchy cmd/crunchyroll-go/main.go ``` ## ๐Ÿ“ Examples From 06a541421039a50682af40e4e4975dfa1ec9f6c9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 26 Apr 2022 08:34:33 +0200 Subject: [PATCH 028/630] Add video, audio and subtitle as locale (#26) --- cmd/crunchyroll-go/cmd/archive.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 23c2c68..deb17ad 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -530,21 +530,25 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s input = append(input, "-i", video) maps = append(maps, "-map", strconv.Itoa(i)) locale := crunchyroll.LOCALE(re.FindStringSubmatch(video)[1]) - metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("language=%s", utils.LocaleLanguage(locale))) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("language=%s", locale)) + metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) } for i, audio := range audioFiles { input = append(input, "-i", audio) maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles))) locale := crunchyroll.LOCALE(re.FindStringSubmatch(audio)[1]) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) } for i, subtitle := range subtitleFiles { input = append(input, "-i", subtitle) maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles)+len(audioFiles))) locale := crunchyroll.LOCALE(re.FindStringSubmatch(subtitle)[1]) + metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("language=%s", locale)) metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) } From 980c28a754609e28c60db249168abb56ef12f239 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 26 Apr 2022 08:34:50 +0200 Subject: [PATCH 029/630] Add scoop installer instructions --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 8ffc2b5..a477171 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,11 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http ```shell $ yay -S crunchyroll-go ``` + - On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): +- ```shell + $ scoop bucket add extra # <- in case you haven't added the extra repository already + $ scoop install crunchyroll-go + ``` - ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 mac): - use `make` (requires `go` to be installed): ```shell From c9ad097d8521a7463019293ec572090923495022 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:42:01 +0200 Subject: [PATCH 030/630] Fix typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a477171..014da47 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http ```shell $ yay -S crunchyroll-go ``` - - On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): -- ```shell +- On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): + ```shell $ scoop bucket add extra # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` From 1e865adaa5e6bd7430a37b4d72b6330cdf470750 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:53:02 +0200 Subject: [PATCH 031/630] Typo fix in scoop install --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 014da47..255f9d9 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http ``` - On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): ```shell - $ scoop bucket add extra # <- in case you haven't added the extra repository already + $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` - ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 mac): From 0c92fc0989641d75491c8e722b4726fc472620e3 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 27 Apr 2022 10:53:40 +0200 Subject: [PATCH 032/630] Deactivate subtitles by default (#26) --- cmd/crunchyroll-go/cmd/archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index deb17ad..6fc0aba 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -568,7 +568,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s file.Close() defer os.Remove(file.Name()) - commandOptions = append(commandOptions, "-c", "copy", "-f", "matroska", file.Name()) + commandOptions = append(commandOptions, "-disposition:s:0", "0", "-c", "copy", "-f", "matroska", file.Name()) // just a little nicer debug output to copy and paste the ffmpeg for debug reasons if out.IsDev() { From 48595f25fa34a3bb7ae01e82191b59e4c8b00060 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 28 Apr 2022 10:21:02 +0200 Subject: [PATCH 033/630] Update debug print variable --- cmd/crunchyroll-go/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index 3a8a6b6..c8830b9 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -41,7 +41,7 @@ var rootCmd = &cobra.Command{ out = newLogger(false, false, false) } - out.DebugLog.Printf("Executing `%s` command with %d arg(s)\n", cmd.Name(), len(args)) + out.Debug("Executing `%s` command with %d arg(s)", cmd.Name(), len(args)) client, err = createOrDefaultClient(proxyFlag, useragentFlag) return From 037df1d16f0febc3bc6e66e4a5921c72d7e87971 Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:31:01 +0200 Subject: [PATCH 034/630] handle session queries not being valid --- crunchyroll.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crunchyroll.go b/crunchyroll.go index d6d2744..5d9031d 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -70,6 +70,10 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h } defer sessResp.Body.Close() + if sessResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Failed to start session: %s", sessResp.Status) + } + var data map[string]interface{} body, _ := io.ReadAll(sessResp.Body) json.Unmarshal(body, &data) @@ -109,6 +113,11 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, err } defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Failed to start session: %s", resp.Status) + } + json.NewDecoder(resp.Body).Decode(&jsonBody) if _, ok := jsonBody["message"]; ok { return nil, errors.New("invalid session id") From db47eeb11c6f30dec008a5802deb9129dc09bea5 Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:34:17 +0200 Subject: [PATCH 035/630] handle login with creds errors --- crunchyroll.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 5d9031d..57548cc 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -71,7 +71,7 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h defer sessResp.Body.Close() if sessResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Failed to start session: %s", sessResp.Status) + return nil, fmt.Errorf("failed to start session for credentials login: %s", sessResp.Status) } var data map[string]interface{} @@ -85,7 +85,15 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h authValues.Set("session_id", sessionID) authValues.Set("account", user) authValues.Set("password", password) - client.Post(loginEndpoint, "application/x-www-form-urlencoded", bytes.NewBufferString(authValues.Encode())) + loginResp, err := client.Post(loginEndpoint, "application/x-www-form-urlencoded", bytes.NewBufferString(authValues.Encode())) + if err != nil { + return nil, err + } + defer loginResp.Body.Close() + + if loginResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to auth with credentials: %s", loginResp.Status) + } return LoginWithSessionID(sessionID, locale, client) } From 353f425bbfeeb9c6b74ff3cc93954309a95ae418 Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:36:45 +0200 Subject: [PATCH 036/630] typo fix --- crunchyroll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchyroll.go b/crunchyroll.go index 57548cc..0d03440 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -123,7 +123,7 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Failed to start session: %s", resp.Status) + return nil, fmt.Errorf("failed to start session: %s", resp.Status) } json.NewDecoder(resp.Body).Decode(&jsonBody) From aa3f8e1b341a39f2a78cf8d5522afd721ecd97d6 Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:39:11 +0200 Subject: [PATCH 037/630] handle json parsing errors --- crunchyroll.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 0d03440..8ae0932 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -76,7 +76,9 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h var data map[string]interface{} body, _ := io.ReadAll(sessResp.Body) - json.Unmarshal(body, &data) + if err = json.Unmarshal(body, &data); err != nil { + return nil, fmt.Errorf("failed to parse start session with credentials response: %w", err) + } sessionID := data["data"].(map[string]interface{})["session_id"].(string) @@ -126,7 +128,9 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, fmt.Errorf("failed to start session: %s", resp.Status) } - json.NewDecoder(resp.Body).Decode(&jsonBody) + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) + } if _, ok := jsonBody["message"]; ok { return nil, errors.New("invalid session id") } @@ -178,7 +182,9 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, err } defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'token' response: %w", err) + } crunchy.Config.TokenType = jsonBody["token_type"].(string) crunchy.Config.AccessToken = jsonBody["access_token"].(string) @@ -189,7 +195,9 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, err } defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'index' response: %w", err) + } cms := jsonBody["cms"].(map[string]interface{}) crunchy.Config.Policy = cms["policy"].(string) @@ -203,7 +211,9 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, err } defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'me' response: %w", err) + } crunchy.Config.AccountID = jsonBody["account_id"].(string) crunchy.Config.ExternalID = jsonBody["external_id"].(string) @@ -215,7 +225,9 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, err } defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) + } crunchy.Config.MaturityRating = jsonBody["maturity_rating"].(string) From 187a0c8817e6ecfe446712bb3f9db4e040776bd1 Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:43:43 +0200 Subject: [PATCH 038/630] add missing defer --- crunchyroll.go | 1 + 1 file changed, 1 insertion(+) diff --git a/crunchyroll.go b/crunchyroll.go index 8ae0932..bf20ed8 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -244,6 +244,7 @@ func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { resp, err := c.Client.Do(req) if err == nil { + defer resp.Body.Close() bodyAsBytes, _ := io.ReadAll(resp.Body) defer resp.Body.Close() if resp.StatusCode == http.StatusUnauthorized { From 362708cf3584c192988a1f37eb659ff3b30271fe Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:45:00 +0200 Subject: [PATCH 039/630] handle json unmarshall error --- crunchyroll.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crunchyroll.go b/crunchyroll.go index bf20ed8..ece4da1 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -295,7 +295,9 @@ func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, defer resp.Body.Close() var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'search' response: %w", err) + } for _, item := range jsonBody["items"].([]interface{}) { item := item.(map[string]interface{}) From 413949797c8ed1f1b75df3515a70d05143b9e5ad Mon Sep 17 00:00:00 2001 From: Hekmon <edouardhur@gmail.com> Date: Fri, 29 Apr 2022 11:49:23 +0200 Subject: [PATCH 040/630] avoid copy error to be shadowed --- downloader.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/downloader.go b/downloader.go index 74546da..0001347 100644 --- a/downloader.go +++ b/downloader.go @@ -199,8 +199,8 @@ func (d Downloader) mergeSegmentsFFmpeg(files []string) error { } } if f, ok := d.Writer.(*os.File); !ok || f.Name() != tmpfile { - file, err := os.Open(tmpfile) - if err != nil { + var file *os.File + if file, err = os.Open(tmpfile); err != nil { return err } defer file.Close() From 1f1f6849dcdb2f0217c841d340c7c5f91c81d93a Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Fri, 29 Apr 2022 15:50:10 +0200 Subject: [PATCH 041/630] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 255f9d9..7bdef2b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` -- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 mac): +- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 Mac): - use `make` (requires `go` to be installed): ```shell $ git clone https://github.com/ByteDream/crunchyroll-go From a49e65e1514fde0dd2b0071a887a95fea5f8d8b8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 30 Apr 2022 21:25:40 +0200 Subject: [PATCH 042/630] Enable autosuggestions but hide it from commands --- cmd/crunchyroll-go/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index c8830b9..5ac91b5 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -58,7 +58,7 @@ func init() { } func Execute() { - rootCmd.CompletionOptions.DisableDefaultCmd = true + rootCmd.CompletionOptions.HiddenDefaultCmd = true defer func() { if r := recover(); r != nil { if out.IsDev() { From 80b0784f50773fadaa0835128ec2e603e6f57ed2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 1 May 2022 13:25:43 +0200 Subject: [PATCH 043/630] Update module to v2 --- cmd/crunchyroll-go/cmd/archive.go | 4 ++-- cmd/crunchyroll-go/cmd/download.go | 4 ++-- cmd/crunchyroll-go/cmd/login.go | 2 +- cmd/crunchyroll-go/cmd/root.go | 2 +- cmd/crunchyroll-go/cmd/utils.go | 4 ++-- cmd/crunchyroll-go/main.go | 2 +- go.mod | 2 +- utils/locale.go | 2 +- utils/sort.go | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 6fc0aba..6f3f6a0 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -8,8 +8,8 @@ import ( "compress/gzip" "context" "fmt" - "github.com/ByteDream/crunchyroll-go" - "github.com/ByteDream/crunchyroll-go/utils" + "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v2/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "io" diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 55b4b1f..1ce202c 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -3,8 +3,8 @@ package cmd import ( "context" "fmt" - "github.com/ByteDream/crunchyroll-go" - "github.com/ByteDream/crunchyroll-go/utils" + "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v2/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "os" diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index 1303516..5bef2a9 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -2,7 +2,7 @@ package cmd import ( "fmt" - "github.com/ByteDream/crunchyroll-go" + "github.com/ByteDream/crunchyroll-go/v2" "github.com/spf13/cobra" "os" "os/user" diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index 5ac91b5..14f0c0e 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -3,7 +3,7 @@ package cmd import ( "context" "fmt" - "github.com/ByteDream/crunchyroll-go" + "github.com/ByteDream/crunchyroll-go/v2" "github.com/spf13/cobra" "net/http" "os" diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 7589010..136fe28 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -2,8 +2,8 @@ package cmd import ( "fmt" - "github.com/ByteDream/crunchyroll-go" - "github.com/ByteDream/crunchyroll-go/utils" + "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v2/utils" "net/http" "net/url" "os" diff --git a/cmd/crunchyroll-go/main.go b/cmd/crunchyroll-go/main.go index efc6a1e..a502afb 100644 --- a/cmd/crunchyroll-go/main.go +++ b/cmd/crunchyroll-go/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd" + "github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd" ) func main() { diff --git a/go.mod b/go.mod index d3e701e..7a4bf42 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ByteDream/crunchyroll-go +module github.com/ByteDream/crunchyroll-go/v2 go 1.16 diff --git a/utils/locale.go b/utils/locale.go index 111496b..537b165 100644 --- a/utils/locale.go +++ b/utils/locale.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/ByteDream/crunchyroll-go" + "github.com/ByteDream/crunchyroll-go/v2" ) // AllLocales is an array of all available locales. diff --git a/utils/sort.go b/utils/sort.go index d4c7925..a44717d 100644 --- a/utils/sort.go +++ b/utils/sort.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/ByteDream/crunchyroll-go" + "github.com/ByteDream/crunchyroll-go/v2" "sort" "strconv" "strings" From ddfd2b44a10e53012827127ff2e4e8d778ad5259 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 1 May 2022 14:03:17 +0200 Subject: [PATCH 044/630] Update library get option to v2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7bdef2b..cb657b6 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ These flags you can use across every sub-command Download the library via `go get` ```shell -$ go get github.com/ByteDream/crunchyroll-go +$ go get github.com/ByteDream/crunchyroll-go/v2 ``` The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go). From a590da82318db2027f80438780366fa20b68f5a1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 1 May 2022 14:04:06 +0200 Subject: [PATCH 045/630] Version 2.2.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bbff663..a73003a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=2.1.0 +VERSION=2.2.0 BINARY_NAME=crunchy VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) From ad703fb98515aab5f418eb091f3b597fb82c2566 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 1 May 2022 17:12:29 +0200 Subject: [PATCH 046/630] Update documentation url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb657b6..c58e2ea 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ Download the library via `go get` $ go get github.com/ByteDream/crunchyroll-go/v2 ``` -The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go). +The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go/v2). Examples how to use the library and some features of it are described in the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Library). From df441049005196566da39031bf29ae383df827eb Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 4 May 2022 08:17:33 +0200 Subject: [PATCH 047/630] Update example urls to their beta equivalents --- README.md | 12 ++++++------ crunchyroll-go.1 | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c58e2ea..23054d0 100644 --- a/README.md +++ b/README.md @@ -102,13 +102,13 @@ By default, the cli tries to download the episode with your system language as a **If your system language is not supported, an error message will be displayed and en-US (american english) will be chosen as language.** ```shell -$ crunchy download https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchy download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` With `-r best` the video(s) will have the best available resolution (mostly 1920x1080 / Full HD). ```shell -$ crunchy download -r best https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchy download -r best https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` The file is by default saved as a `.ts` (mpeg transport stream) file. @@ -117,13 +117,13 @@ file, just name it `whatever.mp4`. **You need [ffmpeg](https://ffmpeg.org) to store the video in other file formats.** ```shell -$ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` With the `--audio` flag you can specify which audio the video should have and with `--subtitle` which subtitle it should have. Type `crunchy help download` to see all available locales. ```shell -$ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/darling-in-the-franxx +$ crunchy download --audio ja-JP --subtitle de-DE https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` ##### Flags @@ -147,13 +147,13 @@ the `--language` flag. Archive a file ```shell -$ crunchy archive https://www.crunchyroll.com/darling-in-the-franxx/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchy archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. ```shell -$ crunchy archive -c "ditf.tar.gz" https://www.crunchyroll.com/darling-in-the-franxx/darling-in-the-franxx +$ crunchy archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` ##### Flags diff --git a/crunchyroll-go.1 b/crunchyroll-go.1 index 2fd5437..558aa23 100644 --- a/crunchyroll-go.1 +++ b/crunchyroll-go.1 @@ -162,23 +162,23 @@ $ crunchyroll-go login user@example.com 12345678 Download a episode normally. Your system locale will be used for the video's audio. .br -$ crunchyroll-go download https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchyroll-go download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Download a episode with 720p and name it to 'darling.mp4'. Note that you need \fBffmpeg\fR to save files which do not have '.ts' as file extension. .br -$ crunchyroll-go download -o "darling.mp4" -r 720p https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchyroll-go download -o "darling.mp4" -r 720p https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Download a episode with japanese audio and american subtitles. .br -$ crunchyroll-go download -a ja-JP -s en-US https://www.crunchyroll.com/darling-in-the-franxx[E3-E5] +$ crunchyroll-go download -a ja-JP -s en-US https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E3-E5] Stores the episode in a .mkv file. .br -$ crunchyroll-go archive https://www.crunchyroll.com/darling-in-the-franxx/darling-in-the-franxx/episode-1-alone-and-lonesome-759575 +$ crunchyroll-go archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. .br -$ crunchyroll-go archive -c "ditf.tar.gz" https://www.crunchyroll.com/darling-in-the-franxx/darling-in-the-franxx[E1-E2] +$ crunchyroll-go archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E2] .SH BUGS If you notice any bug or want an enhancement, feel free to create a new issue or pull request in the GitHub repository. From 7db4ca6b93e0b0dbacf51e549ac6d760fa324537 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 6 May 2022 11:22:32 +0200 Subject: [PATCH 048/630] Fix typos --- downloader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/downloader.go b/downloader.go index 0001347..05622cc 100644 --- a/downloader.go +++ b/downloader.go @@ -36,7 +36,7 @@ func NewDownloader(context context.Context, writer io.Writer, goroutines int, on // Downloader is used to download Format's type Downloader struct { - // The output is all written to Writer + // The output is all written to Writer. Writer io.Writer // TempDir is the directory where the temporary segment files should be stored. @@ -59,7 +59,7 @@ type Downloader struct { // cmd/crunchyroll-go/cmd/download.go for an example. Context context.Context - // Goroutines is the number of goroutines to download segments with + // Goroutines is the number of goroutines to download segments with. Goroutines int // A method to call when a segment was downloaded. @@ -82,7 +82,7 @@ type Downloader struct { FFmpegOpts []string } -// download's the given format. +// download downloads the given format. func (d Downloader) download(format *Format) error { if err := format.InitVideo(); err != nil { return err From 5e3636015b33da6f88b72fb4b4d9b5135b76d666 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 7 May 2022 10:46:25 +0200 Subject: [PATCH 049/630] Remove AccessError error struct --- crunchyroll.go | 11 ++--------- error.go | 21 --------------------- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 error.go diff --git a/crunchyroll.go b/crunchyroll.go index ece4da1..6b1854f 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -248,21 +248,14 @@ func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { bodyAsBytes, _ := io.ReadAll(resp.Body) defer resp.Body.Close() if resp.StatusCode == http.StatusUnauthorized { - return nil, &AccessError{ - URL: endpoint, - Body: bodyAsBytes, - } + return nil, fmt.Errorf("invalid access token") } else { var errStruct struct { Message string `json:"message"` } json.NewDecoder(bytes.NewBuffer(bodyAsBytes)).Decode(&errStruct) if errStruct.Message != "" { - return nil, &AccessError{ - URL: endpoint, - Body: bodyAsBytes, - Message: errStruct.Message, - } + return nil, fmt.Errorf(errStruct.Message) } } resp.Body = io.NopCloser(bytes.NewBuffer(bodyAsBytes)) diff --git a/error.go b/error.go deleted file mode 100644 index b3e887f..0000000 --- a/error.go +++ /dev/null @@ -1,21 +0,0 @@ -package crunchyroll - -import "fmt" - -// AccessError is an error which will be returned when some special sort of api request fails. -// See Crunchyroll.request when the error gets used. -type AccessError struct { - error - - URL string - Body []byte - Message string -} - -func (ae *AccessError) Error() string { - if ae.Message == "" { - return fmt.Sprintf("Access token invalid for url %s\nBody: %s", ae.URL, string(ae.Body)) - } else { - return ae.Message - } -} From ed8e63c26880c630bd1b03d62d46ed076f7f5afc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 8 May 2022 11:06:37 +0200 Subject: [PATCH 050/630] Fix locale panic (#29) --- cmd/crunchyroll-go/cmd/utils.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 136fe28..29a48f7 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -33,9 +33,13 @@ var urlFilter = regexp.MustCompile(`(S(\d+))?(E(\d+))?((-)(S(\d+))?(E(\d+))?)?(, func systemLocale(verbose bool) crunchyroll.LOCALE { if runtime.GOOS != "windows" { if lang, ok := os.LookupEnv("LANG"); ok { - prefix := strings.Split(lang, "_")[0] - suffix := strings.Split(strings.Split(lang, ".")[0], "_")[1] - l := crunchyroll.LOCALE(fmt.Sprintf("%s-%s", prefix, suffix)) + var l crunchyroll.LOCALE + if preSuffix := strings.Split(strings.Split(lang, ".")[0], "_"); len(preSuffix) == 1 { + l = crunchyroll.LOCALE(preSuffix[0]) + } else { + prefix := strings.Split(lang, "_")[0] + l = crunchyroll.LOCALE(fmt.Sprintf("%s-%s", prefix, preSuffix[1])) + } if !utils.ValidateLocale(l) { if verbose { out.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) From 1c1dd5ec4b759e57deecbc6a41dcd7fba127846c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 8 May 2022 11:07:00 +0200 Subject: [PATCH 051/630] Version 2.2.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a73003a..eef19a4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=2.2.0 +VERSION=2.2.1 BINARY_NAME=crunchy VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) From 11b0f2b48baad073694100f596f4f9bef5f32806 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sun, 8 May 2022 15:40:36 +0200 Subject: [PATCH 052/630] Fix typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23054d0..c83493c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -<p align="center"><strong>Version 2 is out ๐Ÿฅณ, see all the <a href="https://github.com/ByteDream/crunchyroll-go/releases/tag/v2.0.0">changes.</a></strong></p> +<p align="center"><strong>Version 2 is out ๐Ÿฅณ, see all the <a href="https://github.com/ByteDream/crunchyroll-go/releases/tag/v2.0.0">changes</a></strong>.</p> # crunchyroll-go @@ -192,7 +192,7 @@ The following flags can be (optional) passed to modify the [archive](#archive) p ### Global flags -These flags you can use across every sub-command +These flags you can use across every sub-command: | Flag | Description | |------|------------------------------------------------------| From e7e106f74d7ddbf054ba14f92e381a2b390753fa Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 8 May 2022 19:14:16 +0200 Subject: [PATCH 053/630] Fix version not set when building with make --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index eef19a4..d23fd11 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DESTDIR= PREFIX=/usr build: - go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go + go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* @@ -24,8 +24,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go strip $(VERSION_BINARY_NAME)_linux From 3ee53c0cab7629c09f76ce56da8520a35515a833 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 13 May 2022 18:38:38 +0200 Subject: [PATCH 054/630] Bump go version to 1.18 --- go.mod | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7a4bf42..9a38468 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,13 @@ module github.com/ByteDream/crunchyroll-go/v2 -go 1.16 +go 1.18 require ( github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.4.0 ) + +require ( + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) From 901bbf0706e22ee37ee4f940b274f40b412261b7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 13 May 2022 19:22:17 +0200 Subject: [PATCH 055/630] Add update command --- cmd/crunchyroll-go/cmd/update.go | 134 +++++++++++++++++++++++++++++++ crunchyroll-go.1 | 9 +++ 2 files changed, 143 insertions(+) create mode 100644 cmd/crunchyroll-go/cmd/update.go diff --git a/cmd/crunchyroll-go/cmd/update.go b/cmd/crunchyroll-go/cmd/update.go new file mode 100644 index 0000000..4c7aaad --- /dev/null +++ b/cmd/crunchyroll-go/cmd/update.go @@ -0,0 +1,134 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "io" + "os" + "os/exec" + "runtime" + "strings" +) + +var ( + updateInstallFlag bool +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Check if updates are available", + Args: cobra.MaximumNArgs(0), + + RunE: func(cmd *cobra.Command, args []string) error { + return update() + }, +} + +func init() { + updateCmd.Flags().BoolVarP(&updateInstallFlag, + "install", + "i", + false, + "If set and a new version is available, the new version gets installed") + + rootCmd.AddCommand(updateCmd) +} + +func update() error { + var release map[string]interface{} + + resp, err := client.Get("https://api.github.com/repos/ByteDream/crunchyroll-go/releases/latest") + if err != nil { + return err + } + defer resp.Body.Close() + if err = json.NewDecoder(resp.Body).Decode(&release); err != nil { + return err + } + releaseVersion := strings.TrimPrefix(release["tag_name"].(string), "v") + + if Version == "development" { + out.Info("Development version, update service not available") + return nil + } + + latestRelease := strings.SplitN(releaseVersion, ".", 4) + if len(latestRelease) != 3 { + return fmt.Errorf("latest tag name (%s) is not parsable", releaseVersion) + } + + internalVersion := strings.SplitN(Version, ".", 4) + if len(internalVersion) != 3 { + return fmt.Errorf("internal version (%s) is not parsable", Version) + } + + var hasUpdate bool + for i := 0; i < 3; i++ { + if latestRelease[i] < internalVersion[i] { + out.Info("Local version (%s) is newer than version in latest release (%s)", Version, releaseVersion) + return nil + } else if latestRelease[i] > internalVersion[i] { + hasUpdate = true + } + } + + if !hasUpdate { + out.Info("Version is up-to-date") + return nil + } + + out.Info("A new version is available (%s). Installed version is %s: https://github.com/ByteDream/crunchyroll-go/releases/tag/v%s", releaseVersion, Version, releaseVersion) + + if updateInstallFlag { + if runtime.GOARCH != "amd64" { + return fmt.Errorf("invalid architecture found (%s), only amd64 is currently supported for automatic updating. "+ + "You have to update manually (https://github.com/ByteDream/crunchyroll-go)", runtime.GOARCH) + } + + var downloadFile string + switch runtime.GOOS { + case "linux": + yayCommand := exec.Command("pacman -Q crunchyroll-go") + if yayCommand.Run() == nil && yayCommand.ProcessState.Success() { + out.Info("crunchyroll-go was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") + return nil + } + downloadFile = fmt.Sprintf("crunchy-v%s_linux", releaseVersion) + case "darwin": + downloadFile = fmt.Sprintf("crunchy-v%s_darwin", releaseVersion) + case "windows": + downloadFile = fmt.Sprintf("crunchy-v%s_windows.exe", releaseVersion) + default: + return fmt.Errorf("invalid operation system found (%s), only linux, windows and darwin / macos are currently supported. "+ + "You have to update manually (https://github.com/ByteDream/crunchyroll-go)", runtime.GOOS) + } + + out.SetProgress("Updating executable %s", os.Args[0]) + + perms, err := os.Stat(os.Args[0]) + if err != nil { + return err + } + os.Remove(os.Args[0]) + executeFile, err := os.OpenFile(os.Args[0], os.O_CREATE|os.O_WRONLY, perms.Mode()) + if err != nil { + return err + } + defer executeFile.Close() + + resp, err := client.Get(fmt.Sprintf("https://github.com/ByteDream/crunchyroll-go/releases/download/v%s/%s", releaseVersion, downloadFile)) + if err != nil { + return err + } + defer resp.Body.Close() + + if _, err = io.Copy(executeFile, resp.Body); err != nil { + return err + } + + out.StopProgress("Updated executable %s", os.Args[0]) + } + + return nil +} diff --git a/crunchyroll-go.1 b/crunchyroll-go.1 index 558aa23..576c291 100644 --- a/crunchyroll-go.1 +++ b/crunchyroll-go.1 @@ -13,6 +13,8 @@ crunchyroll-go login [\fB--persistent\fR] [\fB--session-id\fR \fISESSION_ID\fR] crunchyroll-go download [\fB-a\fR \fIAUDIO\fR] [\fB-s\fR \fISUBTITLE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR .br crunchyroll-go archive [\fB-l\fR \fILANGUAGE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-m\fR \fIMERGE BEHAVIOR\fR] [\fB-c\fR \fICOMPRESS\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR +.br +crunchyroll-go update [\fB-i\fR \fIINSTALL\fR] .SH DESCRIPTION .TP @@ -141,6 +143,13 @@ The video resolution. Can either be specified via the pixels (e.g. 1920x1080), t \fB-g, --goroutines GOROUTINES\fR Sets the number of parallel downloads for the segments the final video is made of. Default is the number of cores the computer has. +.SH UPDATE COMMAND +Checks if a newer version is available. +.TP + +\fB-i, --install INSTALL\fR +If given, the command tries to update the executable with the newer version (if a newer is available). + .SH URL OPTIONS If you want to download only specific episode of a series, you could either pass every single episode url to the downloader (which is fine for 1 - 3 episodes) or use filtering. It works pretty simple, just put a specific pattern surrounded by square brackets at the end of the url from the anime you want to download. A season and / or episode as well as a range from where to where episodes should be downloaded can be specified. From f046b68371ae8c2f8dbe671a5166136d8dcb25a3 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 13 May 2022 19:34:28 +0200 Subject: [PATCH 056/630] Update workflows running on every branch --- .github/workflows/ci.yml | 6 +----- .github/workflows/codeql-analysis.yml | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7887e0c..44c7ac6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,6 @@ name: CI -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] +on: [ push, pull_request ] jobs: test: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b7d4dfe..e0f5470 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,7 @@ name: "CodeQL" on: push: - branches: [ master ] pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] schedule: - cron: '40 3 * * 2' From 192a85afb8ae1c0ffcbf3a4b80c52a4bf3f73043 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 13 May 2022 19:53:16 +0200 Subject: [PATCH 057/630] Use fmt.Errorf instead of errors.New & new invalid session id error message --- crunchyroll.go | 9 ++++----- stream.go | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 6b1854f..00d3123 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -131,15 +130,15 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) } - if _, ok := jsonBody["message"]; ok { - return nil, errors.New("invalid session id") + if isError, ok := jsonBody["error"]; ok && isError.(bool) { + return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"]) } data := jsonBody["data"].(map[string]interface{}) crunchy.Config.CountryCode = data["country_code"].(string) user := data["user"] if user == nil { - return nil, errors.New("invalid session id, user is not logged in") + return nil, fmt.Errorf("invalid session id, user is not logged in") } if user.(map[string]interface{})["premium"] == "" { crunchy.Config.Premium = false @@ -346,7 +345,7 @@ func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { } else if len(m) > 0 { return m[0], nil } - return nil, errors.New("no series or movie could be found") + return nil, fmt.Errorf("no series or movie could be found") } // FindEpisodeByName finds an episode by its crunchyroll series name and episode title. diff --git a/stream.go b/stream.go index d8957e6..7505a24 100644 --- a/stream.go +++ b/stream.go @@ -2,7 +2,6 @@ package crunchyroll import ( "encoding/json" - "errors" "fmt" "github.com/grafov/m3u8" "regexp" @@ -83,7 +82,7 @@ func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, if len(jsonBody) == 0 { // this may get thrown when the crunchyroll account has just a normal account and not one with premium - return nil, errors.New("no stream available") + return nil, fmt.Errorf("no stream available") } audioLocale := jsonBody["audio_locale"].(string) From 62735cf07cb8740db61fa886ddb02fb82e766952 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 19:28:05 +0200 Subject: [PATCH 058/630] Change request url for some request & regex --- crunchyroll.go | 17 ++++++++--------- episode.go | 12 ++++-------- movie_listing.go | 18 ++++++------------ season.go | 16 +++++++--------- stream.go | 8 +++----- video.go | 24 ++++++++---------------- 6 files changed, 36 insertions(+), 59 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 6b1854f..60b4320 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -11,6 +11,7 @@ import ( "net/url" "regexp" "strconv" + "strings" ) // LOCALE represents a locale / language. @@ -33,7 +34,7 @@ const ( type Crunchyroll struct { // Client is the http.Client to perform all requests over. Client *http.Client - // Context can be used to stop requests with Client and is context.Background by default. + // Context can be used to stop requests with Client. Context context.Context // Locale specifies in which language all results should be returned / requested. Locale LOCALE @@ -45,9 +46,10 @@ type Crunchyroll struct { TokenType string AccessToken string + Bucket string + CountryCode string Premium bool - Channel string Policy string Signature string KeyPairID string @@ -141,13 +143,7 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* if user == nil { return nil, errors.New("invalid session id, user is not logged in") } - if user.(map[string]interface{})["premium"] == "" { - crunchy.Config.Premium = false - crunchy.Config.Channel = "-" - } else { - crunchy.Config.Premium = true - crunchy.Config.Channel = "crunchyroll" - } + crunchy.Config.Premium = user.(map[string]interface{})["premium"] != "" var etpRt string for _, cookie := range resp.Cookies() { @@ -200,6 +196,9 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* } cms := jsonBody["cms"].(map[string]interface{}) + // / is trimmed so that urls which require it must be in .../{bucket}/... like format. + // this just looks cleaner + crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") crunchy.Config.Policy = cms["policy"].(string) crunchy.Config.Signature = cms["signature"].(string) crunchy.Config.KeyPairID = cms["key_pair_id"].(string) diff --git a/episode.go b/episode.go index a25844c..3c9d38f 100644 --- a/episode.go +++ b/episode.go @@ -77,10 +77,8 @@ type Episode struct { // EpisodeFromID returns an episode by its api id. func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.CountryCode, - crunchy.Config.MaturityRating, - crunchy.Config.Channel, + resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + crunchy.Config.Bucket, id, crunchy.Locale, crunchy.Config.Signature, @@ -194,10 +192,8 @@ func (e *Episode) Streams() ([]*Stream, error) { return e.children, nil } - streams, err := fromVideoStreams(e.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - e.crunchy.Config.CountryCode, - e.crunchy.Config.MaturityRating, - e.crunchy.Config.Channel, + streams, err := fromVideoStreams(e.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + e.crunchy.Config.Bucket, e.StreamID, e.crunchy.Locale, e.crunchy.Config.Signature, diff --git a/movie_listing.go b/movie_listing.go index 63d7fab..80b7b46 100644 --- a/movie_listing.go +++ b/movie_listing.go @@ -40,10 +40,8 @@ type MovieListing struct { // MovieListingFromID returns a movie listing by its api id. func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.CountryCode, - crunchy.Config.MaturityRating, - crunchy.Config.Channel, + resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + crunchy.Config.Bucket, id, crunchy.Locale, crunchy.Config.Signature, @@ -69,10 +67,8 @@ func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) // AudioLocale is same as Episode.AudioLocale. func (ml *MovieListing) AudioLocale() (LOCALE, error) { - resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - ml.crunchy.Config.CountryCode, - ml.crunchy.Config.MaturityRating, - ml.crunchy.Config.Channel, + resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + ml.crunchy.Config.Bucket, ml.ID, ml.crunchy.Locale, ml.crunchy.Config.Signature, @@ -90,10 +86,8 @@ func (ml *MovieListing) AudioLocale() (LOCALE, error) { // Streams returns all streams which are available for the movie listing. func (ml *MovieListing) Streams() ([]*Stream, error) { - return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - ml.crunchy.Config.CountryCode, - ml.crunchy.Config.MaturityRating, - ml.crunchy.Config.Channel, + return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + ml.crunchy.Config.Bucket, ml.ID, ml.crunchy.Locale, ml.crunchy.Config.Signature, diff --git a/season.go b/season.go index 825a816..b83d214 100644 --- a/season.go +++ b/season.go @@ -44,10 +44,8 @@ type Season struct { // SeasonFromID returns a season by its api id. func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { - resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.CountryCode, - crunchy.Config.MaturityRating, - crunchy.Config.Channel, + resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + crunchy.Config.Bucket, id, crunchy.Locale, crunchy.Config.Signature, @@ -86,10 +84,8 @@ func (s *Season) Episodes() (episodes []*Episode, err error) { return s.children, nil } - resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes?season_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - s.crunchy.Config.CountryCode, - s.crunchy.Config.MaturityRating, - s.crunchy.Config.Channel, + resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/episodes?season_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + s.crunchy.Config.Bucket, s.ID, s.crunchy.Locale, s.crunchy.Config.Signature, @@ -111,8 +107,10 @@ func (s *Season) Episodes() (episodes []*Episode, err error) { } if episode.Playback != "" { streamHref := item.(map[string]interface{})["__links__"].(map[string]interface{})["streams"].(map[string]interface{})["href"].(string) - if match := regexp.MustCompile(`(?m)^/cms/v2/\S+videos/(\w+)/streams$`).FindAllStringSubmatch(streamHref, -1); len(match) > 0 { + if match := regexp.MustCompile(`(?m)(\w+)/streams$`).FindAllStringSubmatch(streamHref, -1); len(match) > 0 { episode.StreamID = match[0][1] + } else { + fmt.Println() } } episodes = append(episodes, episode) diff --git a/stream.go b/stream.go index d8957e6..b1193ed 100644 --- a/stream.go +++ b/stream.go @@ -25,10 +25,8 @@ type Stream struct { // StreamsFromID returns a stream by its api id. func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) { - return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.CountryCode, - crunchy.Config.MaturityRating, - crunchy.Config.Channel, + return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + crunchy.Config.Bucket, id, crunchy.Locale, crunchy.Config.Signature, @@ -105,7 +103,7 @@ func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, var id string var formatType FormatType href := jsonBody["__links__"].(map[string]interface{})["resource"].(map[string]interface{})["href"].(string) - if match := regexp.MustCompile(`(?sm)^/cms/v2/\S+/crunchyroll/(\w+)/(\w+)$`).FindAllStringSubmatch(href, -1); len(match) > 0 { + if match := regexp.MustCompile(`(?sm)/(\w+)/(\w+)$`).FindAllStringSubmatch(href, -1); len(match) > 0 { formatType = FormatType(match[0][1]) id = match[0][2] } diff --git a/video.go b/video.go index 00b7734..8a7ee76 100644 --- a/video.go +++ b/video.go @@ -69,10 +69,8 @@ type Movie struct { // MovieFromID returns a movie by its api id. func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.CountryCode, - crunchy.Config.MaturityRating, - crunchy.Config.Channel, + resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + crunchy.Config.Bucket, id, crunchy.Locale, crunchy.Config.Signature, @@ -102,10 +100,8 @@ func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) { return m.children, nil } - resp, err := m.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - m.crunchy.Config.CountryCode, - m.crunchy.Config.MaturityRating, - m.crunchy.Config.Channel, + resp, err := m.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + m.crunchy.Config.Bucket, m.ID, m.crunchy.Locale, m.crunchy.Config.Signature, @@ -165,10 +161,8 @@ type Series struct { // SeriesFromID returns a series by its api id. func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.CountryCode, - crunchy.Config.MaturityRating, - crunchy.Config.Channel, + resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + crunchy.Config.Bucket, id, crunchy.Locale, crunchy.Config.Signature, @@ -198,10 +192,8 @@ func (s *Series) Seasons() (seasons []*Season, err error) { return s.children, nil } - resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - s.crunchy.Config.CountryCode, - s.crunchy.Config.MaturityRating, - s.crunchy.Config.Channel, + resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", + s.crunchy.Config.Bucket, s.ID, s.crunchy.Locale, s.crunchy.Config.Signature, From f51bdeaec7dcc05c4927b377ec113e1dbefc41c6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 20:03:52 +0200 Subject: [PATCH 059/630] Add error return in some login failure cases (#30) --- crunchyroll.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crunchyroll.go b/crunchyroll.go index 60b4320..d3b1ce8 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -97,6 +97,13 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h if loginResp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to auth with credentials: %s", loginResp.Status) + } else { + var loginRespBody map[string]interface{} + json.NewDecoder(loginResp.Body).Decode(&loginRespBody) + + if loginRespBody["error"].(bool) { + return nil, fmt.Errorf("an unexpected login error occoured: %s", loginRespBody["message"]) + } } return LoginWithSessionID(sessionID, locale, client) From afa975c459947f613ea950ae2927d596918530ca Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 21:21:35 +0200 Subject: [PATCH 060/630] Add session id always cached in temp directory (to prevent #30) --- cmd/crunchyroll-go/cmd/login.go | 61 +++++++++++++++++-------- cmd/crunchyroll-go/cmd/utils.go | 81 ++++++++++++++++----------------- 2 files changed, 79 insertions(+), 63 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index 5bef2a9..c9fc923 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -5,9 +5,7 @@ import ( "github.com/ByteDream/crunchyroll-go/v2" "github.com/spf13/cobra" "os" - "os/user" "path/filepath" - "runtime" ) var ( @@ -21,11 +19,11 @@ var loginCmd = &cobra.Command{ Short: "Login to crunchyroll", Args: cobra.RangeArgs(1, 2), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if loginSessionIDFlag { - loginSessionID(args[0]) + return loginSessionID(args[0]) } else { - loginCredentials(args[0], args[1]) + return loginCredentials(args[0], args[1]) } }, } @@ -46,12 +44,31 @@ func init() { func loginCredentials(user, password string) error { out.Debug("Logging in via credentials") - if _, err := crunchyroll.LoginWithCredentials(user, password, systemLocale(false), client); err != nil { - out.Err(err.Error()) - os.Exit(1) + c, err := crunchyroll.LoginWithCredentials(user, password, systemLocale(false), client) + if err != nil { + return err } - return os.WriteFile(loginStorePath(), []byte(fmt.Sprintf("%s\n%s", user, password)), 0600) + if loginPersistentFlag { + if configDir, err := os.UserConfigDir(); err != nil { + return fmt.Errorf("could not save credentials persistent: %w", err) + } else { + os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) + if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(fmt.Sprintf("%s\n%s", user, password)), 0600); err != nil { + return err + } + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + } + } + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.SessionID), 0600); err != nil { + return err + } + + if !loginPersistentFlag { + out.Info("Due to security reasons, you have to login again on the next reboot") + } + + return nil } func loginSessionID(sessionID string) error { @@ -61,21 +78,25 @@ func loginSessionID(sessionID string) error { os.Exit(1) } - return os.WriteFile(loginStorePath(), []byte(sessionID), 0600) -} - -func loginStorePath() string { - path := filepath.Join(os.TempDir(), ".crunchy") + var err error if loginPersistentFlag { - if runtime.GOOS != "windows" { - usr, _ := user.Current() - path = filepath.Join(usr.HomeDir, ".config/crunchy") + if configDir, err := os.UserConfigDir(); err != nil { + return fmt.Errorf("could not save credentials persistent: %w", err) + } else { + os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) + if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(sessionID), 0600); err != nil { + return err + } + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) } + } + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(sessionID), 0600); err != nil { + return err + } - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", path) - } else if runtime.GOOS != "windows" { + if !loginPersistentFlag { out.Info("Due to security reasons, you have to login again on the next reboot") } - return path + return nil } diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 29a48f7..cafd99d 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -8,7 +8,6 @@ import ( "net/url" "os" "os/exec" - "os/user" "path/filepath" "reflect" "regexp" @@ -141,57 +140,53 @@ func freeFileName(filename string) (string, bool) { func loadCrunchy() { out.SetProgress("Logging in") - files := []string{filepath.Join(os.TempDir(), ".crunchy")} - - if runtime.GOOS != "windows" { - usr, _ := user.Current() - files = append(files, filepath.Join(usr.HomeDir, ".config/crunchy")) - } - - var err error - for _, file := range files { - if _, err = os.Stat(file); os.IsNotExist(err) { - err = nil - continue - } - var body []byte - if body, err = os.ReadFile(file); err != nil { - out.StopProgress("Failed to read login information: %v", err) - os.Exit(1) - } else if body == nil { - continue - } - - split := strings.SplitN(string(body), "\n", 2) - if len(split) == 1 || split[1] == "" { - if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err == nil { - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + if configDir, err := os.UserConfigDir(); err == nil { + persistentFilePath := filepath.Join(configDir, "crunchyroll-go", "crunchy") + if _, statErr := os.Stat(persistentFilePath); statErr == nil { + body, err := os.ReadFile(persistentFilePath) + if err != nil { + out.StopProgress("Failed to read login information: %v", err) + os.Exit(1) } - } else { - if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { - continue - } - out.Debug("Logged in with username '%s' and password '%s'. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0], split[1]) - if file != filepath.Join(os.TempDir(), ".crunchy") { - // the session id is written to a temp file to reduce the amount of re-logging in. - // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600); err != nil { - out.StopProgress("Failed to write session id to temp file") + split := strings.SplitN(string(body), "\n", 2) + if len(split) == 1 || split[1] == "" { + split[0] = url.QueryEscape(split[0]) + if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err != nil { + out.StopProgress(err.Error()) os.Exit(1) } - out.Debug("Wrote session id to temp file") + out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + } else { + if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { + out.StopProgress(err.Error()) + os.Exit(1) + } + out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", crunchy.SessionID) + // the session id is written to a temp file to reduce the amount of re-logging in. + // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time + os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600) } + return + } + } + + tmpFilePath := filepath.Join(os.TempDir(), ".crunchy") + if _, statErr := os.Stat(tmpFilePath); !os.IsNotExist(statErr) { + body, err := os.ReadFile(tmpFilePath) + if err != nil { + out.StopProgress("Failed to read login information: %v", err) + os.Exit(1) + } + if crunchy, err = crunchyroll.LoginWithSessionID(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { + out.StopProgress(err.Error()) + os.Exit(1) } - out.StopProgress("Logged in") + out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) return } - if err != nil { - out.StopProgress(err.Error()) - } else { - out.StopProgress("To use this command, login first. Type `%s login -h` to get help", os.Args[0]) - } + out.StopProgress("To use this command, login first. Type `%s login -h` to get help", os.Args[0]) os.Exit(1) } From 5b3466d06d555639aa27f3c13f54ac50b6e1740d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 22:06:44 +0200 Subject: [PATCH 061/630] Add stream not available with non-premium error notice --- stream.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/stream.go b/stream.go index b1193ed..0fa54b9 100644 --- a/stream.go +++ b/stream.go @@ -80,8 +80,12 @@ func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, json.NewDecoder(resp.Body).Decode(&jsonBody) if len(jsonBody) == 0 { - // this may get thrown when the crunchyroll account has just a normal account and not one with premium - return nil, errors.New("no stream available") + // this may get thrown when the crunchyroll account is just a normal account and not one with premium + if !crunchy.Config.Premium { + return nil, fmt.Errorf("no stream available, this might be the result of using a non-premium account") + } else { + return nil, errors.New("no stream available") + } } audioLocale := jsonBody["audio_locale"].(string) From 6c476df24effcad9d6297c453535ce7f9bcf72db Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 22:07:44 +0200 Subject: [PATCH 062/630] Set beta url notice only if account is premium --- cmd/crunchyroll-go/cmd/archive.go | 6 ++++-- cmd/crunchyroll-go/cmd/download.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 6f3f6a0..63e5d5a 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -182,8 +182,10 @@ func archive(urls []string) error { episodes, err := archiveExtractEpisodes(url) if err != nil { out.StopProgress("Failed to parse url %d", i+1) - out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + if crunchy.Config.Premium { + out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + } return err } out.StopProgress("Parsed url %d", i+1) diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 1ce202c..1254048 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -128,8 +128,10 @@ func download(urls []string) error { episodes, err := downloadExtractEpisodes(url) if err != nil { out.StopProgress("Failed to parse url %d", i+1) - out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + if crunchy.Config.Premium { + out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + } return err } out.StopProgress("Parsed url %d", i+1) From 0fa829828f987ce9b825f04f300c73b142438373 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 22:42:31 +0200 Subject: [PATCH 063/630] Fix 1080p, 720p, ... not working --- cmd/crunchyroll-go/cmd/archive.go | 10 +++++++--- cmd/crunchyroll-go/cmd/download.go | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 6f3f6a0..6dcf61b 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -13,6 +13,7 @@ import ( "github.com/grafov/m3u8" "github.com/spf13/cobra" "io" + "math" "os" "os/exec" "os/signal" @@ -98,9 +99,12 @@ var archiveCmd = &cobra.Command{ } switch archiveResolutionFlag { - case "1080p", "720p", "480p", "360p", "240p": - intRes, _ := strconv.ParseFloat(strings.TrimSuffix(archiveResolutionFlag, "p"), 84) - archiveResolutionFlag = fmt.Sprintf("%dx%s", int(intRes*(16/9)), strings.TrimSuffix(archiveResolutionFlag, "p")) + case "1080p", "720p", "480p", "360p": + intRes, _ := strconv.ParseFloat(strings.TrimSuffix(downloadResolutionFlag, "p"), 84) + archiveResolutionFlag = fmt.Sprintf("%.0fx%s", math.Ceil(intRes*(float64(16)/float64(9))), strings.TrimSuffix(downloadResolutionFlag, "p")) + case "240p": + // 240p would round up to 427x240 if used in the case statement above, so it has to be handled separately + archiveResolutionFlag = "428x240" case "1920x1080", "1280x720", "640x480", "480x360", "428x240", "best", "worst": default: return fmt.Errorf("'%s' is not a valid resolution", archiveResolutionFlag) diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 1ce202c..92b6027 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -7,6 +7,7 @@ import ( "github.com/ByteDream/crunchyroll-go/v2/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" + "math" "os" "os/signal" "path/filepath" @@ -53,9 +54,12 @@ var downloadCmd = &cobra.Command{ out.Debug("Locales: audio: %s / subtitle: %s", downloadAudioFlag, downloadSubtitleFlag) switch downloadResolutionFlag { - case "1080p", "720p", "480p", "360p", "240p": + case "1080p", "720p", "480p", "360p": intRes, _ := strconv.ParseFloat(strings.TrimSuffix(downloadResolutionFlag, "p"), 84) - downloadResolutionFlag = fmt.Sprintf("%dx%s", int(intRes*(16/9)), strings.TrimSuffix(downloadResolutionFlag, "p")) + downloadResolutionFlag = fmt.Sprintf("%.0fx%s", math.Ceil(intRes*(float64(16)/float64(9))), strings.TrimSuffix(downloadResolutionFlag, "p")) + case "240p": + // 240p would round up to 427x240 if used in the case statement above, so it has to be handled separately + downloadResolutionFlag = "428x240" case "1920x1080", "1280x720", "640x480", "480x360", "428x240", "best", "worst": default: return fmt.Errorf("'%s' is not a valid resolution", downloadResolutionFlag) From b70bf9902b1b51ff5aa943f2059cdda8c673cdeb Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 11:01:49 +0200 Subject: [PATCH 064/630] Add error return in some login failure cases (#30) --- crunchyroll.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crunchyroll.go b/crunchyroll.go index 00d3123..1dafee7 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -94,6 +94,13 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h if loginResp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to auth with credentials: %s", loginResp.Status) + } else { + var loginRespBody map[string]interface{} + json.NewDecoder(loginResp.Body).Decode(&loginRespBody) + + if loginRespBody["error"].(bool) { + return nil, fmt.Errorf("an unexpected login error occoured: %s", loginRespBody["message"]) + } } return LoginWithSessionID(sessionID, locale, client) From ea3506b7f6f7f63676df453b8927a97fcc7bf7b4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 11:11:18 +0200 Subject: [PATCH 065/630] Fix logging in message shown for too long --- cmd/crunchyroll-go/cmd/utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index cafd99d..c799165 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -166,6 +166,7 @@ func loadCrunchy() { // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600) } + out.StopProgress("Logged in") return } } @@ -183,6 +184,8 @@ func loadCrunchy() { } out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) + + out.StopProgress("Logged in") return } From b92eddc5d242435b453d4fea1a2ecba32838725b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 11:14:35 +0200 Subject: [PATCH 066/630] Add temporary session id lookup on login first --- cmd/crunchyroll-go/cmd/utils.go | 35 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index c799165..2a632ff 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -140,6 +140,23 @@ func freeFileName(filename string) (string, bool) { func loadCrunchy() { out.SetProgress("Logging in") + tmpFilePath := filepath.Join(os.TempDir(), ".crunchy") + if _, statErr := os.Stat(tmpFilePath); !os.IsNotExist(statErr) { + body, err := os.ReadFile(tmpFilePath) + if err != nil { + out.StopProgress("Failed to read login information: %v", err) + os.Exit(1) + } + if crunchy, err = crunchyroll.LoginWithSessionID(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { + out.Debug("Failed to login with temp session id: %w", err) + } else { + out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) + + out.StopProgress("Logged in") + return + } + } + if configDir, err := os.UserConfigDir(); err == nil { persistentFilePath := filepath.Join(configDir, "crunchyroll-go", "crunchy") if _, statErr := os.Stat(persistentFilePath); statErr == nil { @@ -171,24 +188,6 @@ func loadCrunchy() { } } - tmpFilePath := filepath.Join(os.TempDir(), ".crunchy") - if _, statErr := os.Stat(tmpFilePath); !os.IsNotExist(statErr) { - body, err := os.ReadFile(tmpFilePath) - if err != nil { - out.StopProgress("Failed to read login information: %v", err) - os.Exit(1) - } - if crunchy, err = crunchyroll.LoginWithSessionID(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) - } - - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) - - out.StopProgress("Logged in") - return - } - out.StopProgress("To use this command, login first. Type `%s login -h` to get help", os.Args[0]) os.Exit(1) } From 00ea7635eb23bca6757d105507ba308befab7aa7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 11:18:27 +0200 Subject: [PATCH 067/630] Version 2.2.2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d23fd11..3dd5c3e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=2.2.1 +VERSION=2.2.2 BINARY_NAME=crunchy VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) From 5d732123d97efac57ebf62fd7be0f98eac50f8c0 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 11:35:21 +0200 Subject: [PATCH 068/630] Change cli name from crunchyroll to crunchyroll-go --- cmd/crunchyroll-go/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index 14f0c0e..82ee133 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -27,7 +27,7 @@ var ( ) var rootCmd = &cobra.Command{ - Use: "crunchyroll", + Use: "crunchyroll-go", Version: Version, Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchyroll-go/wiki", From 0ffae4ddda15c72020343837367c6ea9be829dd9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 22:24:21 +0200 Subject: [PATCH 069/630] Add available function to check if episode streams are available --- episode.go | 7 +++++++ url.go | 5 +++++ utils/sort.go | 3 +++ 3 files changed, 15 insertions(+) diff --git a/episode.go b/episode.go index 3c9d38f..7d477ce 100644 --- a/episode.go +++ b/episode.go @@ -112,6 +112,8 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { // Every episode in a season (should) have the same audio locale, // so if you want to get the audio locale of a season, just call // this method on the first episode of the season. +// Will fail if no streams are available, thus use Available to +// prevent any misleading errors. func (e *Episode) AudioLocale() (LOCALE, error) { streams, err := e.Streams() if err != nil { @@ -120,6 +122,11 @@ func (e *Episode) AudioLocale() (LOCALE, error) { return streams[0].AudioLocale, nil } +// Available returns if downloadable streams for this episodes are available. +func (e *Episode) Available() bool { + return e.crunchy.Config.Premium || !e.IsPremiumOnly +} + // GetFormat returns the format which matches the given resolution and subtitle locale. func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) { streams, err := e.Streams() diff --git a/url.go b/url.go index 32603fc..c84a3b9 100644 --- a/url.go +++ b/url.go @@ -49,6 +49,11 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep } for _, episode := range episodes { + // if no episode streams are available, calling episode.AudioLocale + // will result in an unwanted error + if !episode.Available() { + continue + } locale, err := episode.AudioLocale() if err != nil { return nil, err diff --git a/utils/sort.go b/utils/sort.go index a44717d..e06946c 100644 --- a/utils/sort.go +++ b/utils/sort.go @@ -52,6 +52,9 @@ func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCAL var wg sync.WaitGroup var lock sync.Mutex for _, episode := range episodes { + if !episode.Available() { + continue + } episode := episode wg.Add(1) go func() { From 43be2eee146550f9934231bbd1525cf735e41ab1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 16 May 2022 22:25:33 +0200 Subject: [PATCH 070/630] Fix typo & add audio locale todo for non-premium accounts --- season.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/season.go b/season.go index b83d214..f5ec08e 100644 --- a/season.go +++ b/season.go @@ -37,7 +37,7 @@ type Season struct { AvailabilityNotes string `json:"availability_notes"` - // the locales are always empty, idk why this may change in the future + // the locales are always empty, idk why, this may change in the future AudioLocales []LOCALE SubtitleLocales []LOCALE } @@ -71,6 +71,7 @@ func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { // AudioLocale returns the audio locale of the season. func (s *Season) AudioLocale() (LOCALE, error) { + // TODO: Add a function like Episode.Available to prevent this from returning an unwanted error when the account is non-premium episodes, err := s.Episodes() if err != nil { return "", err From f635bf1a2e1783e88fec015a25627d58c5698108 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 21:47:17 +0200 Subject: [PATCH 071/630] Add available function to check if season streams are available --- episode.go | 4 ++-- season.go | 12 +++++++++++- url.go | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/episode.go b/episode.go index 7d477ce..3a2e403 100644 --- a/episode.go +++ b/episode.go @@ -112,8 +112,8 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { // Every episode in a season (should) have the same audio locale, // so if you want to get the audio locale of a season, just call // this method on the first episode of the season. -// Will fail if no streams are available, thus use Available to -// prevent any misleading errors. +// Will fail if no streams are available, thus use Episode.Available +// to prevent any misleading errors. func (e *Episode) AudioLocale() (LOCALE, error) { streams, err := e.Streams() if err != nil { diff --git a/season.go b/season.go index f5ec08e..0bb8623 100644 --- a/season.go +++ b/season.go @@ -70,8 +70,9 @@ func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { } // AudioLocale returns the audio locale of the season. +// Will fail if no streams are available, thus use Season.Available +// to prevent any misleading errors. func (s *Season) AudioLocale() (LOCALE, error) { - // TODO: Add a function like Episode.Available to prevent this from returning an unwanted error when the account is non-premium episodes, err := s.Episodes() if err != nil { return "", err @@ -79,6 +80,15 @@ func (s *Season) AudioLocale() (LOCALE, error) { return episodes[0].AudioLocale() } +// Available returns if downloadable streams for this season are available. +func (s *Season) Available() (bool, error) { + episodes, err := s.Episodes() + if err != nil { + return false, err + } + return episodes[0].Available(), nil +} + // Episodes returns all episodes which are available for the season. func (s *Season) Episodes() (episodes []*Episode, err error) { if s.children != nil { diff --git a/url.go b/url.go index c84a3b9..0f5bb25 100644 --- a/url.go +++ b/url.go @@ -21,6 +21,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep } for _, season := range seasons { if audio != nil { + locale, err := season.AudioLocale() if err != nil { return nil, err From 7be803d485ca88994b1169b24e6c5adf88236c36 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 21:54:31 +0200 Subject: [PATCH 072/630] Add extended error message if account is non-premium --- url.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/url.go b/url.go index 0f5bb25..86874eb 100644 --- a/url.go +++ b/url.go @@ -13,6 +13,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep } var eps []*Episode + var notAvailableContinue bool if series != nil { seasons, err := series.Seasons() @@ -21,6 +22,12 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep } for _, season := range seasons { if audio != nil { + if available, err := season.Available(); err != nil { + return nil, err + } else if !available { + notAvailableContinue = true + continue + } locale, err := season.AudioLocale() if err != nil { @@ -53,6 +60,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep // if no episode streams are available, calling episode.AudioLocale // will result in an unwanted error if !episode.Available() { + notAvailableContinue = true continue } locale, err := episode.AudioLocale() @@ -77,7 +85,11 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep } if len(eps) == 0 { - return nil, fmt.Errorf("could not find any matching episode") + if notAvailableContinue { + return nil, fmt.Errorf("could not find any matching episode which is accessable with an non-premium account") + } else { + return nil, fmt.Errorf("could not find any matching episode") + } } return eps, nil From d4e095a576f1617082ad9952b626c916800e5382 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 21:56:29 +0200 Subject: [PATCH 073/630] Fix typo --- url.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/url.go b/url.go index 86874eb..95f3951 100644 --- a/url.go +++ b/url.go @@ -86,7 +86,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep if len(eps) == 0 { if notAvailableContinue { - return nil, fmt.Errorf("could not find any matching episode which is accessable with an non-premium account") + return nil, fmt.Errorf("could not find any matching episode which is accessable with a non-premium account") } else { return nil, fmt.Errorf("could not find any matching episode") } From 608e03bc11925c9bb78ad6a0b4efbf4137a34c6e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 18 May 2022 22:21:49 +0200 Subject: [PATCH 074/630] Add better update output --- cmd/crunchyroll-go/cmd/update.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/update.go b/cmd/crunchyroll-go/cmd/update.go index 4c7aaad..c8512e6 100644 --- a/cmd/crunchyroll-go/cmd/update.go +++ b/cmd/crunchyroll-go/cmd/update.go @@ -63,10 +63,12 @@ func update() error { return fmt.Errorf("internal version (%s) is not parsable", Version) } + out.Info("Installed version is %s", Version) + var hasUpdate bool for i := 0; i < 3; i++ { if latestRelease[i] < internalVersion[i] { - out.Info("Local version (%s) is newer than version in latest release (%s)", Version, releaseVersion) + out.Info("Local version is newer than version in latest release (%s)", releaseVersion) return nil } else if latestRelease[i] > internalVersion[i] { hasUpdate = true @@ -78,7 +80,7 @@ func update() error { return nil } - out.Info("A new version is available (%s). Installed version is %s: https://github.com/ByteDream/crunchyroll-go/releases/tag/v%s", releaseVersion, Version, releaseVersion) + out.Info("A new version is available (%s): https://github.com/ByteDream/crunchyroll-go/releases/tag/v%s", releaseVersion, releaseVersion) if updateInstallFlag { if runtime.GOARCH != "amd64" { From a4ec163275ab6fb6961cc6e208d959ee02399a80 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 20 May 2022 22:57:07 +0200 Subject: [PATCH 075/630] Add basic encrypted login credentials support --- cmd/crunchyroll-go/cmd/login.go | 75 ++++++++++++++++++++++++++++--- cmd/crunchyroll-go/cmd/unix.go | 48 ++++++++++++++++++++ cmd/crunchyroll-go/cmd/utils.go | 52 ++++++++++++++++++--- cmd/crunchyroll-go/cmd/windows.go | 41 +++++++++++++++++ 4 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 cmd/crunchyroll-go/cmd/unix.go create mode 100644 cmd/crunchyroll-go/cmd/windows.go diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index c9fc923..565dac0 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -1,15 +1,22 @@ package cmd import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" "fmt" "github.com/ByteDream/crunchyroll-go/v2" "github.com/spf13/cobra" + "io" "os" "path/filepath" ) var ( loginPersistentFlag bool + loginEncryptFlag bool loginSessionIDFlag bool ) @@ -33,6 +40,10 @@ func init() { "persistent", false, "If the given credential should be stored persistent") + loginCmd.Flags().BoolVar(&loginEncryptFlag, + "encrypt", + false, + "Encrypt the given credentials (won't do anything if --session-id is given)") loginCmd.Flags().BoolVar(&loginSessionIDFlag, "session-id", @@ -49,20 +60,74 @@ func loginCredentials(user, password string) error { return err } + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.SessionID), 0600); err != nil { + return err + } + if loginPersistentFlag { if configDir, err := os.UserConfigDir(); err != nil { return fmt.Errorf("could not save credentials persistent: %w", err) } else { + var credentials []byte + + if loginEncryptFlag { + var passwd []byte + + for { + fmt.Print("Enter password: ") + passwd, err = readLineSilent() + if err != nil { + return err + } + fmt.Println() + + fmt.Print("Enter password again: ") + repasswd, err := readLineSilent() + if err != nil { + return err + } + fmt.Println() + + if !bytes.Equal(passwd, repasswd) { + fmt.Println("Passwords does not match, try again") + continue + } + + hashedPassword := sha256.Sum256(passwd) + block, err := aes.NewCipher(hashedPassword[:]) + if err != nil { + out.Err("Failed to create block: %w", err) + os.Exit(1) + } + gcm, err := cipher.NewGCM(block) + if err != nil { + out.Err("Failed to create gcm: %w", err) + os.Exit(1) + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + out.Err("Failed to fill nonce: %w", err) + os.Exit(1) + } + + b := gcm.Seal(nonce, nonce, []byte(fmt.Sprintf("%s\n%s", user, password)), nil) + credentials = append([]byte("aes:"), b...) + + break + } + } else { + credentials = []byte(fmt.Sprintf("%s\n%s", user, password)) + } + os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(fmt.Sprintf("%s\n%s", user, password)), 0600); err != nil { + if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), credentials, 0600); err != nil { return err } - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + if !loginEncryptFlag { + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + } } } - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.SessionID), 0600); err != nil { - return err - } if !loginPersistentFlag { out.Info("Due to security reasons, you have to login again on the next reboot") diff --git a/cmd/crunchyroll-go/cmd/unix.go b/cmd/crunchyroll-go/cmd/unix.go new file mode 100644 index 0000000..962088f --- /dev/null +++ b/cmd/crunchyroll-go/cmd/unix.go @@ -0,0 +1,48 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos + +package cmd + +import ( + "bufio" + "os" + "os/exec" + "syscall" +) + +// https://github.com/bgentry/speakeasy/blob/master/speakeasy_unix.go +var stty string + +func init() { + var err error + if stty, err = exec.LookPath("stty"); err != nil { + panic(err) + } +} + +func readLineSilent() ([]byte, error) { + pid, err := setEcho(false) + if err != nil { + return nil, err + } + defer setEcho(true) + + syscall.Wait4(pid, nil, 0, nil) + + l, _, err := bufio.NewReader(os.Stdin).ReadLine() + return l, err +} + +func setEcho(on bool) (pid int, err error) { + fds := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()} + + if on { + pid, err = syscall.ForkExec(stty, []string{"stty", "echo"}, &syscall.ProcAttr{Files: fds}) + } else { + pid, err = syscall.ForkExec(stty, []string{"stty", "-echo"}, &syscall.ProcAttr{Files: fds}) + } + + if err != nil { + return 0, err + } + return +} diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 2a632ff..d7eba3d 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -1,6 +1,9 @@ package cmd import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" "fmt" "github.com/ByteDream/crunchyroll-go/v2" "github.com/ByteDream/crunchyroll-go/v2/utils" @@ -167,13 +170,49 @@ func loadCrunchy() { } split := strings.SplitN(string(body), "\n", 2) if len(split) == 1 || split[1] == "" { - split[0] = url.QueryEscape(split[0]) - if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) + if strings.HasPrefix(split[0], "aes:") { + encrypted := body[4:] + + out.StopProgress("Credentials are encrypted") + fmt.Print("Enter password to encrypt it: ") + passwd, err := readLineSilent() + fmt.Println() + if err != nil { + out.Err("Failed to read password; %w", err) + os.Exit(1) + } + out.SetProgress("Logging in") + + hashedPassword := sha256.Sum256(passwd) + block, err := aes.NewCipher(hashedPassword[:]) + if err != nil { + out.Err("Failed to create block: %w", err) + os.Exit(1) + } + gcm, err := cipher.NewGCM(block) + if err != nil { + out.Err("Failed to create gcm: %w", err) + os.Exit(1) + } + nonce, c := encrypted[:gcm.NonceSize()], encrypted[gcm.NonceSize():] + + b, err := gcm.Open(nil, nonce, c, nil) + if err != nil { + out.StopProgress("Invalid password") + os.Exit(1) + } + split = strings.SplitN(string(b), "\n", 2) + } else { + split[0] = url.QueryEscape(split[0]) + if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err != nil { + out.StopProgress(err.Error()) + os.Exit(1) + } + out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) } - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) - } else { + } + + if len(split) == 2 { if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) os.Exit(1) @@ -183,6 +222,7 @@ func loadCrunchy() { // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600) } + out.StopProgress("Logged in") return } diff --git a/cmd/crunchyroll-go/cmd/windows.go b/cmd/crunchyroll-go/cmd/windows.go new file mode 100644 index 0000000..d6eecb1 --- /dev/null +++ b/cmd/crunchyroll-go/cmd/windows.go @@ -0,0 +1,41 @@ +//go:build windows + +package cmd + +import ( + "bufio" + "os" + "syscall" +) + +// https://github.com/bgentry/speakeasy/blob/master/speakeasy_windows.go +func readLineSilent() ([]byte, error) { + var oldMode uint32 + + if err := syscall.GetConsoleMode(syscall.Stdin, &oldMode); err != nil { + return nil, err + } + + newMode := oldMode &^ 0x0004 + + err := setConsoleMode(syscall.Stdin, newMode) + defer setConsoleMode(syscall.Stdin, oldMode) + + if err != nil { + return nil, err + } + + l, _, err := bufio.NewReader(os.Stdin).ReadLine() + if err != nil { + return nil, err + } + return l, err +} + +func setConsoleMode(console syscall.Handle, mode uint32) error { + dll := syscall.MustLoadDLL("kernel32") + proc := dll.MustFindProc("SetConsoleMode") + _, _, err := proc.Call(uintptr(console), uintptr(mode)) + + return err +} From 4d65d2f2dff842b1708f3907bd6c5a7c0076d8fc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 20 May 2022 23:05:38 +0200 Subject: [PATCH 076/630] Bump CI go version to 1.18 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44c7ac6..7c54dd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.16 + go-version: 1.18 - name: Build run: go build -v cmd/crunchyroll-go/main.go From b78d6a7871f4f177fdeb6729a2a011a3f48b7747 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 21 May 2022 00:29:03 +0200 Subject: [PATCH 077/630] Change default Makefile version variable to development --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3dd5c3e..9b96538 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=2.2.2 +VERSION=development BINARY_NAME=crunchy VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) From eb2414d0120bd1067b9324d4b4c31d4dcea9a568 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 21 May 2022 21:38:45 +0200 Subject: [PATCH 078/630] Update module version to v3 --- Makefile | 8 ++++---- README.md | 4 ++-- cmd/crunchyroll-go/cmd/archive.go | 4 ++-- cmd/crunchyroll-go/cmd/download.go | 4 ++-- cmd/crunchyroll-go/cmd/login.go | 2 +- cmd/crunchyroll-go/cmd/root.go | 2 +- cmd/crunchyroll-go/cmd/utils.go | 4 ++-- cmd/crunchyroll-go/main.go | 2 +- go.mod | 2 +- utils/locale.go | 2 +- utils/sort.go | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 9b96538..a747c1d 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DESTDIR= PREFIX=/usr build: - go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go + go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* @@ -24,8 +24,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go strip $(VERSION_BINARY_NAME)_linux diff --git a/README.md b/README.md index c83493c..f875018 100644 --- a/README.md +++ b/README.md @@ -205,10 +205,10 @@ These flags you can use across every sub-command: Download the library via `go get` ```shell -$ go get github.com/ByteDream/crunchyroll-go/v2 +$ go get github.com/ByteDream/crunchyroll-go/v3 ``` -The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go/v2). +The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go/v3). Examples how to use the library and some features of it are described in the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Library). diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 6dcf61b..d4a7e48 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -8,8 +8,8 @@ import ( "compress/gzip" "context" "fmt" - "github.com/ByteDream/crunchyroll-go/v2" - "github.com/ByteDream/crunchyroll-go/v2/utils" + "github.com/ByteDream/crunchyroll-go/v3" + "github.com/ByteDream/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "io" diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 92b6027..3dddf48 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -3,8 +3,8 @@ package cmd import ( "context" "fmt" - "github.com/ByteDream/crunchyroll-go/v2" - "github.com/ByteDream/crunchyroll-go/v2/utils" + "github.com/ByteDream/crunchyroll-go/v3" + "github.com/ByteDream/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "math" diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index c9fc923..4bee3b0 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -2,7 +2,7 @@ package cmd import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v3" "github.com/spf13/cobra" "os" "path/filepath" diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index 82ee133..02873f1 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -3,7 +3,7 @@ package cmd import ( "context" "fmt" - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v3" "github.com/spf13/cobra" "net/http" "os" diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 2a632ff..2fddd60 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -2,8 +2,8 @@ package cmd import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v2" - "github.com/ByteDream/crunchyroll-go/v2/utils" + "github.com/ByteDream/crunchyroll-go/v3" + "github.com/ByteDream/crunchyroll-go/v3/utils" "net/http" "net/url" "os" diff --git a/cmd/crunchyroll-go/main.go b/cmd/crunchyroll-go/main.go index a502afb..0ced8aa 100644 --- a/cmd/crunchyroll-go/main.go +++ b/cmd/crunchyroll-go/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd" + "github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd" ) func main() { diff --git a/go.mod b/go.mod index 9a38468..5c64ddf 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ByteDream/crunchyroll-go/v2 +module github.com/ByteDream/crunchyroll-go/v3 go 1.18 diff --git a/utils/locale.go b/utils/locale.go index 537b165..85a8650 100644 --- a/utils/locale.go +++ b/utils/locale.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v3" ) // AllLocales is an array of all available locales. diff --git a/utils/sort.go b/utils/sort.go index a44717d..eacb96b 100644 --- a/utils/sort.go +++ b/utils/sort.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/ByteDream/crunchyroll-go/v3" "sort" "strconv" "strings" From 2d28991a70e80d2c92f2b4d091bd0aa88ca00a6f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 21 May 2022 21:41:25 +0200 Subject: [PATCH 079/630] Remove v2 release notice --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c83493c..b8d48fa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -<p align="center"><strong>Version 2 is out ๐Ÿฅณ, see all the <a href="https://github.com/ByteDream/crunchyroll-go/releases/tag/v2.0.0">changes</a></strong>.</p> - # crunchyroll-go A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](https://www.crunchyroll.com) api. To use it, you need a crunchyroll premium account to for full (api) access. From 382d19ee94e15e4415aa26da98ccb2fc1d84d863 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 27 May 2022 11:17:08 +0200 Subject: [PATCH 080/630] Fix login not working with session id --- crunchyroll.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 1dafee7..dcfc375 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -10,6 +10,7 @@ import ( "net/url" "regexp" "strconv" + "strings" ) // LOCALE represents a locale / language. @@ -143,17 +144,6 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* data := jsonBody["data"].(map[string]interface{}) crunchy.Config.CountryCode = data["country_code"].(string) - user := data["user"] - if user == nil { - return nil, fmt.Errorf("invalid session id, user is not logged in") - } - if user.(map[string]interface{})["premium"] == "" { - crunchy.Config.Premium = false - crunchy.Config.Channel = "-" - } else { - crunchy.Config.Premium = true - crunchy.Config.Channel = "crunchyroll" - } var etpRt string for _, cookie := range resp.Cookies() { @@ -206,6 +196,13 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* } cms := jsonBody["cms"].(map[string]interface{}) + if strings.Contains(cms["bucket"].(string), "crunchyroll") { + crunchy.Config.Premium = true + crunchy.Config.Channel = "crunchyroll" + } else { + crunchy.Config.Premium = false + crunchy.Config.Channel = "-" + } crunchy.Config.Policy = cms["policy"].(string) crunchy.Config.Signature = cms["signature"].(string) crunchy.Config.KeyPairID = cms["key_pair_id"].(string) From 638689ee327f4b5df4571baa0afd9763b222a9ee Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 27 May 2022 15:13:15 +0200 Subject: [PATCH 081/630] Add new login method & deprecated login with session id --- crunchyroll.go | 298 +++++++++++++++++++++++++------------------------ 1 file changed, 151 insertions(+), 147 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 1dafee7..0aa4a2e 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -10,6 +10,7 @@ import ( "net/url" "regexp" "strconv" + "strings" ) // LOCALE represents a locale / language. @@ -36,14 +37,17 @@ type Crunchyroll struct { Context context.Context // Locale specifies in which language all results should be returned / requested. Locale LOCALE - // SessionID is the crunchyroll session id which was used for authentication. - SessionID string + // EtpRt is the crunchyroll beta equivalent to a session id (prior SessionID field in + // this struct in v2 and below). + EtpRt string // Config stores parameters which are needed by some api calls. Config struct { TokenType string AccessToken string + Bucket string + CountryCode string Premium bool Channel string @@ -59,101 +63,38 @@ type Crunchyroll struct { cache bool } -// LoginWithCredentials logs in via crunchyroll username or email and password. -func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { - sessionIDEndpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?version=1.0&access_token=%s&device_type=%s&device_id=%s", - "LNDJgOit5yaRIWN", "com.crunchyroll.windows.desktop", "Az2srGnChW65fuxYz2Xxl1GcZQgtGgI") - sessResp, err := client.Get(sessionIDEndpoint) - if err != nil { - return nil, err - } - defer sessResp.Body.Close() - - if sessResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to start session for credentials login: %s", sessResp.Status) - } - - var data map[string]interface{} - body, _ := io.ReadAll(sessResp.Body) - if err = json.Unmarshal(body, &data); err != nil { - return nil, fmt.Errorf("failed to parse start session with credentials response: %w", err) - } - - sessionID := data["data"].(map[string]interface{})["session_id"].(string) - - loginEndpoint := "https://api.crunchyroll.com/login.0.json" - authValues := url.Values{} - authValues.Set("session_id", sessionID) - authValues.Set("account", user) - authValues.Set("password", password) - loginResp, err := client.Post(loginEndpoint, "application/x-www-form-urlencoded", bytes.NewBufferString(authValues.Encode())) - if err != nil { - return nil, err - } - defer loginResp.Body.Close() - - if loginResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to auth with credentials: %s", loginResp.Status) - } else { - var loginRespBody map[string]interface{} - json.NewDecoder(loginResp.Body).Decode(&loginRespBody) - - if loginRespBody["error"].(bool) { - return nil, fmt.Errorf("an unexpected login error occoured: %s", loginRespBody["message"]) - } - } - - return LoginWithSessionID(sessionID, locale, client) +type loginResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` + Country string `json:"country"` + AccountID string `json:"account_id"` } -// LoginWithSessionID logs in via a crunchyroll session id. -// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com. -func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { - crunchy := &Crunchyroll{ - Client: client, - Context: context.Background(), - Locale: locale, - SessionID: sessionID, - cache: true, - } - var endpoint string - var err error - var resp *http.Response - var jsonBody map[string]interface{} +// LoginWithCredentials logs in via crunchyroll username or email and password. +func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { + endpoint := "https://beta-api.crunchyroll.com/auth/v1/token" + values := url.Values{} + values.Set("username", user) + values.Set("password", password) + values.Set("grant_type", "password") - // start session - endpoint = fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?session_id=%s", - sessionID) - resp, err = client.Get(endpoint) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBufferString(values.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Basic aHJobzlxM2F3dnNrMjJ1LXRzNWE6cHROOURteXRBU2Z6QjZvbXVsSzh6cUxzYTczVE1TY1k=") + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := request(req, client) if err != nil { return nil, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to start session: %s", resp.Status) - } - - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) - } - if isError, ok := jsonBody["error"]; ok && isError.(bool) { - return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"]) - } - data := jsonBody["data"].(map[string]interface{}) - - crunchy.Config.CountryCode = data["country_code"].(string) - user := data["user"] - if user == nil { - return nil, fmt.Errorf("invalid session id, user is not logged in") - } - if user.(map[string]interface{})["premium"] == "" { - crunchy.Config.Premium = false - crunchy.Config.Channel = "-" - } else { - crunchy.Config.Premium = true - crunchy.Config.Channel = "crunchyroll" - } + var loginResp loginResponse + json.NewDecoder(resp.Body).Decode(&loginResp) var etpRt string for _, cookie := range resp.Cookies() { @@ -163,83 +104,164 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* } } - // token - endpoint = "https://beta-api.crunchyroll.com/auth/v1/token" + return postLogin(loginResp, etpRt, locale, client) +} + +// LoginWithSessionID logs in via a crunchyroll session id. +// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com. +// +// Deprecated: Login via session id caused some trouble in the past (e.g. #15 or #30) which resulted in +// login not working. Use LoginWithEtpRt instead. EtpRt practically the crunchyroll beta equivalent to +// a session id. +// The method will stay in the library until session id login is removed completely or login with it +// does not work for a longer period of time. +func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { + endpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?session_id=%s", + sessionID) + resp, err := client.Get(endpoint) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]any + json.NewDecoder(resp.Body).Decode(&jsonBody) + + var etpRt string + for _, cookie := range resp.Cookies() { + if cookie.Name == "etp_rt" { + etpRt = cookie.Value + break + } + } + + return LoginWithEtpRt(etpRt, locale, client) +} + +// LoginWithEtpRt logs in via the crunchyroll etp rt cookie. This cookie is the crunchyroll beta +// equivalent to the classic session id. +// The etp_rt cookie is automatically set when visiting https://beta.crunchyroll.com. Note that you +// need a crunchyroll account to access it. +func LoginWithEtpRt(etpRt string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { + endpoint := "https://beta-api.crunchyroll.com/auth/v1/token" grantType := url.Values{} grantType.Set("grant_type", "etp_rt_cookie") - authRequest, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBufferString(grantType.Encode())) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBufferString(grantType.Encode())) if err != nil { return nil, err } - authRequest.Header.Add("Authorization", "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6") - authRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded") - authRequest.AddCookie(&http.Cookie{ - Name: "session_id", - Value: sessionID, - }) - authRequest.AddCookie(&http.Cookie{ + req.Header.Add("Authorization", "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6") + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.AddCookie(&http.Cookie{ Name: "etp_rt", Value: etpRt, }) + resp, err := request(req, client) + if err != nil { + return nil, err + } - resp, err = client.Do(authRequest) + var loginResp loginResponse + json.NewDecoder(resp.Body).Decode(&loginResp) + + return postLogin(loginResp, etpRt, locale, client) +} + +func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { + crunchy := &Crunchyroll{ + Client: client, + Context: context.Background(), + Locale: locale, + EtpRt: etpRt, + cache: true, + } + + crunchy.Config.TokenType = loginResp.TokenType + crunchy.Config.AccessToken = loginResp.AccessToken + crunchy.Config.AccountID = loginResp.AccountID + + var jsonBody map[string]any + + endpoint := "https://beta-api.crunchyroll.com/index/v2" + resp, err := crunchy.request(endpoint) if err != nil { return nil, err } defer resp.Body.Close() - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'token' response: %w", err) + json.NewDecoder(resp.Body).Decode(&jsonBody) + cms := jsonBody["cms"].(map[string]any) + crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") + if strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") { + crunchy.Config.Premium = true + crunchy.Config.Channel = "crunchyroll" + } else { + crunchy.Config.Premium = false + crunchy.Config.Channel = "-" } - crunchy.Config.TokenType = jsonBody["token_type"].(string) - crunchy.Config.AccessToken = jsonBody["access_token"].(string) - - // index - endpoint = "https://beta-api.crunchyroll.com/index/v2" - resp, err = crunchy.request(endpoint) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'index' response: %w", err) - } - cms := jsonBody["cms"].(map[string]interface{}) - crunchy.Config.Policy = cms["policy"].(string) crunchy.Config.Signature = cms["signature"].(string) crunchy.Config.KeyPairID = cms["key_pair_id"].(string) - // me endpoint = "https://beta-api.crunchyroll.com/accounts/v1/me" resp, err = crunchy.request(endpoint) if err != nil { return nil, err } defer resp.Body.Close() - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'me' response: %w", err) - } - - crunchy.Config.AccountID = jsonBody["account_id"].(string) + json.NewDecoder(resp.Body).Decode(&jsonBody) crunchy.Config.ExternalID = jsonBody["external_id"].(string) - //profile endpoint = "https://beta-api.crunchyroll.com/accounts/v1/me/profile" resp, err = crunchy.request(endpoint) if err != nil { return nil, err } defer resp.Body.Close() - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) - } - + json.NewDecoder(resp.Body).Decode(&jsonBody) crunchy.Config.MaturityRating = jsonBody["maturity_rating"].(string) return crunchy, nil } +func request(req *http.Request, client *http.Client) (*http.Response, error) { + resp, err := client.Do(req) + if err == nil { + var buf bytes.Buffer + io.Copy(&buf, resp.Body) + defer resp.Body.Close() + defer func() { + resp.Body = io.NopCloser(&buf) + }() + + if buf.Len() != 0 { + var errMap map[string]any + + if err = json.Unmarshal(buf.Bytes(), &errMap); err != nil { + return nil, fmt.Errorf("invalid json response: %w", err) + } + + if val, ok := errMap["error"]; ok { + if errorAsString, ok := val.(string); ok { + if code, ok := errMap["code"].(string); ok { + return nil, fmt.Errorf("error for endpoint %s (%d): %s - %s", req.URL.String(), resp.StatusCode, errorAsString, code) + } + return nil, fmt.Errorf("error for endpoint %s (%d): %s", req.URL.String(), resp.StatusCode, errorAsString) + } else if errorAsBool, ok := val.(bool); ok && errorAsBool { + if msg, ok := errMap["message"].(string); ok { + return nil, fmt.Errorf("error for endpoint %s (%d): %s", req.URL.String(), resp.StatusCode, msg) + } + } + } + } + + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("error for endpoint %s: %s", req.URL.String(), resp.Status) + } + } + return resp, err +} + // request is a base function which handles api requests. func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, endpoint, nil) @@ -248,25 +270,7 @@ func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { } req.Header.Add("Authorization", fmt.Sprintf("%s %s", c.Config.TokenType, c.Config.AccessToken)) - resp, err := c.Client.Do(req) - if err == nil { - defer resp.Body.Close() - bodyAsBytes, _ := io.ReadAll(resp.Body) - defer resp.Body.Close() - if resp.StatusCode == http.StatusUnauthorized { - return nil, fmt.Errorf("invalid access token") - } else { - var errStruct struct { - Message string `json:"message"` - } - json.NewDecoder(bytes.NewBuffer(bodyAsBytes)).Decode(&errStruct) - if errStruct.Message != "" { - return nil, fmt.Errorf(errStruct.Message) - } - } - resp.Body = io.NopCloser(bytes.NewBuffer(bodyAsBytes)) - } - return resp, err + return request(req, c.Client) } // IsCaching returns if data gets cached or not. From c94ce0fb59dddb29bcbce4ad7e1e5b2dc1814b84 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 27 May 2022 15:58:28 +0200 Subject: [PATCH 082/630] Fix country code not set --- crunchyroll.go | 1 + 1 file changed, 1 insertion(+) diff --git a/crunchyroll.go b/crunchyroll.go index 0aa4a2e..06a28ec 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -180,6 +180,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt crunchy.Config.TokenType = loginResp.TokenType crunchy.Config.AccessToken = loginResp.AccessToken crunchy.Config.AccountID = loginResp.AccountID + crunchy.Config.CountryCode = loginResp.Country var jsonBody map[string]any From 0780a2a2bceb42d83fb533540de247718585e6a9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 27 May 2022 15:59:00 +0200 Subject: [PATCH 083/630] Change login type from session id to etp rt --- cmd/crunchyroll-go/cmd/login.go | 46 ++++++++++++++++++++++++++++++--- cmd/crunchyroll-go/cmd/utils.go | 14 +++++----- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index 4bee3b0..28a784e 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -12,6 +12,7 @@ var ( loginPersistentFlag bool loginSessionIDFlag bool + loginEtpRtFlag bool ) var loginCmd = &cobra.Command{ @@ -22,6 +23,8 @@ var loginCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { if loginSessionIDFlag { return loginSessionID(args[0]) + } else if loginEtpRtFlag { + return loginEtpRt(args[0]) } else { return loginCredentials(args[0], args[1]) } @@ -38,6 +41,10 @@ func init() { "session-id", false, "Use a session id to login instead of username and password") + loginCmd.Flags().BoolVar(&loginEtpRtFlag, + "etp-rt", + false, + "Use a etp rt cookie to login instead of username and password") rootCmd.AddCommand(loginCmd) } @@ -60,7 +67,7 @@ func loginCredentials(user, password string) error { out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) } } - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.SessionID), 0600); err != nil { + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { return err } @@ -73,7 +80,38 @@ func loginCredentials(user, password string) error { func loginSessionID(sessionID string) error { out.Debug("Logging in via session id") - if _, err := crunchyroll.LoginWithSessionID(sessionID, systemLocale(false), client); err != nil { + var c *crunchyroll.Crunchyroll + var err error + if c, err = crunchyroll.LoginWithSessionID(sessionID, systemLocale(false), client); err != nil { + out.Err(err.Error()) + os.Exit(1) + } + + if loginPersistentFlag { + if configDir, err := os.UserConfigDir(); err != nil { + return fmt.Errorf("could not save credentials persistent: %w", err) + } else { + os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) + if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(c.EtpRt), 0600); err != nil { + return err + } + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + } + } + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { + return err + } + + if !loginPersistentFlag { + out.Info("Due to security reasons, you have to login again on the next reboot") + } + + return nil +} + +func loginEtpRt(etpRt string) error { + out.Debug("Logging in via etp rt") + if _, err := crunchyroll.LoginWithEtpRt(etpRt, systemLocale(false), client); err != nil { out.Err(err.Error()) os.Exit(1) } @@ -84,13 +122,13 @@ func loginSessionID(sessionID string) error { return fmt.Errorf("could not save credentials persistent: %w", err) } else { os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(sessionID), 0600); err != nil { + if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(etpRt), 0600); err != nil { return err } out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) } } - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(sessionID), 0600); err != nil { + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(etpRt), 0600); err != nil { return err } diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 2fddd60..86691ab 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -147,10 +147,10 @@ func loadCrunchy() { out.StopProgress("Failed to read login information: %v", err) os.Exit(1) } - if crunchy, err = crunchyroll.LoginWithSessionID(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { - out.Debug("Failed to login with temp session id: %w", err) + if crunchy, err = crunchyroll.LoginWithEtpRt(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { + out.Debug("Failed to login with temp etp rt: %w", err) } else { - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) + out.Debug("Logged in with etp rt %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) out.StopProgress("Logged in") return @@ -168,20 +168,20 @@ func loadCrunchy() { split := strings.SplitN(string(body), "\n", 2) if len(split) == 1 || split[1] == "" { split[0] = url.QueryEscape(split[0]) - if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err != nil { + if crunchy, err = crunchyroll.LoginWithEtpRt(split[0], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) os.Exit(1) } - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + out.Debug("Logged in with etp rt %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) } else { if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) os.Exit(1) } - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", crunchy.SessionID) + out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", crunchy.EtpRt) // the session id is written to a temp file to reduce the amount of re-logging in. // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time - os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.SessionID), 0600) + os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600) } out.StopProgress("Logged in") return From b4ba8c45996eeb3fce5484cd7af23199b868bff6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 27 May 2022 16:00:08 +0200 Subject: [PATCH 084/630] Bump go CI version to 1.18 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44c7ac6..7c54dd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.16 + go-version: 1.18 - name: Build run: go build -v cmd/crunchyroll-go/main.go From 15373ed7d6b689e900ba3c44cf35376048bed679 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 27 May 2022 16:03:37 +0200 Subject: [PATCH 085/630] Fix typo --- cmd/crunchyroll-go/cmd/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 86691ab..aa98399 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -179,7 +179,7 @@ func loadCrunchy() { os.Exit(1) } out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", crunchy.EtpRt) - // the session id is written to a temp file to reduce the amount of re-logging in. + // the etp rt is written to a temp file to reduce the amount of re-logging in. // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600) } From 08c46e50bb1cdc6bc23dc73efb94fd5dc0c1cc6a Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Fri, 27 May 2022 19:05:35 +0200 Subject: [PATCH 086/630] Add new endpoints --- category.go | 43 +++++++ crunchyroll.go | 300 +++++++++++++++++++++++++++++++++++++++++++++++++ news.go | 19 ++++ simulcast.go | 13 +++ utils.go | 48 ++++++++ 5 files changed, 423 insertions(+) create mode 100644 category.go create mode 100644 news.go create mode 100644 simulcast.go diff --git a/category.go b/category.go new file mode 100644 index 0000000..58855fc --- /dev/null +++ b/category.go @@ -0,0 +1,43 @@ +package crunchyroll + +// Category contains all information about a category. +type Category struct { + Category string `json:"tenant_category"` + + SubCategories []struct { + Category string `json:"tenant_category"` + ParentCategory string `json:"parent_category"` + + Localization struct { + Title string `json:"title"` + Description string `json:"description"` + Locale LOCALE `json:"locale"` + } `json:"localization"` + + Slug string `json:"slug"` + } `json:"sub_categories"` + + Images struct { + Background []struct { + Width int `json:"width"` + Height int `json:"height"` + Type string `json:"type"` + Source string `json:"source"` + } `json:"background"` + + Low []struct { + Width int `json:"width"` + Height int `json:"height"` + Type string `json:"type"` + Source string `json:"source"` + } `json:"low"` + } `json:"images"` + + Localization struct { + Title string `json:"title"` + Description string `json:"description"` + Locale LOCALE `json:"locale"` + } `json:"localization"` + + Slug string `json:"slug"` +} diff --git a/crunchyroll.go b/crunchyroll.go index dcfc375..a81f373 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -30,6 +30,23 @@ const ( AR = "ar-SA" ) +// SortOrder represents a sort order. +type SortOrder string + +const ( + POPULARITY SortOrder = "popularity" + NEWLYADDED = "newly_added" + ALPHABETICAL = "alphabetical" +) + +// MediaType represents a media type. +type MediaType string + +const ( + SERIES MediaType = "series" + MOVIELISTING = "movie_listing" +) + type Crunchyroll struct { // Client is the http.Client to perform all requests over. Client *http.Client @@ -60,6 +77,30 @@ type Crunchyroll struct { cache bool } +// BrowseOptions represents options for browsing the crunchyroll catalog. +type BrowseOptions struct { + // Categories specifies the categories of the results. + Categories []string `param:"categories"` + + // IsDubbed specifies whether the results should be dubbed. + IsDubbed bool `param:"is_dubbed"` + + // IsSubbed specifies whether the results should be subbed. + IsSubbed bool `param:"is_subbed"` + + // SimulcastID specifies a particular simulcast season in which the results have been aired. + SimulcastID string `param:"season_tag"` + + // SortBy specifies how the results should be sorted. + SortBy SortOrder `param:"sort_by"` + + // Start specifies the index from which the results should be returned. + Start uint `param:"start"` + + // Type specifies the media type of the results. + Type MediaType `param:"type"` +} + // LoginWithCredentials logs in via crunchyroll username or email and password. func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { sessionIDEndpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?version=1.0&access_token=%s&device_type=%s&device_id=%s", @@ -446,3 +487,262 @@ func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) { } return } + +// Browse browses the crunchyroll catalog filtered by the specified options and returns all found series and movies within the given limit. +func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m []*Movie, err error) { + query, err := encodeStructToQueryValues(options) + if err != nil { + return nil, nil, err + } + + browseEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/browse?%s&n=%d&locale=%s", + query, limit, c.Locale) + resp, err := c.request(browseEndpoint) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'browse' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + switch item.(map[string]interface{})["type"] { + case "series": + series := &Series{ + crunchy: c, + } + if err := decodeMapToStruct(item, series); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { + return nil, nil, err + } + + s = append(s, series) + case "movie_listing": + movie := &Movie{ + crunchy: c, + } + if err := decodeMapToStruct(item, movie); err != nil { + return nil, nil, err + } + + m = append(m, movie) + } + } + + return s, m, nil +} + +// Categories returns all available categories and possible subcategories. +func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err error) { + tenantCategoriesEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/tenant_categories?include_subcategories=%t&locale=%s", + includeSubcategories, c.Locale) + resp, err := c.request(tenantCategoriesEndpoint) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'tenant_categories' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + category := &Category{} + if err := decodeMapToStruct(item, category); err != nil { + return nil, err + } + + ca = append(ca, category) + } + + return ca, nil +} + +// Simulcasts returns all available simulcast seasons for the current locale. +func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { + seasonListEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/season_list?locale=%s", c.Locale) + resp, err := c.request(seasonListEndpoint) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'season_list' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + simulcast := &Simulcast{} + if err := decodeMapToStruct(item, simulcast); err != nil { + return nil, err + } + + s = append(s, simulcast) + } + + return s, nil +} + +// News returns the top and latest news from crunchyroll for the current locale within the given limits. +func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*TopNews, l []*LatestNews, err error) { + newsFeedEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/news_feed?top_news_n=%d&latest_news_n=%d&locale=%s", + topLimit, latestLimit, c.Locale) + resp, err := c.request(newsFeedEndpoint) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'news_feed' response: %w", err) + } + + topNews := jsonBody["top_news"].(map[string]interface{}) + for _, item := range topNews["items"].([]interface{}) { + topNews := &TopNews{} + if err := decodeMapToStruct(item, topNews); err != nil { + return nil, nil, err + } + + t = append(t, topNews) + } + + latestNews := jsonBody["latest_news"].(map[string]interface{}) + for _, item := range latestNews["items"].([]interface{}) { + latestNews := &LatestNews{} + if err := decodeMapToStruct(item, latestNews); err != nil { + return nil, nil, err + } + + l = append(l, latestNews) + } + + return t, l, nil +} + +// Recommendations returns series and movie recommendations from crunchyroll based on your account within the given limit. +func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err error) { + recommendationsEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/recommendations?n=%d&locale=%s", + c.Config.AccountID, limit, c.Locale) + resp, err := c.request(recommendationsEndpoint) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'recommendations' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + switch item.(map[string]interface{})["type"] { + case "series": + series := &Series{ + crunchy: c, + } + if err := decodeMapToStruct(item, series); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { + return nil, nil, err + } + + s = append(s, series) + case "movie_listing": + movie := &Movie{ + crunchy: c, + } + if err := decodeMapToStruct(item, movie); err != nil { + return nil, nil, err + } + + m = append(m, movie) + } + } + + return s, m, nil +} + +// UpNext returns the next episodes that you can continue watching based on your account within the given limit. +func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { + upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", + c.Config.AccountID, limit, c.Locale) + resp, err := c.request(upNextAccountEndpoint) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'up_next_account' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + panel := item.(map[string]interface{})["panel"] + + episode := &Episode{ + crunchy: c, + } + if err := decodeMapToStruct(panel, episode); err != nil { + return nil, err + } + + e = append(e, episode) + } + + return e, nil +} + +// SimilarTo returns similar series and movies to the one specified by id within the given limits. +func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, err error) { + similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", + c.Config.AccountID, id, limit, c.Locale) + resp, err := c.request(similarToEndpoint) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'similar_to' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + switch item.(map[string]interface{})["type"] { + case "series": + series := &Series{ + crunchy: c, + } + if err := decodeMapToStruct(item, series); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { + return nil, nil, err + } + + s = append(s, series) + case "movie_listing": + movie := &Movie{ + crunchy: c, + } + if err := decodeMapToStruct(item, movie); err != nil { + return nil, nil, err + } + + m = append(m, movie) + } + } + + return s, m, nil +} diff --git a/news.go b/news.go new file mode 100644 index 0000000..3985cb4 --- /dev/null +++ b/news.go @@ -0,0 +1,19 @@ +package crunchyroll + +// News contains all information about a news. +type News struct { + Title string `json:"title"` + Link string `json:"link"` + Image string `json:"image"` + Creator string `json:"creator"` + PublishDate string `json:"publish_date"` + Description string `json:"description"` +} + +type TopNews struct { + News +} + +type LatestNews struct { + News +} diff --git a/simulcast.go b/simulcast.go new file mode 100644 index 0000000..d02f348 --- /dev/null +++ b/simulcast.go @@ -0,0 +1,13 @@ +package crunchyroll + +// Simulcast contains all information about a simulcast season. +type Simulcast struct { + ID string `json:"id"` + + Localization struct { + Title string `json:"title"` + + // appears to be always an empty string. + Description string `json:"description"` + } `json:"localization"` +} diff --git a/utils.go b/utils.go index a3d4191..672772f 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,10 @@ package crunchyroll import ( "encoding/json" + "fmt" + "net/url" + "reflect" + "strings" ) func decodeMapToStruct(m interface{}, s interface{}) error { @@ -23,3 +27,47 @@ func regexGroups(parsed [][]string, subexpNames ...string) map[string]string { } return groups } + +func encodeStructToQueryValues(s interface{}) (string, error) { + values := make(url.Values) + v := reflect.ValueOf(s) + + for i := 0; i < v.Type().NumField(); i++ { + + // don't include parameters with default or without values in the query to avoid corruption of the API response. + if isEmptyValue(v.Field(i)) { + continue + } + + key := v.Type().Field(i).Tag.Get("param") + var val string + + if v.Field(i).Kind() == reflect.Slice { + var items []string + + for _, i := range v.Field(i).Interface().([]string) { + items = append(items, i) + } + + val = strings.Join(items, ",") + } else { + val = fmt.Sprint(v.Field(i).Interface()) + } + + values.Add(key, val) + } + + return values.Encode(), nil +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Uint: + return v.Uint() == 0 + } + return false +} From cf3559698582ee2589570fc59f4b67797b7c9554 Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Sat, 28 May 2022 19:55:56 +0200 Subject: [PATCH 087/630] Add watch history --- crunchyroll.go | 48 +++++++++++++++++++++++++++++++++++++++++++----- episode.go | 12 ++++++++++++ news.go | 8 -------- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index a81f373..aefce1a 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -591,7 +591,7 @@ func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { } // News returns the top and latest news from crunchyroll for the current locale within the given limits. -func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*TopNews, l []*LatestNews, err error) { +func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*News, err error) { newsFeedEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/news_feed?top_news_n=%d&latest_news_n=%d&locale=%s", topLimit, latestLimit, c.Locale) resp, err := c.request(newsFeedEndpoint) @@ -607,7 +607,7 @@ func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*TopNews, l []* topNews := jsonBody["top_news"].(map[string]interface{}) for _, item := range topNews["items"].([]interface{}) { - topNews := &TopNews{} + topNews := &News{} if err := decodeMapToStruct(item, topNews); err != nil { return nil, nil, err } @@ -617,7 +617,7 @@ func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*TopNews, l []* latestNews := jsonBody["latest_news"].(map[string]interface{}) for _, item := range latestNews["items"].([]interface{}) { - latestNews := &LatestNews{} + latestNews := &News{} if err := decodeMapToStruct(item, latestNews); err != nil { return nil, nil, err } @@ -672,7 +672,7 @@ func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err return s, m, nil } -// UpNext returns the next episodes that you can continue watching based on your account within the given limit. +// UpNext returns the episodes that are up next based on your account within the given limit. func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", c.Config.AccountID, limit, c.Locale) @@ -703,7 +703,7 @@ func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { return e, nil } -// SimilarTo returns similar series and movies to the one specified by id within the given limits. +// SimilarTo returns similar series and movies according to crunchyroll to the one specified by id within the given limits. func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, err error) { similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", c.Config.AccountID, id, limit, c.Locale) @@ -746,3 +746,41 @@ func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, return s, m, nil } + +// WatchHistory returns the history of watched episodes based on your account from the given page with the given size. +func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, err error) { + watchHistoryEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/watch-history/%s?page=%d&page_size=%d&locale=%s", + c.Config.AccountID, page, size, c.Locale) + resp, err := c.request(watchHistoryEndpoint) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'watch-history' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + panel := item.(map[string]interface{})["panel"] + + episode := &Episode{ + crunchy: c, + } + if err := decodeMapToStruct(panel, episode); err != nil { + return nil, err + } + + historyEpisode := &HistoryEpisode{ + Episode: episode, + } + if err := decodeMapToStruct(item, historyEpisode); err != nil { + return nil, err + } + + e = append(e, historyEpisode) + } + + return e, nil +} diff --git a/episode.go b/episode.go index a25844c..07f6705 100644 --- a/episode.go +++ b/episode.go @@ -75,6 +75,18 @@ type Episode struct { StreamID string } +// HistoryEpisode contains additional information about an episode if the account has watched or started to watch the episode. +type HistoryEpisode struct { + *Episode + + ID string `json:"id"` + DatePlayed string `json:"date_played"` + ParentID string `json:"parent_id"` + ParentType MediaType `json:"parent_type"` + Playhead uint `json:"playhead"` + FullyWatched bool `json:"fully_watched"` +} + // EpisodeFromID returns an episode by its api id. func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", diff --git a/news.go b/news.go index 3985cb4..d90dd65 100644 --- a/news.go +++ b/news.go @@ -9,11 +9,3 @@ type News struct { PublishDate string `json:"publish_date"` Description string `json:"description"` } - -type TopNews struct { - News -} - -type LatestNews struct { - News -} From 7897da3baf200a77f9b9f876e7a7da9981d77e05 Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Sun, 29 May 2022 00:21:17 +0200 Subject: [PATCH 088/630] Add account and update comments --- account.go | 27 ++++++++++++++++++ crunchyroll.go | 74 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 account.go diff --git a/account.go b/account.go new file mode 100644 index 0000000..f6f87f1 --- /dev/null +++ b/account.go @@ -0,0 +1,27 @@ +package crunchyroll + +// Account contains information about a crunchyroll account. +type Account struct { + AccountID string `json:"account_id"` + ExternalID string `json:"external_id"` + EmailVerified bool `json:"email_verified"` + Created string `json:"created"` + + Avatar string `json:"avatar"` + CrBetaOptIn bool `json:"cr_beta_opt_in"` + Email string `json:"email"` + MatureContentFlagManga string `json:"mature_content_flag_manga"` + MaturityRating string `json:"maturity_rating"` + OptOutAndroidInAppMarketing bool `json:"opt_out_android_in_app_marketing"` + OptOutFreeTrials bool `json:"opt_out_free_trials"` + OptOutNewMediaQueueUpdates bool `json:"opt_out_new_media_queue_updates"` + OptOutNewsletters bool `json:"opt_out_newsletters"` + OptOutPmUpdates bool `json:"opt_out_pm_updates"` + OptOutPromotionalUpdates bool `json:"opt_out_promotional_updates"` + OptOutQueueUpdates bool `json:"opt_out_queue_updates"` + OptOutStoreDeals bool `json:"opt_out_store_deals"` + PreferredCommunicationLanguage LOCALE `json:"preferred_communication_language"` + PreferredContentSubtitleLanguage LOCALE `json:"preferred_content_subtitle_language"` + QaUser bool `json:"qa_user"` + Username string `json:"username"` +} diff --git a/crunchyroll.go b/crunchyroll.go index aefce1a..4e5a436 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -30,15 +30,6 @@ const ( AR = "ar-SA" ) -// SortOrder represents a sort order. -type SortOrder string - -const ( - POPULARITY SortOrder = "popularity" - NEWLYADDED = "newly_added" - ALPHABETICAL = "alphabetical" -) - // MediaType represents a media type. type MediaType string @@ -47,6 +38,15 @@ const ( MOVIELISTING = "movie_listing" ) +// SortType represents a sort type. +type SortType string + +const ( + POPULARITY SortType = "popularity" + NEWLYADDED = "newly_added" + ALPHABETICAL = "alphabetical" +) + type Crunchyroll struct { // Client is the http.Client to perform all requests over. Client *http.Client @@ -79,25 +79,25 @@ type Crunchyroll struct { // BrowseOptions represents options for browsing the crunchyroll catalog. type BrowseOptions struct { - // Categories specifies the categories of the results. + // Categories specifies the categories of the entries. Categories []string `param:"categories"` - // IsDubbed specifies whether the results should be dubbed. + // IsDubbed specifies whether the entries should be dubbed. IsDubbed bool `param:"is_dubbed"` - // IsSubbed specifies whether the results should be subbed. + // IsSubbed specifies whether the entries should be subbed. IsSubbed bool `param:"is_subbed"` - // SimulcastID specifies a particular simulcast season in which the results have been aired. - SimulcastID string `param:"season_tag"` + // Simulcast specifies a particular simulcast season by id in which the entries have been aired. + Simulcast string `param:"season_tag"` - // SortBy specifies how the results should be sorted. - SortBy SortOrder `param:"sort_by"` + // Sort specifies how the entries should be sorted. + Sort SortType `param:"sort_by"` - // Start specifies the index from which the results should be returned. + // Start specifies the index from which the entries should be returned. Start uint `param:"start"` - // Type specifies the media type of the results. + // Type specifies the media type of the entries. Type MediaType `param:"type"` } @@ -672,7 +672,7 @@ func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err return s, m, nil } -// UpNext returns the episodes that are up next based on your account within the given limit. +// UpNext returns the episodes that are up next based on the currently logged in account within the given limit. func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", c.Config.AccountID, limit, c.Locale) @@ -703,7 +703,7 @@ func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { return e, nil } -// SimilarTo returns similar series and movies according to crunchyroll to the one specified by id within the given limits. +// SimilarTo returns similar series and movies according to crunchyroll to the one specified by id within the given limit. func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, err error) { similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", c.Config.AccountID, id, limit, c.Locale) @@ -747,7 +747,7 @@ func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, return s, m, nil } -// WatchHistory returns the history of watched episodes based on your account from the given page with the given size. +// WatchHistory returns the history of watched episodes based on the currently logged in account from the given page with the given size. func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, err error) { watchHistoryEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/watch-history/%s?page=%d&page_size=%d&locale=%s", c.Config.AccountID, page, size, c.Locale) @@ -784,3 +784,35 @@ func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, e return e, nil } + +// Account returns information about the currently logged in crunchyroll account. +func (c *Crunchyroll) Account() (*Account, error) { + resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'me' response: %w", err) + } + + resp, err = c.request("https://beta.crunchyroll.com/accounts/v1/me/profile") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) + } + + account := &Account{} + + if err := decodeMapToStruct(jsonBody, account); err != nil { + return nil, err + } + + return account, nil +} From acc6c63ebd3f65e2e325018f20217e4433e7982b Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Sun, 29 May 2022 01:42:24 +0200 Subject: [PATCH 089/630] Fix comment --- crunchyroll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchyroll.go b/crunchyroll.go index 4e5a436..5ee3a97 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -628,7 +628,7 @@ func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*New return t, l, nil } -// Recommendations returns series and movie recommendations from crunchyroll based on your account within the given limit. +// Recommendations returns series and movie recommendations from crunchyroll based on the currently logged in account within the given limit. func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err error) { recommendationsEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/recommendations?n=%d&locale=%s", c.Config.AccountID, limit, c.Locale) From b53256ca3fde95e834c4019b37eb230f7881713e Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sun, 29 May 2022 10:10:48 +0200 Subject: [PATCH 090/630] Use Account struct directly instead of maps --- crunchyroll.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 5ee3a97..d20dd20 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -793,8 +793,9 @@ func (c *Crunchyroll) Account() (*Account, error) { } defer resp.Body.Close() - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + account := &Account{} + + if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { return nil, fmt.Errorf("failed to parse 'me' response: %w", err) } @@ -804,15 +805,9 @@ func (c *Crunchyroll) Account() (*Account, error) { } defer resp.Body.Close() - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) } - account := &Account{} - - if err := decodeMapToStruct(jsonBody, account); err != nil { - return nil, err - } - return account, nil } From 29343d1c6f8326332f4bba57c49de543fd63c2ae Mon Sep 17 00:00:00 2001 From: IchBinLeoon <50367411+IchBinLeoon@users.noreply.github.com> Date: Sun, 29 May 2022 15:08:38 +0200 Subject: [PATCH 091/630] Add requested changes --- account.go | 10 ++++++---- episode.go | 3 +-- utils.go | 27 +++++++++++++-------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/account.go b/account.go index f6f87f1..d22eba5 100644 --- a/account.go +++ b/account.go @@ -1,11 +1,13 @@ package crunchyroll +import "time" + // Account contains information about a crunchyroll account. type Account struct { - AccountID string `json:"account_id"` - ExternalID string `json:"external_id"` - EmailVerified bool `json:"email_verified"` - Created string `json:"created"` + AccountID string `json:"account_id"` + ExternalID string `json:"external_id"` + EmailVerified bool `json:"email_verified"` + Created time.Time `json:"created"` Avatar string `json:"avatar"` CrBetaOptIn bool `json:"cr_beta_opt_in"` diff --git a/episode.go b/episode.go index 07f6705..de38c65 100644 --- a/episode.go +++ b/episode.go @@ -79,8 +79,7 @@ type Episode struct { type HistoryEpisode struct { *Episode - ID string `json:"id"` - DatePlayed string `json:"date_played"` + DatePlayed time.Time `json:"date_played"` ParentID string `json:"parent_id"` ParentType MediaType `json:"parent_type"` Playhead uint `json:"playhead"` diff --git a/utils.go b/utils.go index 672772f..d32d39b 100644 --- a/utils.go +++ b/utils.go @@ -35,8 +35,19 @@ func encodeStructToQueryValues(s interface{}) (string, error) { for i := 0; i < v.Type().NumField(); i++ { // don't include parameters with default or without values in the query to avoid corruption of the API response. - if isEmptyValue(v.Field(i)) { - continue + switch v.Field(i).Kind() { + case reflect.Slice, reflect.String: + if v.Field(i).Len() == 0 { + continue + } + case reflect.Bool: + if !v.Field(i).Bool() { + continue + } + case reflect.Uint: + if v.Field(i).Uint() == 0 { + continue + } } key := v.Type().Field(i).Tag.Get("param") @@ -59,15 +70,3 @@ func encodeStructToQueryValues(s interface{}) (string, error) { return values.Encode(), nil } - -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Uint: - return v.Uint() == 0 - } - return false -} From 6581a5bd0f14f0601c2e4b84478bad84df74998d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 29 May 2022 16:10:37 +0200 Subject: [PATCH 092/630] Fix typo --- cmd/crunchyroll-go/cmd/utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index aa98399..0a89921 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -148,9 +148,9 @@ func loadCrunchy() { os.Exit(1) } if crunchy, err = crunchyroll.LoginWithEtpRt(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { - out.Debug("Failed to login with temp etp rt: %w", err) + out.Debug("Failed to login with temp etp rt cookie: %w", err) } else { - out.Debug("Logged in with etp rt %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) + out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) out.StopProgress("Logged in") return @@ -172,7 +172,7 @@ func loadCrunchy() { out.StopProgress(err.Error()) os.Exit(1) } - out.Debug("Logged in with etp rt %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) } else { if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) From 2471042d02b743e9925493e2b69e78c237a70ecb Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 30 May 2022 08:56:30 +0200 Subject: [PATCH 093/630] Change error formatter for non Errorf --- cmd/crunchyroll-go/cmd/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 0a89921..5c4a36a 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -148,7 +148,7 @@ func loadCrunchy() { os.Exit(1) } if crunchy, err = crunchyroll.LoginWithEtpRt(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { - out.Debug("Failed to login with temp etp rt cookie: %w", err) + out.Debug("Failed to login with temp etp rt cookie: %v", err) } else { out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) From 1c37c3e699e033f2eedf41ca8326c125ef826319 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 30 May 2022 12:08:52 +0200 Subject: [PATCH 094/630] Add method parameter to internal request function --- crunchyroll.go | 12 ++++++------ episode.go | 3 ++- movie_listing.go | 5 +++-- season.go | 3 ++- stream.go | 3 ++- video.go | 9 +++++---- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 06a28ec..bdffa27 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -185,7 +185,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt var jsonBody map[string]any endpoint := "https://beta-api.crunchyroll.com/index/v2" - resp, err := crunchy.request(endpoint) + resp, err := crunchy.request(endpoint, http.MethodGet) if err != nil { return nil, err } @@ -205,7 +205,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt crunchy.Config.KeyPairID = cms["key_pair_id"].(string) endpoint = "https://beta-api.crunchyroll.com/accounts/v1/me" - resp, err = crunchy.request(endpoint) + resp, err = crunchy.request(endpoint, http.MethodGet) if err != nil { return nil, err } @@ -214,7 +214,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt crunchy.Config.ExternalID = jsonBody["external_id"].(string) endpoint = "https://beta-api.crunchyroll.com/accounts/v1/me/profile" - resp, err = crunchy.request(endpoint) + resp, err = crunchy.request(endpoint, http.MethodGet) if err != nil { return nil, err } @@ -264,8 +264,8 @@ func request(req *http.Request, client *http.Client) (*http.Response, error) { } // request is a base function which handles api requests. -func (c *Crunchyroll) request(endpoint string) (*http.Response, error) { - req, err := http.NewRequest(http.MethodGet, endpoint, nil) +func (c *Crunchyroll) request(endpoint string, method string) (*http.Response, error) { + req, err := http.NewRequest(method, endpoint, nil) if err != nil { return nil, err } @@ -292,7 +292,7 @@ func (c *Crunchyroll) SetCaching(caching bool) { func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) { searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s", query, limit, c.Locale) - resp, err := c.request(searchEndpoint) + resp, err := c.request(searchEndpoint, http.MethodGet) if err != nil { return nil, nil, err } diff --git a/episode.go b/episode.go index a25844c..e9dd16a 100644 --- a/episode.go +++ b/episode.go @@ -3,6 +3,7 @@ package crunchyroll import ( "encoding/json" "fmt" + "net/http" "regexp" "strconv" "strings" @@ -85,7 +86,7 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { crunchy.Locale, crunchy.Config.Signature, crunchy.Config.Policy, - crunchy.Config.KeyPairID)) + crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } diff --git a/movie_listing.go b/movie_listing.go index 63d7fab..110f67a 100644 --- a/movie_listing.go +++ b/movie_listing.go @@ -3,6 +3,7 @@ package crunchyroll import ( "encoding/json" "fmt" + "net/http" ) // MovieListing contains information about something which is called @@ -48,7 +49,7 @@ func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) crunchy.Locale, crunchy.Config.Signature, crunchy.Config.Policy, - crunchy.Config.KeyPairID)) + crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } @@ -77,7 +78,7 @@ func (ml *MovieListing) AudioLocale() (LOCALE, error) { ml.crunchy.Locale, ml.crunchy.Config.Signature, ml.crunchy.Config.Policy, - ml.crunchy.Config.KeyPairID)) + ml.crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return "", err } diff --git a/season.go b/season.go index 825a816..f5513f5 100644 --- a/season.go +++ b/season.go @@ -3,6 +3,7 @@ package crunchyroll import ( "encoding/json" "fmt" + "net/http" "regexp" ) @@ -94,7 +95,7 @@ func (s *Season) Episodes() (episodes []*Episode, err error) { s.crunchy.Locale, s.crunchy.Config.Signature, s.crunchy.Config.Policy, - s.crunchy.Config.KeyPairID)) + s.crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } diff --git a/stream.go b/stream.go index 7505a24..00060ef 100644 --- a/stream.go +++ b/stream.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "github.com/grafov/m3u8" + "net/http" "regexp" ) @@ -72,7 +73,7 @@ func (s *Stream) Formats() ([]*Format, error) { // fromVideoStreams returns all streams which are accessible via the endpoint. func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, err error) { - resp, err := crunchy.request(endpoint) + resp, err := crunchy.request(endpoint, http.MethodGet) if err != nil { return nil, err } diff --git a/video.go b/video.go index 00b7734..365f954 100644 --- a/video.go +++ b/video.go @@ -3,6 +3,7 @@ package crunchyroll import ( "encoding/json" "fmt" + "net/http" ) type video struct { @@ -77,7 +78,7 @@ func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) { crunchy.Locale, crunchy.Config.Signature, crunchy.Config.Policy, - crunchy.Config.KeyPairID)) + crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } @@ -110,7 +111,7 @@ func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) { m.crunchy.Locale, m.crunchy.Config.Signature, m.crunchy.Config.Policy, - m.crunchy.Config.KeyPairID)) + m.crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } @@ -173,7 +174,7 @@ func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { crunchy.Locale, crunchy.Config.Signature, crunchy.Config.Policy, - crunchy.Config.KeyPairID)) + crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } @@ -206,7 +207,7 @@ func (s *Series) Seasons() (seasons []*Season, err error) { s.crunchy.Locale, s.crunchy.Config.Signature, s.crunchy.Config.Policy, - s.crunchy.Config.KeyPairID)) + s.crunchy.Config.KeyPairID), http.MethodGet) if err != nil { return nil, err } From 3a7ec02598ea1f857b51a51307b5036a0742db0c Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 30 May 2022 12:24:54 +0200 Subject: [PATCH 095/630] Resolve merge conflicts --- crunchyroll.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 268562d..e5c4a9d 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -169,15 +169,13 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* return nil, fmt.Errorf("failed to start session: %s", resp.Status) } + var jsonBody map[string]any if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) } if isError, ok := jsonBody["error"]; ok && isError.(bool) { return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"]) } - data := jsonBody["data"].(map[string]interface{}) - - crunchy.Config.CountryCode = data["country_code"].(string) var etpRt string for _, cookie := range resp.Cookies() { @@ -243,6 +241,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt } defer resp.Body.Close() json.NewDecoder(resp.Body).Decode(&jsonBody) + cms := jsonBody["cms"].(map[string]any) crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") if strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") { @@ -261,7 +260,6 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt crunchy.Config.Channel = "-" } - cms := jsonBody["cms"].(map[string]interface{}) crunchy.Config.Policy = cms["policy"].(string) crunchy.Config.Signature = cms["signature"].(string) crunchy.Config.KeyPairID = cms["key_pair_id"].(string) @@ -526,7 +524,7 @@ func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m browseEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/browse?%s&n=%d&locale=%s", query, limit, c.Locale) - resp, err := c.request(browseEndpoint) + resp, err := c.request(browseEndpoint, http.MethodGet) if err != nil { return nil, nil, err } @@ -570,7 +568,7 @@ func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err error) { tenantCategoriesEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/tenant_categories?include_subcategories=%t&locale=%s", includeSubcategories, c.Locale) - resp, err := c.request(tenantCategoriesEndpoint) + resp, err := c.request(tenantCategoriesEndpoin, http.MethodGet) if err != nil { return nil, err } @@ -623,7 +621,7 @@ func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*News, err error) { newsFeedEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/news_feed?top_news_n=%d&latest_news_n=%d&locale=%s", topLimit, latestLimit, c.Locale) - resp, err := c.request(newsFeedEndpoint) + resp, err := c.request(newsFeedEndpoint, http.MethodGet) if err != nil { return nil, nil, err } From 35b1cbbdb42328d848fbef5ff3d6f157f9923297 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 30 May 2022 12:27:20 +0200 Subject: [PATCH 096/630] Fix typo --- crunchyroll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchyroll.go b/crunchyroll.go index e5c4a9d..5154c9c 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -568,7 +568,7 @@ func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err error) { tenantCategoriesEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/tenant_categories?include_subcategories=%t&locale=%s", includeSubcategories, c.Locale) - resp, err := c.request(tenantCategoriesEndpoin, http.MethodGet) + resp, err := c.request(tenantCategoriesEndpoint, http.MethodGet) if err != nil { return nil, err } From 38fe521d55a8377697c03e86490443688fb9295d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 30 May 2022 12:32:13 +0200 Subject: [PATCH 097/630] Resolve more merge conflicts which GitHub didn't save lul --- crunchyroll.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 5154c9c..38b889b 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -241,7 +241,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt } defer resp.Body.Close() json.NewDecoder(resp.Body).Decode(&jsonBody) - + cms := jsonBody["cms"].(map[string]any) crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") if strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") { @@ -259,7 +259,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt crunchy.Config.Premium = false crunchy.Config.Channel = "-" } - + crunchy.Config.Policy = cms["policy"].(string) crunchy.Config.Signature = cms["signature"].(string) crunchy.Config.KeyPairID = cms["key_pair_id"].(string) @@ -594,7 +594,7 @@ func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err // Simulcasts returns all available simulcast seasons for the current locale. func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { seasonListEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/season_list?locale=%s", c.Locale) - resp, err := c.request(seasonListEndpoint) + resp, err := c.request(seasonListEndpoint, http.MethodGet) if err != nil { return nil, err } @@ -659,7 +659,7 @@ func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*New func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err error) { recommendationsEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/recommendations?n=%d&locale=%s", c.Config.AccountID, limit, c.Locale) - resp, err := c.request(recommendationsEndpoint) + resp, err := c.request(recommendationsEndpoint, http.MethodGet) if err != nil { return nil, nil, err } @@ -703,7 +703,7 @@ func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", c.Config.AccountID, limit, c.Locale) - resp, err := c.request(upNextAccountEndpoint) + resp, err := c.request(upNextAccountEndpoint, http.MethodGet) if err != nil { return nil, err } @@ -734,7 +734,7 @@ func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, err error) { similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", c.Config.AccountID, id, limit, c.Locale) - resp, err := c.request(similarToEndpoint) + resp, err := c.request(similarToEndpoint, http.MethodGet) if err != nil { return nil, nil, err } @@ -778,7 +778,7 @@ func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, err error) { watchHistoryEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/watch-history/%s?page=%d&page_size=%d&locale=%s", c.Config.AccountID, page, size, c.Locale) - resp, err := c.request(watchHistoryEndpoint) + resp, err := c.request(watchHistoryEndpoint, http.MethodGet) if err != nil { return nil, err } @@ -814,7 +814,7 @@ func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, e // Account returns information about the currently logged in crunchyroll account. func (c *Crunchyroll) Account() (*Account, error) { - resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me") + resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me", http.MethodGet) if err != nil { return nil, err } @@ -826,7 +826,7 @@ func (c *Crunchyroll) Account() (*Account, error) { return nil, fmt.Errorf("failed to parse 'me' response: %w", err) } - resp, err = c.request("https://beta.crunchyroll.com/accounts/v1/me/profile") + resp, err = c.request("https://beta.crunchyroll.com/accounts/v1/me/profile", http.MethodGet) if err != nil { return nil, err } From b9ff56c111e3a5aa1cfa7f730d04a173e3524210 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 31 May 2022 17:01:03 +0200 Subject: [PATCH 098/630] Extend encrypt flag description --- cmd/crunchyroll-go/cmd/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index 565dac0..1c47340 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -43,7 +43,7 @@ func init() { loginCmd.Flags().BoolVar(&loginEncryptFlag, "encrypt", false, - "Encrypt the given credentials (won't do anything if --session-id is given)") + "Encrypt the given credentials (won't do anything if --session-id is given or --persistent is not given)") loginCmd.Flags().BoolVar(&loginSessionIDFlag, "session-id", From ce29e31164ba25396cbcbb3b619723e5ffa283d4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 31 May 2022 17:04:39 +0200 Subject: [PATCH 099/630] Add encrypt flag notice when use login without it --- cmd/crunchyroll-go/cmd/login.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index 1c47340..d31179e 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -124,7 +124,8 @@ func loginCredentials(user, password string) error { return err } if !loginEncryptFlag { - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s). "+ + "To encrypt it, use the `--encrypt` flag", filepath.Join(configDir, "crunchyroll-go", "crunchy")) } } } From 51b5e7b2ffba9bfd62f736441ab1b5eb8218570c Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 6 Jun 2022 21:23:29 +0200 Subject: [PATCH 100/630] Add bug issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..3cbda16 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Bug target** +Set a `x` between the square brackets if your bug occurred while using the CLI or the library + +- [ ] CLI (command line client) +- [ ] Library + +**To Reproduce** +Steps to reproduce the behavior: +``` +$ crunchy ... +``` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Client (please complete the following information):** + - OS: [e.g. Windows] + - Version [e.g. v2.2.2] + +**Additional context** +Add any other context about the problem here. From c963af1f11c2585494372243d31a7c508c466247 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Wed, 8 Jun 2022 14:10:46 +0200 Subject: [PATCH 101/630] Why github --- cmd/crunchyroll-go/cmd/utils.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 7fe498f..dd1254c 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -170,7 +170,6 @@ func loadCrunchy() { } split := strings.SplitN(string(body), "\n", 2) if len(split) == 1 || split[1] == "" { -<<<<<<< v3/feature/encrypted-credentials if strings.HasPrefix(split[0], "aes:") { encrypted := body[4:] @@ -214,7 +213,6 @@ func loadCrunchy() { } if len(split) == 2 { -======= split[0] = url.QueryEscape(split[0]) if crunchy, err = crunchyroll.LoginWithEtpRt(split[0], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) @@ -222,7 +220,6 @@ func loadCrunchy() { } out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) } else { ->>>>>>> next/v3 if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) os.Exit(1) From 735136077eeb1cbd916d5c95627898b90525237e Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Wed, 8 Jun 2022 14:12:30 +0200 Subject: [PATCH 102/630] I removed it but ok github if you want i do it a second time, no problem --- cmd/crunchyroll-go/cmd/login.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index bfef794..0c00100 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -135,13 +135,10 @@ func loginCredentials(user, password string) error { "To encrypt it, use the `--encrypt` flag", filepath.Join(configDir, "crunchyroll-go", "crunchy")) } } - } -<<<<<<< v3/feature/encrypted-credentials -======= + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { return err } ->>>>>>> next/v3 if !loginPersistentFlag { out.Info("Due to security reasons, you have to login again on the next reboot") From 69d2e10362929c415ecc050ac73535eedc7defdf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 8 Jun 2022 14:18:20 +0200 Subject: [PATCH 103/630] I fucking hate it --- cmd/crunchyroll-go/cmd/login.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index 0c00100..d8cc5c3 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -67,10 +67,6 @@ func loginCredentials(user, password string) error { return err } - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.SessionID), 0600); err != nil { - return err - } - if loginPersistentFlag { if configDir, err := os.UserConfigDir(); err != nil { return fmt.Errorf("could not save credentials persistent: %w", err) @@ -135,7 +131,8 @@ func loginCredentials(user, password string) error { "To encrypt it, use the `--encrypt` flag", filepath.Join(configDir, "crunchyroll-go", "crunchy")) } } - + } + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { return err } From dcdde6749e971aa0dbc1ee104d9fbf123883c217 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 9 Jun 2022 09:56:32 +0200 Subject: [PATCH 104/630] Add info command --- cmd/crunchyroll-go/cmd/info.go | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 cmd/crunchyroll-go/cmd/info.go diff --git a/cmd/crunchyroll-go/cmd/info.go b/cmd/crunchyroll-go/cmd/info.go new file mode 100644 index 0000000..f5ed995 --- /dev/null +++ b/cmd/crunchyroll-go/cmd/info.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + "github.com/ByteDream/crunchyroll-go/v3/utils" + "github.com/spf13/cobra" +) + +var infoCmd = &cobra.Command{ + Use: "info", + Short: "Shows information about the logged in user", + Args: cobra.MinimumNArgs(0), + + RunE: func(cmd *cobra.Command, args []string) error { + loadCrunchy() + + return info() + }, +} + +func init() { + rootCmd.AddCommand(infoCmd) +} + +func info() error { + account, err := crunchy.Account() + if err != nil { + return err + } + + fmt.Println("Username: ", account.Username) + fmt.Println("Email: ", account.Email) + fmt.Println("Premium: ", crunchy.Config.Premium) + fmt.Println("Interface language:", utils.LocaleLanguage(account.PreferredCommunicationLanguage)) + fmt.Println("Subtitle language: ", utils.LocaleLanguage(account.PreferredContentSubtitleLanguage)) + fmt.Println("Created: ", account.Created) + fmt.Println("Account ID: ", account.AccountID) + + return nil +} From 31d3065e7bcc30ac6693254be4ae4a68570894ce Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 9 Jun 2022 10:31:16 +0200 Subject: [PATCH 105/630] Fix weird code which was probably caused by a wonderful merge --- cmd/crunchyroll-go/cmd/utils.go | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index dd1254c..12d3d4e 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -150,7 +150,7 @@ func loadCrunchy() { out.StopProgress("Failed to read login information: %v", err) os.Exit(1) } - if crunchy, err = crunchyroll.LoginWithEtpRt(url.QueryEscape(string(body)), systemLocale(true), client); err != nil { + if crunchy, err = crunchyroll.LoginWithEtpRt(string(body), systemLocale(true), client); err != nil { out.Debug("Failed to login with temp etp rt cookie: %v", err) } else { out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) @@ -202,34 +202,27 @@ func loadCrunchy() { os.Exit(1) } split = strings.SplitN(string(b), "\n", 2) - } else { - split[0] = url.QueryEscape(split[0]) - if crunchy, err = crunchyroll.LoginWithSessionID(split[0], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) - } - out.Debug("Logged in with session id %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) } } if len(split) == 2 { - split[0] = url.QueryEscape(split[0]) + if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { + out.StopProgress(err.Error()) + os.Exit(1) + } + out.Debug("Logged in with credentials") + } else { if crunchy, err = crunchyroll.LoginWithEtpRt(split[0], systemLocale(true), client); err != nil { out.StopProgress(err.Error()) os.Exit(1) } out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) - } else { - if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) - } - out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", crunchy.EtpRt) - // the etp rt is written to a temp file to reduce the amount of re-logging in. - // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time - os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600) } + // the etp rt is written to a temp file to reduce the amount of re-logging in. + // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time + os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600) + out.StopProgress("Logged in") return } From 137f3779ea2e2182ab1c15ed35c89fd931587732 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 10 Jun 2022 16:12:58 +0200 Subject: [PATCH 106/630] Fix merge issues again, I love this shit --- crunchyroll.go | 41 +++++++++++++---------------------------- stream.go | 2 +- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 73cba57..be5af03 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -110,6 +111,9 @@ type loginResponse struct { Scope string `json:"scope"` Country string `json:"country"` AccountID string `json:"account_id"` + + Error bool `json:"error"` + Message string `json:"message"` } // LoginWithCredentials logs in via crunchyroll username or email and password. @@ -133,16 +137,13 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h } defer resp.Body.Close() - if loginResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to auth with credentials: %s", loginResp.Status) - } else { - var loginRespBody map[string]interface{} - json.NewDecoder(loginResp.Body).Decode(&loginRespBody) - - if loginRespBody["error"].(bool) { - return nil, fmt.Errorf("an unexpected login error occoured: %s", loginRespBody["message"]) - } - } + var loginResp loginResponse + json.NewDecoder(resp.Body).Decode(&loginResp) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to auth with credentials: %s", resp.Status) + } else if loginResp.Error { + return nil, fmt.Errorf("an unexpected login error occoured: %s", loginResp.Message) + } var etpRt string for _, cookie := range resp.Cookies() { @@ -180,18 +181,16 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (* if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) } - + if isError, ok := jsonBody["error"]; ok && isError.(bool) { return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"]) } data := jsonBody["data"].(map[string]interface{}) - crunchy.Config.CountryCode = data["country_code"].(string) user := data["user"] if user == nil { return nil, errors.New("invalid session id, user is not logged in") } - crunchy.Config.Premium = user.(map[string]interface{})["premium"] != "" var etpRt string for _, cookie := range resp.Cookies() { @@ -260,21 +259,7 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt cms := jsonBody["cms"].(map[string]any) crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") - if strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") { - crunchy.Config.Premium = true - crunchy.Config.Channel = "crunchyroll" - } else { - crunchy.Config.Premium = false - crunchy.Config.Channel = "-" - } - - if strings.Contains(cms["bucket"].(string), "crunchyroll") { - crunchy.Config.Premium = true - crunchy.Config.Channel = "crunchyroll" - } else { - crunchy.Config.Premium = false - crunchy.Config.Channel = "-" - } + crunchy.Config.Premium = strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") // / is trimmed so that urls which require it must be in .../{bucket}/... like format. // this just looks cleaner diff --git a/stream.go b/stream.go index 042d5f7..e5ce54f 100644 --- a/stream.go +++ b/stream.go @@ -84,7 +84,7 @@ func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, if !crunchy.Config.Premium { return nil, fmt.Errorf("no stream available, this might be the result of using a non-premium account") } else { - return nil, errors.New("no stream available") + return nil, fmt.Errorf("no stream available") } } From 8d69b7775b621eb65957b489b524439539b0ac7b Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sun, 12 Jun 2022 14:23:13 +0200 Subject: [PATCH 107/630] Add name poll notice --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b8d48fa..10d6c99 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http <a href="#-license">License โš–</a> </p> +_There is currently a [poll](https://github.com/ByteDream/crunchyroll-go/issues/39) going on how to name the CLI in the next version. Please have a moment and participate to it._ + # ๐Ÿ–ฅ๏ธ CLI ## โœจ Features From c5f2b55f346d2e45a6cf09ffa8750c915a180970 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 00:22:38 +0200 Subject: [PATCH 108/630] Add watchlist endpoint, add new request method & change SortType name and consts --- crunchyroll.go | 93 +++++++++++++++++++++++++++++++++++++++++++---- episode.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++---- video.go | 21 +++++++++++ 3 files changed, 199 insertions(+), 12 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 38b889b..7618fdf 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -38,13 +38,28 @@ const ( MOVIELISTING = "movie_listing" ) -// SortType represents a sort type. -type SortType string +// BrowseSortType represents a sort type to sort Crunchyroll.Browse items after. +type BrowseSortType string const ( - POPULARITY SortType = "popularity" - NEWLYADDED = "newly_added" - ALPHABETICAL = "alphabetical" + BROWSESORTPOPULARITY BrowseSortType = "popularity" + BROWSESORTNEWLYADDED = "newly_added" + BROWSESORTALPHABETICAL = "alphabetical" +) + +// WatchlistLanguageType represents a filter type to filter Crunchyroll.WatchList entries after. +type WatchlistLanguageType int + +const ( + WATCHLISTLANGUAGESUBBED WatchlistLanguageType = iota + 1 + WATCHLISTLANGUAGEDUBBED +) + +type WatchlistContentType string + +const ( + WATCHLISTCONTENTSERIES WatchlistContentType = "series" + WATCHLISTCONTENTMOVIES = "movie_listing" ) type Crunchyroll struct { @@ -95,7 +110,7 @@ type BrowseOptions struct { Simulcast string `param:"season_tag"` // Sort specifies how the entries should be sorted. - Sort SortType `param:"sort_by"` + Sort BrowseSortType `param:"sort_by"` // Start specifies the index from which the entries should be returned. Start uint `param:"start"` @@ -329,6 +344,10 @@ func (c *Crunchyroll) request(endpoint string, method string) (*http.Response, e if err != nil { return nil, err } + return c.requestFull(req) +} + +func (c *Crunchyroll) requestFull(req *http.Request) (*http.Response, error) { req.Header.Add("Authorization", fmt.Sprintf("%s %s", c.Config.TokenType, c.Config.AccessToken)) return request(req, c.Client) @@ -812,6 +831,68 @@ func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, e return e, nil } +type WatchlistOptions struct { + // OrderAsc specified whether the results should be order ascending or descending. + OrderAsc bool + + // OnlyFavorites specifies whether only episodes which are marked as favorite should be returned. + OnlyFavorites bool + + // LanguageType specifies whether returning episodes should be only subbed or dubbed. + LanguageType WatchlistLanguageType + + // ContentType specified whether returning videos should only be series episodes or movies. + // But tbh all movies I've searched on crunchy were flagged as series too, so this + // parameter is kinda useless. + ContentType WatchlistContentType +} + +func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*WatchlistEntry, error) { + values := url.Values{} + if options.OrderAsc { + values.Set("order", "asc") + } else { + values.Set("order", "desc") + } + if options.OnlyFavorites { + values.Set("only_favorites", "true") + } + switch options.LanguageType { + case WATCHLISTLANGUAGESUBBED: + values.Set("is_subbed", "true") + case WATCHLISTLANGUAGEDUBBED: + values.Set("is_dubbed", "true") + } + values.Set("n", strconv.Itoa(int(limit))) + values.Set("locale", string(c.Locale)) + + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/%s/watchlist?%s", c.Config.AccountID, values.Encode()) + resp, err := c.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + json.NewDecoder(resp.Body).Decode(&jsonBody) + + var watchlistEntries []*WatchlistEntry + if err := decodeMapToStruct(jsonBody["items"], &watchlistEntries); err != nil { + return nil, err + } + + for _, entry := range watchlistEntries { + switch entry.Panel.Type { + case WATCHLISTENTRYEPISODE: + entry.Panel.EpisodeMetadata.crunchy = c + case WATCHLISTENTRYSERIES: + entry.Panel.SeriesMetadata.crunchy = c + } + } + + return watchlistEntries, nil +} + // Account returns information about the currently logged in crunchyroll account. func (c *Crunchyroll) Account() (*Account, error) { resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me", http.MethodGet) diff --git a/episode.go b/episode.go index 0405e0e..e99bd12 100644 --- a/episode.go +++ b/episode.go @@ -1,6 +1,7 @@ package crunchyroll import ( + "bytes" "encoding/json" "fmt" "net/http" @@ -39,11 +40,14 @@ type Episode struct { NextEpisodeID string `json:"next_episode_id"` NextEpisodeTitle string `json:"next_episode_title"` - HDFlag bool `json:"hd_flag"` - IsMature bool `json:"is_mature"` - MatureBlocked bool `json:"mature_blocked"` + HDFlag bool `json:"hd_flag"` + MaturityRatings []string `json:"maturity_ratings"` + IsMature bool `json:"is_mature"` + MatureBlocked bool `json:"mature_blocked"` - EpisodeAirDate time.Time `json:"episode_air_date"` + EpisodeAirDate time.Time `json:"episode_air_date"` + FreeAvailableDate time.Time `json:"free_available_date"` + PremiumAvailableDate time.Time `json:"premium_available_date"` IsSubbed bool `json:"is_subbed"` IsDubbed bool `json:"is_dubbed"` @@ -52,8 +56,9 @@ type Episode struct { SeoDescription string `json:"seo_description"` SeasonTags []string `json:"season_tags"` - AvailableOffline bool `json:"available_offline"` - Slug string `json:"slug"` + AvailableOffline bool `json:"available_offline"` + MediaType MediaType `json:"media_type"` + Slug string `json:"slug"` Images struct { Thumbnail [][]struct { @@ -87,6 +92,62 @@ type HistoryEpisode struct { FullyWatched bool `json:"fully_watched"` } +// WatchlistEntryType specifies which type a watchlist entry has. +type WatchlistEntryType string + +const ( + WATCHLISTENTRYEPISODE = "episode" + WATCHLISTENTRYSERIES = "series" +) + +// WatchlistEntry contains information about an entry on the watchlist. +type WatchlistEntry struct { + Panel struct { + Title string `json:"title"` + PromoTitle string `json:"promo_title"` + Slug string `json:"slug"` + Playback string `json:"playback"` + PromoDescription string `json:"promo_description"` + Images struct { + Thumbnail [][]struct { + Height int `json:"height"` + Source string `json:"source"` + Type string `json:"type"` + Width int `json:"width"` + } `json:"thumbnail"` + PosterTall [][]struct { + Width int `json:"width"` + Height int `json:"height"` + Type string `json:"type"` + Source string `json:"source"` + } `json:"poster_tall"` + PosterWide [][]struct { + Width int `json:"width"` + Height int `json:"height"` + Type string `json:"type"` + Source string `json:"source"` + } `json:"poster_wide"` + } `json:"images"` + ID string `json:"id"` + Description string `json:"description"` + ChannelID string `json:"channel_id"` + Type WatchlistEntryType `json:"type"` + ExternalID string `json:"external_id"` + SlugTitle string `json:"slug_title"` + // not null if Type is WATCHLISTENTRYEPISODE + EpisodeMetadata *Episode `json:"episode_metadata"` + // not null if Type is WATCHLISTENTRYSERIES + SeriesMetadata *Series `json:"series_metadata"` + } + + New bool `json:"new"` + NewContent bool `json:"new_content"` + IsFavorite bool `json:"is_favorite"` + NeverWatched bool `json:"never_watched"` + CompleteStatus bool `json:"complete_status"` + Playahead uint `json:"playahead"` +} + // EpisodeFromID returns an episode by its api id. func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", @@ -122,6 +183,30 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { return episode, nil } +// AddToWatchlist adds the current episode to the watchlist. +// There is currently a bug, or as I like to say in context of the crunchyroll api, feature, +// that only series and not individual episode can be added to the watchlist. Even though +// I somehow got an episode to my watchlist on the crunchyroll website, it never worked with the +// api here. So this function actually adds the whole series to the watchlist. +func (e *Episode) AddToWatchlist() error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s?locale=%s", e.crunchy.Config.AccountID, e.crunchy.Locale) + body, _ := json.Marshal(map[string]string{"content_id": e.SeriesID}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = e.crunchy.requestFull(req) + return err +} + +// RemoveFromWatchlist removes the current episode from the watchlist. +func (e *Episode) RemoveFromWatchlist() error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s/%s?locale=%s", e.crunchy.Config.AccountID, e.SeriesID, e.crunchy.Locale) + _, err := e.crunchy.request(endpoint, http.MethodDelete) + return err +} + // AudioLocale returns the audio locale of the episode. // Every episode in a season (should) have the same audio locale, // so if you want to get the audio locale of a season, just call diff --git a/video.go b/video.go index 365f954..42efacd 100644 --- a/video.go +++ b/video.go @@ -1,6 +1,7 @@ package crunchyroll import ( + "bytes" "encoding/json" "fmt" "net/http" @@ -193,6 +194,26 @@ func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { return series, nil } +// AddToWatchlist adds the current episode to the watchlist. +func (s *Series) AddToWatchlist() error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s?locale=%s", s.crunchy.Config.AccountID, s.crunchy.Locale) + body, _ := json.Marshal(map[string]string{"content_id": s.ID}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = s.crunchy.requestFull(req) + return err +} + +// RemoveFromWatchlist removes the current episode from the watchlist. +func (s *Series) RemoveFromWatchlist() error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s/%s?locale=%s", s.crunchy.Config.AccountID, s.ID, s.crunchy.Locale) + _, err := s.crunchy.request(endpoint, http.MethodDelete) + return err +} + // Seasons returns all seasons of a series. func (s *Series) Seasons() (seasons []*Season, err error) { if s.children != nil { From 5709012dfe5f625426ba28b99b52720a6ff32b8f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 00:36:27 +0200 Subject: [PATCH 109/630] Add custom error for internal request --- crunchyroll.go | 10 +++++----- episode.go | 9 +++++---- error.go | 17 +++++++++++++++++ video.go | 2 ++ 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 error.go diff --git a/crunchyroll.go b/crunchyroll.go index 7618fdf..4c29ab4 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -314,25 +314,25 @@ func request(req *http.Request, client *http.Client) (*http.Response, error) { var errMap map[string]any if err = json.Unmarshal(buf.Bytes(), &errMap); err != nil { - return nil, fmt.Errorf("invalid json response: %w", err) + return nil, &RequestError{Response: resp, Message: fmt.Sprintf("invalid json response: %w", err)} } if val, ok := errMap["error"]; ok { if errorAsString, ok := val.(string); ok { if code, ok := errMap["code"].(string); ok { - return nil, fmt.Errorf("error for endpoint %s (%d): %s - %s", req.URL.String(), resp.StatusCode, errorAsString, code) + return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", errorAsString, code)} } - return nil, fmt.Errorf("error for endpoint %s (%d): %s", req.URL.String(), resp.StatusCode, errorAsString) + return nil, &RequestError{Response: resp, Message: errorAsString} } else if errorAsBool, ok := val.(bool); ok && errorAsBool { if msg, ok := errMap["message"].(string); ok { - return nil, fmt.Errorf("error for endpoint %s (%d): %s", req.URL.String(), resp.StatusCode, msg) + return nil, &RequestError{Response: resp, Message: msg} } } } } if resp.StatusCode >= 400 { - return nil, fmt.Errorf("error for endpoint %s: %s", req.URL.String(), resp.Status) + return nil, &RequestError{Response: resp, Message: resp.Status} } } return resp, err diff --git a/episode.go b/episode.go index e99bd12..d0e9b14 100644 --- a/episode.go +++ b/episode.go @@ -184,10 +184,10 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { } // AddToWatchlist adds the current episode to the watchlist. -// There is currently a bug, or as I like to say in context of the crunchyroll api, feature, -// that only series and not individual episode can be added to the watchlist. Even though -// I somehow got an episode to my watchlist on the crunchyroll website, it never worked with the -// api here. So this function actually adds the whole series to the watchlist. +// Will return an RequestError with the response status code of 409 if the series was already on the watchlist before. +// There is currently a bug, or as I like to say in context of the crunchyroll api, feature, that only series and not +// individual episode can be added to the watchlist. Even though I somehow got an episode to my watchlist on the +// crunchyroll website, it never worked with the api here. So this function actually adds the whole series to the watchlist. func (e *Episode) AddToWatchlist() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s?locale=%s", e.crunchy.Config.AccountID, e.crunchy.Locale) body, _ := json.Marshal(map[string]string{"content_id": e.SeriesID}) @@ -201,6 +201,7 @@ func (e *Episode) AddToWatchlist() error { } // RemoveFromWatchlist removes the current episode from the watchlist. +// Will return an RequestError with the response status code of 404 if the series was not on the watchlist before. func (e *Episode) RemoveFromWatchlist() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s/%s?locale=%s", e.crunchy.Config.AccountID, e.SeriesID, e.crunchy.Locale) _, err := e.crunchy.request(endpoint, http.MethodDelete) diff --git a/error.go b/error.go new file mode 100644 index 0000000..b4f7329 --- /dev/null +++ b/error.go @@ -0,0 +1,17 @@ +package crunchyroll + +import ( + "fmt" + "net/http" +) + +type RequestError struct { + error + + Response *http.Response + Message string +} + +func (re *RequestError) String() string { + return fmt.Sprintf("error for endpoint %s (%d): %s", re.Response.Request.URL.String(), re.Response.StatusCode, re.Message) +} diff --git a/video.go b/video.go index 42efacd..3e7d7cf 100644 --- a/video.go +++ b/video.go @@ -195,6 +195,7 @@ func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { } // AddToWatchlist adds the current episode to the watchlist. +// Will return an RequestError with the response status code of 409 if the series was already on the watchlist before. func (s *Series) AddToWatchlist() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s?locale=%s", s.crunchy.Config.AccountID, s.crunchy.Locale) body, _ := json.Marshal(map[string]string{"content_id": s.ID}) @@ -208,6 +209,7 @@ func (s *Series) AddToWatchlist() error { } // RemoveFromWatchlist removes the current episode from the watchlist. +// Will return an RequestError with the response status code of 404 if the series was not on the watchlist before. func (s *Series) RemoveFromWatchlist() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s/%s?locale=%s", s.crunchy.Config.AccountID, s.ID, s.crunchy.Locale) _, err := s.crunchy.request(endpoint, http.MethodDelete) From f9792aa84781b893c9b589202caacd7325cdb347 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 00:54:30 +0200 Subject: [PATCH 110/630] Add extra file for common in different places used elements --- common.go | 39 +++++++++++++++++++++++++++++++++++++++ episode.go | 38 +------------------------------------- 2 files changed, 40 insertions(+), 37 deletions(-) create mode 100644 common.go diff --git a/common.go b/common.go new file mode 100644 index 0000000..91e1312 --- /dev/null +++ b/common.go @@ -0,0 +1,39 @@ +package crunchyroll + +type Panel struct { + Title string `json:"title"` + PromoTitle string `json:"promo_title"` + Slug string `json:"slug"` + Playback string `json:"playback"` + PromoDescription string `json:"promo_description"` + Images struct { + Thumbnail [][]struct { + Height int `json:"height"` + Source string `json:"source"` + Type string `json:"type"` + Width int `json:"width"` + } `json:"thumbnail"` + PosterTall [][]struct { + Width int `json:"width"` + Height int `json:"height"` + Type string `json:"type"` + Source string `json:"source"` + } `json:"poster_tall"` + PosterWide [][]struct { + Width int `json:"width"` + Height int `json:"height"` + Type string `json:"type"` + Source string `json:"source"` + } `json:"poster_wide"` + } `json:"images"` + ID string `json:"id"` + Description string `json:"description"` + ChannelID string `json:"channel_id"` + Type WatchlistEntryType `json:"type"` + ExternalID string `json:"external_id"` + SlugTitle string `json:"slug_title"` + // not null if Type is WATCHLISTENTRYEPISODE + EpisodeMetadata *Episode `json:"episode_metadata"` + // not null if Type is WATCHLISTENTRYSERIES + SeriesMetadata *Series `json:"series_metadata"` +} diff --git a/episode.go b/episode.go index d0e9b14..f97de31 100644 --- a/episode.go +++ b/episode.go @@ -102,43 +102,7 @@ const ( // WatchlistEntry contains information about an entry on the watchlist. type WatchlistEntry struct { - Panel struct { - Title string `json:"title"` - PromoTitle string `json:"promo_title"` - Slug string `json:"slug"` - Playback string `json:"playback"` - PromoDescription string `json:"promo_description"` - Images struct { - Thumbnail [][]struct { - Height int `json:"height"` - Source string `json:"source"` - Type string `json:"type"` - Width int `json:"width"` - } `json:"thumbnail"` - PosterTall [][]struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"poster_tall"` - PosterWide [][]struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"poster_wide"` - } `json:"images"` - ID string `json:"id"` - Description string `json:"description"` - ChannelID string `json:"channel_id"` - Type WatchlistEntryType `json:"type"` - ExternalID string `json:"external_id"` - SlugTitle string `json:"slug_title"` - // not null if Type is WATCHLISTENTRYEPISODE - EpisodeMetadata *Episode `json:"episode_metadata"` - // not null if Type is WATCHLISTENTRYSERIES - SeriesMetadata *Series `json:"series_metadata"` - } + Panel Panel `json:"panel"` New bool `json:"new"` NewContent bool `json:"new_content"` From 8ddb436fac31628aa016e29600fe0cef4f523f61 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 01:03:10 +0200 Subject: [PATCH 111/630] Move WatchlistEntry struct to own file --- episode.go | 12 ------------ watchlist.go | 13 +++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 watchlist.go diff --git a/episode.go b/episode.go index f97de31..7f48e93 100644 --- a/episode.go +++ b/episode.go @@ -100,18 +100,6 @@ const ( WATCHLISTENTRYSERIES = "series" ) -// WatchlistEntry contains information about an entry on the watchlist. -type WatchlistEntry struct { - Panel Panel `json:"panel"` - - New bool `json:"new"` - NewContent bool `json:"new_content"` - IsFavorite bool `json:"is_favorite"` - NeverWatched bool `json:"never_watched"` - CompleteStatus bool `json:"complete_status"` - Playahead uint `json:"playahead"` -} - // EpisodeFromID returns an episode by its api id. func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", diff --git a/watchlist.go b/watchlist.go new file mode 100644 index 0000000..73fbb3d --- /dev/null +++ b/watchlist.go @@ -0,0 +1,13 @@ +package crunchyroll + +// WatchlistEntry contains information about an entry on the watchlist. +type WatchlistEntry struct { + Panel Panel `json:"panel"` + + New bool `json:"new"` + NewContent bool `json:"new_content"` + IsFavorite bool `json:"is_favorite"` + NeverWatched bool `json:"never_watched"` + CompleteStatus bool `json:"complete_status"` + Playahead uint `json:"playahead"` +} From aa088cb318afd60deed167356a2d8938cacccad9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 01:42:57 +0200 Subject: [PATCH 112/630] Fix error printing caused panic --- error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error.go b/error.go index b4f7329..9bc2ae6 100644 --- a/error.go +++ b/error.go @@ -12,6 +12,6 @@ type RequestError struct { Message string } -func (re *RequestError) String() string { +func (re *RequestError) Error() string { return fmt.Sprintf("error for endpoint %s (%d): %s", re.Response.Request.URL.String(), re.Response.StatusCode, re.Message) } From 72484c78af9ec33060e7f5c5b8f64f8625683e69 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 01:56:39 +0200 Subject: [PATCH 113/630] Add crunchylists endpoint --- crunchylists.go | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ crunchyroll.go | 19 +++++++ 2 files changed, 167 insertions(+) create mode 100644 crunchylists.go diff --git a/crunchylists.go b/crunchylists.go new file mode 100644 index 0000000..1117dd7 --- /dev/null +++ b/crunchylists.go @@ -0,0 +1,148 @@ +package crunchyroll + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +type CrunchyLists struct { + crunchy *Crunchyroll + + Items []*CrunchyListPreview `json:"items"` + TotalPublic int `json:"total_public"` + TotalPrivate int `json:"total_private"` + MaxPrivate int `json:"max_private"` +} + +func (cl *CrunchyLists) Create(name string) (*CrunchyList, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", cl.crunchy.Config.AccountID, cl.crunchy.Locale) + body, _ := json.Marshal(map[string]string{"title": name}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + resp, err := cl.crunchy.requestFull(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + json.NewDecoder(resp.Body).Decode(&jsonBody) + + return CrunchyListFromID(cl.crunchy, jsonBody["list_id"].(string)) +} + +type CrunchyListPreview struct { + crunchy *Crunchyroll + + ListID string `json:"list_id"` + IsPublic bool `json:"is_public"` + Total int `json:"total"` + ModifiedAt time.Time `json:"modified_at"` + Title string `json:"title"` +} + +func (clp *CrunchyListPreview) CrunchyList() (*CrunchyList, error) { + return CrunchyListFromID(clp.crunchy, clp.ListID) +} + +func CrunchyListFromID(crunchy *Crunchyroll, id string) (*CrunchyList, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", crunchy.Config.AccountID, id, crunchy.Locale) + resp, err := crunchy.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + crunchyList := &CrunchyList{ + crunchy: crunchy, + ID: id, + } + if err := json.NewDecoder(resp.Body).Decode(crunchyList); err != nil { + return nil, err + } + for _, item := range crunchyList.Items { + item.crunchy = crunchy + } + return crunchyList, nil +} + +type CrunchyList struct { + crunchy *Crunchyroll + + ID string `json:"id"` + + Max int `json:"max"` + Total int `json:"total"` + Title string `json:"title"` + IsPublic bool `json:"is_public"` + ModifiedAt time.Time `json:"modified_at"` + Items []*CrunchyListItem `json:"items"` +} + +func (cl *CrunchyList) AddSeries(series *Series) error { + return cl.AddSeriesFromID(series.ID) +} + +func (cl *CrunchyList) AddSeriesFromID(id string) error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) + body, _ := json.Marshal(map[string]string{"content_id": id}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = cl.crunchy.requestFull(req) + return err +} + +func (cl *CrunchyList) RemoveSeries(series *Series) error { + return cl.RemoveSeriesFromID(series.ID) +} + +func (cl *CrunchyList) RemoveSeriesFromID(id string) error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, id, cl.crunchy.Locale) + _, err := cl.crunchy.request(endpoint, http.MethodDelete) + return err +} + +func (cl *CrunchyList) Delete() error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) + _, err := cl.crunchy.request(endpoint, http.MethodDelete) + return err +} + +func (cl *CrunchyList) Rename(name string) error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) + body, _ := json.Marshal(map[string]string{"title": name}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = cl.crunchy.requestFull(req) + if err == nil { + cl.Title = name + } + return err +} + +type CrunchyListItem struct { + crunchy *Crunchyroll + + ListID string `json:"list_id"` + ID string `json:"id"` + ModifiedAt time.Time `json:"modified_at"` + Panel Panel `json:"panel"` +} + +func (cli *CrunchyListItem) Remove() error { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s", cli.crunchy.Config.AccountID, cli.ListID, cli.ID) + _, err := cli.crunchy.request(endpoint, http.MethodDelete) + return err +} diff --git a/crunchyroll.go b/crunchyroll.go index 4c29ab4..b9593f3 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -893,6 +893,25 @@ func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*Watchl return watchlistEntries, nil } +func (c *Crunchyroll) CrunchyLists() (*CrunchyLists, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", c.Config.AccountID, c.Locale) + resp, err := c.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + crunchyLists := &CrunchyLists{ + crunchy: c, + } + json.NewDecoder(resp.Body).Decode(crunchyLists) + for _, item := range crunchyLists.Items { + item.crunchy = c + } + + return crunchyLists, nil +} + // Account returns information about the currently logged in crunchyroll account. func (c *Crunchyroll) Account() (*Account, error) { resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me", http.MethodGet) From 9f6a225caf58dc9b8f35133cc2c9fb4a1af71c94 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 02:00:58 +0200 Subject: [PATCH 114/630] Add better error output --- crunchyroll.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crunchyroll.go b/crunchyroll.go index b9593f3..3d07252 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -328,6 +328,11 @@ func request(req *http.Request, client *http.Client) (*http.Response, error) { return nil, &RequestError{Response: resp, Message: msg} } } + } else if _, ok := errMap["code"]; ok { + if errContext, ok := errMap["context"]; ok { + errField := errContext.([]any)[0].(map[string]any) + return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", errField["code"].(string), errField["field"].(string))} + } } } From c86595d2c6f3a6d07d7798814b074beba13c47c2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 02:28:03 +0200 Subject: [PATCH 115/630] Add image to common structs --- common.go | 28 ++++++++++------------------ episode.go | 7 +------ movie_listing.go | 7 +------ video.go | 14 ++------------ 4 files changed, 14 insertions(+), 42 deletions(-) diff --git a/common.go b/common.go index 91e1312..b76a8fe 100644 --- a/common.go +++ b/common.go @@ -1,5 +1,12 @@ package crunchyroll +type Image struct { + Height int `json:"height"` + Source string `json:"source"` + Type string `json:"type"` + Width int `json:"width"` +} + type Panel struct { Title string `json:"title"` PromoTitle string `json:"promo_title"` @@ -7,24 +14,9 @@ type Panel struct { Playback string `json:"playback"` PromoDescription string `json:"promo_description"` Images struct { - Thumbnail [][]struct { - Height int `json:"height"` - Source string `json:"source"` - Type string `json:"type"` - Width int `json:"width"` - } `json:"thumbnail"` - PosterTall [][]struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"poster_tall"` - PosterWide [][]struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"poster_wide"` + Thumbnail [][]Image `json:"thumbnail"` + PosterTall [][]Image `json:"poster_tall"` + PosterWide [][]Image `json:"poster_wide"` } `json:"images"` ID string `json:"id"` Description string `json:"description"` diff --git a/episode.go b/episode.go index 7f48e93..7d9caac 100644 --- a/episode.go +++ b/episode.go @@ -61,12 +61,7 @@ type Episode struct { Slug string `json:"slug"` Images struct { - Thumbnail [][]struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"thumbnail"` + Thumbnail [][]Image `json:"thumbnail"` } `json:"images"` DurationMS int `json:"duration_ms"` diff --git a/movie_listing.go b/movie_listing.go index 110f67a..81cb48d 100644 --- a/movie_listing.go +++ b/movie_listing.go @@ -19,12 +19,7 @@ type MovieListing struct { Description string `json:"description"` Images struct { - Thumbnail [][]struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"thumbnail"` + Thumbnail [][]Image `json:"thumbnail"` } `json:"images"` DurationMS int `json:"duration_ms"` diff --git a/video.go b/video.go index 3e7d7cf..3a67f5c 100644 --- a/video.go +++ b/video.go @@ -17,18 +17,8 @@ type video struct { SlugTitle string `json:"slug_title"` Images struct { - PosterTall [][]struct { - Height int `json:"height"` - Source string `json:"source"` - Type string `json:"type"` - Width int `json:"width"` - } `json:"poster_tall"` - PosterWide [][]struct { - Height int `json:"height"` - Source string `json:"source"` - Type string `json:"type"` - Width int `json:"width"` - } `json:"poster_wide"` + PosterTall [][]Image `json:"poster_tall"` + PosterWide [][]Image `json:"poster_wide"` } `json:"images"` } From 475dc34f7af0ab57de8def3b0489a2030f9983f9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 13:31:29 +0200 Subject: [PATCH 116/630] Fix error handling caused panic --- crunchyroll.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crunchyroll.go b/crunchyroll.go index 3d07252..0544c70 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -331,7 +331,11 @@ func request(req *http.Request, client *http.Client) (*http.Response, error) { } else if _, ok := errMap["code"]; ok { if errContext, ok := errMap["context"]; ok { errField := errContext.([]any)[0].(map[string]any) - return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", errField["code"].(string), errField["field"].(string))} + var code string + if code, ok = errField["message"].(string); !ok { + code = errField["code"].(string) + } + return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", code, errField["field"].(string))} } } } From 0c93893627e4f0906f9de4c9158514bf2fc06016 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 13:38:41 +0200 Subject: [PATCH 117/630] Fix error handling caused panic (again) --- crunchyroll.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 0544c70..8b4fad8 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -329,13 +329,15 @@ func request(req *http.Request, client *http.Client) (*http.Response, error) { } } } else if _, ok := errMap["code"]; ok { - if errContext, ok := errMap["context"]; ok { - errField := errContext.([]any)[0].(map[string]any) + if errContext, ok := errMap["context"].([]any); ok && len(errContext) > 0 { + errField := errContext[0].(map[string]any) var code string if code, ok = errField["message"].(string); !ok { code = errField["code"].(string) } return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", code, errField["field"].(string))} + } else if errMessage, ok := errMap["message"].(string); ok { + return nil, &RequestError{Response: resp, Message: errMessage} } } } From cee34105327856a37b5619c67615b1b2cb6a8445 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Jun 2022 14:43:46 +0200 Subject: [PATCH 118/630] Add comment endpoint --- comment.go | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++ episode.go | 80 ++++++++++++++++ utils.go | 12 +++ 3 files changed, 354 insertions(+) create mode 100644 comment.go diff --git a/comment.go b/comment.go new file mode 100644 index 0000000..4dba10d --- /dev/null +++ b/comment.go @@ -0,0 +1,262 @@ +package crunchyroll + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +type Comment struct { + crunchy *Crunchyroll + + EpisodeID string `json:"episode_id"` + + CommentID string `json:"comment_id"` + DomainID string `json:"domain_id"` + + GuestbookKey string `json:"guestbook_key"` + + User struct { + UserKey string `json:"user_key"` + UserAttributes struct { + Username string `json:"username"` + Avatar struct { + Locked []Image `json:"locked"` + Unlocked []Image `json:"unlocked"` + } `json:"avatar"` + } `json:"user_attributes"` + UserFlags []any `json:"user_flags"` + } `json:"user"` + + Message string `json:"message"` + ParentCommentID int `json:"parent_comment_id"` + + Locale LOCALE `json:"locale"` + + UserVotes []string `json:"user_votes"` + Flags []string `json:"flags"` + Votes struct { + Inappropriate int `json:"inappropriate"` + Like int `json:"like"` + Spoiler int `json:"spoiler"` + } `json:"votes"` + + DeleteReason any `json:"delete_reason"` + + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + + IsOwner bool `json:"is_owner"` + RepliesCount int `json:"replies_count"` +} + +// Delete deleted the current comment. Works only if the user has written the comment. +func (c *Comment) Delete() error { + if !c.IsOwner { + return fmt.Errorf("cannot delete, user is not the comment author") + } + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) + resp, err := c.crunchy.request(endpoint, http.MethodDelete) + if err != nil { + return err + } + defer resp.Body.Close() + + // the api returns a new comment object when modifying it. + // hopefully this does not change + json.NewDecoder(resp.Body).Decode(c) + + return nil +} + +// MarkAsSpoiler marks the current comment as spoiler. Works only if the user has written the comment, +// and it isn't already marked as spoiler. +func (c *Comment) MarkAsSpoiler() error { + if !c.IsOwner { + return fmt.Errorf("cannot mark as spoiler, user is not the comment author") + } else if c.markedAs("spoiler") { + return fmt.Errorf("comment is already marked as spoiler") + } + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) + body, _ := json.Marshal(map[string][]string{"add": {"spoiler"}}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + resp, err := c.crunchy.requestFull(req) + if err != nil { + return err + } + defer resp.Body.Close() + + json.NewDecoder(resp.Body).Decode(c) + + return nil +} + +// UnmarkAsSpoiler unmarks the current comment as spoiler. Works only if the user has written the comment, +// and it is already marked as spoiler. +func (c *Comment) UnmarkAsSpoiler() error { + if !c.IsOwner { + return fmt.Errorf("cannot mark as spoiler, user is not the comment author") + } else if !c.markedAs("spoiler") { + return fmt.Errorf("comment is not marked as spoiler") + } + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) + body, _ := json.Marshal(map[string][]string{"remove": {"spoiler"}}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + resp, err := c.crunchy.requestFull(req) + if err != nil { + return err + } + defer resp.Body.Close() + + json.NewDecoder(resp.Body).Decode(c) + + return nil +} + +// Like likes the comment. Works only if the user hasn't already liked it. +func (c *Comment) Like() error { + if err := c.vote("like", "liked"); err != nil { + return err + } + c.Votes.Like += 1 + + return nil +} + +// RemoveLike removes the like from the comment. Works only if the user has liked it. +func (c *Comment) RemoveLike() error { + if err := c.unVote("like", "liked"); err != nil { + return err + } + c.Votes.Like -= 1 + + return nil +} + +// Reply replies to the current comment. +func (c *Comment) Reply(message string, spoiler bool) (*Comment, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments?locale=%s", c.EpisodeID, c.crunchy.Locale) + var flags []string + if spoiler { + flags = append(flags, "spoiler") + } + body, _ := json.Marshal(map[string]any{"locale": string(c.crunchy.Locale), "message": message, "flags": flags, "parent_id": c.CommentID}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + resp, err := c.crunchy.requestFull(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + reply := &Comment{} + if err = json.NewDecoder(resp.Body).Decode(reply); err != nil { + return nil, err + } + + return reply, nil +} + +// Replies shows all replies to the current comment. +func (c *Comment) Replies(page uint, size uint) ([]*Comment, error) { + if c.RepliesCount == 0 { + return []*Comment{}, nil + } + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/replies?page_size=%d&page=%d&locale=%s", c.EpisodeID, c.CommentID, size, page, c.Locale) + resp, err := c.crunchy.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]any + json.NewDecoder(resp.Body).Decode(&jsonBody) + + var comments []*Comment + if err = decodeMapToStruct(jsonBody["items"].([]any), &comments); err != nil { + return nil, err + } + return comments, nil +} + +// Report reports the comment. Only works if the comment hasn't been reported yet. +func (c *Comment) Report() error { + return c.vote("inappropriate", "reported") +} + +// UnreportComment removes the report request from the comment. Only works if the user +// has reported the comment. +func (c *Comment) UnreportComment() error { + return c.unVote("inappropriate", "reported") +} + +// FlagAsSpoiler sends a request to the user (and / or crunchyroll?) to mark the comment +// as spoiler. Only works if the comment hasn't been flagged as spoiler yet. +func (c *Comment) FlagAsSpoiler() error { + return c.vote("spoiler", "spoiler") +} + +// UnflagAsSpoiler rewokes the request to the user (and / or crunchyroll?) to mark the +// comment as spoiler. Only works if the user has flagged the comment as spoiler. +func (c *Comment) UnflagAsSpoiler() error { + return c.unVote("spoiler", "spoiler") +} + +func (c *Comment) markedAs(voteType string) bool { + for _, userVote := range c.UserVotes { + if userVote == voteType { + return true + } + } + return false +} + +func (c *Comment) vote(voteType, readableName string) error { + if c.markedAs(voteType) { + return fmt.Errorf("comment is already marked as %s", readableName) + } + + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/votes?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) + body, _ := json.Marshal(map[string]string{"vote_type": voteType}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = c.crunchy.requestFull(req) + if err != nil { + return err + } + c.UserVotes = append(c.UserVotes, voteType) + + return nil +} + +func (c *Comment) unVote(voteType, readableName string) error { + for i, userVote := range c.UserVotes { + if userVote == voteType { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/votes?vote_type=%s&locale=%s", c.EpisodeID, c.CommentID, voteType, c.crunchy.Locale) + _, err := c.crunchy.request(endpoint, http.MethodDelete) + if err != nil { + return err + } + c.UserVotes = append(c.UserVotes[:i], c.UserVotes[i+1:]...) + return nil + } + } + + return fmt.Errorf("comment is not marked as %s", readableName) +} diff --git a/episode.go b/episode.go index 7d9caac..9e20510 100644 --- a/episode.go +++ b/episode.go @@ -167,6 +167,86 @@ func (e *Episode) AudioLocale() (LOCALE, error) { return streams[0].AudioLocale, nil } +// Comment creates a new comment under the episode. +func (e *Episode) Comment(message string, spoiler bool) (*Comment, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments?locale=%s", e.ID, e.crunchy.Locale) + var flags []string + if spoiler { + flags = append(flags, "spoiler") + } + body, _ := json.Marshal(map[string]any{"locale": string(e.crunchy.Locale), "flags": flags, "message": message}) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + resp, err := e.crunchy.requestFull(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + c := &Comment{ + crunchy: e.crunchy, + EpisodeID: e.ID, + } + if err = json.NewDecoder(resp.Body).Decode(c); err != nil { + return nil, err + } + + return c, nil +} + +// CommentsOrderType represents a sort type to sort Episode.Comments after. +type CommentsOrderType string + +const ( + CommentsOrderAsc CommentsOrderType = "asc" + CommentsOrderDesc = "desc" +) + +type CommentsSortType string + +const ( + CommentsSortPopular CommentsSortType = "popular" + CommentsSortDate = "date" +) + +type CommentsOptions struct { + // Order specified the order how the comments should be returned. + Order CommentsOrderType `json:"order"` + + // Sort specified after which key the comments should be sorted. + Sort CommentsSortType `json:"sort"` +} + +// Comments returns comments under the given episode. +func (e *Episode) Comments(options CommentsOptions, page uint, size uint) (c []*Comment, err error) { + options, err = structDefaults(CommentsOptions{Order: CommentsOrderDesc, Sort: CommentsSortPopular}, options) + if err != nil { + return nil, err + } + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments?page=%d&page_size=%d&order=%s&sort=%s&locale=%s", e.ID, page, size, options.Order, options.Sort, e.crunchy.Locale) + resp, err := e.crunchy.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]any + json.NewDecoder(resp.Body).Decode(&jsonBody) + + if err = decodeMapToStruct(jsonBody["items"].([]any), &c); err != nil { + return nil, err + } + for _, comment := range c { + comment.crunchy = e.crunchy + comment.EpisodeID = e.ID + } + + return +} + // GetFormat returns the format which matches the given resolution and subtitle locale. func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) { streams, err := e.Streams() diff --git a/utils.go b/utils.go index d32d39b..a665040 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,7 @@ package crunchyroll import ( + "bytes" "encoding/json" "fmt" "net/url" @@ -70,3 +71,14 @@ func encodeStructToQueryValues(s interface{}) (string, error) { return values.Encode(), nil } + +func structDefaults[T any](defaultStruct T, customStruct T) (T, error) { + rawDefaultStruct, err := json.Marshal(defaultStruct) + if err != nil { + return *new(T), err + } + if err = json.NewDecoder(bytes.NewBuffer(rawDefaultStruct)).Decode(&customStruct); err != nil { + return *new(T), err + } + return customStruct, nil +} From 715ade831ca0387e5d45c447201e86bab5389552 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 20 Jun 2022 10:22:17 +0200 Subject: [PATCH 119/630] Add more account and profile endpoints --- account.go | 116 ++++++++++++++++++++++++++++++++++++++++++++++++- crunchyroll.go | 4 +- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/account.go b/account.go index d22eba5..87a6ef8 100644 --- a/account.go +++ b/account.go @@ -1,9 +1,16 @@ package crunchyroll -import "time" +import ( + "bytes" + "encoding/json" + "net/http" + "time" +) // Account contains information about a crunchyroll account. type Account struct { + crunchy *Crunchyroll + AccountID string `json:"account_id"` ExternalID string `json:"external_id"` EmailVerified bool `json:"email_verified"` @@ -25,5 +32,110 @@ type Account struct { PreferredCommunicationLanguage LOCALE `json:"preferred_communication_language"` PreferredContentSubtitleLanguage LOCALE `json:"preferred_content_subtitle_language"` QaUser bool `json:"qa_user"` - Username string `json:"username"` + + Username string `json:"username"` + Wallpaper *Wallpaper `json:"wallpaper"` +} + +// UpdateEmailLanguage sets in which language emails should be received. +func (a *Account) UpdateEmailLanguage(language LOCALE) error { + err := a.updatePreferences("preferred_communication_language", string(language)) + if err == nil { + a.PreferredCommunicationLanguage = language + } + return err +} + +// UpdateVideoSubtitleLanguage sets in which language default subtitles should be shown +func (a *Account) UpdateVideoSubtitleLanguage(language LOCALE) error { + err := a.updatePreferences("preferred_content_subtitle_language", string(language)) + if err == nil { + a.PreferredContentSubtitleLanguage = language + } + return err +} + +// UpdateMatureVideoContent sets if mature video content / 18+ content should be shown +func (a *Account) UpdateMatureVideoContent(enabled bool) error { + if enabled { + return a.updatePreferences("maturity_rating", "M3") + } else { + return a.updatePreferences("maturity_rating", "M2") + } +} + +// UpdateMatureMangaContent sets if mature manga content / 18+ content should be shown +func (a *Account) UpdateMatureMangaContent(enabled bool) error { + if enabled { + return a.updatePreferences("mature_content_flag_manga", "1") + } else { + return a.updatePreferences("mature_content_flag_manga", "0") + } +} + +func (a *Account) updatePreferences(name, value string) error { + endpoint := "https://beta.crunchyroll.com/accounts/v1/me/profile" + body, _ := json.Marshal(map[string]string{name: value}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = a.crunchy.requestFull(req) + return err +} + +// ChangePassword changes the password for the current account. +func (a *Account) ChangePassword(currentPassword, newPassword string) error { + endpoint := "https://beta.crunchyroll.com/accounts/v1/me/credentials" + body, _ := json.Marshal(map[string]string{"accountId": a.AccountID, "current_password": currentPassword, "new_password": newPassword}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = a.crunchy.requestFull(req) + return err +} + +// ChangeEmail changes the email address for the current account. +func (a *Account) ChangeEmail(currentPassword, newEmail string) error { + endpoint := "https://beta.crunchyroll.com/accounts/v1/me/credentials" + body, _ := json.Marshal(map[string]string{"current_password": currentPassword, "email": newEmail}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + _, err = a.crunchy.requestFull(req) + return err +} + +// AvailableWallpapers returns all available wallpapers which can be set as profile wallpaper. +func (a *Account) AvailableWallpapers() (w []*Wallpaper, err error) { + endpoint := "https://beta.crunchyroll.com/assets/v1/wallpaper" + resp, err := a.crunchy.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]any + json.NewDecoder(resp.Body).Decode(&jsonBody) + + err = decodeMapToStruct(jsonBody["items"].([]any), &w) + return +} + +// ChangeWallpaper changes the profile wallpaper of the current user. Use AvailableWallpapers +// to get all available ones. +func (a *Account) ChangeWallpaper(wallpaper *Wallpaper) error { + endpoint := "https://beta.crunchyroll.com/accounts/v1/me/profile" + body, _ := json.Marshal(map[string]string{"wallpaper": string(*wallpaper)}) + req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + _, err = a.crunchy.requestFull(req) + return err } diff --git a/crunchyroll.go b/crunchyroll.go index 8b4fad8..704003d 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -931,7 +931,9 @@ func (c *Crunchyroll) Account() (*Account, error) { } defer resp.Body.Close() - account := &Account{} + account := &Account{ + crunchy: c, + } if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { return nil, fmt.Errorf("failed to parse 'me' response: %w", err) From 256c97c2b748ed8e871a713b7d4451c9f56b55d2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 20 Jun 2022 10:24:01 +0200 Subject: [PATCH 120/630] Fix Wallpaper type not found --- wallpaper.go | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 wallpaper.go diff --git a/wallpaper.go b/wallpaper.go new file mode 100644 index 0000000..f64cedf --- /dev/null +++ b/wallpaper.go @@ -0,0 +1,4 @@ +package crunchyroll + +// Wallpaper contains a wallpaper name which can be set via Account.ChangeWallpaper. +type Wallpaper string From d1859b4c25c3f7f627b50f03f0f5202da681144a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 Jun 2022 17:43:12 +0200 Subject: [PATCH 121/630] Change const names to make them more readable --- crunchyroll.go | 26 +++++++++++++------------- episode.go | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 704003d..5de3ab2 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -34,32 +34,32 @@ const ( type MediaType string const ( - SERIES MediaType = "series" - MOVIELISTING = "movie_listing" + MediaTypeSeries MediaType = "series" + MediaTypeMovie = "movie_listing" ) // BrowseSortType represents a sort type to sort Crunchyroll.Browse items after. type BrowseSortType string const ( - BROWSESORTPOPULARITY BrowseSortType = "popularity" - BROWSESORTNEWLYADDED = "newly_added" - BROWSESORTALPHABETICAL = "alphabetical" + BrowseSortPopularity BrowseSortType = "popularity" + BrowseSortNewlyAdded = "newly_added" + BrowseSortAlphabetical = "alphabetical" ) // WatchlistLanguageType represents a filter type to filter Crunchyroll.WatchList entries after. type WatchlistLanguageType int const ( - WATCHLISTLANGUAGESUBBED WatchlistLanguageType = iota + 1 - WATCHLISTLANGUAGEDUBBED + WatchlistLanguageSubbed WatchlistLanguageType = iota + 1 + WatchlistLanguageDubbed ) type WatchlistContentType string const ( - WATCHLISTCONTENTSERIES WatchlistContentType = "series" - WATCHLISTCONTENTMOVIES = "movie_listing" + WatchlistContentSeries WatchlistContentType = "series" + WatchlistContentMovies = "movie_listing" ) type Crunchyroll struct { @@ -869,9 +869,9 @@ func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*Watchl values.Set("only_favorites", "true") } switch options.LanguageType { - case WATCHLISTLANGUAGESUBBED: + case WatchlistLanguageSubbed: values.Set("is_subbed", "true") - case WATCHLISTLANGUAGEDUBBED: + case WatchlistLanguageDubbed: values.Set("is_dubbed", "true") } values.Set("n", strconv.Itoa(int(limit))) @@ -894,9 +894,9 @@ func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*Watchl for _, entry := range watchlistEntries { switch entry.Panel.Type { - case WATCHLISTENTRYEPISODE: + case WatchlistEntryEpisode: entry.Panel.EpisodeMetadata.crunchy = c - case WATCHLISTENTRYSERIES: + case WatchlistEntrySeries: entry.Panel.SeriesMetadata.crunchy = c } } diff --git a/episode.go b/episode.go index 9e20510..b02280d 100644 --- a/episode.go +++ b/episode.go @@ -91,8 +91,8 @@ type HistoryEpisode struct { type WatchlistEntryType string const ( - WATCHLISTENTRYEPISODE = "episode" - WATCHLISTENTRYSERIES = "series" + WatchlistEntryEpisode = "episode" + WatchlistEntrySeries = "series" ) // EpisodeFromID returns an episode by its api id. From ec872d8c86dbfca2757f8ccaa0fa754e21c13703 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 Jun 2022 21:15:49 +0200 Subject: [PATCH 122/630] Move functions into their own, separate files & add docs --- account.go | 30 +++ category.go | 48 +++- crunchylists.go | 19 ++ crunchyroll.go | 624 ----------------------------------------------- news.go | 46 +++- parse.go | 69 ++++++ search.go | 193 +++++++++++++++ simulcast.go | 32 +++ suggestions.go | 82 +++++++ video.go | 43 ++++ watch_history.go | 45 ++++ watchlist.go | 88 +++++++ 12 files changed, 681 insertions(+), 638 deletions(-) create mode 100644 parse.go create mode 100644 search.go create mode 100644 suggestions.go create mode 100644 watch_history.go diff --git a/account.go b/account.go index 87a6ef8..f2d6a70 100644 --- a/account.go +++ b/account.go @@ -3,10 +3,40 @@ package crunchyroll import ( "bytes" "encoding/json" + "fmt" "net/http" "time" ) +// Account returns information about the currently logged in crunchyroll account. +func (c *Crunchyroll) Account() (*Account, error) { + resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me", http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + account := &Account{ + crunchy: c, + } + + if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { + return nil, fmt.Errorf("failed to parse 'me' response: %w", err) + } + + resp, err = c.request("https://beta.crunchyroll.com/accounts/v1/me/profile", http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { + return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) + } + + return account, nil +} + // Account contains information about a crunchyroll account. type Account struct { crunchy *Crunchyroll diff --git a/category.go b/category.go index 58855fc..eccefff 100644 --- a/category.go +++ b/category.go @@ -1,5 +1,38 @@ package crunchyroll +import ( + "encoding/json" + "fmt" + "net/http" +) + +// Categories returns all available categories and possible subcategories. +func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err error) { + tenantCategoriesEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/tenant_categories?include_subcategories=%t&locale=%s", + includeSubcategories, c.Locale) + resp, err := c.request(tenantCategoriesEndpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'tenant_categories' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + category := &Category{} + if err := decodeMapToStruct(item, category); err != nil { + return nil, err + } + + ca = append(ca, category) + } + + return ca, nil +} + // Category contains all information about a category. type Category struct { Category string `json:"tenant_category"` @@ -18,19 +51,8 @@ type Category struct { } `json:"sub_categories"` Images struct { - Background []struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"background"` - - Low []struct { - Width int `json:"width"` - Height int `json:"height"` - Type string `json:"type"` - Source string `json:"source"` - } `json:"low"` + Background []Image `json:"background"` + Low []Image `json:"low"` } `json:"images"` Localization struct { diff --git a/crunchylists.go b/crunchylists.go index 1117dd7..a2a7a1f 100644 --- a/crunchylists.go +++ b/crunchylists.go @@ -8,6 +8,25 @@ import ( "time" ) +func (c *Crunchyroll) CrunchyLists() (*CrunchyLists, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", c.Config.AccountID, c.Locale) + resp, err := c.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + crunchyLists := &CrunchyLists{ + crunchy: c, + } + json.NewDecoder(resp.Body).Decode(crunchyLists) + for _, item := range crunchyLists.Items { + item.crunchy = c + } + + return crunchyLists, nil +} + type CrunchyLists struct { crunchy *Crunchyroll diff --git a/crunchyroll.go b/crunchyroll.go index 5de3ab2..a83415c 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -8,8 +8,6 @@ import ( "io" "net/http" "net/url" - "regexp" - "strconv" "strings" ) @@ -38,30 +36,6 @@ const ( MediaTypeMovie = "movie_listing" ) -// BrowseSortType represents a sort type to sort Crunchyroll.Browse items after. -type BrowseSortType string - -const ( - BrowseSortPopularity BrowseSortType = "popularity" - BrowseSortNewlyAdded = "newly_added" - BrowseSortAlphabetical = "alphabetical" -) - -// WatchlistLanguageType represents a filter type to filter Crunchyroll.WatchList entries after. -type WatchlistLanguageType int - -const ( - WatchlistLanguageSubbed WatchlistLanguageType = iota + 1 - WatchlistLanguageDubbed -) - -type WatchlistContentType string - -const ( - WatchlistContentSeries WatchlistContentType = "series" - WatchlistContentMovies = "movie_listing" -) - type Crunchyroll struct { // Client is the http.Client to perform all requests over. Client *http.Client @@ -95,30 +69,6 @@ type Crunchyroll struct { cache bool } -// BrowseOptions represents options for browsing the crunchyroll catalog. -type BrowseOptions struct { - // Categories specifies the categories of the entries. - Categories []string `param:"categories"` - - // IsDubbed specifies whether the entries should be dubbed. - IsDubbed bool `param:"is_dubbed"` - - // IsSubbed specifies whether the entries should be subbed. - IsSubbed bool `param:"is_subbed"` - - // Simulcast specifies a particular simulcast season by id in which the entries have been aired. - Simulcast string `param:"season_tag"` - - // Sort specifies how the entries should be sorted. - Sort BrowseSortType `param:"sort_by"` - - // Start specifies the index from which the entries should be returned. - Start uint `param:"start"` - - // Type specifies the media type of the entries. - Type MediaType `param:"type"` -} - type loginResponse struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` @@ -377,577 +327,3 @@ func (c *Crunchyroll) IsCaching() bool { func (c *Crunchyroll) SetCaching(caching bool) { c.cache = caching } - -// Search searches a query and returns all found series and movies within the given limit. -func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) { - searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s", - query, limit, c.Locale) - resp, err := c.request(searchEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'search' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - item := item.(map[string]interface{}) - if item["total"].(float64) > 0 { - switch item["type"] { - case "series": - for _, series := range item["items"].([]interface{}) { - series2 := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(series, series2); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(series.(map[string]interface{})["series_metadata"].(map[string]interface{}), series2); err != nil { - return nil, nil, err - } - - s = append(s, series2) - } - case "movie_listing": - for _, movie := range item["items"].([]interface{}) { - movie2 := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(movie, movie2); err != nil { - return nil, nil, err - } - - m = append(m, movie2) - } - } - } - } - - return s, m, nil -} - -// FindVideoByName finds a Video (Season or Movie) by its name. -// Use this in combination with ParseVideoURL and hand over the corresponding results -// to this function. -// -// Deprecated: Use Search instead. The first result sometimes isn't the correct one -// so this function is inaccurate in some cases. -// See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information. -func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { - s, m, err := c.Search(seriesName, 1) - if err != nil { - return nil, err - } - - if len(s) > 0 { - return s[0], nil - } else if len(m) > 0 { - return m[0], nil - } - return nil, fmt.Errorf("no series or movie could be found") -} - -// FindEpisodeByName finds an episode by its crunchyroll series name and episode title. -// Use this in combination with ParseEpisodeURL and hand over the corresponding results -// to this function. -func (c *Crunchyroll) FindEpisodeByName(seriesName, episodeTitle string) ([]*Episode, error) { - series, _, err := c.Search(seriesName, 5) - if err != nil { - return nil, err - } - - var matchingEpisodes []*Episode - for _, s := range series { - seasons, err := s.Seasons() - if err != nil { - return nil, err - } - - for _, season := range seasons { - episodes, err := season.Episodes() - if err != nil { - return nil, err - } - for _, episode := range episodes { - if episode.SlugTitle == episodeTitle { - matchingEpisodes = append(matchingEpisodes, episode) - } - } - } - } - - return matchingEpisodes, nil -} - -// ParseVideoURL tries to extract the crunchyroll series / movie name out of the given url. -// -// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaSeriesURL -// if possible since beta url are always safe to use. -// The method will stay in the library until only beta urls are supported by crunchyroll itself. -func ParseVideoURL(url string) (seriesName string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)(/videos)?/?$`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - seriesName = groups["series"] - - if seriesName != "" { - ok = true - } - } - return -} - -// ParseEpisodeURL tries to extract the crunchyroll series name, title, episode number and web id out of the given crunchyroll url -// Note that the episode number can be misleading. For example if an episode has the episode number 23.5 (slime isekai) -// the episode number will be 235. -// -// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaEpisodeURL -// if possible since beta url are always safe to use. -// The method will stay in the library until only beta urls are supported by crunchyroll itself. -func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, webId int, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)/episode-(?P<number>\d+)-(?P<title>.+)-(?P<webId>\d+).*`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - seriesName = groups["series"] - episodeNumber, _ = strconv.Atoi(groups["number"]) - title = groups["title"] - webId, _ = strconv.Atoi(groups["webId"]) - - if seriesName != "" && title != "" && webId != 0 { - ok = true - } - } - return -} - -// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season. -func ParseBetaSeriesURL(url string) (seasonId string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - seasonId = groups["seasonId"] - ok = true - } - return -} - -// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode. -func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - episodeId = groups["episodeId"] - ok = true - } - return -} - -// Browse browses the crunchyroll catalog filtered by the specified options and returns all found series and movies within the given limit. -func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m []*Movie, err error) { - query, err := encodeStructToQueryValues(options) - if err != nil { - return nil, nil, err - } - - browseEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/browse?%s&n=%d&locale=%s", - query, limit, c.Locale) - resp, err := c.request(browseEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'browse' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - switch item.(map[string]interface{})["type"] { - case "series": - series := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(item, series); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { - return nil, nil, err - } - - s = append(s, series) - case "movie_listing": - movie := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(item, movie); err != nil { - return nil, nil, err - } - - m = append(m, movie) - } - } - - return s, m, nil -} - -// Categories returns all available categories and possible subcategories. -func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err error) { - tenantCategoriesEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/tenant_categories?include_subcategories=%t&locale=%s", - includeSubcategories, c.Locale) - resp, err := c.request(tenantCategoriesEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'tenant_categories' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - category := &Category{} - if err := decodeMapToStruct(item, category); err != nil { - return nil, err - } - - ca = append(ca, category) - } - - return ca, nil -} - -// Simulcasts returns all available simulcast seasons for the current locale. -func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { - seasonListEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/season_list?locale=%s", c.Locale) - resp, err := c.request(seasonListEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'season_list' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - simulcast := &Simulcast{} - if err := decodeMapToStruct(item, simulcast); err != nil { - return nil, err - } - - s = append(s, simulcast) - } - - return s, nil -} - -// News returns the top and latest news from crunchyroll for the current locale within the given limits. -func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*News, err error) { - newsFeedEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/news_feed?top_news_n=%d&latest_news_n=%d&locale=%s", - topLimit, latestLimit, c.Locale) - resp, err := c.request(newsFeedEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'news_feed' response: %w", err) - } - - topNews := jsonBody["top_news"].(map[string]interface{}) - for _, item := range topNews["items"].([]interface{}) { - topNews := &News{} - if err := decodeMapToStruct(item, topNews); err != nil { - return nil, nil, err - } - - t = append(t, topNews) - } - - latestNews := jsonBody["latest_news"].(map[string]interface{}) - for _, item := range latestNews["items"].([]interface{}) { - latestNews := &News{} - if err := decodeMapToStruct(item, latestNews); err != nil { - return nil, nil, err - } - - l = append(l, latestNews) - } - - return t, l, nil -} - -// Recommendations returns series and movie recommendations from crunchyroll based on the currently logged in account within the given limit. -func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err error) { - recommendationsEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/recommendations?n=%d&locale=%s", - c.Config.AccountID, limit, c.Locale) - resp, err := c.request(recommendationsEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'recommendations' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - switch item.(map[string]interface{})["type"] { - case "series": - series := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(item, series); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { - return nil, nil, err - } - - s = append(s, series) - case "movie_listing": - movie := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(item, movie); err != nil { - return nil, nil, err - } - - m = append(m, movie) - } - } - - return s, m, nil -} - -// UpNext returns the episodes that are up next based on the currently logged in account within the given limit. -func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { - upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", - c.Config.AccountID, limit, c.Locale) - resp, err := c.request(upNextAccountEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'up_next_account' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - panel := item.(map[string]interface{})["panel"] - - episode := &Episode{ - crunchy: c, - } - if err := decodeMapToStruct(panel, episode); err != nil { - return nil, err - } - - e = append(e, episode) - } - - return e, nil -} - -// SimilarTo returns similar series and movies according to crunchyroll to the one specified by id within the given limit. -func (c *Crunchyroll) SimilarTo(id string, limit uint) (s []*Series, m []*Movie, err error) { - similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", - c.Config.AccountID, id, limit, c.Locale) - resp, err := c.request(similarToEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'similar_to' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - switch item.(map[string]interface{})["type"] { - case "series": - series := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(item, series); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { - return nil, nil, err - } - - s = append(s, series) - case "movie_listing": - movie := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(item, movie); err != nil { - return nil, nil, err - } - - m = append(m, movie) - } - } - - return s, m, nil -} - -// WatchHistory returns the history of watched episodes based on the currently logged in account from the given page with the given size. -func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, err error) { - watchHistoryEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/watch-history/%s?page=%d&page_size=%d&locale=%s", - c.Config.AccountID, page, size, c.Locale) - resp, err := c.request(watchHistoryEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'watch-history' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - panel := item.(map[string]interface{})["panel"] - - episode := &Episode{ - crunchy: c, - } - if err := decodeMapToStruct(panel, episode); err != nil { - return nil, err - } - - historyEpisode := &HistoryEpisode{ - Episode: episode, - } - if err := decodeMapToStruct(item, historyEpisode); err != nil { - return nil, err - } - - e = append(e, historyEpisode) - } - - return e, nil -} - -type WatchlistOptions struct { - // OrderAsc specified whether the results should be order ascending or descending. - OrderAsc bool - - // OnlyFavorites specifies whether only episodes which are marked as favorite should be returned. - OnlyFavorites bool - - // LanguageType specifies whether returning episodes should be only subbed or dubbed. - LanguageType WatchlistLanguageType - - // ContentType specified whether returning videos should only be series episodes or movies. - // But tbh all movies I've searched on crunchy were flagged as series too, so this - // parameter is kinda useless. - ContentType WatchlistContentType -} - -func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*WatchlistEntry, error) { - values := url.Values{} - if options.OrderAsc { - values.Set("order", "asc") - } else { - values.Set("order", "desc") - } - if options.OnlyFavorites { - values.Set("only_favorites", "true") - } - switch options.LanguageType { - case WatchlistLanguageSubbed: - values.Set("is_subbed", "true") - case WatchlistLanguageDubbed: - values.Set("is_dubbed", "true") - } - values.Set("n", strconv.Itoa(int(limit))) - values.Set("locale", string(c.Locale)) - - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/%s/watchlist?%s", c.Config.AccountID, values.Encode()) - resp, err := c.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - var watchlistEntries []*WatchlistEntry - if err := decodeMapToStruct(jsonBody["items"], &watchlistEntries); err != nil { - return nil, err - } - - for _, entry := range watchlistEntries { - switch entry.Panel.Type { - case WatchlistEntryEpisode: - entry.Panel.EpisodeMetadata.crunchy = c - case WatchlistEntrySeries: - entry.Panel.SeriesMetadata.crunchy = c - } - } - - return watchlistEntries, nil -} - -func (c *Crunchyroll) CrunchyLists() (*CrunchyLists, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", c.Config.AccountID, c.Locale) - resp, err := c.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - crunchyLists := &CrunchyLists{ - crunchy: c, - } - json.NewDecoder(resp.Body).Decode(crunchyLists) - for _, item := range crunchyLists.Items { - item.crunchy = c - } - - return crunchyLists, nil -} - -// Account returns information about the currently logged in crunchyroll account. -func (c *Crunchyroll) Account() (*Account, error) { - resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me", http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - account := &Account{ - crunchy: c, - } - - if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { - return nil, fmt.Errorf("failed to parse 'me' response: %w", err) - } - - resp, err = c.request("https://beta.crunchyroll.com/accounts/v1/me/profile", http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { - return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) - } - - return account, nil -} diff --git a/news.go b/news.go index d90dd65..074dc0e 100644 --- a/news.go +++ b/news.go @@ -1,6 +1,50 @@ package crunchyroll -// News contains all information about a news. +import ( + "encoding/json" + "fmt" + "net/http" +) + +// News returns the top and latest news from crunchyroll for the current locale within the given limits. +func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*News, err error) { + newsFeedEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/news_feed?top_news_n=%d&latest_news_n=%d&locale=%s", + topLimit, latestLimit, c.Locale) + resp, err := c.request(newsFeedEndpoint, http.MethodGet) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'news_feed' response: %w", err) + } + + topNews := jsonBody["top_news"].(map[string]interface{}) + for _, item := range topNews["items"].([]interface{}) { + topNews := &News{} + if err := decodeMapToStruct(item, topNews); err != nil { + return nil, nil, err + } + + t = append(t, topNews) + } + + latestNews := jsonBody["latest_news"].(map[string]interface{}) + for _, item := range latestNews["items"].([]interface{}) { + latestNews := &News{} + if err := decodeMapToStruct(item, latestNews); err != nil { + return nil, nil, err + } + + l = append(l, latestNews) + } + + return t, l, nil +} + +// News contains all information about news. type News struct { Title string `json:"title"` Link string `json:"link"` diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..392e971 --- /dev/null +++ b/parse.go @@ -0,0 +1,69 @@ +package crunchyroll + +import ( + "regexp" + "strconv" +) + +// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season. +func ParseBetaSeriesURL(url string) (seasonId string, ok bool) { + pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`) + if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { + groups := regexGroups(urlMatch, pattern.SubexpNames()...) + seasonId = groups["seasonId"] + ok = true + } + return +} + +// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode. +func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) { + pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`) + if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { + groups := regexGroups(urlMatch, pattern.SubexpNames()...) + episodeId = groups["episodeId"] + ok = true + } + return +} + +// ParseVideoURL tries to extract the crunchyroll series / movie name out of the given url. +// +// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaSeriesURL +// if possible since beta url are always safe to use. +// The method will stay in the library until only beta urls are supported by crunchyroll itself. +func ParseVideoURL(url string) (seriesName string, ok bool) { + pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)(/videos)?/?$`) + if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { + groups := regexGroups(urlMatch, pattern.SubexpNames()...) + seriesName = groups["series"] + + if seriesName != "" { + ok = true + } + } + return +} + +// ParseEpisodeURL tries to extract the crunchyroll series name, title, episode number and web id out of the given crunchyroll url +// Note that the episode number can be misleading. For example if an episode has the episode number 23.5 (slime isekai) +// the episode number will be 235. +// +// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaEpisodeURL +// if possible since beta url are always safe to use. +// The method will stay in the library until only beta urls are supported by crunchyroll itself. +func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, webId int, ok bool) { + pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)/episode-(?P<number>\d+)-(?P<title>.+)-(?P<webId>\d+).*`) + if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { + groups := regexGroups(urlMatch, pattern.SubexpNames()...) + seriesName = groups["series"] + episodeNumber, _ = strconv.Atoi(groups["number"]) + title = groups["title"] + webId, _ = strconv.Atoi(groups["webId"]) + + if seriesName != "" && title != "" && webId != 0 { + ok = true + } + } + return +} diff --git a/search.go b/search.go new file mode 100644 index 0000000..d7f6e76 --- /dev/null +++ b/search.go @@ -0,0 +1,193 @@ +package crunchyroll + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// BrowseSortType represents a sort type to sort Crunchyroll.Browse items after. +type BrowseSortType string + +const ( + BrowseSortPopularity BrowseSortType = "popularity" + BrowseSortNewlyAdded = "newly_added" + BrowseSortAlphabetical = "alphabetical" +) + +// BrowseOptions represents options for browsing the crunchyroll catalog. +type BrowseOptions struct { + // Categories specifies the categories of the entries. + Categories []string `param:"categories"` + + // IsDubbed specifies whether the entries should be dubbed. + IsDubbed bool `param:"is_dubbed"` + + // IsSubbed specifies whether the entries should be subbed. + IsSubbed bool `param:"is_subbed"` + + // Simulcast specifies a particular simulcast season by id in which the entries have been aired. + Simulcast string `param:"season_tag"` + + // Sort specifies how the entries should be sorted. + Sort BrowseSortType `param:"sort_by"` + + // Start specifies the index from which the entries should be returned. + Start uint `param:"start"` + + // Type specifies the media type of the entries. + Type MediaType `param:"type"` +} + +// Browse browses the crunchyroll catalog filtered by the specified options and returns all found series and movies within the given limit. +func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m []*Movie, err error) { + query, err := encodeStructToQueryValues(options) + if err != nil { + return nil, nil, err + } + + browseEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/browse?%s&n=%d&locale=%s", + query, limit, c.Locale) + resp, err := c.request(browseEndpoint, http.MethodGet) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'browse' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + switch item.(map[string]interface{})["type"] { + case "series": + series := &Series{ + crunchy: c, + } + if err := decodeMapToStruct(item, series); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { + return nil, nil, err + } + + s = append(s, series) + case "movie_listing": + movie := &Movie{ + crunchy: c, + } + if err := decodeMapToStruct(item, movie); err != nil { + return nil, nil, err + } + + m = append(m, movie) + } + } + + return s, m, nil +} + +// FindVideoByName finds a Video (Season or Movie) by its name. +// Use this in combination with ParseVideoURL and hand over the corresponding results +// to this function. +// +// Deprecated: Use Search instead. The first result sometimes isn't the correct one +// so this function is inaccurate in some cases. +// See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information. +func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { + s, m, err := c.Search(seriesName, 1) + if err != nil { + return nil, err + } + + if len(s) > 0 { + return s[0], nil + } else if len(m) > 0 { + return m[0], nil + } + return nil, fmt.Errorf("no series or movie could be found") +} + +// FindEpisodeByName finds an episode by its crunchyroll series name and episode title. +// Use this in combination with ParseEpisodeURL and hand over the corresponding results +// to this function. +func (c *Crunchyroll) FindEpisodeByName(seriesName, episodeTitle string) ([]*Episode, error) { + series, _, err := c.Search(seriesName, 5) + if err != nil { + return nil, err + } + + var matchingEpisodes []*Episode + for _, s := range series { + seasons, err := s.Seasons() + if err != nil { + return nil, err + } + + for _, season := range seasons { + episodes, err := season.Episodes() + if err != nil { + return nil, err + } + for _, episode := range episodes { + if episode.SlugTitle == episodeTitle { + matchingEpisodes = append(matchingEpisodes, episode) + } + } + } + } + + return matchingEpisodes, nil +} + +// Search searches a query and returns all found series and movies within the given limit. +func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) { + searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s", + query, limit, c.Locale) + resp, err := c.request(searchEndpoint, http.MethodGet) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'search' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + item := item.(map[string]interface{}) + if item["total"].(float64) > 0 { + switch item["type"] { + case "series": + for _, series := range item["items"].([]interface{}) { + series2 := &Series{ + crunchy: c, + } + if err := decodeMapToStruct(series, series2); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(series.(map[string]interface{})["series_metadata"].(map[string]interface{}), series2); err != nil { + return nil, nil, err + } + + s = append(s, series2) + } + case "movie_listing": + for _, movie := range item["items"].([]interface{}) { + movie2 := &Movie{ + crunchy: c, + } + if err := decodeMapToStruct(movie, movie2); err != nil { + return nil, nil, err + } + + m = append(m, movie2) + } + } + } + } + + return s, m, nil +} diff --git a/simulcast.go b/simulcast.go index d02f348..e77d2f8 100644 --- a/simulcast.go +++ b/simulcast.go @@ -1,5 +1,37 @@ package crunchyroll +import ( + "encoding/json" + "fmt" + "net/http" +) + +// Simulcasts returns all available simulcast seasons for the current locale. +func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { + seasonListEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/season_list?locale=%s", c.Locale) + resp, err := c.request(seasonListEndpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'season_list' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + simulcast := &Simulcast{} + if err := decodeMapToStruct(item, simulcast); err != nil { + return nil, err + } + + s = append(s, simulcast) + } + + return s, nil +} + // Simulcast contains all information about a simulcast season. type Simulcast struct { ID string `json:"id"` diff --git a/suggestions.go b/suggestions.go new file mode 100644 index 0000000..d6bd770 --- /dev/null +++ b/suggestions.go @@ -0,0 +1,82 @@ +package crunchyroll + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// Recommendations returns series and movie recommendations from crunchyroll based on the currently logged in account within the given limit. +func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err error) { + recommendationsEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/recommendations?n=%d&locale=%s", + c.Config.AccountID, limit, c.Locale) + resp, err := c.request(recommendationsEndpoint, http.MethodGet) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'recommendations' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + switch item.(map[string]interface{})["type"] { + case "series": + series := &Series{ + crunchy: c, + } + if err := decodeMapToStruct(item, series); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { + return nil, nil, err + } + + s = append(s, series) + case "movie_listing": + movie := &Movie{ + crunchy: c, + } + if err := decodeMapToStruct(item, movie); err != nil { + return nil, nil, err + } + + m = append(m, movie) + } + } + + return s, m, nil +} + +// UpNext returns the episodes that are up next based on the currently logged in account within the given limit. +func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { + upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", + c.Config.AccountID, limit, c.Locale) + resp, err := c.request(upNextAccountEndpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'up_next_account' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + panel := item.(map[string]interface{})["panel"] + + episode := &Episode{ + crunchy: c, + } + if err := decodeMapToStruct(panel, episode); err != nil { + return nil, err + } + + e = append(e, episode) + } + + return e, nil +} diff --git a/video.go b/video.go index 3a67f5c..2d76499 100644 --- a/video.go +++ b/video.go @@ -206,6 +206,49 @@ func (s *Series) RemoveFromWatchlist() error { return err } +// Similar returns similar series and movies to the current series within the given limit. +func (s *Series) Similar(limit uint) (ss []*Series, m []*Movie, err error) { + similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", + s.crunchy.Config.AccountID, s.ID, limit, s.crunchy.Locale) + resp, err := s.crunchy.request(similarToEndpoint, http.MethodGet) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, nil, fmt.Errorf("failed to parse 'similar_to' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + switch item.(map[string]interface{})["type"] { + case "series": + series := &Series{ + crunchy: s.crunchy, + } + if err := decodeMapToStruct(item, series); err != nil { + return nil, nil, err + } + if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { + return nil, nil, err + } + + ss = append(ss, series) + case "movie_listing": + movie := &Movie{ + crunchy: s.crunchy, + } + if err := decodeMapToStruct(item, movie); err != nil { + return nil, nil, err + } + + m = append(m, movie) + } + } + return +} + // Seasons returns all seasons of a series. func (s *Series) Seasons() (seasons []*Season, err error) { if s.children != nil { diff --git a/watch_history.go b/watch_history.go new file mode 100644 index 0000000..d9ce265 --- /dev/null +++ b/watch_history.go @@ -0,0 +1,45 @@ +package crunchyroll + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// WatchHistory returns the history of watched episodes based on the currently logged in account from the given page with the given size. +func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, err error) { + watchHistoryEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/watch-history/%s?page=%d&page_size=%d&locale=%s", + c.Config.AccountID, page, size, c.Locale) + resp, err := c.request(watchHistoryEndpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { + return nil, fmt.Errorf("failed to parse 'watch-history' response: %w", err) + } + + for _, item := range jsonBody["items"].([]interface{}) { + panel := item.(map[string]interface{})["panel"] + + episode := &Episode{ + crunchy: c, + } + if err := decodeMapToStruct(panel, episode); err != nil { + return nil, err + } + + historyEpisode := &HistoryEpisode{ + Episode: episode, + } + if err := decodeMapToStruct(item, historyEpisode); err != nil { + return nil, err + } + + e = append(e, historyEpisode) + } + + return e, nil +} diff --git a/watchlist.go b/watchlist.go index 73fbb3d..b4db34d 100644 --- a/watchlist.go +++ b/watchlist.go @@ -1,5 +1,93 @@ package crunchyroll +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" +) + +// WatchlistLanguageType represents a filter type to filter Crunchyroll.Watchlist entries after sub or dub. +type WatchlistLanguageType int + +const ( + WatchlistLanguageSubbed WatchlistLanguageType = iota + 1 + WatchlistLanguageDubbed +) + +// WatchlistContentType represents a filter type to filter Crunchyroll.Watchlist entries if they're series or movies. +type WatchlistContentType string + +const ( + WatchlistContentSeries WatchlistContentType = "series" + WatchlistContentMovies = "movie_listing" +) + +// WatchlistOptions represents options for receiving the user watchlist. +type WatchlistOptions struct { + // OrderAsc specified whether the results should be order ascending or descending. + OrderAsc bool + + // OnlyFavorites specifies whether only episodes which are marked as favorite should be returned. + OnlyFavorites bool + + // LanguageType specifies whether returning episodes should be only subbed or dubbed. + LanguageType WatchlistLanguageType + + // ContentType specified whether returning videos should only be series episodes or movies. + // But tbh all movies I've searched on crunchy were flagged as series too, so this + // parameter is kinda useless. + ContentType WatchlistContentType +} + +// Watchlist returns the watchlist entries for the currently logged in user. +func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*WatchlistEntry, error) { + values := url.Values{} + if options.OrderAsc { + values.Set("order", "asc") + } else { + values.Set("order", "desc") + } + if options.OnlyFavorites { + values.Set("only_favorites", "true") + } + switch options.LanguageType { + case WatchlistLanguageSubbed: + values.Set("is_subbed", "true") + case WatchlistLanguageDubbed: + values.Set("is_dubbed", "true") + } + values.Set("n", strconv.Itoa(int(limit))) + values.Set("locale", string(c.Locale)) + + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/%s/watchlist?%s", c.Config.AccountID, values.Encode()) + resp, err := c.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jsonBody map[string]interface{} + json.NewDecoder(resp.Body).Decode(&jsonBody) + + var watchlistEntries []*WatchlistEntry + if err := decodeMapToStruct(jsonBody["items"], &watchlistEntries); err != nil { + return nil, err + } + + for _, entry := range watchlistEntries { + switch entry.Panel.Type { + case WatchlistEntryEpisode: + entry.Panel.EpisodeMetadata.crunchy = c + case WatchlistEntrySeries: + entry.Panel.SeriesMetadata.crunchy = c + } + } + + return watchlistEntries, nil +} + // WatchlistEntry contains information about an entry on the watchlist. type WatchlistEntry struct { Panel Panel `json:"panel"` From f71846628daf690ea7dd0a9ab32760658fd155e9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 Jun 2022 22:02:18 +0200 Subject: [PATCH 123/630] Refactor crunchyroll.go --- crunchyroll.go | 125 +++++++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index a83415c..214cbe4 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -36,39 +36,6 @@ const ( MediaTypeMovie = "movie_listing" ) -type Crunchyroll struct { - // Client is the http.Client to perform all requests over. - Client *http.Client - // Context can be used to stop requests with Client and is context.Background by default. - Context context.Context - // Locale specifies in which language all results should be returned / requested. - Locale LOCALE - // EtpRt is the crunchyroll beta equivalent to a session id (prior SessionID field in - // this struct in v2 and below). - EtpRt string - - // Config stores parameters which are needed by some api calls. - Config struct { - TokenType string - AccessToken string - - Bucket string - - CountryCode string - Premium bool - Channel string - Policy string - Signature string - KeyPairID string - AccountID string - ExternalID string - MaturityRating string - } - - // If cache is true, internal caching is enabled. - cache bool -} - type loginResponse struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` @@ -250,6 +217,69 @@ func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *htt return crunchy, nil } +type Crunchyroll struct { + // Client is the http.Client to perform all requests over. + Client *http.Client + // Context can be used to stop requests with Client and is context.Background by default. + Context context.Context + // Locale specifies in which language all results should be returned / requested. + Locale LOCALE + // EtpRt is the crunchyroll beta equivalent to a session id (prior SessionID field in + // this struct in v2 and below). + EtpRt string + + // Config stores parameters which are needed by some api calls. + Config struct { + TokenType string + AccessToken string + + Bucket string + + CountryCode string + Premium bool + Channel string + Policy string + Signature string + KeyPairID string + AccountID string + ExternalID string + MaturityRating string + } + + // If cache is true, internal caching is enabled. + cache bool +} + +// IsCaching returns if data gets cached or not. +// See SetCaching for more information. +func (c *Crunchyroll) IsCaching() bool { + return c.cache +} + +// SetCaching enables or disables internal caching of requests made. +// Caching is enabled by default. +// If it is disabled the already cached data still gets called. +// The best way to prevent this is to create a complete new Crunchyroll struct. +func (c *Crunchyroll) SetCaching(caching bool) { + c.cache = caching +} + +// request is a base function which handles simple api requests. +func (c *Crunchyroll) request(endpoint string, method string) (*http.Response, error) { + req, err := http.NewRequest(method, endpoint, nil) + if err != nil { + return nil, err + } + return c.requestFull(req) +} + +// requestFull is a base function which handles full user controlled api requests. +func (c *Crunchyroll) requestFull(req *http.Request) (*http.Response, error) { + req.Header.Add("Authorization", fmt.Sprintf("%s %s", c.Config.TokenType, c.Config.AccessToken)) + + return request(req, c.Client) +} + func request(req *http.Request, client *http.Client) (*http.Response, error) { resp, err := client.Do(req) if err == nil { @@ -298,32 +328,3 @@ func request(req *http.Request, client *http.Client) (*http.Response, error) { } return resp, err } - -// request is a base function which handles api requests. -func (c *Crunchyroll) request(endpoint string, method string) (*http.Response, error) { - req, err := http.NewRequest(method, endpoint, nil) - if err != nil { - return nil, err - } - return c.requestFull(req) -} - -func (c *Crunchyroll) requestFull(req *http.Request) (*http.Response, error) { - req.Header.Add("Authorization", fmt.Sprintf("%s %s", c.Config.TokenType, c.Config.AccessToken)) - - return request(req, c.Client) -} - -// IsCaching returns if data gets cached or not. -// See SetCaching for more information. -func (c *Crunchyroll) IsCaching() bool { - return c.cache -} - -// SetCaching enables or disables internal caching of requests made. -// Caching is enabled by default. -// If it is disabled the already cached data still gets called. -// The best way to prevent this is to create a complete new Crunchyroll struct. -func (c *Crunchyroll) SetCaching(caching bool) { - c.cache = caching -} From 2067c50937497fadf540107e5502ee005e9433b8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 Jun 2022 22:04:22 +0200 Subject: [PATCH 124/630] Add logout endpoint --- crunchyroll.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crunchyroll.go b/crunchyroll.go index 214cbe4..9c786c6 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -250,6 +250,16 @@ type Crunchyroll struct { cache bool } +// Logout logs the user out which invalidates the current session. +// You have to call a login method again and create a new Crunchyroll instance +// if you want to perform any further actions since this instance is not usable +// anymore after calling this. +func (c *Crunchyroll) Logout() error { + endpoint := "https://crunchyroll.com/logout" + _, err := c.request(endpoint, http.MethodGet) + return err +} + // IsCaching returns if data gets cached or not. // See SetCaching for more information. func (c *Crunchyroll) IsCaching() bool { From 810f3ae12ea332d83a550a561d3495ed7334105c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 01:35:26 +0000 Subject: [PATCH 125/630] Bump github.com/spf13/cobra from 1.4.0 to 1.5.0 Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 9a38468..99877f2 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/grafov/m3u8 v0.11.1 - github.com/spf13/cobra v1.4.0 + github.com/spf13/cobra v1.5.0 ) require ( diff --git a/go.sum b/go.sum index 34693ca..aa512f1 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From bfad0caa9a95fc93b663f6897b25acbf0e596f38 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 13:52:10 +0200 Subject: [PATCH 126/630] Update email language and video sub language setting function names --- account.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/account.go b/account.go index f2d6a70..cb45a77 100644 --- a/account.go +++ b/account.go @@ -67,8 +67,8 @@ type Account struct { Wallpaper *Wallpaper `json:"wallpaper"` } -// UpdateEmailLanguage sets in which language emails should be received. -func (a *Account) UpdateEmailLanguage(language LOCALE) error { +// UpdatePreferredEmailLanguage sets in which language emails should be received. +func (a *Account) UpdatePreferredEmailLanguage(language LOCALE) error { err := a.updatePreferences("preferred_communication_language", string(language)) if err == nil { a.PreferredCommunicationLanguage = language @@ -76,8 +76,8 @@ func (a *Account) UpdateEmailLanguage(language LOCALE) error { return err } -// UpdateVideoSubtitleLanguage sets in which language default subtitles should be shown -func (a *Account) UpdateVideoSubtitleLanguage(language LOCALE) error { +// UpdatePreferredVideoSubtitleLanguage sets in which language default subtitles should be shown +func (a *Account) UpdatePreferredVideoSubtitleLanguage(language LOCALE) error { err := a.updatePreferences("preferred_content_subtitle_language", string(language)) if err == nil { a.PreferredContentSubtitleLanguage = language From a283ba724785d537c56ce3967dd0f1b2fe489290 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 14:27:15 +0200 Subject: [PATCH 127/630] Add functions to get wallpaper urls --- wallpaper.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wallpaper.go b/wallpaper.go index f64cedf..da7a032 100644 --- a/wallpaper.go +++ b/wallpaper.go @@ -1,4 +1,14 @@ package crunchyroll +import "fmt" + // Wallpaper contains a wallpaper name which can be set via Account.ChangeWallpaper. type Wallpaper string + +func (w *Wallpaper) TinyUrl() string { + return fmt.Sprintf("https://static.crunchyroll.com/assets/wallpaper/360x115/%s", *w) +} + +func (w *Wallpaper) BigUrl() string { + return fmt.Sprintf("https://static.crunchyroll.com/assets/wallpaper/1920x400/%s", *w) +} From 28070bd32dbd8d5f1c126836ce130b5d1a79323a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 16:44:34 +0200 Subject: [PATCH 128/630] Add function to check if a comment is marked as spoiler --- comment.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/comment.go b/comment.go index 4dba10d..47005cf 100644 --- a/comment.go +++ b/comment.go @@ -71,6 +71,16 @@ func (c *Comment) Delete() error { return nil } +// IsSpoiler returns if the comment is marked as spoiler or not. +func (c *Comment) IsSpoiler() bool { + for _, flag := range c.Flags { + if flag == "spoiler" { + return true + } + } + return false +} + // MarkAsSpoiler marks the current comment as spoiler. Works only if the user has written the comment, // and it isn't already marked as spoiler. func (c *Comment) MarkAsSpoiler() error { From ead1db2be8f7bc46c9ce60e7be24acb946a626e0 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 16:50:48 +0200 Subject: [PATCH 129/630] Add function to check if a comment is liked by the logged-in user --- comment.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/comment.go b/comment.go index 47005cf..762b57b 100644 --- a/comment.go +++ b/comment.go @@ -143,6 +143,16 @@ func (c *Comment) Like() error { return nil } +// Liked returns if the user has liked the comment. +func (c *Comment) Liked() bool { + for _, flag := range c.Flags { + if flag == "liked" { + return true + } + } + return false +} + // RemoveLike removes the like from the comment. Works only if the user has liked it. func (c *Comment) RemoveLike() error { if err := c.unVote("like", "liked"); err != nil { From 9919a48e9ae52ea6050537f5a4adca9723b8e4b6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 16:57:49 +0200 Subject: [PATCH 130/630] Change 'UnreportComment' to 'RemoveReport' --- comment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/comment.go b/comment.go index 762b57b..811433f 100644 --- a/comment.go +++ b/comment.go @@ -217,9 +217,9 @@ func (c *Comment) Report() error { return c.vote("inappropriate", "reported") } -// UnreportComment removes the report request from the comment. Only works if the user +// RemoveReport removes the report request from the comment. Only works if the user // has reported the comment. -func (c *Comment) UnreportComment() error { +func (c *Comment) RemoveReport() error { return c.unVote("inappropriate", "reported") } From f03287856b3f509f9ac1fbe68fe9580deef13a8b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 16:59:04 +0200 Subject: [PATCH 131/630] Add function to check if comment is reported --- comment.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/comment.go b/comment.go index 811433f..98c400c 100644 --- a/comment.go +++ b/comment.go @@ -217,6 +217,10 @@ func (c *Comment) Report() error { return c.vote("inappropriate", "reported") } +func (c *Comment) IsReported() bool { + return c.markedAs("reported") +} + // RemoveReport removes the report request from the comment. Only works if the user // has reported the comment. func (c *Comment) RemoveReport() error { From 0521895f11e9cbafe49585f1fe65f637f888036c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 16:59:54 +0200 Subject: [PATCH 132/630] Add function to check if comment is flagged as spoiler --- comment.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/comment.go b/comment.go index 98c400c..dc68201 100644 --- a/comment.go +++ b/comment.go @@ -233,6 +233,10 @@ func (c *Comment) FlagAsSpoiler() error { return c.vote("spoiler", "spoiler") } +func (c *Comment) IsFlaggedAsSpoiler() bool { + return c.markedAs("spoiler") +} + // UnflagAsSpoiler rewokes the request to the user (and / or crunchyroll?) to mark the // comment as spoiler. Only works if the user has flagged the comment as spoiler. func (c *Comment) UnflagAsSpoiler() error { From 4cfcc11e206cda21ffd3ff7c7413bac088a5cdc6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 17:00:46 +0200 Subject: [PATCH 133/630] Simplified 'Liked' --- comment.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/comment.go b/comment.go index dc68201..333ec4b 100644 --- a/comment.go +++ b/comment.go @@ -145,12 +145,7 @@ func (c *Comment) Like() error { // Liked returns if the user has liked the comment. func (c *Comment) Liked() bool { - for _, flag := range c.Flags { - if flag == "liked" { - return true - } - } - return false + return c.markedAs("liked") } // RemoveLike removes the like from the comment. Works only if the user has liked it. From 14491ce6c954cdcd486ccc4876027ee003da5aee Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 17:01:08 +0200 Subject: [PATCH 134/630] Rename 'markedAs' to 'votedAs' --- comment.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/comment.go b/comment.go index 333ec4b..d58fade 100644 --- a/comment.go +++ b/comment.go @@ -86,7 +86,7 @@ func (c *Comment) IsSpoiler() bool { func (c *Comment) MarkAsSpoiler() error { if !c.IsOwner { return fmt.Errorf("cannot mark as spoiler, user is not the comment author") - } else if c.markedAs("spoiler") { + } else if c.votedAs("spoiler") { return fmt.Errorf("comment is already marked as spoiler") } endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) @@ -112,7 +112,7 @@ func (c *Comment) MarkAsSpoiler() error { func (c *Comment) UnmarkAsSpoiler() error { if !c.IsOwner { return fmt.Errorf("cannot mark as spoiler, user is not the comment author") - } else if !c.markedAs("spoiler") { + } else if !c.votedAs("spoiler") { return fmt.Errorf("comment is not marked as spoiler") } endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) @@ -145,7 +145,7 @@ func (c *Comment) Like() error { // Liked returns if the user has liked the comment. func (c *Comment) Liked() bool { - return c.markedAs("liked") + return c.votedAs("liked") } // RemoveLike removes the like from the comment. Works only if the user has liked it. @@ -213,7 +213,7 @@ func (c *Comment) Report() error { } func (c *Comment) IsReported() bool { - return c.markedAs("reported") + return c.votedAs("reported") } // RemoveReport removes the report request from the comment. Only works if the user @@ -229,7 +229,7 @@ func (c *Comment) FlagAsSpoiler() error { } func (c *Comment) IsFlaggedAsSpoiler() bool { - return c.markedAs("spoiler") + return c.votedAs("spoiler") } // UnflagAsSpoiler rewokes the request to the user (and / or crunchyroll?) to mark the @@ -238,7 +238,7 @@ func (c *Comment) UnflagAsSpoiler() error { return c.unVote("spoiler", "spoiler") } -func (c *Comment) markedAs(voteType string) bool { +func (c *Comment) votedAs(voteType string) bool { for _, userVote := range c.UserVotes { if userVote == voteType { return true @@ -248,7 +248,7 @@ func (c *Comment) markedAs(voteType string) bool { } func (c *Comment) vote(voteType, readableName string) error { - if c.markedAs(voteType) { + if c.votedAs(voteType) { return fmt.Errorf("comment is already marked as %s", readableName) } From e6172cdf90685e5f363c984f77301a34c2dc5076 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 17:16:47 +0200 Subject: [PATCH 135/630] Change watchlist option order type and content type --- watchlist.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/watchlist.go b/watchlist.go index b4db34d..6fa0bc0 100644 --- a/watchlist.go +++ b/watchlist.go @@ -16,18 +16,17 @@ const ( WatchlistLanguageDubbed ) -// WatchlistContentType represents a filter type to filter Crunchyroll.Watchlist entries if they're series or movies. -type WatchlistContentType string +type WatchlistOrderType string const ( - WatchlistContentSeries WatchlistContentType = "series" - WatchlistContentMovies = "movie_listing" + WatchlistOrderAsc = "asc" + WatchlistOrderDesc = "desc" ) // WatchlistOptions represents options for receiving the user watchlist. type WatchlistOptions struct { - // OrderAsc specified whether the results should be order ascending or descending. - OrderAsc bool + // Order specified whether the results should be order ascending or descending. + Order WatchlistOrderType // OnlyFavorites specifies whether only episodes which are marked as favorite should be returned. OnlyFavorites bool @@ -38,17 +37,16 @@ type WatchlistOptions struct { // ContentType specified whether returning videos should only be series episodes or movies. // But tbh all movies I've searched on crunchy were flagged as series too, so this // parameter is kinda useless. - ContentType WatchlistContentType + ContentType MediaType } // Watchlist returns the watchlist entries for the currently logged in user. func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*WatchlistEntry, error) { values := url.Values{} - if options.OrderAsc { - values.Set("order", "asc") - } else { - values.Set("order", "desc") + if options.Order == "" { + options.Order = WatchlistOrderDesc } + values.Set("order", string(options.Order)) if options.OnlyFavorites { values.Set("only_favorites", "true") } From 1a4abdc4d817f3a95ca09ee7e249a905ead3ac32 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 17:31:26 +0200 Subject: [PATCH 136/630] Made 'l' in crunchylist lowercase and made CrunchylistFromID priave --- crunchylists.go | 86 ++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/crunchylists.go b/crunchylists.go index a2a7a1f..b23ae00 100644 --- a/crunchylists.go +++ b/crunchylists.go @@ -8,7 +8,7 @@ import ( "time" ) -func (c *Crunchyroll) CrunchyLists() (*CrunchyLists, error) { +func (c *Crunchyroll) Crunchylists() (*Crunchylists, error) { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", c.Config.AccountID, c.Locale) resp, err := c.request(endpoint, http.MethodGet) if err != nil { @@ -16,27 +16,27 @@ func (c *Crunchyroll) CrunchyLists() (*CrunchyLists, error) { } defer resp.Body.Close() - crunchyLists := &CrunchyLists{ + crunchylists := &Crunchylists{ crunchy: c, } - json.NewDecoder(resp.Body).Decode(crunchyLists) - for _, item := range crunchyLists.Items { + json.NewDecoder(resp.Body).Decode(crunchylists) + for _, item := range crunchylists.Items { item.crunchy = c } - return crunchyLists, nil + return crunchylists, nil } -type CrunchyLists struct { +type Crunchylists struct { crunchy *Crunchyroll - Items []*CrunchyListPreview `json:"items"` + Items []*CrunchylistPreview `json:"items"` TotalPublic int `json:"total_public"` TotalPrivate int `json:"total_private"` MaxPrivate int `json:"max_private"` } -func (cl *CrunchyLists) Create(name string) (*CrunchyList, error) { +func (cl *Crunchylists) Create(name string) (*Crunchylist, error) { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", cl.crunchy.Config.AccountID, cl.crunchy.Locale) body, _ := json.Marshal(map[string]string{"title": name}) req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) @@ -53,10 +53,10 @@ func (cl *CrunchyLists) Create(name string) (*CrunchyList, error) { var jsonBody map[string]interface{} json.NewDecoder(resp.Body).Decode(&jsonBody) - return CrunchyListFromID(cl.crunchy, jsonBody["list_id"].(string)) + return crunchylistFromID(cl.crunchy, jsonBody["list_id"].(string)) } -type CrunchyListPreview struct { +type CrunchylistPreview struct { crunchy *Crunchyroll ListID string `json:"list_id"` @@ -66,32 +66,11 @@ type CrunchyListPreview struct { Title string `json:"title"` } -func (clp *CrunchyListPreview) CrunchyList() (*CrunchyList, error) { - return CrunchyListFromID(clp.crunchy, clp.ListID) +func (clp *CrunchylistPreview) Crunchylist() (*Crunchylist, error) { + return crunchylistFromID(clp.crunchy, clp.ListID) } -func CrunchyListFromID(crunchy *Crunchyroll, id string) (*CrunchyList, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", crunchy.Config.AccountID, id, crunchy.Locale) - resp, err := crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - crunchyList := &CrunchyList{ - crunchy: crunchy, - ID: id, - } - if err := json.NewDecoder(resp.Body).Decode(crunchyList); err != nil { - return nil, err - } - for _, item := range crunchyList.Items { - item.crunchy = crunchy - } - return crunchyList, nil -} - -type CrunchyList struct { +type Crunchylist struct { crunchy *Crunchyroll ID string `json:"id"` @@ -101,14 +80,14 @@ type CrunchyList struct { Title string `json:"title"` IsPublic bool `json:"is_public"` ModifiedAt time.Time `json:"modified_at"` - Items []*CrunchyListItem `json:"items"` + Items []*CrunchylistItem `json:"items"` } -func (cl *CrunchyList) AddSeries(series *Series) error { +func (cl *Crunchylist) AddSeries(series *Series) error { return cl.AddSeriesFromID(series.ID) } -func (cl *CrunchyList) AddSeriesFromID(id string) error { +func (cl *Crunchylist) AddSeriesFromID(id string) error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) body, _ := json.Marshal(map[string]string{"content_id": id}) req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) @@ -120,23 +99,23 @@ func (cl *CrunchyList) AddSeriesFromID(id string) error { return err } -func (cl *CrunchyList) RemoveSeries(series *Series) error { +func (cl *Crunchylist) RemoveSeries(series *Series) error { return cl.RemoveSeriesFromID(series.ID) } -func (cl *CrunchyList) RemoveSeriesFromID(id string) error { +func (cl *Crunchylist) RemoveSeriesFromID(id string) error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, id, cl.crunchy.Locale) _, err := cl.crunchy.request(endpoint, http.MethodDelete) return err } -func (cl *CrunchyList) Delete() error { +func (cl *Crunchylist) Delete() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) _, err := cl.crunchy.request(endpoint, http.MethodDelete) return err } -func (cl *CrunchyList) Rename(name string) error { +func (cl *Crunchylist) Rename(name string) error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) body, _ := json.Marshal(map[string]string{"title": name}) req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) @@ -151,7 +130,28 @@ func (cl *CrunchyList) Rename(name string) error { return err } -type CrunchyListItem struct { +func crunchylistFromID(crunchy *Crunchyroll, id string) (*Crunchylist, error) { + endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", crunchy.Config.AccountID, id, crunchy.Locale) + resp, err := crunchy.request(endpoint, http.MethodGet) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + crunchyList := &Crunchylist{ + crunchy: crunchy, + ID: id, + } + if err := json.NewDecoder(resp.Body).Decode(crunchyList); err != nil { + return nil, err + } + for _, item := range crunchyList.Items { + item.crunchy = crunchy + } + return crunchyList, nil +} + +type CrunchylistItem struct { crunchy *Crunchyroll ListID string `json:"list_id"` @@ -160,7 +160,7 @@ type CrunchyListItem struct { Panel Panel `json:"panel"` } -func (cli *CrunchyListItem) Remove() error { +func (cli *CrunchylistItem) Remove() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s", cli.crunchy.Config.AccountID, cli.ListID, cli.ID) _, err := cli.crunchy.request(endpoint, http.MethodDelete) return err From fa2321e9e8517ab66a55a95e0d7bfb71c495ec36 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 Jun 2022 17:40:29 +0200 Subject: [PATCH 137/630] Rename 'Logout' to 'InvalidateSession' --- crunchyroll.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchyroll.go b/crunchyroll.go index 9c786c6..a8b3284 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -250,11 +250,11 @@ type Crunchyroll struct { cache bool } -// Logout logs the user out which invalidates the current session. +// InvalidateSession logs the user out which invalidates the current session. // You have to call a login method again and create a new Crunchyroll instance // if you want to perform any further actions since this instance is not usable // anymore after calling this. -func (c *Crunchyroll) Logout() error { +func (c *Crunchyroll) InvalidateSession() error { endpoint := "https://crunchyroll.com/logout" _, err := c.request(endpoint, http.MethodGet) return err From 2569ddd1c7ed1a6c3006fd87a5bc79bff2e11661 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 24 Jun 2022 11:34:02 +0200 Subject: [PATCH 138/630] Add docs to crunchylists --- crunchylists.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crunchylists.go b/crunchylists.go index b23ae00..a297da2 100644 --- a/crunchylists.go +++ b/crunchylists.go @@ -8,6 +8,7 @@ import ( "time" ) +// Crunchylists returns a struct to control crunchylists. func (c *Crunchyroll) Crunchylists() (*Crunchylists, error) { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", c.Config.AccountID, c.Locale) resp, err := c.request(endpoint, http.MethodGet) @@ -36,6 +37,7 @@ type Crunchylists struct { MaxPrivate int `json:"max_private"` } +// Create creates a new crunchylist with the given name. Duplicate names for lists are allowed. func (cl *Crunchylists) Create(name string) (*Crunchylist, error) { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", cl.crunchy.Config.AccountID, cl.crunchy.Locale) body, _ := json.Marshal(map[string]string{"title": name}) @@ -66,6 +68,7 @@ type CrunchylistPreview struct { Title string `json:"title"` } +// Crunchylist returns the belonging Crunchylist struct. func (clp *CrunchylistPreview) Crunchylist() (*Crunchylist, error) { return crunchylistFromID(clp.crunchy, clp.ListID) } @@ -83,10 +86,12 @@ type Crunchylist struct { Items []*CrunchylistItem `json:"items"` } +// AddSeries adds a series. func (cl *Crunchylist) AddSeries(series *Series) error { return cl.AddSeriesFromID(series.ID) } +// AddSeriesFromID adds a series from its id func (cl *Crunchylist) AddSeriesFromID(id string) error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) body, _ := json.Marshal(map[string]string{"content_id": id}) @@ -99,22 +104,26 @@ func (cl *Crunchylist) AddSeriesFromID(id string) error { return err } +// RemoveSeries removes a series func (cl *Crunchylist) RemoveSeries(series *Series) error { return cl.RemoveSeriesFromID(series.ID) } +// RemoveSeriesFromID removes a series by its id func (cl *Crunchylist) RemoveSeriesFromID(id string) error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, id, cl.crunchy.Locale) _, err := cl.crunchy.request(endpoint, http.MethodDelete) return err } +// Delete deleted the current crunchylist. func (cl *Crunchylist) Delete() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) _, err := cl.crunchy.request(endpoint, http.MethodDelete) return err } +// Rename renames the current crunchylist. func (cl *Crunchylist) Rename(name string) error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) body, _ := json.Marshal(map[string]string{"title": name}) @@ -160,6 +169,7 @@ type CrunchylistItem struct { Panel Panel `json:"panel"` } +// Remove removes the current item from its crunchylist. func (cli *CrunchylistItem) Remove() error { endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s", cli.crunchy.Config.AccountID, cli.ListID, cli.ID) _, err := cli.crunchy.request(endpoint, http.MethodDelete) From 3dcfbc0fbb5c00caad58d3309145356e21ea019b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 24 Jun 2022 11:51:12 +0200 Subject: [PATCH 139/630] Add docs to wallpaper --- wallpaper.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wallpaper.go b/wallpaper.go index da7a032..e99bafb 100644 --- a/wallpaper.go +++ b/wallpaper.go @@ -5,10 +5,12 @@ import "fmt" // Wallpaper contains a wallpaper name which can be set via Account.ChangeWallpaper. type Wallpaper string +// TinyUrl returns the url to the wallpaper in low resolution. func (w *Wallpaper) TinyUrl() string { return fmt.Sprintf("https://static.crunchyroll.com/assets/wallpaper/360x115/%s", *w) } +// BigUrl returns the url to the wallpaper in high resolution. func (w *Wallpaper) BigUrl() string { return fmt.Sprintf("https://static.crunchyroll.com/assets/wallpaper/1920x400/%s", *w) } From 79c3ba2636a188697a6161d0d68b4ba8cdedc664 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 24 Jun 2022 11:51:48 +0200 Subject: [PATCH 140/630] Refactor to use consts instead of string --- search.go | 8 ++++---- suggestions.go | 4 ++-- video.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/search.go b/search.go index d7f6e76..cbbf7e9 100644 --- a/search.go +++ b/search.go @@ -61,7 +61,7 @@ func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m for _, item := range jsonBody["items"].([]interface{}) { switch item.(map[string]interface{})["type"] { - case "series": + case MediaTypeSeries: series := &Series{ crunchy: c, } @@ -73,7 +73,7 @@ func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m } s = append(s, series) - case "movie_listing": + case MediaTypeMovie: movie := &Movie{ crunchy: c, } @@ -160,7 +160,7 @@ func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, item := item.(map[string]interface{}) if item["total"].(float64) > 0 { switch item["type"] { - case "series": + case MediaTypeSeries: for _, series := range item["items"].([]interface{}) { series2 := &Series{ crunchy: c, @@ -174,7 +174,7 @@ func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, s = append(s, series2) } - case "movie_listing": + case MediaTypeMovie: for _, movie := range item["items"].([]interface{}) { movie2 := &Movie{ crunchy: c, diff --git a/suggestions.go b/suggestions.go index d6bd770..35d7a9b 100644 --- a/suggestions.go +++ b/suggestions.go @@ -23,7 +23,7 @@ func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err for _, item := range jsonBody["items"].([]interface{}) { switch item.(map[string]interface{})["type"] { - case "series": + case MediaTypeSeries: series := &Series{ crunchy: c, } @@ -35,7 +35,7 @@ func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err } s = append(s, series) - case "movie_listing": + case MediaTypeMovie: movie := &Movie{ crunchy: c, } diff --git a/video.go b/video.go index 2d76499..04ea5e9 100644 --- a/video.go +++ b/video.go @@ -223,7 +223,7 @@ func (s *Series) Similar(limit uint) (ss []*Series, m []*Movie, err error) { for _, item := range jsonBody["items"].([]interface{}) { switch item.(map[string]interface{})["type"] { - case "series": + case MediaTypeSeries: series := &Series{ crunchy: s.crunchy, } @@ -235,7 +235,7 @@ func (s *Series) Similar(limit uint) (ss []*Series, m []*Movie, err error) { } ss = append(ss, series) - case "movie_listing": + case MediaTypeMovie: movie := &Movie{ crunchy: s.crunchy, } From 0fed0f8d3be02ecd395b4ece8467e40b2b08c2c9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 27 Jun 2022 22:31:36 +0200 Subject: [PATCH 141/630] Change license to GPL-3.0 --- LICENSE | 687 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 650 insertions(+), 37 deletions(-) diff --git a/LICENSE b/LICENSE index 36688ae..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -1,61 +1,674 @@ -Copyright ยฉ 2007 Free Software Foundation, Inc. <https://fsf.org/> + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. -0. Additional Definitions. + Preamble -As used herein, โ€œthis Licenseโ€ refers to version 3 of the GNU Lesser General Public License, and the โ€œGNU GPLโ€ refers to version 3 of the GNU General Public License. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. -โ€œThe Libraryโ€ refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. -An โ€œApplicationโ€ is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. -A โ€œCombined Workโ€ is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the โ€œLinked Versionโ€. + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. -The โ€œMinimal Corresponding Sourceโ€ for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. -The โ€œCorresponding Application Codeโ€ for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. -1. Exception to Section 3 of the GNU GPL. + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. -You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. -2. Conveying Modified Versions. + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. -If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. - a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or - b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. -3. Object Code Incorporating Material from Library Header Files. + The precise terms and conditions for copying, distribution and +modification follow. -The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + TERMS AND CONDITIONS - a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. - b) Accompany the object code with a copy of the GNU GPL and this license document. + 0. Definitions. -4. Combined Works. + "This License" refers to version 3 of the GNU General Public License. -You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. - a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. - b) Accompany the Combined Work with a copy of the GNU GPL and this license document. - c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. - d) Do one of the following: - 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. - 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. - e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. -5. Combined Libraries. + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. -You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + A "covered work" means either the unmodified Program or a work based +on the Program. - a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. - b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. -6. Revised Versions of the GNU Lesser General Public License. + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. -The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. -Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License โ€œor any later versionโ€ applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + 1. Source Code. -If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. From 8a3e42e4d1cb3b4447d878ee04867030d401f9b4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 27 Jun 2022 22:33:26 +0200 Subject: [PATCH 142/630] Remove library & refactor cli --- .github/workflows/ci.yml | 4 +- Makefile | 22 +- README.md | 80 ++-- account.go | 171 -------- category.go | 65 --- cmd/crunchyroll-go/main.go | 9 - .../cmd => commands}/archive.go | 4 +- .../cmd => commands}/download.go | 4 +- {cmd/crunchyroll-go/cmd => commands}/info.go | 2 +- .../crunchyroll-go/cmd => commands}/logger.go | 2 +- {cmd/crunchyroll-go/cmd => commands}/login.go | 20 +- {cmd/crunchyroll-go/cmd => commands}/root.go | 8 +- {cmd/crunchyroll-go/cmd => commands}/unix.go | 2 +- .../crunchyroll-go/cmd => commands}/update.go | 16 +- {cmd/crunchyroll-go/cmd => commands}/utils.go | 4 +- .../cmd => commands}/windows.go | 2 +- comment.go | 285 ------------- common.go | 31 -- crunchyroll-go.1 => crunchy-cli.1 | 49 ++- crunchylists.go | 177 -------- crunchyroll.go | 327 --------------- downloader.go | 395 ------------------ episode.go | 342 --------------- error.go | 17 - format.go | 52 --- go.mod | 3 +- go.sum | 2 + main.go | 7 + movie_listing.go | 92 ---- news.go | 55 --- parse.go | 69 --- search.go | 193 --------- season.go | 135 ------ simulcast.go | 45 -- stream.go | 129 ------ subtitle.go | 32 -- suggestions.go | 82 ---- url.go | 128 ------ utils.go | 84 ---- utils/locale.go | 60 --- utils/sort.go | 158 ------- video.go | 280 ------------- wallpaper.go | 16 - watch_history.go | 45 -- watchlist.go | 99 ----- 45 files changed, 117 insertions(+), 3687 deletions(-) delete mode 100644 account.go delete mode 100644 category.go delete mode 100644 cmd/crunchyroll-go/main.go rename {cmd/crunchyroll-go/cmd => commands}/archive.go (99%) rename {cmd/crunchyroll-go/cmd => commands}/download.go (99%) rename {cmd/crunchyroll-go/cmd => commands}/info.go (98%) rename {cmd/crunchyroll-go/cmd => commands}/logger.go (99%) rename {cmd/crunchyroll-go/cmd => commands}/login.go (88%) rename {cmd/crunchyroll-go/cmd => commands}/root.go (92%) rename {cmd/crunchyroll-go/cmd => commands}/unix.go (98%) rename {cmd/crunchyroll-go/cmd => commands}/update.go (87%) rename {cmd/crunchyroll-go/cmd => commands}/utils.go (99%) rename {cmd/crunchyroll-go/cmd => commands}/windows.go (97%) delete mode 100644 comment.go delete mode 100644 common.go rename crunchyroll-go.1 => crunchy-cli.1 (78%) delete mode 100644 crunchylists.go delete mode 100644 crunchyroll.go delete mode 100644 downloader.go delete mode 100644 episode.go delete mode 100644 error.go delete mode 100644 format.go create mode 100644 main.go delete mode 100644 movie_listing.go delete mode 100644 news.go delete mode 100644 parse.go delete mode 100644 search.go delete mode 100644 season.go delete mode 100644 simulcast.go delete mode 100644 stream.go delete mode 100644 subtitle.go delete mode 100644 suggestions.go delete mode 100644 url.go delete mode 100644 utils.go delete mode 100644 utils/locale.go delete mode 100644 utils/sort.go delete mode 100644 video.go delete mode 100644 wallpaper.go delete mode 100644 watch_history.go delete mode 100644 watchlist.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c54dd3..b2409a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: go-version: 1.18 - name: Build - run: go build -v cmd/crunchyroll-go/main.go + run: go build -v . - name: Test - run: go test -v cmd/crunchyroll-go/main.go + run: go test -v . diff --git a/Makefile b/Makefile index a747c1d..4266a91 100644 --- a/Makefile +++ b/Makefile @@ -6,26 +6,26 @@ DESTDIR= PREFIX=/usr build: - go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go + go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(BINARY_NAME) . clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* install: - install -Dm755 $(BINARY_NAME) $(DESTDIR)$(PREFIX)/bin/crunchyroll-go - ln -sf ./crunchyroll-go $(DESTDIR)$(PREFIX)/bin/crunchy - install -Dm644 crunchyroll-go.1 $(DESTDIR)$(PREFIX)/share/man/man1/crunchyroll-go.1 - install -Dm644 LICENSE $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE + install -Dm755 $(BINARY_NAME) $(DESTDIR)$(PREFIX)/bin/crunchy-cli + ln -sf ./crunchy-cli $(DESTDIR)$(PREFIX)/bin/crunchy + install -Dm644 crunchy-cli.1 $(DESTDIR)$(PREFIX)/share/man/man1/crunchy-cli.1 + install -Dm644 LICENSE $(DESTDIR)$(PREFIX)/share/licenses/crunchy-cli/LICENSE uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/crunchyroll-go + rm -f $(DESTDIR)$(PREFIX)/bin/crunchy-cli rm -f $(DESTDIR)$(PREFIX)/bin/crunchy - rm -f $(DESTDIR)$(PREFIX)/share/man/man1/crunchyroll-go.1 - rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE + rm -f $(DESTDIR)$(PREFIX)/share/man/man1/crunchy-cli.1 + rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchy-cli/LICENSE release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe . + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin . strip $(VERSION_BINARY_NAME)_linux diff --git a/README.md b/README.md index 46862f0..bce05b9 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,42 @@ -# crunchyroll-go +# crunchy-cli -A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](https://www.crunchyroll.com) api. To use it, you need a crunchyroll premium account to for full (api) access. +A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account to for full (api) access. <p align="center"> - <a href="https://github.com/ByteDream/crunchyroll-go"> - <img src="https://img.shields.io/github/languages/code-size/ByteDream/crunchyroll-go?style=flat-square" alt="Code size"> + <a href="https://github.com/ByteDream/crunchy-cli"> + <img src="https://img.shields.io/github/languages/code-size/ByteDream/crunchy-cli?style=flat-square" alt="Code size"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/releases/latest"> - <img src="https://img.shields.io/github/downloads/ByteDream/crunchyroll-go/total?style=flat-square" alt="Download Badge"> + <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/downloads/ByteDream/crunchy-cli/total?style=flat-square" alt="Download Badge"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/blob/master/LICENSE"> - <img src="https://img.shields.io/github/license/ByteDream/crunchyroll-go?style=flat-square" alt="License"> + <a href="https://github.com/ByteDream/crunchy-cli/blob/master/LICENSE"> + <img src="https://img.shields.io/github/license/ByteDream/crunchy-cli?style=flat-square" alt="License"> </a> <a href="https://golang.org"> - <img src="https://img.shields.io/github/go-mod/go-version/ByteDream/crunchyroll-go?style=flat-square" alt="Go version"> + <img src="https://img.shields.io/github/go-mod/go-version/ByteDream/crunchy-cli?style=flat-square" alt="Go version"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/releases/latest"> - <img src="https://img.shields.io/github/v/release/ByteDream/crunchyroll-go?style=flat-square" alt="Release"> + <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/v/release/ByteDream/crunchy-cli?style=flat-square" alt="Release"> </a> <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/actions/workflows/ci.yml"> - <img src="https://github.com/ByteDream/crunchyroll-go/workflows/CI/badge.svg?style=flat" alt="CI"> + <a href="https://github.com/ByteDream/crunchy-cli/actions/workflows/ci.yml"> + <img src="https://github.com/ByteDream/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> </a> </p> <p align="center"> <a href="#%EF%B8%8F-cli">CLI ๐Ÿ–ฅ๏ธ</a> โ€ข - <a href="#-library">Library ๐Ÿ“š</a> - โ€ข <a href="#%EF%B8%8F-disclaimer">Disclaimer โ˜๏ธ</a> โ€ข <a href="#-license">License โš–</a> </p> +_This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility. +See #39 for more information._ + # ๐Ÿ–ฅ๏ธ CLI ## โœจ Features @@ -46,37 +47,42 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http ## ๐Ÿ’พ Get the executable -- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchyroll-go/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_darwin) +- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchy-cli/releases/latest) or get it from below: + - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell $ yay -S crunchyroll-go ``` -- On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): +- <del> + + On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): ```shell $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` -- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 Mac): - - use `make` (requires `go` to be installed): + + </del> + <i>Currently not working because the repo got renamed!</i> +- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): + - use `make` (requires `go` to be installed): ```shell - $ git clone https://github.com/ByteDream/crunchyroll-go - $ cd crunchyroll-go + $ git clone https://github.com/ByteDream/crunchy-cli + $ cd crunchy-cli $ make $ sudo make install # <- only if you want to install it on your system ``` - - use `go`: + - use `go`: ```shell - $ git clone https://github.com/ByteDream/crunchyroll-go - $ cd crunchyroll-go - $ go build -o crunchy cmd/crunchyroll-go/main.go + $ git clone https://github.com/ByteDream/crunchy-cli + $ cd crunchy-cli + $ go build -o crunchy . ``` ## ๐Ÿ“ Examples -_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Cli), further usages and options are described there. +_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli), further usages and options are described there. ### Login @@ -163,7 +169,7 @@ The following flags can be (optional) passed to modify the [archive](#archive) p | `-l` | `--language` | Audio locale which should be downloaded. Can be used multiple times. | | `-d` | `--directory` | Directory to download the video(s) to. | | `-o` | `--output` | Name of the output file. | -| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Cli#archive) for more information. | +| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli#archive) for more information. | | `-c` | `--compress` | If is set, all output will be compresses into an archive. This flag sets the name of the compressed output file and the file ending specifies the compression algorithm (gzip, tar, zip are supported). | | `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | | `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | @@ -198,18 +204,6 @@ These flags you can use across every sub-command: | `-v` | Shows additional debug output. | | `-p` | Use a proxy to hide your ip / redirect your traffic. | -# ๐Ÿ“š Library - -Download the library via `go get` - -```shell -$ go get github.com/ByteDream/crunchyroll-go/v3 -``` - -The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go/v3). - -Examples how to use the library and some features of it are described in the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Library). - # โ˜๏ธ Disclaimer This tool is **ONLY** meant to be used for private purposes. To use this tool you need crunchyroll premium anyway, so there is no reason why rip and share the episodes. @@ -218,4 +212,4 @@ This tool is **ONLY** meant to be used for private purposes. To use this tool yo # โš– License -This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the [LICENSE](LICENSE) file for more details. +This project is licensed under the GNU General Public License v3.0 (GPL-3.0) - see the [LICENSE](LICENSE) file for more details. diff --git a/account.go b/account.go deleted file mode 100644 index cb45a77..0000000 --- a/account.go +++ /dev/null @@ -1,171 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "time" -) - -// Account returns information about the currently logged in crunchyroll account. -func (c *Crunchyroll) Account() (*Account, error) { - resp, err := c.request("https://beta.crunchyroll.com/accounts/v1/me", http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - account := &Account{ - crunchy: c, - } - - if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { - return nil, fmt.Errorf("failed to parse 'me' response: %w", err) - } - - resp, err = c.request("https://beta.crunchyroll.com/accounts/v1/me/profile", http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if err = json.NewDecoder(resp.Body).Decode(&account); err != nil { - return nil, fmt.Errorf("failed to parse 'profile' response: %w", err) - } - - return account, nil -} - -// Account contains information about a crunchyroll account. -type Account struct { - crunchy *Crunchyroll - - AccountID string `json:"account_id"` - ExternalID string `json:"external_id"` - EmailVerified bool `json:"email_verified"` - Created time.Time `json:"created"` - - Avatar string `json:"avatar"` - CrBetaOptIn bool `json:"cr_beta_opt_in"` - Email string `json:"email"` - MatureContentFlagManga string `json:"mature_content_flag_manga"` - MaturityRating string `json:"maturity_rating"` - OptOutAndroidInAppMarketing bool `json:"opt_out_android_in_app_marketing"` - OptOutFreeTrials bool `json:"opt_out_free_trials"` - OptOutNewMediaQueueUpdates bool `json:"opt_out_new_media_queue_updates"` - OptOutNewsletters bool `json:"opt_out_newsletters"` - OptOutPmUpdates bool `json:"opt_out_pm_updates"` - OptOutPromotionalUpdates bool `json:"opt_out_promotional_updates"` - OptOutQueueUpdates bool `json:"opt_out_queue_updates"` - OptOutStoreDeals bool `json:"opt_out_store_deals"` - PreferredCommunicationLanguage LOCALE `json:"preferred_communication_language"` - PreferredContentSubtitleLanguage LOCALE `json:"preferred_content_subtitle_language"` - QaUser bool `json:"qa_user"` - - Username string `json:"username"` - Wallpaper *Wallpaper `json:"wallpaper"` -} - -// UpdatePreferredEmailLanguage sets in which language emails should be received. -func (a *Account) UpdatePreferredEmailLanguage(language LOCALE) error { - err := a.updatePreferences("preferred_communication_language", string(language)) - if err == nil { - a.PreferredCommunicationLanguage = language - } - return err -} - -// UpdatePreferredVideoSubtitleLanguage sets in which language default subtitles should be shown -func (a *Account) UpdatePreferredVideoSubtitleLanguage(language LOCALE) error { - err := a.updatePreferences("preferred_content_subtitle_language", string(language)) - if err == nil { - a.PreferredContentSubtitleLanguage = language - } - return err -} - -// UpdateMatureVideoContent sets if mature video content / 18+ content should be shown -func (a *Account) UpdateMatureVideoContent(enabled bool) error { - if enabled { - return a.updatePreferences("maturity_rating", "M3") - } else { - return a.updatePreferences("maturity_rating", "M2") - } -} - -// UpdateMatureMangaContent sets if mature manga content / 18+ content should be shown -func (a *Account) UpdateMatureMangaContent(enabled bool) error { - if enabled { - return a.updatePreferences("mature_content_flag_manga", "1") - } else { - return a.updatePreferences("mature_content_flag_manga", "0") - } -} - -func (a *Account) updatePreferences(name, value string) error { - endpoint := "https://beta.crunchyroll.com/accounts/v1/me/profile" - body, _ := json.Marshal(map[string]string{name: value}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = a.crunchy.requestFull(req) - return err -} - -// ChangePassword changes the password for the current account. -func (a *Account) ChangePassword(currentPassword, newPassword string) error { - endpoint := "https://beta.crunchyroll.com/accounts/v1/me/credentials" - body, _ := json.Marshal(map[string]string{"accountId": a.AccountID, "current_password": currentPassword, "new_password": newPassword}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = a.crunchy.requestFull(req) - return err -} - -// ChangeEmail changes the email address for the current account. -func (a *Account) ChangeEmail(currentPassword, newEmail string) error { - endpoint := "https://beta.crunchyroll.com/accounts/v1/me/credentials" - body, _ := json.Marshal(map[string]string{"current_password": currentPassword, "email": newEmail}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = a.crunchy.requestFull(req) - return err -} - -// AvailableWallpapers returns all available wallpapers which can be set as profile wallpaper. -func (a *Account) AvailableWallpapers() (w []*Wallpaper, err error) { - endpoint := "https://beta.crunchyroll.com/assets/v1/wallpaper" - resp, err := a.crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]any - json.NewDecoder(resp.Body).Decode(&jsonBody) - - err = decodeMapToStruct(jsonBody["items"].([]any), &w) - return -} - -// ChangeWallpaper changes the profile wallpaper of the current user. Use AvailableWallpapers -// to get all available ones. -func (a *Account) ChangeWallpaper(wallpaper *Wallpaper) error { - endpoint := "https://beta.crunchyroll.com/accounts/v1/me/profile" - body, _ := json.Marshal(map[string]string{"wallpaper": string(*wallpaper)}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - _, err = a.crunchy.requestFull(req) - return err -} diff --git a/category.go b/category.go deleted file mode 100644 index eccefff..0000000 --- a/category.go +++ /dev/null @@ -1,65 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// Categories returns all available categories and possible subcategories. -func (c *Crunchyroll) Categories(includeSubcategories bool) (ca []*Category, err error) { - tenantCategoriesEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/tenant_categories?include_subcategories=%t&locale=%s", - includeSubcategories, c.Locale) - resp, err := c.request(tenantCategoriesEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'tenant_categories' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - category := &Category{} - if err := decodeMapToStruct(item, category); err != nil { - return nil, err - } - - ca = append(ca, category) - } - - return ca, nil -} - -// Category contains all information about a category. -type Category struct { - Category string `json:"tenant_category"` - - SubCategories []struct { - Category string `json:"tenant_category"` - ParentCategory string `json:"parent_category"` - - Localization struct { - Title string `json:"title"` - Description string `json:"description"` - Locale LOCALE `json:"locale"` - } `json:"localization"` - - Slug string `json:"slug"` - } `json:"sub_categories"` - - Images struct { - Background []Image `json:"background"` - Low []Image `json:"low"` - } `json:"images"` - - Localization struct { - Title string `json:"title"` - Description string `json:"description"` - Locale LOCALE `json:"locale"` - } `json:"localization"` - - Slug string `json:"slug"` -} diff --git a/cmd/crunchyroll-go/main.go b/cmd/crunchyroll-go/main.go deleted file mode 100644 index 0ced8aa..0000000 --- a/cmd/crunchyroll-go/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/ByteDream/crunchyroll-go/v3/cmd/crunchyroll-go/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/cmd/crunchyroll-go/cmd/archive.go b/commands/archive.go similarity index 99% rename from cmd/crunchyroll-go/cmd/archive.go rename to commands/archive.go index 0d47917..5ad6900 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/commands/archive.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "archive/tar" @@ -188,7 +188,7 @@ func archive(urls []string) error { out.StopProgress("Failed to parse url %d", i+1) if crunchy.Config.Premium { out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchy-cli/issues/22 for more information") } return err } diff --git a/cmd/crunchyroll-go/cmd/download.go b/commands/download.go similarity index 99% rename from cmd/crunchyroll-go/cmd/download.go rename to commands/download.go index 74cc3f2..82b68bf 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/commands/download.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "context" @@ -134,7 +134,7 @@ func download(urls []string) error { out.StopProgress("Failed to parse url %d", i+1) if crunchy.Config.Premium { out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchy-cli/issues/22 for more information") } return err } diff --git a/cmd/crunchyroll-go/cmd/info.go b/commands/info.go similarity index 98% rename from cmd/crunchyroll-go/cmd/info.go rename to commands/info.go index f5ed995..fe9693d 100644 --- a/cmd/crunchyroll-go/cmd/info.go +++ b/commands/info.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "fmt" diff --git a/cmd/crunchyroll-go/cmd/logger.go b/commands/logger.go similarity index 99% rename from cmd/crunchyroll-go/cmd/logger.go rename to commands/logger.go index 83bc214..fab8515 100644 --- a/cmd/crunchyroll-go/cmd/logger.go +++ b/commands/logger.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "fmt" diff --git a/cmd/crunchyroll-go/cmd/login.go b/commands/login.go similarity index 88% rename from cmd/crunchyroll-go/cmd/login.go rename to commands/login.go index d8cc5c3..a710bb6 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/commands/login.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "bytes" @@ -122,13 +122,13 @@ func loginCredentials(user, password string) error { credentials = []byte(fmt.Sprintf("%s\n%s", user, password)) } - os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), credentials, 0600); err != nil { + os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755) + if err = os.WriteFile(filepath.Join(configDir, "crunchy-cli", "crunchy"), credentials, 0600); err != nil { return err } if !loginEncryptFlag { out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s). "+ - "To encrypt it, use the `--encrypt` flag", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + "To encrypt it, use the `--encrypt` flag", filepath.Join(configDir, "crunchy-cli", "crunchy")) } } } @@ -157,11 +157,11 @@ func loginSessionID(sessionID string) error { if configDir, err := os.UserConfigDir(); err != nil { return fmt.Errorf("could not save credentials persistent: %w", err) } else { - os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(c.EtpRt), 0600); err != nil { + os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755) + if err = os.WriteFile(filepath.Join(configDir, "crunchy-cli", "crunchy"), []byte(c.EtpRt), 0600); err != nil { return err } - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchy-cli", "crunchy")) } } if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { @@ -187,11 +187,11 @@ func loginEtpRt(etpRt string) error { if configDir, err := os.UserConfigDir(); err != nil { return fmt.Errorf("could not save credentials persistent: %w", err) } else { - os.MkdirAll(filepath.Join(configDir, "crunchyroll-go"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchyroll-go", "crunchy"), []byte(etpRt), 0600); err != nil { + os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755) + if err = os.WriteFile(filepath.Join(configDir, "crunchy-cli", "crunchy"), []byte(etpRt), 0600); err != nil { return err } - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchyroll-go", "crunchy")) + out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchy-cli", "crunchy")) } } if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(etpRt), 0600); err != nil { diff --git a/cmd/crunchyroll-go/cmd/root.go b/commands/root.go similarity index 92% rename from cmd/crunchyroll-go/cmd/root.go rename to commands/root.go index 02873f1..ffcf35a 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/commands/root.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "context" @@ -27,9 +27,9 @@ var ( ) var rootCmd = &cobra.Command{ - Use: "crunchyroll-go", + Use: "crunchy-cli", Version: Version, - Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchyroll-go/wiki", + Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchy-cli/wiki", SilenceErrors: true, SilenceUsage: true, @@ -54,7 +54,7 @@ func init() { rootCmd.PersistentFlags().StringVarP(&proxyFlag, "proxy", "p", "", "Proxy to use") - rootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchyroll-go/%s", Version), "Useragent to do all request with") + rootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchy-cli/%s", Version), "Useragent to do all request with") } func Execute() { diff --git a/cmd/crunchyroll-go/cmd/unix.go b/commands/unix.go similarity index 98% rename from cmd/crunchyroll-go/cmd/unix.go rename to commands/unix.go index 962088f..955695c 100644 --- a/cmd/crunchyroll-go/cmd/unix.go +++ b/commands/unix.go @@ -1,6 +1,6 @@ //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos -package cmd +package commands import ( "bufio" diff --git a/cmd/crunchyroll-go/cmd/update.go b/commands/update.go similarity index 87% rename from cmd/crunchyroll-go/cmd/update.go rename to commands/update.go index c8512e6..fb4c1e9 100644 --- a/cmd/crunchyroll-go/cmd/update.go +++ b/commands/update.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "encoding/json" @@ -38,7 +38,7 @@ func init() { func update() error { var release map[string]interface{} - resp, err := client.Get("https://api.github.com/repos/ByteDream/crunchyroll-go/releases/latest") + resp, err := client.Get("https://api.github.com/repos/ByteDream/crunchy-cli/releases/latest") if err != nil { return err } @@ -80,20 +80,20 @@ func update() error { return nil } - out.Info("A new version is available (%s): https://github.com/ByteDream/crunchyroll-go/releases/tag/v%s", releaseVersion, releaseVersion) + out.Info("A new version is available (%s): https://github.com/ByteDream/crunchy-cli/releases/tag/v%s", releaseVersion, releaseVersion) if updateInstallFlag { if runtime.GOARCH != "amd64" { return fmt.Errorf("invalid architecture found (%s), only amd64 is currently supported for automatic updating. "+ - "You have to update manually (https://github.com/ByteDream/crunchyroll-go)", runtime.GOARCH) + "You have to update manually (https://github.com/ByteDream/crunchy-cli)", runtime.GOARCH) } var downloadFile string switch runtime.GOOS { case "linux": - yayCommand := exec.Command("pacman -Q crunchyroll-go") + yayCommand := exec.Command("pacman -Q crunchy-cli") if yayCommand.Run() == nil && yayCommand.ProcessState.Success() { - out.Info("crunchyroll-go was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") + out.Info("crunchy-cli was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") return nil } downloadFile = fmt.Sprintf("crunchy-v%s_linux", releaseVersion) @@ -103,7 +103,7 @@ func update() error { downloadFile = fmt.Sprintf("crunchy-v%s_windows.exe", releaseVersion) default: return fmt.Errorf("invalid operation system found (%s), only linux, windows and darwin / macos are currently supported. "+ - "You have to update manually (https://github.com/ByteDream/crunchyroll-go)", runtime.GOOS) + "You have to update manually (https://github.com/ByteDream/crunchy-cli", runtime.GOOS) } out.SetProgress("Updating executable %s", os.Args[0]) @@ -119,7 +119,7 @@ func update() error { } defer executeFile.Close() - resp, err := client.Get(fmt.Sprintf("https://github.com/ByteDream/crunchyroll-go/releases/download/v%s/%s", releaseVersion, downloadFile)) + resp, err := client.Get(fmt.Sprintf("https://github.com/ByteDream/crunchy-cli/releases/download/v%s/%s", releaseVersion, downloadFile)) if err != nil { return err } diff --git a/cmd/crunchyroll-go/cmd/utils.go b/commands/utils.go similarity index 99% rename from cmd/crunchyroll-go/cmd/utils.go rename to commands/utils.go index 12d3d4e..486c61f 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/commands/utils.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "crypto/aes" @@ -161,7 +161,7 @@ func loadCrunchy() { } if configDir, err := os.UserConfigDir(); err == nil { - persistentFilePath := filepath.Join(configDir, "crunchyroll-go", "crunchy") + persistentFilePath := filepath.Join(configDir, "crunchy-cli", "crunchy") if _, statErr := os.Stat(persistentFilePath); statErr == nil { body, err := os.ReadFile(persistentFilePath) if err != nil { diff --git a/cmd/crunchyroll-go/cmd/windows.go b/commands/windows.go similarity index 97% rename from cmd/crunchyroll-go/cmd/windows.go rename to commands/windows.go index d6eecb1..2ae47b5 100644 --- a/cmd/crunchyroll-go/cmd/windows.go +++ b/commands/windows.go @@ -1,6 +1,6 @@ //go:build windows -package cmd +package commands import ( "bufio" diff --git a/comment.go b/comment.go deleted file mode 100644 index d58fade..0000000 --- a/comment.go +++ /dev/null @@ -1,285 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "time" -) - -type Comment struct { - crunchy *Crunchyroll - - EpisodeID string `json:"episode_id"` - - CommentID string `json:"comment_id"` - DomainID string `json:"domain_id"` - - GuestbookKey string `json:"guestbook_key"` - - User struct { - UserKey string `json:"user_key"` - UserAttributes struct { - Username string `json:"username"` - Avatar struct { - Locked []Image `json:"locked"` - Unlocked []Image `json:"unlocked"` - } `json:"avatar"` - } `json:"user_attributes"` - UserFlags []any `json:"user_flags"` - } `json:"user"` - - Message string `json:"message"` - ParentCommentID int `json:"parent_comment_id"` - - Locale LOCALE `json:"locale"` - - UserVotes []string `json:"user_votes"` - Flags []string `json:"flags"` - Votes struct { - Inappropriate int `json:"inappropriate"` - Like int `json:"like"` - Spoiler int `json:"spoiler"` - } `json:"votes"` - - DeleteReason any `json:"delete_reason"` - - Created time.Time `json:"created"` - Modified time.Time `json:"modified"` - - IsOwner bool `json:"is_owner"` - RepliesCount int `json:"replies_count"` -} - -// Delete deleted the current comment. Works only if the user has written the comment. -func (c *Comment) Delete() error { - if !c.IsOwner { - return fmt.Errorf("cannot delete, user is not the comment author") - } - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) - resp, err := c.crunchy.request(endpoint, http.MethodDelete) - if err != nil { - return err - } - defer resp.Body.Close() - - // the api returns a new comment object when modifying it. - // hopefully this does not change - json.NewDecoder(resp.Body).Decode(c) - - return nil -} - -// IsSpoiler returns if the comment is marked as spoiler or not. -func (c *Comment) IsSpoiler() bool { - for _, flag := range c.Flags { - if flag == "spoiler" { - return true - } - } - return false -} - -// MarkAsSpoiler marks the current comment as spoiler. Works only if the user has written the comment, -// and it isn't already marked as spoiler. -func (c *Comment) MarkAsSpoiler() error { - if !c.IsOwner { - return fmt.Errorf("cannot mark as spoiler, user is not the comment author") - } else if c.votedAs("spoiler") { - return fmt.Errorf("comment is already marked as spoiler") - } - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) - body, _ := json.Marshal(map[string][]string{"add": {"spoiler"}}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - resp, err := c.crunchy.requestFull(req) - if err != nil { - return err - } - defer resp.Body.Close() - - json.NewDecoder(resp.Body).Decode(c) - - return nil -} - -// UnmarkAsSpoiler unmarks the current comment as spoiler. Works only if the user has written the comment, -// and it is already marked as spoiler. -func (c *Comment) UnmarkAsSpoiler() error { - if !c.IsOwner { - return fmt.Errorf("cannot mark as spoiler, user is not the comment author") - } else if !c.votedAs("spoiler") { - return fmt.Errorf("comment is not marked as spoiler") - } - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/flags?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) - body, _ := json.Marshal(map[string][]string{"remove": {"spoiler"}}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - resp, err := c.crunchy.requestFull(req) - if err != nil { - return err - } - defer resp.Body.Close() - - json.NewDecoder(resp.Body).Decode(c) - - return nil -} - -// Like likes the comment. Works only if the user hasn't already liked it. -func (c *Comment) Like() error { - if err := c.vote("like", "liked"); err != nil { - return err - } - c.Votes.Like += 1 - - return nil -} - -// Liked returns if the user has liked the comment. -func (c *Comment) Liked() bool { - return c.votedAs("liked") -} - -// RemoveLike removes the like from the comment. Works only if the user has liked it. -func (c *Comment) RemoveLike() error { - if err := c.unVote("like", "liked"); err != nil { - return err - } - c.Votes.Like -= 1 - - return nil -} - -// Reply replies to the current comment. -func (c *Comment) Reply(message string, spoiler bool) (*Comment, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments?locale=%s", c.EpisodeID, c.crunchy.Locale) - var flags []string - if spoiler { - flags = append(flags, "spoiler") - } - body, _ := json.Marshal(map[string]any{"locale": string(c.crunchy.Locale), "message": message, "flags": flags, "parent_id": c.CommentID}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", "application/json") - resp, err := c.crunchy.requestFull(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - reply := &Comment{} - if err = json.NewDecoder(resp.Body).Decode(reply); err != nil { - return nil, err - } - - return reply, nil -} - -// Replies shows all replies to the current comment. -func (c *Comment) Replies(page uint, size uint) ([]*Comment, error) { - if c.RepliesCount == 0 { - return []*Comment{}, nil - } - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/replies?page_size=%d&page=%d&locale=%s", c.EpisodeID, c.CommentID, size, page, c.Locale) - resp, err := c.crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]any - json.NewDecoder(resp.Body).Decode(&jsonBody) - - var comments []*Comment - if err = decodeMapToStruct(jsonBody["items"].([]any), &comments); err != nil { - return nil, err - } - return comments, nil -} - -// Report reports the comment. Only works if the comment hasn't been reported yet. -func (c *Comment) Report() error { - return c.vote("inappropriate", "reported") -} - -func (c *Comment) IsReported() bool { - return c.votedAs("reported") -} - -// RemoveReport removes the report request from the comment. Only works if the user -// has reported the comment. -func (c *Comment) RemoveReport() error { - return c.unVote("inappropriate", "reported") -} - -// FlagAsSpoiler sends a request to the user (and / or crunchyroll?) to mark the comment -// as spoiler. Only works if the comment hasn't been flagged as spoiler yet. -func (c *Comment) FlagAsSpoiler() error { - return c.vote("spoiler", "spoiler") -} - -func (c *Comment) IsFlaggedAsSpoiler() bool { - return c.votedAs("spoiler") -} - -// UnflagAsSpoiler rewokes the request to the user (and / or crunchyroll?) to mark the -// comment as spoiler. Only works if the user has flagged the comment as spoiler. -func (c *Comment) UnflagAsSpoiler() error { - return c.unVote("spoiler", "spoiler") -} - -func (c *Comment) votedAs(voteType string) bool { - for _, userVote := range c.UserVotes { - if userVote == voteType { - return true - } - } - return false -} - -func (c *Comment) vote(voteType, readableName string) error { - if c.votedAs(voteType) { - return fmt.Errorf("comment is already marked as %s", readableName) - } - - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/votes?locale=%s", c.EpisodeID, c.CommentID, c.crunchy.Locale) - body, _ := json.Marshal(map[string]string{"vote_type": voteType}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = c.crunchy.requestFull(req) - if err != nil { - return err - } - c.UserVotes = append(c.UserVotes, voteType) - - return nil -} - -func (c *Comment) unVote(voteType, readableName string) error { - for i, userVote := range c.UserVotes { - if userVote == voteType { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments/%s/votes?vote_type=%s&locale=%s", c.EpisodeID, c.CommentID, voteType, c.crunchy.Locale) - _, err := c.crunchy.request(endpoint, http.MethodDelete) - if err != nil { - return err - } - c.UserVotes = append(c.UserVotes[:i], c.UserVotes[i+1:]...) - return nil - } - } - - return fmt.Errorf("comment is not marked as %s", readableName) -} diff --git a/common.go b/common.go deleted file mode 100644 index b76a8fe..0000000 --- a/common.go +++ /dev/null @@ -1,31 +0,0 @@ -package crunchyroll - -type Image struct { - Height int `json:"height"` - Source string `json:"source"` - Type string `json:"type"` - Width int `json:"width"` -} - -type Panel struct { - Title string `json:"title"` - PromoTitle string `json:"promo_title"` - Slug string `json:"slug"` - Playback string `json:"playback"` - PromoDescription string `json:"promo_description"` - Images struct { - Thumbnail [][]Image `json:"thumbnail"` - PosterTall [][]Image `json:"poster_tall"` - PosterWide [][]Image `json:"poster_wide"` - } `json:"images"` - ID string `json:"id"` - Description string `json:"description"` - ChannelID string `json:"channel_id"` - Type WatchlistEntryType `json:"type"` - ExternalID string `json:"external_id"` - SlugTitle string `json:"slug_title"` - // not null if Type is WATCHLISTENTRYEPISODE - EpisodeMetadata *Episode `json:"episode_metadata"` - // not null if Type is WATCHLISTENTRYSERIES - SeriesMetadata *Series `json:"series_metadata"` -} diff --git a/crunchyroll-go.1 b/crunchy-cli.1 similarity index 78% rename from crunchyroll-go.1 rename to crunchy-cli.1 index 576c291..d9b1075 100644 --- a/crunchyroll-go.1 +++ b/crunchy-cli.1 @@ -1,24 +1,24 @@ -.TH crunchyroll-go 1 "21 March 2022" "crunchyroll-go" "Crunchyroll Downloader" +.TH crunchy-cli 1 "27 June 2022" "crunchy-cli" "Crunchyroll Cli Client" .SH NAME -crunchyroll-go - A cli for downloading videos and entire series from crunchyroll. +crunchy-cli - A cli for downloading videos and entire series from crunchyroll. .SH SYNOPSIS -crunchyroll-go [\fB-h\fR] [\fB-p\fR \fIPROXY\fR] [\fB-q\fR] [\fB-v\fR] +crunchy-cli [\fB-h\fR] [\fB-p\fR \fIPROXY\fR] [\fB-q\fR] [\fB-v\fR] .br -crunchyroll-go help +crunchy-cli help .br -crunchyroll-go login [\fB--persistent\fR] [\fB--session-id\fR \fISESSION_ID\fR] [\fIusername\fR, \fIpassword\fR] +crunchy-cli login [\fB--persistent\fR] [\fB--session-id\fR \fISESSION_ID\fR] [\fIusername\fR, \fIpassword\fR] .br -crunchyroll-go download [\fB-a\fR \fIAUDIO\fR] [\fB-s\fR \fISUBTITLE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR +crunchy-cli download [\fB-a\fR \fIAUDIO\fR] [\fB-s\fR \fISUBTITLE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR .br -crunchyroll-go archive [\fB-l\fR \fILANGUAGE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-m\fR \fIMERGE BEHAVIOR\fR] [\fB-c\fR \fICOMPRESS\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR +crunchy-cli archive [\fB-l\fR \fILANGUAGE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-m\fR \fIMERGE BEHAVIOR\fR] [\fB-c\fR \fICOMPRESS\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR .br -crunchyroll-go update [\fB-i\fR \fIINSTALL\fR] +crunchy-cli update [\fB-i\fR \fIINSTALL\fR] .SH DESCRIPTION .TP -With \fBcrunchyroll-go\fR you can easily download video and series from crunchyroll. +With \fBcrunchy-cli\fR you can easily download video and series from crunchyroll. .TP Note that you need an \fBcrunchyroll premium\fR account in order to use this tool! @@ -167,27 +167,27 @@ The \fBS\fR, followed by the number indicates the season number, \fBE\fR, follow .SH EXAMPLES Login via crunchyroll account email and password. .br -$ crunchyroll-go login user@example.com 12345678 +$ crunchy-cli login user@example.com 12345678 Download a episode normally. Your system locale will be used for the video's audio. .br -$ crunchyroll-go download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy-cli download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Download a episode with 720p and name it to 'darling.mp4'. Note that you need \fBffmpeg\fR to save files which do not have '.ts' as file extension. .br -$ crunchyroll-go download -o "darling.mp4" -r 720p https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy-cli download -o "darling.mp4" -r 720p https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Download a episode with japanese audio and american subtitles. .br -$ crunchyroll-go download -a ja-JP -s en-US https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E3-E5] +$ crunchy-cli download -a ja-JP -s en-US https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E3-E5] Stores the episode in a .mkv file. .br -$ crunchyroll-go archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy-cli archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. .br -$ crunchyroll-go archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E2] +$ crunchy-cli archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E2] .SH BUGS If you notice any bug or want an enhancement, feel free to create a new issue or pull request in the GitHub repository. @@ -195,22 +195,21 @@ If you notice any bug or want an enhancement, feel free to create a new issue or .SH AUTHOR ByteDream .br -Source: https://github.com/ByteDream/crunchyroll-go +Source: https://github.com/ByteDream/crunchy-cli .SH COPYRIGHT Copyright (C) 2022 ByteDream -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3 of the License, or (at your option) any later version. +This program is free software: you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, either version 3 +of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -You should have received a copy of the GNU Lesser General Public License -along with this program; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +You should have received a copy of the GNU General Public License +along with this program. If not, see <https://www.gnu.org/licenses/>. diff --git a/crunchylists.go b/crunchylists.go deleted file mode 100644 index a297da2..0000000 --- a/crunchylists.go +++ /dev/null @@ -1,177 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "time" -) - -// Crunchylists returns a struct to control crunchylists. -func (c *Crunchyroll) Crunchylists() (*Crunchylists, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", c.Config.AccountID, c.Locale) - resp, err := c.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - crunchylists := &Crunchylists{ - crunchy: c, - } - json.NewDecoder(resp.Body).Decode(crunchylists) - for _, item := range crunchylists.Items { - item.crunchy = c - } - - return crunchylists, nil -} - -type Crunchylists struct { - crunchy *Crunchyroll - - Items []*CrunchylistPreview `json:"items"` - TotalPublic int `json:"total_public"` - TotalPrivate int `json:"total_private"` - MaxPrivate int `json:"max_private"` -} - -// Create creates a new crunchylist with the given name. Duplicate names for lists are allowed. -func (cl *Crunchylists) Create(name string) (*Crunchylist, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s?locale=%s", cl.crunchy.Config.AccountID, cl.crunchy.Locale) - body, _ := json.Marshal(map[string]string{"title": name}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", "application/json") - resp, err := cl.crunchy.requestFull(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - return crunchylistFromID(cl.crunchy, jsonBody["list_id"].(string)) -} - -type CrunchylistPreview struct { - crunchy *Crunchyroll - - ListID string `json:"list_id"` - IsPublic bool `json:"is_public"` - Total int `json:"total"` - ModifiedAt time.Time `json:"modified_at"` - Title string `json:"title"` -} - -// Crunchylist returns the belonging Crunchylist struct. -func (clp *CrunchylistPreview) Crunchylist() (*Crunchylist, error) { - return crunchylistFromID(clp.crunchy, clp.ListID) -} - -type Crunchylist struct { - crunchy *Crunchyroll - - ID string `json:"id"` - - Max int `json:"max"` - Total int `json:"total"` - Title string `json:"title"` - IsPublic bool `json:"is_public"` - ModifiedAt time.Time `json:"modified_at"` - Items []*CrunchylistItem `json:"items"` -} - -// AddSeries adds a series. -func (cl *Crunchylist) AddSeries(series *Series) error { - return cl.AddSeriesFromID(series.ID) -} - -// AddSeriesFromID adds a series from its id -func (cl *Crunchylist) AddSeriesFromID(id string) error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) - body, _ := json.Marshal(map[string]string{"content_id": id}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = cl.crunchy.requestFull(req) - return err -} - -// RemoveSeries removes a series -func (cl *Crunchylist) RemoveSeries(series *Series) error { - return cl.RemoveSeriesFromID(series.ID) -} - -// RemoveSeriesFromID removes a series by its id -func (cl *Crunchylist) RemoveSeriesFromID(id string) error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, id, cl.crunchy.Locale) - _, err := cl.crunchy.request(endpoint, http.MethodDelete) - return err -} - -// Delete deleted the current crunchylist. -func (cl *Crunchylist) Delete() error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) - _, err := cl.crunchy.request(endpoint, http.MethodDelete) - return err -} - -// Rename renames the current crunchylist. -func (cl *Crunchylist) Rename(name string) error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", cl.crunchy.Config.AccountID, cl.ID, cl.crunchy.Locale) - body, _ := json.Marshal(map[string]string{"title": name}) - req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = cl.crunchy.requestFull(req) - if err == nil { - cl.Title = name - } - return err -} - -func crunchylistFromID(crunchy *Crunchyroll, id string) (*Crunchylist, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s?locale=%s", crunchy.Config.AccountID, id, crunchy.Locale) - resp, err := crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - crunchyList := &Crunchylist{ - crunchy: crunchy, - ID: id, - } - if err := json.NewDecoder(resp.Body).Decode(crunchyList); err != nil { - return nil, err - } - for _, item := range crunchyList.Items { - item.crunchy = crunchy - } - return crunchyList, nil -} - -type CrunchylistItem struct { - crunchy *Crunchyroll - - ListID string `json:"list_id"` - ID string `json:"id"` - ModifiedAt time.Time `json:"modified_at"` - Panel Panel `json:"panel"` -} - -// Remove removes the current item from its crunchylist. -func (cli *CrunchylistItem) Remove() error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/custom-lists/%s/%s/%s", cli.crunchy.Config.AccountID, cli.ListID, cli.ID) - _, err := cli.crunchy.request(endpoint, http.MethodDelete) - return err -} diff --git a/crunchyroll.go b/crunchyroll.go deleted file mode 100644 index c697034..0000000 --- a/crunchyroll.go +++ /dev/null @@ -1,327 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" -) - -// LOCALE represents a locale / language. -type LOCALE string - -const ( - JP LOCALE = "ja-JP" - US = "en-US" - LA = "es-419" - ES = "es-ES" - FR = "fr-FR" - PT = "pt-PT" - BR = "pt-BR" - IT = "it-IT" - DE = "de-DE" - RU = "ru-RU" - AR = "ar-SA" -) - -// MediaType represents a media type. -type MediaType string - -const ( - MediaTypeSeries MediaType = "series" - MediaTypeMovie = "movie_listing" -) - -type loginResponse struct { - AccessToken string `json:"access_token"` - ExpiresIn int `json:"expires_in"` - TokenType string `json:"token_type"` - Scope string `json:"scope"` - Country string `json:"country"` - AccountID string `json:"account_id"` -} - -// LoginWithCredentials logs in via crunchyroll username or email and password. -func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { - endpoint := "https://beta-api.crunchyroll.com/auth/v1/token" - values := url.Values{} - values.Set("username", user) - values.Set("password", password) - values.Set("grant_type", "password") - - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBufferString(values.Encode())) - if err != nil { - return nil, err - } - req.Header.Set("Authorization", "Basic aHJobzlxM2F3dnNrMjJ1LXRzNWE6cHROOURteXRBU2Z6QjZvbXVsSzh6cUxzYTczVE1TY1k=") - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - resp, err := request(req, client) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var loginResp loginResponse - json.NewDecoder(resp.Body).Decode(&loginResp) - - var etpRt string - for _, cookie := range resp.Cookies() { - if cookie.Name == "etp_rt" { - etpRt = cookie.Value - break - } - } - - return postLogin(loginResp, etpRt, locale, client) -} - -// LoginWithSessionID logs in via a crunchyroll session id. -// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com. -// -// Deprecated: Login via session id caused some trouble in the past (e.g. #15 or #30) which resulted in -// login not working. Use LoginWithEtpRt instead. EtpRt practically the crunchyroll beta equivalent to -// a session id. -// The method will stay in the library until session id login is removed completely or login with it -// does not work for a longer period of time. -func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { - endpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?session_id=%s", - sessionID) - resp, err := client.Get(endpoint) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to start session: %s", resp.Status) - } - - var jsonBody map[string]any - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse start session with session id response: %w", err) - } - if isError, ok := jsonBody["error"]; ok && isError.(bool) { - return nil, fmt.Errorf("invalid session id (%s): %s", jsonBody["message"].(string), jsonBody["code"]) - } - - var etpRt string - for _, cookie := range resp.Cookies() { - if cookie.Name == "etp_rt" { - etpRt = cookie.Value - break - } - } - - return LoginWithEtpRt(etpRt, locale, client) -} - -// LoginWithEtpRt logs in via the crunchyroll etp rt cookie. This cookie is the crunchyroll beta -// equivalent to the classic session id. -// The etp_rt cookie is automatically set when visiting https://beta.crunchyroll.com. Note that you -// need a crunchyroll account to access it. -func LoginWithEtpRt(etpRt string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { - endpoint := "https://beta-api.crunchyroll.com/auth/v1/token" - grantType := url.Values{} - grantType.Set("grant_type", "etp_rt_cookie") - - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBufferString(grantType.Encode())) - if err != nil { - return nil, err - } - req.Header.Add("Authorization", "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6") - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.AddCookie(&http.Cookie{ - Name: "etp_rt", - Value: etpRt, - }) - resp, err := request(req, client) - if err != nil { - return nil, err - } - - var loginResp loginResponse - json.NewDecoder(resp.Body).Decode(&loginResp) - - return postLogin(loginResp, etpRt, locale, client) -} - -func postLogin(loginResp loginResponse, etpRt string, locale LOCALE, client *http.Client) (*Crunchyroll, error) { - crunchy := &Crunchyroll{ - Client: client, - Context: context.Background(), - Locale: locale, - EtpRt: etpRt, - cache: true, - } - - crunchy.Config.TokenType = loginResp.TokenType - crunchy.Config.AccessToken = loginResp.AccessToken - crunchy.Config.AccountID = loginResp.AccountID - crunchy.Config.CountryCode = loginResp.Country - - var jsonBody map[string]any - - endpoint := "https://beta-api.crunchyroll.com/index/v2" - resp, err := crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) - - cms := jsonBody["cms"].(map[string]any) - crunchy.Config.Premium = strings.HasSuffix(crunchy.Config.Bucket, "crunchyroll") - // / is trimmed so that urls which require it must be in .../{bucket}/... like format. - // this just looks cleaner - crunchy.Config.Bucket = strings.TrimPrefix(cms["bucket"].(string), "/") - crunchy.Config.Policy = cms["policy"].(string) - crunchy.Config.Signature = cms["signature"].(string) - crunchy.Config.KeyPairID = cms["key_pair_id"].(string) - - endpoint = "https://beta-api.crunchyroll.com/accounts/v1/me" - resp, err = crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) - crunchy.Config.ExternalID = jsonBody["external_id"].(string) - - endpoint = "https://beta-api.crunchyroll.com/accounts/v1/me/profile" - resp, err = crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - json.NewDecoder(resp.Body).Decode(&jsonBody) - crunchy.Config.MaturityRating = jsonBody["maturity_rating"].(string) - - return crunchy, nil -} - -type Crunchyroll struct { - // Client is the http.Client to perform all requests over. - Client *http.Client - // Context can be used to stop requests with Client and is context.Background by default. - Context context.Context - // Locale specifies in which language all results should be returned / requested. - Locale LOCALE - // EtpRt is the crunchyroll beta equivalent to a session id (prior SessionID field in - // this struct in v2 and below). - EtpRt string - - // Config stores parameters which are needed by some api calls. - Config struct { - TokenType string - AccessToken string - - Bucket string - - CountryCode string - Premium bool - Channel string - Policy string - Signature string - KeyPairID string - AccountID string - ExternalID string - MaturityRating string - } - - // If cache is true, internal caching is enabled. - cache bool -} - -// InvalidateSession logs the user out which invalidates the current session. -// You have to call a login method again and create a new Crunchyroll instance -// if you want to perform any further actions since this instance is not usable -// anymore after calling this. -func (c *Crunchyroll) InvalidateSession() error { - endpoint := "https://crunchyroll.com/logout" - _, err := c.request(endpoint, http.MethodGet) - return err -} - -// IsCaching returns if data gets cached or not. -// See SetCaching for more information. -func (c *Crunchyroll) IsCaching() bool { - return c.cache -} - -// SetCaching enables or disables internal caching of requests made. -// Caching is enabled by default. -// If it is disabled the already cached data still gets called. -// The best way to prevent this is to create a complete new Crunchyroll struct. -func (c *Crunchyroll) SetCaching(caching bool) { - c.cache = caching -} - -// request is a base function which handles simple api requests. -func (c *Crunchyroll) request(endpoint string, method string) (*http.Response, error) { - req, err := http.NewRequest(method, endpoint, nil) - if err != nil { - return nil, err - } - return c.requestFull(req) -} - -// requestFull is a base function which handles full user controlled api requests. -func (c *Crunchyroll) requestFull(req *http.Request) (*http.Response, error) { - req.Header.Add("Authorization", fmt.Sprintf("%s %s", c.Config.TokenType, c.Config.AccessToken)) - - return request(req, c.Client) -} - -func request(req *http.Request, client *http.Client) (*http.Response, error) { - resp, err := client.Do(req) - if err == nil { - var buf bytes.Buffer - io.Copy(&buf, resp.Body) - defer resp.Body.Close() - defer func() { - resp.Body = io.NopCloser(&buf) - }() - - if buf.Len() != 0 { - var errMap map[string]any - - if err = json.Unmarshal(buf.Bytes(), &errMap); err != nil { - return nil, &RequestError{Response: resp, Message: fmt.Sprintf("invalid json response: %w", err)} - } - - if val, ok := errMap["error"]; ok { - if errorAsString, ok := val.(string); ok { - if code, ok := errMap["code"].(string); ok { - return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", errorAsString, code)} - } - return nil, &RequestError{Response: resp, Message: errorAsString} - } else if errorAsBool, ok := val.(bool); ok && errorAsBool { - if msg, ok := errMap["message"].(string); ok { - return nil, &RequestError{Response: resp, Message: msg} - } - } - } else if _, ok := errMap["code"]; ok { - if errContext, ok := errMap["context"].([]any); ok && len(errContext) > 0 { - errField := errContext[0].(map[string]any) - var code string - if code, ok = errField["message"].(string); !ok { - code = errField["code"].(string) - } - return nil, &RequestError{Response: resp, Message: fmt.Sprintf("%s - %s", code, errField["field"].(string))} - } else if errMessage, ok := errMap["message"].(string); ok { - return nil, &RequestError{Response: resp, Message: errMessage} - } - } - } - - if resp.StatusCode >= 400 { - return nil, &RequestError{Response: resp, Message: resp.Status} - } - } - return resp, err -} diff --git a/downloader.go b/downloader.go deleted file mode 100644 index 05622cc..0000000 --- a/downloader.go +++ /dev/null @@ -1,395 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "context" - "crypto/aes" - "crypto/cipher" - "fmt" - "github.com/grafov/m3u8" - "io" - "math" - "net/http" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "time" -) - -// NewDownloader creates a downloader with default settings which should -// fit the most needs. -func NewDownloader(context context.Context, writer io.Writer, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) Downloader { - tmp, _ := os.MkdirTemp("", "crunchy_") - - return Downloader{ - Writer: writer, - TempDir: tmp, - DeleteTempAfter: true, - Context: context, - Goroutines: goroutines, - OnSegmentDownload: onSegmentDownload, - } -} - -// Downloader is used to download Format's -type Downloader struct { - // The output is all written to Writer. - Writer io.Writer - - // TempDir is the directory where the temporary segment files should be stored. - // The files will be placed directly into the root of the directory. - // If empty a random temporary directory on the system's default tempdir - // will be created. - // If the directory does not exist, it will be created. - TempDir string - // If DeleteTempAfter is true, the temp directory gets deleted afterwards. - // Note that in case of a hard signal exit (os.Interrupt, ...) the directory - // will NOT be deleted. In such situations try to catch the signal and - // cancel Context. - DeleteTempAfter bool - - // Context to control the download process with. - // There is a tiny delay when canceling the context and the actual stop of the - // process. So it is not recommend stopping the program immediately after calling - // the cancel function. It's better when canceling it and then exit the program - // when Format.Download throws an error. See the signal handling section in - // cmd/crunchyroll-go/cmd/download.go for an example. - Context context.Context - - // Goroutines is the number of goroutines to download segments with. - Goroutines int - - // A method to call when a segment was downloaded. - // Note that the segments are downloaded asynchronously (depending on the count of - // Goroutines) and the function gets called asynchronously too, so for example it is - // first called on segment 1, then segment 254, then segment 3 and so on. - OnSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error - // If LockOnSegmentDownload is true, only one OnSegmentDownload function can be called at - // once. Normally (because of the use of goroutines while downloading) multiple could get - // called simultaneously. - LockOnSegmentDownload bool - - // If FFmpegOpts is not nil, ffmpeg will be used to merge and convert files. - // The given opts will be used as ffmpeg parameters while merging. - // - // If Writer is *os.File and -f (which sets the output format) is not specified, the output - // format will be retrieved by its file ending. If this is not the case and -f is not given, - // the output format will be mpegts / mpeg transport stream. - // Execute 'ffmpeg -muxers' to see all available output formats. - FFmpegOpts []string -} - -// download downloads the given format. -func (d Downloader) download(format *Format) error { - if err := format.InitVideo(); err != nil { - return err - } - - if _, err := os.Stat(d.TempDir); os.IsNotExist(err) { - if err = os.Mkdir(d.TempDir, 0700); err != nil { - return err - } - } - if d.DeleteTempAfter { - defer os.RemoveAll(d.TempDir) - } - - files, err := d.downloadSegments(format) - if err != nil { - return err - } - if d.FFmpegOpts == nil { - return d.mergeSegments(files) - } else { - return d.mergeSegmentsFFmpeg(files) - } -} - -// mergeSegments reads every file in tempDir and writes their content to Downloader.Writer. -// The given output file gets created or overwritten if already existing. -func (d Downloader) mergeSegments(files []string) error { - for _, file := range files { - select { - case <-d.Context.Done(): - return d.Context.Err() - default: - f, err := os.Open(file) - if err != nil { - return err - } - if _, err = io.Copy(d.Writer, f); err != nil { - f.Close() - return err - } - f.Close() - } - } - return nil -} - -// mergeSegmentsFFmpeg reads every file in tempDir and merges their content to the outputFile -// with ffmpeg (https://ffmpeg.org/). -// The given output file gets created or overwritten if already existing. -func (d Downloader) mergeSegmentsFFmpeg(files []string) error { - list, err := os.Create(filepath.Join(d.TempDir, "list.txt")) - if err != nil { - return err - } - - for _, file := range files { - if _, err = fmt.Fprintf(list, "file '%s'\n", file); err != nil { - list.Close() - return err - } - } - list.Close() - - // predefined options ... custom options ... predefined output filename - command := []string{ - "-y", - "-f", "concat", - "-safe", "0", - "-i", list.Name(), - "-c", "copy", - } - if d.FFmpegOpts != nil { - command = append(command, d.FFmpegOpts...) - } - - var tmpfile string - if _, ok := d.Writer.(*io.PipeWriter); !ok { - if file, ok := d.Writer.(*os.File); ok { - tmpfile = file.Name() - } - } - if filepath.Ext(tmpfile) == "" { - // checks if the -f flag is set (overwrites the output format) - var hasF bool - for _, opts := range d.FFmpegOpts { - if strings.TrimSpace(opts) == "-f" { - hasF = true - break - } - } - if !hasF { - command = append(command, "-f", "matroska") - f, err := os.CreateTemp(d.TempDir, "") - if err != nil { - return err - } - f.Close() - tmpfile = f.Name() - } - } - command = append(command, tmpfile) - - var errBuf bytes.Buffer - cmd := exec.CommandContext(d.Context, "ffmpeg", - command...) - cmd.Stderr = &errBuf - - if err = cmd.Run(); err != nil { - if errBuf.Len() > 0 { - return fmt.Errorf(errBuf.String()) - } else { - return err - } - } - if f, ok := d.Writer.(*os.File); !ok || f.Name() != tmpfile { - var file *os.File - if file, err = os.Open(tmpfile); err != nil { - return err - } - defer file.Close() - _, err = io.Copy(d.Writer, file) - } - return err -} - -// downloadSegments downloads every mpeg transport stream segment to a given -// directory (more information below). -// After every segment download onSegmentDownload will be called with: -// the downloaded segment, the current position, the total size of segments to download, -// the file where the segment content was written to an error (if occurred). -// The filename is always <number of downloaded segment>.ts. -// -// Short explanation: -// The actual crunchyroll video is split up in multiple segments (or video files) which -// have to be downloaded and merged after to generate a single video file. -// And this function just downloads each of this segment into the given directory. -// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information. -func (d Downloader) downloadSegments(format *Format) ([]string, error) { - if err := format.InitVideo(); err != nil { - return nil, err - } - - var wg sync.WaitGroup - var lock sync.Mutex - chunkSize := int(math.Ceil(float64(format.Video.Chunklist.Count()) / float64(d.Goroutines))) - - // when a onSegmentDownload call returns an error, this context will be set cancelled and stop all goroutines - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // receives the decrypt block and iv from the first segment. - // in my tests, only the first segment has specified this data, so the decryption data from this first segments will be used in every other segment too - block, iv, err := getCrypt(format, format.Video.Chunklist.Segments[0]) - if err != nil { - return nil, err - } - - var total int32 - for i := 0; i < int(format.Video.Chunklist.Count()); i += chunkSize { - wg.Add(1) - end := i + chunkSize - if end > int(format.Video.Chunklist.Count()) { - end = int(format.Video.Chunklist.Count()) - } - i := i - - go func() { - defer wg.Done() - - for j, segment := range format.Video.Chunklist.Segments[i:end] { - select { - case <-d.Context.Done(): - case <-ctx.Done(): - return - default: - var file *os.File - for k := 0; k < 3; k++ { - filename := filepath.Join(d.TempDir, fmt.Sprintf("%d.ts", i+j)) - file, err = d.downloadSegment(format, segment, filename, block, iv) - if err == nil { - break - } - if k == 2 { - file.Close() - cancel() - return - } - select { - case <-d.Context.Done(): - case <-ctx.Done(): - file.Close() - return - case <-time.After(5 * time.Duration(k) * time.Second): - // sleep if an error occurs. very useful because sometimes the connection times out - } - } - if d.OnSegmentDownload != nil { - if d.LockOnSegmentDownload { - lock.Lock() - } - - if err = d.OnSegmentDownload(segment, int(atomic.AddInt32(&total, 1)), int(format.Video.Chunklist.Count()), file); err != nil { - if d.LockOnSegmentDownload { - lock.Unlock() - } - file.Close() - return - } - if d.LockOnSegmentDownload { - lock.Unlock() - } - } - file.Close() - } - } - }() - } - wg.Wait() - - select { - case <-d.Context.Done(): - return nil, d.Context.Err() - case <-ctx.Done(): - return nil, err - default: - var files []string - for i := 0; i < int(total); i++ { - files = append(files, filepath.Join(d.TempDir, fmt.Sprintf("%d.ts", i))) - } - - return files, nil - } -} - -// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence. -func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, iv []byte, err error) { - var resp *http.Response - - resp, err = format.crunchy.Client.Get(segment.Key.URI) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - key, err := io.ReadAll(resp.Body) - - block, err = aes.NewCipher(key) - if err != nil { - return nil, nil, err - } - iv = []byte(segment.Key.IV) - if len(iv) == 0 { - iv = key - } - - return block, iv, nil -} - -// downloadSegment downloads a segment, decrypts it and names it after the given index. -func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment, filename string, block cipher.Block, iv []byte) (*os.File, error) { - // every segment is aes-128 encrypted and has to be decrypted when downloaded - content, err := d.decryptSegment(format.crunchy.Client, segment, block, iv) - if err != nil { - return nil, err - } - - file, err := os.Create(filename) - if err != nil { - return nil, err - } - defer file.Close() - if _, err = file.Write(content); err != nil { - return nil, err - } - - return file, nil -} - -// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25. -func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegment, block cipher.Block, iv []byte) ([]byte, error) { - req, err := http.NewRequestWithContext(d.Context, http.MethodGet, segment.URI, nil) - if err != nil { - return nil, err - } - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()]) - decrypted := make([]byte, len(raw)) - blockMode.CryptBlocks(decrypted, raw) - raw = d.pkcs5UnPadding(decrypted) - - return raw, nil -} - -// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47. -func (d Downloader) pkcs5UnPadding(origData []byte) []byte { - length := len(origData) - unPadding := int(origData[length-1]) - return origData[:(length - unPadding)] -} diff --git a/episode.go b/episode.go deleted file mode 100644 index caf1e09..0000000 --- a/episode.go +++ /dev/null @@ -1,342 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "regexp" - "strconv" - "strings" - "time" -) - -// Episode contains all information about an episode. -type Episode struct { - crunchy *Crunchyroll - - children []*Stream - - ID string `json:"id"` - ChannelID string `json:"channel_id"` - - SeriesID string `json:"series_id"` - SeriesTitle string `json:"series_title"` - SeriesSlugTitle string `json:"series_slug_title"` - - SeasonID string `json:"season_id"` - SeasonTitle string `json:"season_title"` - SeasonSlugTitle string `json:"season_slug_title"` - SeasonNumber int `json:"season_number"` - - Episode string `json:"episode"` - EpisodeNumber int `json:"episode_number"` - SequenceNumber float64 `json:"sequence_number"` - ProductionEpisodeID string `json:"production_episode_id"` - - Title string `json:"title"` - SlugTitle string `json:"slug_title"` - Description string `json:"description"` - NextEpisodeID string `json:"next_episode_id"` - NextEpisodeTitle string `json:"next_episode_title"` - - HDFlag bool `json:"hd_flag"` - MaturityRatings []string `json:"maturity_ratings"` - IsMature bool `json:"is_mature"` - MatureBlocked bool `json:"mature_blocked"` - - EpisodeAirDate time.Time `json:"episode_air_date"` - FreeAvailableDate time.Time `json:"free_available_date"` - PremiumAvailableDate time.Time `json:"premium_available_date"` - - IsSubbed bool `json:"is_subbed"` - IsDubbed bool `json:"is_dubbed"` - IsClip bool `json:"is_clip"` - SeoTitle string `json:"seo_title"` - SeoDescription string `json:"seo_description"` - SeasonTags []string `json:"season_tags"` - - AvailableOffline bool `json:"available_offline"` - MediaType MediaType `json:"media_type"` - Slug string `json:"slug"` - - Images struct { - Thumbnail [][]Image `json:"thumbnail"` - } `json:"images"` - - DurationMS int `json:"duration_ms"` - IsPremiumOnly bool `json:"is_premium_only"` - ListingID string `json:"listing_id"` - - SubtitleLocales []LOCALE `json:"subtitle_locales"` - Playback string `json:"playback"` - - AvailabilityNotes string `json:"availability_notes"` - - StreamID string -} - -// HistoryEpisode contains additional information about an episode if the account has watched or started to watch the episode. -type HistoryEpisode struct { - *Episode - - DatePlayed time.Time `json:"date_played"` - ParentID string `json:"parent_id"` - ParentType MediaType `json:"parent_type"` - Playhead uint `json:"playhead"` - FullyWatched bool `json:"fully_watched"` -} - -// WatchlistEntryType specifies which type a watchlist entry has. -type WatchlistEntryType string - -const ( - WatchlistEntryEpisode = "episode" - WatchlistEntrySeries = "series" -) - -// EpisodeFromID returns an episode by its api id. -func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.Bucket, - id, - crunchy.Locale, - crunchy.Config.Signature, - crunchy.Config.Policy, - crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - episode := &Episode{ - crunchy: crunchy, - ID: id, - } - if err := decodeMapToStruct(jsonBody, episode); err != nil { - return nil, err - } - if episode.Playback != "" { - streamHref := jsonBody["__links__"].(map[string]interface{})["streams"].(map[string]interface{})["href"].(string) - if match := regexp.MustCompile(`(?m)^/cms/v2/\S+videos/(\w+)/streams$`).FindAllStringSubmatch(streamHref, -1); len(match) > 0 { - episode.StreamID = match[0][1] - } - } - - return episode, nil -} - -// AddToWatchlist adds the current episode to the watchlist. -// Will return an RequestError with the response status code of 409 if the series was already on the watchlist before. -// There is currently a bug, or as I like to say in context of the crunchyroll api, feature, that only series and not -// individual episode can be added to the watchlist. Even though I somehow got an episode to my watchlist on the -// crunchyroll website, it never worked with the api here. So this function actually adds the whole series to the watchlist. -func (e *Episode) AddToWatchlist() error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s?locale=%s", e.crunchy.Config.AccountID, e.crunchy.Locale) - body, _ := json.Marshal(map[string]string{"content_id": e.SeriesID}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = e.crunchy.requestFull(req) - return err -} - -// RemoveFromWatchlist removes the current episode from the watchlist. -// Will return an RequestError with the response status code of 404 if the series was not on the watchlist before. -func (e *Episode) RemoveFromWatchlist() error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s/%s?locale=%s", e.crunchy.Config.AccountID, e.SeriesID, e.crunchy.Locale) - _, err := e.crunchy.request(endpoint, http.MethodDelete) - return err -} - -// AudioLocale returns the audio locale of the episode. -// Every episode in a season (should) have the same audio locale, -// so if you want to get the audio locale of a season, just call -// this method on the first episode of the season. -// Will fail if no streams are available, thus use Episode.Available -// to prevent any misleading errors. -func (e *Episode) AudioLocale() (LOCALE, error) { - streams, err := e.Streams() - if err != nil { - return "", err - } - return streams[0].AudioLocale, nil -} - -// Comment creates a new comment under the episode. -func (e *Episode) Comment(message string, spoiler bool) (*Comment, error) { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments?locale=%s", e.ID, e.crunchy.Locale) - var flags []string - if spoiler { - flags = append(flags, "spoiler") - } - body, _ := json.Marshal(map[string]any{"locale": string(e.crunchy.Locale), "flags": flags, "message": message}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", "application/json") - resp, err := e.crunchy.requestFull(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - c := &Comment{ - crunchy: e.crunchy, - EpisodeID: e.ID, - } - if err = json.NewDecoder(resp.Body).Decode(c); err != nil { - return nil, err - } - - return c, nil -} - -// CommentsOrderType represents a sort type to sort Episode.Comments after. -type CommentsOrderType string - -const ( - CommentsOrderAsc CommentsOrderType = "asc" - CommentsOrderDesc = "desc" -) - -type CommentsSortType string - -const ( - CommentsSortPopular CommentsSortType = "popular" - CommentsSortDate = "date" -) - -type CommentsOptions struct { - // Order specified the order how the comments should be returned. - Order CommentsOrderType `json:"order"` - - // Sort specified after which key the comments should be sorted. - Sort CommentsSortType `json:"sort"` -} - -// Comments returns comments under the given episode. -func (e *Episode) Comments(options CommentsOptions, page uint, size uint) (c []*Comment, err error) { - options, err = structDefaults(CommentsOptions{Order: CommentsOrderDesc, Sort: CommentsSortPopular}, options) - if err != nil { - return nil, err - } - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/talkbox/guestbooks/%s/comments?page=%d&page_size=%d&order=%s&sort=%s&locale=%s", e.ID, page, size, options.Order, options.Sort, e.crunchy.Locale) - resp, err := e.crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]any - json.NewDecoder(resp.Body).Decode(&jsonBody) - - if err = decodeMapToStruct(jsonBody["items"].([]any), &c); err != nil { - return nil, err - } - for _, comment := range c { - comment.crunchy = e.crunchy - comment.EpisodeID = e.ID - } - - return -} - -// Available returns if downloadable streams for this episodes are available. -func (e *Episode) Available() bool { - return e.crunchy.Config.Premium || !e.IsPremiumOnly -} - -// GetFormat returns the format which matches the given resolution and subtitle locale. -func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) { - streams, err := e.Streams() - if err != nil { - return nil, err - } - var foundStream *Stream - for _, stream := range streams { - if hardsub && stream.HardsubLocale == subtitle || stream.HardsubLocale == "" && subtitle == "" { - foundStream = stream - break - } else if !hardsub { - for _, streamSubtitle := range stream.Subtitles { - if streamSubtitle.Locale == subtitle { - foundStream = stream - break - } - } - if foundStream != nil { - break - } - } - } - - if foundStream == nil { - return nil, fmt.Errorf("no matching stream found") - } - formats, err := foundStream.Formats() - if err != nil { - return nil, err - } - var res *Format - for _, format := range formats { - if resolution == "worst" || resolution == "best" { - if res == nil { - res = format - continue - } - - curSplitRes := strings.SplitN(format.Video.Resolution, "x", 2) - curResX, _ := strconv.Atoi(curSplitRes[0]) - curResY, _ := strconv.Atoi(curSplitRes[1]) - - resSplitRes := strings.SplitN(res.Video.Resolution, "x", 2) - resResX, _ := strconv.Atoi(resSplitRes[0]) - resResY, _ := strconv.Atoi(resSplitRes[1]) - - if resolution == "worst" && curResX+curResY < resResX+resResY { - res = format - } else if resolution == "best" && curResX+curResY > resResX+resResY { - res = format - } - } - - if format.Video.Resolution == resolution { - return format, nil - } - } - - if res != nil { - return res, nil - } - - return nil, fmt.Errorf("no matching resolution found") -} - -// Streams returns all streams which are available for the episode. -func (e *Episode) Streams() ([]*Stream, error) { - if e.children != nil { - return e.children, nil - } - - streams, err := fromVideoStreams(e.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - e.crunchy.Config.Bucket, - e.StreamID, - e.crunchy.Locale, - e.crunchy.Config.Signature, - e.crunchy.Config.Policy, - e.crunchy.Config.KeyPairID)) - if err != nil { - return nil, err - } - - if e.crunchy.cache { - e.children = streams - } - return streams, nil -} diff --git a/error.go b/error.go deleted file mode 100644 index 9bc2ae6..0000000 --- a/error.go +++ /dev/null @@ -1,17 +0,0 @@ -package crunchyroll - -import ( - "fmt" - "net/http" -) - -type RequestError struct { - error - - Response *http.Response - Message string -} - -func (re *RequestError) Error() string { - return fmt.Sprintf("error for endpoint %s (%d): %s", re.Response.Request.URL.String(), re.Response.StatusCode, re.Message) -} diff --git a/format.go b/format.go deleted file mode 100644 index 6f17bf7..0000000 --- a/format.go +++ /dev/null @@ -1,52 +0,0 @@ -package crunchyroll - -import ( - "github.com/grafov/m3u8" -) - -type FormatType string - -const ( - EPISODE FormatType = "episodes" - MOVIE = "movies" -) - -// Format contains detailed information about an episode video stream. -type Format struct { - crunchy *Crunchyroll - - ID string - // FormatType represents if the format parent is an episode or a movie. - FormatType FormatType - Video *m3u8.Variant - AudioLocale LOCALE - Hardsub LOCALE - Subtitles []*Subtitle -} - -// InitVideo initializes the Format.Video completely. -// The Format.Video.Chunklist pointer is, by default, nil because an additional -// request must be made to receive its content. The request is not made when -// initializing a Format struct because it would probably cause an intense overhead -// since Format.Video.Chunklist is only used sometimes. -func (f *Format) InitVideo() error { - if f.Video.Chunklist == nil { - resp, err := f.crunchy.Client.Get(f.Video.URI) - if err != nil { - return err - } - defer resp.Body.Close() - - playlist, _, err := m3u8.DecodeFrom(resp.Body, true) - if err != nil { - return err - } - f.Video.Chunklist = playlist.(*m3u8.MediaPlaylist) - } - return nil -} - -// Download downloads the Format with the via Downloader specified options. -func (f *Format) Download(downloader Downloader) error { - return downloader.download(f) -} diff --git a/go.mod b/go.mod index 5c64ddf..988f612 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ -module github.com/ByteDream/crunchyroll-go/v3 +module github.com/ByteDream/crunchy-cli go 1.18 require ( + github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.4.0 ) diff --git a/go.sum b/go.sum index 34693ca..31a2cd9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c h1:jPabd/Zl/zdoSo8ZGtZLm43+42nIFHIJABvrvdMOYtY= +github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c/go.mod h1:L4M1sOPjJ4ui0YXFnpVUb4AzQIa+D/i/B0QG5iz9op4= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f9c3a90 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/ByteDream/crunchy-cli/commands" + +func main() { + commands.Execute() +} diff --git a/movie_listing.go b/movie_listing.go deleted file mode 100644 index f9bbc21..0000000 --- a/movie_listing.go +++ /dev/null @@ -1,92 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// MovieListing contains information about something which is called -// movie listing. I don't know what this means thb. -type MovieListing struct { - crunchy *Crunchyroll - - ID string `json:"id"` - - Title string `json:"title"` - Slug string `json:"slug"` - SlugTitle string `json:"slug_title"` - Description string `json:"description"` - - Images struct { - Thumbnail [][]Image `json:"thumbnail"` - } `json:"images"` - - DurationMS int `json:"duration_ms"` - IsPremiumOnly bool `json:"is_premium_only"` - ListeningID string `json:"listening_id"` - IsMature bool `json:"is_mature"` - AvailableOffline bool `json:"available_offline"` - IsSubbed bool `json:"is_subbed"` - IsDubbed bool `json:"is_dubbed"` - - Playback string `json:"playback"` - AvailabilityNotes string `json:"availability_notes"` -} - -// MovieListingFromID returns a movie listing by its api id. -func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.Bucket, - id, - crunchy.Locale, - crunchy.Config.Signature, - crunchy.Config.Policy, - crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - movieListing := &MovieListing{ - crunchy: crunchy, - ID: id, - } - if err = decodeMapToStruct(jsonBody, movieListing); err != nil { - return nil, err - } - - return movieListing, nil -} - -// AudioLocale is same as Episode.AudioLocale. -func (ml *MovieListing) AudioLocale() (LOCALE, error) { - resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - ml.crunchy.Config.Bucket, - ml.ID, - ml.crunchy.Locale, - ml.crunchy.Config.Signature, - ml.crunchy.Config.Policy, - ml.crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return "", err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - return LOCALE(jsonBody["audio_locale"].(string)), nil -} - -// Streams returns all streams which are available for the movie listing. -func (ml *MovieListing) Streams() ([]*Stream, error) { - return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - ml.crunchy.Config.Bucket, - ml.ID, - ml.crunchy.Locale, - ml.crunchy.Config.Signature, - ml.crunchy.Config.Policy, - ml.crunchy.Config.KeyPairID)) -} diff --git a/news.go b/news.go deleted file mode 100644 index 074dc0e..0000000 --- a/news.go +++ /dev/null @@ -1,55 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// News returns the top and latest news from crunchyroll for the current locale within the given limits. -func (c *Crunchyroll) News(topLimit uint, latestLimit uint) (t []*News, l []*News, err error) { - newsFeedEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/news_feed?top_news_n=%d&latest_news_n=%d&locale=%s", - topLimit, latestLimit, c.Locale) - resp, err := c.request(newsFeedEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'news_feed' response: %w", err) - } - - topNews := jsonBody["top_news"].(map[string]interface{}) - for _, item := range topNews["items"].([]interface{}) { - topNews := &News{} - if err := decodeMapToStruct(item, topNews); err != nil { - return nil, nil, err - } - - t = append(t, topNews) - } - - latestNews := jsonBody["latest_news"].(map[string]interface{}) - for _, item := range latestNews["items"].([]interface{}) { - latestNews := &News{} - if err := decodeMapToStruct(item, latestNews); err != nil { - return nil, nil, err - } - - l = append(l, latestNews) - } - - return t, l, nil -} - -// News contains all information about news. -type News struct { - Title string `json:"title"` - Link string `json:"link"` - Image string `json:"image"` - Creator string `json:"creator"` - PublishDate string `json:"publish_date"` - Description string `json:"description"` -} diff --git a/parse.go b/parse.go deleted file mode 100644 index 392e971..0000000 --- a/parse.go +++ /dev/null @@ -1,69 +0,0 @@ -package crunchyroll - -import ( - "regexp" - "strconv" -) - -// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season. -func ParseBetaSeriesURL(url string) (seasonId string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - seasonId = groups["seasonId"] - ok = true - } - return -} - -// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode. -func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - episodeId = groups["episodeId"] - ok = true - } - return -} - -// ParseVideoURL tries to extract the crunchyroll series / movie name out of the given url. -// -// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaSeriesURL -// if possible since beta url are always safe to use. -// The method will stay in the library until only beta urls are supported by crunchyroll itself. -func ParseVideoURL(url string) (seriesName string, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)(/videos)?/?$`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - seriesName = groups["series"] - - if seriesName != "" { - ok = true - } - } - return -} - -// ParseEpisodeURL tries to extract the crunchyroll series name, title, episode number and web id out of the given crunchyroll url -// Note that the episode number can be misleading. For example if an episode has the episode number 23.5 (slime isekai) -// the episode number will be 235. -// -// Deprecated: Crunchyroll classic urls are sometimes not safe to use, use ParseBetaEpisodeURL -// if possible since beta url are always safe to use. -// The method will stay in the library until only beta urls are supported by crunchyroll itself. -func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, webId int, ok bool) { - pattern := regexp.MustCompile(`(?m)^https?://(www\.)?crunchyroll\.com(/\w{2}(-\w{2})?)?/(?P<series>[^/]+)/episode-(?P<number>\d+)-(?P<title>.+)-(?P<webId>\d+).*`) - if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 { - groups := regexGroups(urlMatch, pattern.SubexpNames()...) - seriesName = groups["series"] - episodeNumber, _ = strconv.Atoi(groups["number"]) - title = groups["title"] - webId, _ = strconv.Atoi(groups["webId"]) - - if seriesName != "" && title != "" && webId != 0 { - ok = true - } - } - return -} diff --git a/search.go b/search.go deleted file mode 100644 index cbbf7e9..0000000 --- a/search.go +++ /dev/null @@ -1,193 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// BrowseSortType represents a sort type to sort Crunchyroll.Browse items after. -type BrowseSortType string - -const ( - BrowseSortPopularity BrowseSortType = "popularity" - BrowseSortNewlyAdded = "newly_added" - BrowseSortAlphabetical = "alphabetical" -) - -// BrowseOptions represents options for browsing the crunchyroll catalog. -type BrowseOptions struct { - // Categories specifies the categories of the entries. - Categories []string `param:"categories"` - - // IsDubbed specifies whether the entries should be dubbed. - IsDubbed bool `param:"is_dubbed"` - - // IsSubbed specifies whether the entries should be subbed. - IsSubbed bool `param:"is_subbed"` - - // Simulcast specifies a particular simulcast season by id in which the entries have been aired. - Simulcast string `param:"season_tag"` - - // Sort specifies how the entries should be sorted. - Sort BrowseSortType `param:"sort_by"` - - // Start specifies the index from which the entries should be returned. - Start uint `param:"start"` - - // Type specifies the media type of the entries. - Type MediaType `param:"type"` -} - -// Browse browses the crunchyroll catalog filtered by the specified options and returns all found series and movies within the given limit. -func (c *Crunchyroll) Browse(options BrowseOptions, limit uint) (s []*Series, m []*Movie, err error) { - query, err := encodeStructToQueryValues(options) - if err != nil { - return nil, nil, err - } - - browseEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/browse?%s&n=%d&locale=%s", - query, limit, c.Locale) - resp, err := c.request(browseEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'browse' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - switch item.(map[string]interface{})["type"] { - case MediaTypeSeries: - series := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(item, series); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { - return nil, nil, err - } - - s = append(s, series) - case MediaTypeMovie: - movie := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(item, movie); err != nil { - return nil, nil, err - } - - m = append(m, movie) - } - } - - return s, m, nil -} - -// FindVideoByName finds a Video (Season or Movie) by its name. -// Use this in combination with ParseVideoURL and hand over the corresponding results -// to this function. -// -// Deprecated: Use Search instead. The first result sometimes isn't the correct one -// so this function is inaccurate in some cases. -// See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information. -func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { - s, m, err := c.Search(seriesName, 1) - if err != nil { - return nil, err - } - - if len(s) > 0 { - return s[0], nil - } else if len(m) > 0 { - return m[0], nil - } - return nil, fmt.Errorf("no series or movie could be found") -} - -// FindEpisodeByName finds an episode by its crunchyroll series name and episode title. -// Use this in combination with ParseEpisodeURL and hand over the corresponding results -// to this function. -func (c *Crunchyroll) FindEpisodeByName(seriesName, episodeTitle string) ([]*Episode, error) { - series, _, err := c.Search(seriesName, 5) - if err != nil { - return nil, err - } - - var matchingEpisodes []*Episode - for _, s := range series { - seasons, err := s.Seasons() - if err != nil { - return nil, err - } - - for _, season := range seasons { - episodes, err := season.Episodes() - if err != nil { - return nil, err - } - for _, episode := range episodes { - if episode.SlugTitle == episodeTitle { - matchingEpisodes = append(matchingEpisodes, episode) - } - } - } - } - - return matchingEpisodes, nil -} - -// Search searches a query and returns all found series and movies within the given limit. -func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) { - searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s", - query, limit, c.Locale) - resp, err := c.request(searchEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'search' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - item := item.(map[string]interface{}) - if item["total"].(float64) > 0 { - switch item["type"] { - case MediaTypeSeries: - for _, series := range item["items"].([]interface{}) { - series2 := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(series, series2); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(series.(map[string]interface{})["series_metadata"].(map[string]interface{}), series2); err != nil { - return nil, nil, err - } - - s = append(s, series2) - } - case MediaTypeMovie: - for _, movie := range item["items"].([]interface{}) { - movie2 := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(movie, movie2); err != nil { - return nil, nil, err - } - - m = append(m, movie2) - } - } - } - } - - return s, m, nil -} diff --git a/season.go b/season.go deleted file mode 100644 index 907122d..0000000 --- a/season.go +++ /dev/null @@ -1,135 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" - "regexp" -) - -// Season contains information about an anime season. -type Season struct { - crunchy *Crunchyroll - - children []*Episode - - ID string `json:"id"` - ChannelID string `json:"channel_id"` - - Title string `json:"title"` - SlugTitle string `json:"slug_title"` - - SeriesID string `json:"series_id"` - SeasonNumber int `json:"season_number"` - - IsComplete bool `json:"is_complete"` - - Description string `json:"description"` - Keywords []string `json:"keywords"` - SeasonTags []string `json:"season_tags"` - IsMature bool `json:"is_mature"` - MatureBlocked bool `json:"mature_blocked"` - IsSubbed bool `json:"is_subbed"` - IsDubbed bool `json:"is_dubbed"` - IsSimulcast bool `json:"is_simulcast"` - - SeoTitle string `json:"seo_title"` - SeoDescription string `json:"seo_description"` - - AvailabilityNotes string `json:"availability_notes"` - - // the locales are always empty, idk why, this may change in the future - AudioLocales []LOCALE - SubtitleLocales []LOCALE -} - -// SeasonFromID returns a season by its api id. -func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) { - resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.Bucket, - id, - crunchy.Locale, - crunchy.Config.Signature, - crunchy.Config.Policy, - crunchy.Config.KeyPairID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - season := &Season{ - crunchy: crunchy, - ID: id, - } - if err := decodeMapToStruct(jsonBody, season); err != nil { - return nil, err - } - - return season, nil -} - -// AudioLocale returns the audio locale of the season. -// Will fail if no streams are available, thus use Season.Available -// to prevent any misleading errors. -func (s *Season) AudioLocale() (LOCALE, error) { - episodes, err := s.Episodes() - if err != nil { - return "", err - } - return episodes[0].AudioLocale() -} - -// Available returns if downloadable streams for this season are available. -func (s *Season) Available() (bool, error) { - episodes, err := s.Episodes() - if err != nil { - return false, err - } - return episodes[0].Available(), nil -} - -// Episodes returns all episodes which are available for the season. -func (s *Season) Episodes() (episodes []*Episode, err error) { - if s.children != nil { - return s.children, nil - } - - resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/episodes?season_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - s.crunchy.Config.Bucket, - s.ID, - s.crunchy.Locale, - s.crunchy.Config.Signature, - s.crunchy.Config.Policy, - s.crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - for _, item := range jsonBody["items"].([]interface{}) { - episode := &Episode{ - crunchy: s.crunchy, - } - if err = decodeMapToStruct(item, episode); err != nil { - return nil, err - } - if episode.Playback != "" { - streamHref := item.(map[string]interface{})["__links__"].(map[string]interface{})["streams"].(map[string]interface{})["href"].(string) - if match := regexp.MustCompile(`(?m)(\w+)/streams$`).FindAllStringSubmatch(streamHref, -1); len(match) > 0 { - episode.StreamID = match[0][1] - } else { - fmt.Println() - } - } - episodes = append(episodes, episode) - } - - if s.crunchy.cache { - s.children = episodes - } - return -} diff --git a/simulcast.go b/simulcast.go deleted file mode 100644 index e77d2f8..0000000 --- a/simulcast.go +++ /dev/null @@ -1,45 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// Simulcasts returns all available simulcast seasons for the current locale. -func (c *Crunchyroll) Simulcasts() (s []*Simulcast, err error) { - seasonListEndpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/season_list?locale=%s", c.Locale) - resp, err := c.request(seasonListEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'season_list' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - simulcast := &Simulcast{} - if err := decodeMapToStruct(item, simulcast); err != nil { - return nil, err - } - - s = append(s, simulcast) - } - - return s, nil -} - -// Simulcast contains all information about a simulcast season. -type Simulcast struct { - ID string `json:"id"` - - Localization struct { - Title string `json:"title"` - - // appears to be always an empty string. - Description string `json:"description"` - } `json:"localization"` -} diff --git a/stream.go b/stream.go deleted file mode 100644 index e5ce54f..0000000 --- a/stream.go +++ /dev/null @@ -1,129 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "github.com/grafov/m3u8" - "net/http" - "regexp" -) - -// Stream contains information about all available video stream of an episode. -type Stream struct { - crunchy *Crunchyroll - - children []*Format - - HardsubLocale LOCALE - AudioLocale LOCALE - Subtitles []*Subtitle - - formatType FormatType - id string - streamURL string -} - -// StreamsFromID returns a stream by its api id. -func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) { - return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.Bucket, - id, - crunchy.Locale, - crunchy.Config.Signature, - crunchy.Config.Policy, - crunchy.Config.KeyPairID)) -} - -// Formats returns all formats which are available for the stream. -func (s *Stream) Formats() ([]*Format, error) { - if s.children != nil { - return s.children, nil - } - - resp, err := s.crunchy.Client.Get(s.streamURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - playlist, _, err := m3u8.DecodeFrom(resp.Body, true) - if err != nil { - return nil, err - } - var formats []*Format - for _, variant := range playlist.(*m3u8.MasterPlaylist).Variants { - formats = append(formats, &Format{ - crunchy: s.crunchy, - ID: s.id, - FormatType: s.formatType, - Video: variant, - AudioLocale: s.AudioLocale, - Hardsub: s.HardsubLocale, - Subtitles: s.Subtitles, - }) - } - - if s.crunchy.cache { - s.children = formats - } - return formats, nil -} - -// fromVideoStreams returns all streams which are accessible via the endpoint. -func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, err error) { - resp, err := crunchy.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - if len(jsonBody) == 0 { - // this may get thrown when the crunchyroll account is just a normal account and not one with premium - if !crunchy.Config.Premium { - return nil, fmt.Errorf("no stream available, this might be the result of using a non-premium account") - } else { - return nil, fmt.Errorf("no stream available") - } - } - - audioLocale := jsonBody["audio_locale"].(string) - - var subtitles []*Subtitle - for _, rawSubtitle := range jsonBody["subtitles"].(map[string]interface{}) { - subtitle := &Subtitle{ - crunchy: crunchy, - } - decodeMapToStruct(rawSubtitle.(map[string]interface{}), subtitle) - subtitles = append(subtitles, subtitle) - } - - for _, streamData := range jsonBody["streams"].(map[string]interface{})["adaptive_hls"].(map[string]interface{}) { - streamData := streamData.(map[string]interface{}) - - hardsubLocale := streamData["hardsub_locale"].(string) - - var id string - var formatType FormatType - href := jsonBody["__links__"].(map[string]interface{})["resource"].(map[string]interface{})["href"].(string) - if match := regexp.MustCompile(`(?sm)/(\w+)/(\w+)$`).FindAllStringSubmatch(href, -1); len(match) > 0 { - formatType = FormatType(match[0][1]) - id = match[0][2] - } - - stream := &Stream{ - crunchy: crunchy, - HardsubLocale: LOCALE(hardsubLocale), - formatType: formatType, - id: id, - streamURL: streamData["url"].(string), - AudioLocale: LOCALE(audioLocale), - Subtitles: subtitles, - } - - streams = append(streams, stream) - } - - return -} diff --git a/subtitle.go b/subtitle.go deleted file mode 100644 index 164b56e..0000000 --- a/subtitle.go +++ /dev/null @@ -1,32 +0,0 @@ -package crunchyroll - -import ( - "io" - "net/http" -) - -// Subtitle contains the information about a video subtitle. -type Subtitle struct { - crunchy *Crunchyroll - - Locale LOCALE `json:"locale"` - URL string `json:"url"` - Format string `json:"format"` -} - -// Save writes the subtitle to the given io.Writer. -func (s Subtitle) Save(writer io.Writer) error { - req, err := http.NewRequestWithContext(s.crunchy.Context, http.MethodGet, s.URL, nil) - if err != nil { - return err - } - - resp, err := s.crunchy.Client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - _, err = io.Copy(writer, resp.Body) - return err -} diff --git a/suggestions.go b/suggestions.go deleted file mode 100644 index 35d7a9b..0000000 --- a/suggestions.go +++ /dev/null @@ -1,82 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// Recommendations returns series and movie recommendations from crunchyroll based on the currently logged in account within the given limit. -func (c *Crunchyroll) Recommendations(limit uint) (s []*Series, m []*Movie, err error) { - recommendationsEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/recommendations?n=%d&locale=%s", - c.Config.AccountID, limit, c.Locale) - resp, err := c.request(recommendationsEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'recommendations' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - switch item.(map[string]interface{})["type"] { - case MediaTypeSeries: - series := &Series{ - crunchy: c, - } - if err := decodeMapToStruct(item, series); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { - return nil, nil, err - } - - s = append(s, series) - case MediaTypeMovie: - movie := &Movie{ - crunchy: c, - } - if err := decodeMapToStruct(item, movie); err != nil { - return nil, nil, err - } - - m = append(m, movie) - } - } - - return s, m, nil -} - -// UpNext returns the episodes that are up next based on the currently logged in account within the given limit. -func (c *Crunchyroll) UpNext(limit uint) (e []*Episode, err error) { - upNextAccountEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/up_next_account?n=%d&locale=%s", - c.Config.AccountID, limit, c.Locale) - resp, err := c.request(upNextAccountEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'up_next_account' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - panel := item.(map[string]interface{})["panel"] - - episode := &Episode{ - crunchy: c, - } - if err := decodeMapToStruct(panel, episode); err != nil { - return nil, err - } - - e = append(e, episode) - } - - return e, nil -} diff --git a/url.go b/url.go deleted file mode 100644 index 95f3951..0000000 --- a/url.go +++ /dev/null @@ -1,128 +0,0 @@ -package crunchyroll - -import ( - "fmt" -) - -// ExtractEpisodesFromUrl extracts all episodes from an url. -// If audio is not empty, the episodes gets filtered after the given locale. -func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Episode, error) { - series, episodes, err := c.ParseUrl(url) - if err != nil { - return nil, err - } - - var eps []*Episode - var notAvailableContinue bool - - if series != nil { - seasons, err := series.Seasons() - if err != nil { - return nil, err - } - for _, season := range seasons { - if audio != nil { - if available, err := season.Available(); err != nil { - return nil, err - } else if !available { - notAvailableContinue = true - continue - } - - locale, err := season.AudioLocale() - if err != nil { - return nil, err - } - - var found bool - for _, l := range audio { - if locale == l { - found = true - break - } - } - if !found { - continue - } - } - e, err := season.Episodes() - if err != nil { - return nil, err - } - eps = append(eps, e...) - } - } else if episodes != nil { - if audio == nil { - return episodes, nil - } - - for _, episode := range episodes { - // if no episode streams are available, calling episode.AudioLocale - // will result in an unwanted error - if !episode.Available() { - notAvailableContinue = true - continue - } - locale, err := episode.AudioLocale() - if err != nil { - return nil, err - } - if audio != nil { - var found bool - for _, l := range audio { - if locale == l { - found = true - break - } - } - if !found { - continue - } - } - - eps = append(eps, episode) - } - } - - if len(eps) == 0 { - if notAvailableContinue { - return nil, fmt.Errorf("could not find any matching episode which is accessable with a non-premium account") - } else { - return nil, fmt.Errorf("could not find any matching episode") - } - } - - return eps, nil -} - -// ParseUrl parses the given url into a series or episode. -// The returning episode is a slice because non-beta urls have the same episode with different languages. -func (c *Crunchyroll) ParseUrl(url string) (*Series, []*Episode, error) { - if seriesId, ok := ParseBetaSeriesURL(url); ok { - series, err := SeriesFromID(c, seriesId) - if err != nil { - return nil, nil, err - } - return series, nil, nil - } else if episodeId, ok := ParseBetaEpisodeURL(url); ok { - episode, err := EpisodeFromID(c, episodeId) - if err != nil { - return nil, nil, err - } - return nil, []*Episode{episode}, nil - } else if seriesName, ok := ParseVideoURL(url); ok { - video, err := c.FindVideoByName(seriesName) - if err != nil { - return nil, nil, err - } - return video.(*Series), nil, nil - } else if seriesName, title, _, _, ok := ParseEpisodeURL(url); ok { - episodes, err := c.FindEpisodeByName(seriesName, title) - if err != nil { - return nil, nil, err - } - return nil, episodes, nil - } else { - return nil, nil, fmt.Errorf("invalid url %s", url) - } -} diff --git a/utils.go b/utils.go deleted file mode 100644 index a665040..0000000 --- a/utils.go +++ /dev/null @@ -1,84 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "encoding/json" - "fmt" - "net/url" - "reflect" - "strings" -) - -func decodeMapToStruct(m interface{}, s interface{}) error { - jsonBody, err := json.Marshal(m) - if err != nil { - return err - } - return json.Unmarshal(jsonBody, s) -} - -func regexGroups(parsed [][]string, subexpNames ...string) map[string]string { - groups := map[string]string{} - for _, match := range parsed { - for i, content := range match { - if subexpName := subexpNames[i]; subexpName != "" { - groups[subexpName] = content - } - } - } - return groups -} - -func encodeStructToQueryValues(s interface{}) (string, error) { - values := make(url.Values) - v := reflect.ValueOf(s) - - for i := 0; i < v.Type().NumField(); i++ { - - // don't include parameters with default or without values in the query to avoid corruption of the API response. - switch v.Field(i).Kind() { - case reflect.Slice, reflect.String: - if v.Field(i).Len() == 0 { - continue - } - case reflect.Bool: - if !v.Field(i).Bool() { - continue - } - case reflect.Uint: - if v.Field(i).Uint() == 0 { - continue - } - } - - key := v.Type().Field(i).Tag.Get("param") - var val string - - if v.Field(i).Kind() == reflect.Slice { - var items []string - - for _, i := range v.Field(i).Interface().([]string) { - items = append(items, i) - } - - val = strings.Join(items, ",") - } else { - val = fmt.Sprint(v.Field(i).Interface()) - } - - values.Add(key, val) - } - - return values.Encode(), nil -} - -func structDefaults[T any](defaultStruct T, customStruct T) (T, error) { - rawDefaultStruct, err := json.Marshal(defaultStruct) - if err != nil { - return *new(T), err - } - if err = json.NewDecoder(bytes.NewBuffer(rawDefaultStruct)).Decode(&customStruct); err != nil { - return *new(T), err - } - return customStruct, nil -} diff --git a/utils/locale.go b/utils/locale.go deleted file mode 100644 index 85a8650..0000000 --- a/utils/locale.go +++ /dev/null @@ -1,60 +0,0 @@ -package utils - -import ( - "github.com/ByteDream/crunchyroll-go/v3" -) - -// AllLocales is an array of all available locales. -var AllLocales = []crunchyroll.LOCALE{ - crunchyroll.JP, - crunchyroll.US, - crunchyroll.LA, - crunchyroll.ES, - crunchyroll.FR, - crunchyroll.PT, - crunchyroll.BR, - crunchyroll.IT, - crunchyroll.DE, - crunchyroll.RU, - crunchyroll.AR, -} - -// ValidateLocale validates if the given locale actually exist. -func ValidateLocale(locale crunchyroll.LOCALE) bool { - for _, l := range AllLocales { - if l == locale { - return true - } - } - return false -} - -// LocaleLanguage returns the country by its locale. -func LocaleLanguage(locale crunchyroll.LOCALE) string { - switch locale { - case crunchyroll.JP: - return "Japanese" - case crunchyroll.US: - return "English (US)" - case crunchyroll.LA: - return "Spanish (Latin America)" - case crunchyroll.ES: - return "Spanish (Spain)" - case crunchyroll.FR: - return "French" - case crunchyroll.PT: - return "Portuguese (Europe)" - case crunchyroll.BR: - return "Portuguese (Brazil)" - case crunchyroll.IT: - return "Italian" - case crunchyroll.DE: - return "German" - case crunchyroll.RU: - return "Russian" - case crunchyroll.AR: - return "Arabic" - default: - return "" - } -} diff --git a/utils/sort.go b/utils/sort.go deleted file mode 100644 index 661eea8..0000000 --- a/utils/sort.go +++ /dev/null @@ -1,158 +0,0 @@ -package utils - -import ( - "github.com/ByteDream/crunchyroll-go/v3" - "sort" - "strconv" - "strings" - "sync" -) - -// SortEpisodesBySeason sorts the given episodes by their seasons. -// Note that the same episodes just with different audio locales will cause problems. -func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Episode { - sortMap := map[string]map[int][]*crunchyroll.Episode{} - - for _, episode := range episodes { - if _, ok := sortMap[episode.SeriesID]; !ok { - sortMap[episode.SeriesID] = map[int][]*crunchyroll.Episode{} - } - if _, ok := sortMap[episode.SeriesID][episode.SeasonNumber]; !ok { - sortMap[episode.SeriesID][episode.SeasonNumber] = make([]*crunchyroll.Episode, 0) - } - sortMap[episode.SeriesID][episode.SeasonNumber] = append(sortMap[episode.SeriesID][episode.SeasonNumber], episode) - } - - var eps [][]*crunchyroll.Episode - for _, series := range sortMap { - var keys []int - for seriesNumber := range series { - keys = append(keys, seriesNumber) - } - sort.Ints(keys) - - for _, key := range keys { - es := series[key] - if len(es) > 0 { - sort.Sort(EpisodesByNumber(es)) - eps = append(eps, es) - } - } - } - - return eps -} - -// SortEpisodesByAudio sort the given episodes by their audio locale. -func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCALE][]*crunchyroll.Episode, error) { - eps := map[crunchyroll.LOCALE][]*crunchyroll.Episode{} - - errChan := make(chan error) - - var wg sync.WaitGroup - var lock sync.Mutex - for _, episode := range episodes { - if !episode.Available() { - continue - } - episode := episode - wg.Add(1) - go func() { - defer wg.Done() - audioLocale, err := episode.AudioLocale() - if err != nil { - errChan <- err - return - } - lock.Lock() - defer lock.Unlock() - - if _, ok := eps[audioLocale]; !ok { - eps[audioLocale] = make([]*crunchyroll.Episode, 0) - } - eps[audioLocale] = append(eps[audioLocale], episode) - }() - } - go func() { - wg.Wait() - errChan <- nil - }() - - if err := <-errChan; err != nil { - return nil, err - } - return eps, nil -} - -// MovieListingsByDuration sorts movie listings by their duration. -type MovieListingsByDuration []*crunchyroll.MovieListing - -func (mlbd MovieListingsByDuration) Len() int { - return len(mlbd) -} -func (mlbd MovieListingsByDuration) Swap(i, j int) { - mlbd[i], mlbd[j] = mlbd[j], mlbd[i] -} -func (mlbd MovieListingsByDuration) Less(i, j int) bool { - return mlbd[i].DurationMS < mlbd[j].DurationMS -} - -// EpisodesByDuration sorts episodes by their duration. -type EpisodesByDuration []*crunchyroll.Episode - -func (ebd EpisodesByDuration) Len() int { - return len(ebd) -} -func (ebd EpisodesByDuration) Swap(i, j int) { - ebd[i], ebd[j] = ebd[j], ebd[i] -} -func (ebd EpisodesByDuration) Less(i, j int) bool { - return ebd[i].DurationMS < ebd[j].DurationMS -} - -// EpisodesByNumber sorts episodes after their episode number. -type EpisodesByNumber []*crunchyroll.Episode - -func (ebn EpisodesByNumber) Len() int { - return len(ebn) -} -func (ebn EpisodesByNumber) Swap(i, j int) { - ebn[i], ebn[j] = ebn[j], ebn[i] -} -func (ebn EpisodesByNumber) Less(i, j int) bool { - return ebn[i].EpisodeNumber < ebn[j].EpisodeNumber -} - -// FormatsByResolution sorts formats after their resolution. -type FormatsByResolution []*crunchyroll.Format - -func (fbr FormatsByResolution) Len() int { - return len(fbr) -} -func (fbr FormatsByResolution) Swap(i, j int) { - fbr[i], fbr[j] = fbr[j], fbr[i] -} -func (fbr FormatsByResolution) Less(i, j int) bool { - iSplitRes := strings.SplitN(fbr[i].Video.Resolution, "x", 2) - iResX, _ := strconv.Atoi(iSplitRes[0]) - iResY, _ := strconv.Atoi(iSplitRes[1]) - - jSplitRes := strings.SplitN(fbr[j].Video.Resolution, "x", 2) - jResX, _ := strconv.Atoi(jSplitRes[0]) - jResY, _ := strconv.Atoi(jSplitRes[1]) - - return iResX+iResY < jResX+jResY -} - -// SubtitlesByLocale sorts subtitles after their locale. -type SubtitlesByLocale []*crunchyroll.Subtitle - -func (sbl SubtitlesByLocale) Len() int { - return len(sbl) -} -func (sbl SubtitlesByLocale) Swap(i, j int) { - sbl[i], sbl[j] = sbl[j], sbl[i] -} -func (sbl SubtitlesByLocale) Less(i, j int) bool { - return LocaleLanguage(sbl[i].Locale) < LocaleLanguage(sbl[j].Locale) -} diff --git a/video.go b/video.go deleted file mode 100644 index 810244f..0000000 --- a/video.go +++ /dev/null @@ -1,280 +0,0 @@ -package crunchyroll - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" -) - -type video struct { - ID string `json:"id"` - ExternalID string `json:"external_id"` - - Description string `json:"description"` - Title string `json:"title"` - Slug string `json:"slug"` - SlugTitle string `json:"slug_title"` - - Images struct { - PosterTall [][]Image `json:"poster_tall"` - PosterWide [][]Image `json:"poster_wide"` - } `json:"images"` -} - -// Video is the base for Movie and Season. -type Video interface{} - -// Movie contains information about a movie. -type Movie struct { - video - Video - - crunchy *Crunchyroll - - children []*MovieListing - - // not generated when calling MovieFromID. - MovieListingMetadata struct { - AvailabilityNotes string `json:"availability_notes"` - AvailableOffline bool `json:"available_offline"` - DurationMS int `json:"duration_ms"` - ExtendedDescription string `json:"extended_description"` - FirstMovieID string `json:"first_movie_id"` - IsDubbed bool `json:"is_dubbed"` - IsMature bool `json:"is_mature"` - IsPremiumOnly bool `json:"is_premium_only"` - IsSubbed bool `json:"is_subbed"` - MatureRatings []string `json:"mature_ratings"` - MovieReleaseYear int `json:"movie_release_year"` - SubtitleLocales []LOCALE `json:"subtitle_locales"` - } `json:"movie_listing_metadata"` - - Playback string `json:"playback"` - - PromoDescription string `json:"promo_description"` - PromoTitle string `json:"promo_title"` - SearchMetadata struct { - Score float64 `json:"score"` - } -} - -// MovieFromID returns a movie by its api id. -func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.Bucket, - id, - crunchy.Locale, - crunchy.Config.Signature, - crunchy.Config.Policy, - crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - movieListing := &Movie{ - crunchy: crunchy, - } - movieListing.ID = id - if err = decodeMapToStruct(jsonBody, movieListing); err != nil { - return nil, err - } - - return movieListing, nil -} - -// MovieListing returns all videos corresponding with the movie. -func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) { - if m.children != nil { - return m.children, nil - } - - resp, err := m.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - m.crunchy.Config.Bucket, - m.ID, - m.crunchy.Locale, - m.crunchy.Config.Signature, - m.crunchy.Config.Policy, - m.crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - for _, item := range jsonBody["items"].([]interface{}) { - movieListing := &MovieListing{ - crunchy: m.crunchy, - } - if err = decodeMapToStruct(item, movieListing); err != nil { - return nil, err - } - movieListings = append(movieListings, movieListing) - } - - if m.crunchy.cache { - m.children = movieListings - } - return movieListings, nil -} - -// Series contains information about an anime series. -type Series struct { - video - Video - - crunchy *Crunchyroll - - children []*Season - - PromoDescription string `json:"promo_description"` - PromoTitle string `json:"promo_title"` - - AvailabilityNotes string `json:"availability_notes"` - EpisodeCount int `json:"episode_count"` - ExtendedDescription string `json:"extended_description"` - IsDubbed bool `json:"is_dubbed"` - IsMature bool `json:"is_mature"` - IsSimulcast bool `json:"is_simulcast"` - IsSubbed bool `json:"is_subbed"` - MatureBlocked bool `json:"mature_blocked"` - MatureRatings []string `json:"mature_ratings"` - SeasonCount int `json:"season_count"` - - // not generated when calling SeriesFromID. - SearchMetadata struct { - Score float64 `json:"score"` - } -} - -// SeriesFromID returns a series by its api id. -func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) { - resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - crunchy.Config.Bucket, - id, - crunchy.Locale, - crunchy.Config.Signature, - crunchy.Config.Policy, - crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - series := &Series{ - crunchy: crunchy, - } - series.ID = id - if err = decodeMapToStruct(jsonBody, series); err != nil { - return nil, err - } - - return series, nil -} - -// AddToWatchlist adds the current episode to the watchlist. -// Will return an RequestError with the response status code of 409 if the series was already on the watchlist before. -func (s *Series) AddToWatchlist() error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s?locale=%s", s.crunchy.Config.AccountID, s.crunchy.Locale) - body, _ := json.Marshal(map[string]string{"content_id": s.ID}) - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - _, err = s.crunchy.requestFull(req) - return err -} - -// RemoveFromWatchlist removes the current episode from the watchlist. -// Will return an RequestError with the response status code of 404 if the series was not on the watchlist before. -func (s *Series) RemoveFromWatchlist() error { - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/watchlist/%s/%s?locale=%s", s.crunchy.Config.AccountID, s.ID, s.crunchy.Locale) - _, err := s.crunchy.request(endpoint, http.MethodDelete) - return err -} - -// Similar returns similar series and movies to the current series within the given limit. -func (s *Series) Similar(limit uint) (ss []*Series, m []*Movie, err error) { - similarToEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/%s/similar_to?guid=%s&n=%d&locale=%s", - s.crunchy.Config.AccountID, s.ID, limit, s.crunchy.Locale) - resp, err := s.crunchy.request(similarToEndpoint, http.MethodGet) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, nil, fmt.Errorf("failed to parse 'similar_to' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - switch item.(map[string]interface{})["type"] { - case MediaTypeSeries: - series := &Series{ - crunchy: s.crunchy, - } - if err := decodeMapToStruct(item, series); err != nil { - return nil, nil, err - } - if err := decodeMapToStruct(item.(map[string]interface{})["series_metadata"].(map[string]interface{}), series); err != nil { - return nil, nil, err - } - - ss = append(ss, series) - case MediaTypeMovie: - movie := &Movie{ - crunchy: s.crunchy, - } - if err := decodeMapToStruct(item, movie); err != nil { - return nil, nil, err - } - - m = append(m, movie) - } - } - return -} - -// Seasons returns all seasons of a series. -func (s *Series) Seasons() (seasons []*Season, err error) { - if s.children != nil { - return s.children, nil - } - - resp, err := s.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/seasons?series_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s", - s.crunchy.Config.Bucket, - s.ID, - s.crunchy.Locale, - s.crunchy.Config.Signature, - s.crunchy.Config.Policy, - s.crunchy.Config.KeyPairID), http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - for _, item := range jsonBody["items"].([]interface{}) { - season := &Season{ - crunchy: s.crunchy, - } - if err = decodeMapToStruct(item, season); err != nil { - return nil, err - } - seasons = append(seasons, season) - } - - if s.crunchy.cache { - s.children = seasons - } - return -} diff --git a/wallpaper.go b/wallpaper.go deleted file mode 100644 index e99bafb..0000000 --- a/wallpaper.go +++ /dev/null @@ -1,16 +0,0 @@ -package crunchyroll - -import "fmt" - -// Wallpaper contains a wallpaper name which can be set via Account.ChangeWallpaper. -type Wallpaper string - -// TinyUrl returns the url to the wallpaper in low resolution. -func (w *Wallpaper) TinyUrl() string { - return fmt.Sprintf("https://static.crunchyroll.com/assets/wallpaper/360x115/%s", *w) -} - -// BigUrl returns the url to the wallpaper in high resolution. -func (w *Wallpaper) BigUrl() string { - return fmt.Sprintf("https://static.crunchyroll.com/assets/wallpaper/1920x400/%s", *w) -} diff --git a/watch_history.go b/watch_history.go deleted file mode 100644 index d9ce265..0000000 --- a/watch_history.go +++ /dev/null @@ -1,45 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// WatchHistory returns the history of watched episodes based on the currently logged in account from the given page with the given size. -func (c *Crunchyroll) WatchHistory(page uint, size uint) (e []*HistoryEpisode, err error) { - watchHistoryEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/watch-history/%s?page=%d&page_size=%d&locale=%s", - c.Config.AccountID, page, size, c.Locale) - resp, err := c.request(watchHistoryEndpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - if err = json.NewDecoder(resp.Body).Decode(&jsonBody); err != nil { - return nil, fmt.Errorf("failed to parse 'watch-history' response: %w", err) - } - - for _, item := range jsonBody["items"].([]interface{}) { - panel := item.(map[string]interface{})["panel"] - - episode := &Episode{ - crunchy: c, - } - if err := decodeMapToStruct(panel, episode); err != nil { - return nil, err - } - - historyEpisode := &HistoryEpisode{ - Episode: episode, - } - if err := decodeMapToStruct(item, historyEpisode); err != nil { - return nil, err - } - - e = append(e, historyEpisode) - } - - return e, nil -} diff --git a/watchlist.go b/watchlist.go deleted file mode 100644 index 6fa0bc0..0000000 --- a/watchlist.go +++ /dev/null @@ -1,99 +0,0 @@ -package crunchyroll - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" -) - -// WatchlistLanguageType represents a filter type to filter Crunchyroll.Watchlist entries after sub or dub. -type WatchlistLanguageType int - -const ( - WatchlistLanguageSubbed WatchlistLanguageType = iota + 1 - WatchlistLanguageDubbed -) - -type WatchlistOrderType string - -const ( - WatchlistOrderAsc = "asc" - WatchlistOrderDesc = "desc" -) - -// WatchlistOptions represents options for receiving the user watchlist. -type WatchlistOptions struct { - // Order specified whether the results should be order ascending or descending. - Order WatchlistOrderType - - // OnlyFavorites specifies whether only episodes which are marked as favorite should be returned. - OnlyFavorites bool - - // LanguageType specifies whether returning episodes should be only subbed or dubbed. - LanguageType WatchlistLanguageType - - // ContentType specified whether returning videos should only be series episodes or movies. - // But tbh all movies I've searched on crunchy were flagged as series too, so this - // parameter is kinda useless. - ContentType MediaType -} - -// Watchlist returns the watchlist entries for the currently logged in user. -func (c *Crunchyroll) Watchlist(options WatchlistOptions, limit uint) ([]*WatchlistEntry, error) { - values := url.Values{} - if options.Order == "" { - options.Order = WatchlistOrderDesc - } - values.Set("order", string(options.Order)) - if options.OnlyFavorites { - values.Set("only_favorites", "true") - } - switch options.LanguageType { - case WatchlistLanguageSubbed: - values.Set("is_subbed", "true") - case WatchlistLanguageDubbed: - values.Set("is_dubbed", "true") - } - values.Set("n", strconv.Itoa(int(limit))) - values.Set("locale", string(c.Locale)) - - endpoint := fmt.Sprintf("https://beta.crunchyroll.com/content/v1/%s/watchlist?%s", c.Config.AccountID, values.Encode()) - resp, err := c.request(endpoint, http.MethodGet) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var jsonBody map[string]interface{} - json.NewDecoder(resp.Body).Decode(&jsonBody) - - var watchlistEntries []*WatchlistEntry - if err := decodeMapToStruct(jsonBody["items"], &watchlistEntries); err != nil { - return nil, err - } - - for _, entry := range watchlistEntries { - switch entry.Panel.Type { - case WatchlistEntryEpisode: - entry.Panel.EpisodeMetadata.crunchy = c - case WatchlistEntrySeries: - entry.Panel.SeriesMetadata.crunchy = c - } - } - - return watchlistEntries, nil -} - -// WatchlistEntry contains information about an entry on the watchlist. -type WatchlistEntry struct { - Panel Panel `json:"panel"` - - New bool `json:"new"` - NewContent bool `json:"new_content"` - IsFavorite bool `json:"is_favorite"` - NeverWatched bool `json:"never_watched"` - CompleteStatus bool `json:"complete_status"` - Playahead uint `json:"playahead"` -} From d65226252da808e9a8f892ff791332d878360809 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 27 Jun 2022 22:36:46 +0200 Subject: [PATCH 143/630] Add split notice to README --- README.md | 80 +++++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 10d6c99..b6d960f 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,41 @@ -# crunchyroll-go +# crunchy-cli -A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](https://www.crunchyroll.com) api. To use it, you need a crunchyroll premium account to for full (api) access. +A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account to for full (api) access. <p align="center"> - <a href="https://github.com/ByteDream/crunchyroll-go"> - <img src="https://img.shields.io/github/languages/code-size/ByteDream/crunchyroll-go?style=flat-square" alt="Code size"> + <a href="https://github.com/ByteDream/crunchy-cli"> + <img src="https://img.shields.io/github/languages/code-size/ByteDream/crunchy-cli?style=flat-square" alt="Code size"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/releases/latest"> - <img src="https://img.shields.io/github/downloads/ByteDream/crunchyroll-go/total?style=flat-square" alt="Download Badge"> + <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/downloads/ByteDream/crunchy-cli/total?style=flat-square" alt="Download Badge"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/blob/master/LICENSE"> - <img src="https://img.shields.io/github/license/ByteDream/crunchyroll-go?style=flat-square" alt="License"> + <a href="https://github.com/ByteDream/crunchy-cli/blob/master/LICENSE"> + <img src="https://img.shields.io/github/license/ByteDream/crunchy-cli?style=flat-square" alt="License"> </a> <a href="https://golang.org"> - <img src="https://img.shields.io/github/go-mod/go-version/ByteDream/crunchyroll-go?style=flat-square" alt="Go version"> + <img src="https://img.shields.io/github/go-mod/go-version/ByteDream/crunchy-cli?style=flat-square" alt="Go version"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/releases/latest"> - <img src="https://img.shields.io/github/v/release/ByteDream/crunchyroll-go?style=flat-square" alt="Release"> + <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/v/release/ByteDream/crunchy-cli?style=flat-square" alt="Release"> </a> <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> - <a href="https://github.com/ByteDream/crunchyroll-go/actions/workflows/ci.yml"> - <img src="https://github.com/ByteDream/crunchyroll-go/workflows/CI/badge.svg?style=flat" alt="CI"> + <a href="https://github.com/ByteDream/crunchy-cli/actions/workflows/ci.yml"> + <img src="https://github.com/ByteDream/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> </a> </p> <p align="center"> <a href="#%EF%B8%8F-cli">CLI ๐Ÿ–ฅ๏ธ</a> โ€ข - <a href="#-library">Library ๐Ÿ“š</a> - โ€ข <a href="#%EF%B8%8F-disclaimer">Disclaimer โ˜๏ธ</a> โ€ข <a href="#-license">License โš–</a> </p> -_There is currently a [poll](https://github.com/ByteDream/crunchyroll-go/issues/39) going on how to name the CLI in the next version. Please have a moment and participate to it._ +_This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility. +See [#39](https://github.com/ByteDream/crunchy-cli/issues/39) for more information._ # ๐Ÿ–ฅ๏ธ CLI @@ -48,37 +47,42 @@ _There is currently a [poll](https://github.com/ByteDream/crunchyroll-go/issues/ ## ๐Ÿ’พ Get the executable -- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchyroll-go/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchyroll-go/crunchy-{tag}_darwin) +- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchy-cli/releases/latest) or get it from below: + - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell $ yay -S crunchyroll-go ``` -- On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): +- <del> + + On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): ```shell $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` -- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchyroll-go/releases/latest) (like Raspberry Pi or M1 Mac): - - use `make` (requires `go` to be installed): + + </del> + <i>Currently not working because the repo got renamed!</i> +- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): + - use `make` (requires `go` to be installed): ```shell - $ git clone https://github.com/ByteDream/crunchyroll-go - $ cd crunchyroll-go + $ git clone https://github.com/ByteDream/crunchy-cli + $ cd crunchy-cli $ make $ sudo make install # <- only if you want to install it on your system ``` - - use `go`: + - use `go`: ```shell - $ git clone https://github.com/ByteDream/crunchyroll-go - $ cd crunchyroll-go - $ go build -o crunchy cmd/crunchyroll-go/main.go + $ git clone https://github.com/ByteDream/crunchy-cli + $ cd crunchy-cli + $ go build -o crunchy . ``` ## ๐Ÿ“ Examples -_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Cli), further usages and options are described there. +_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli), further usages and options are described there. ### Login @@ -165,7 +169,7 @@ The following flags can be (optional) passed to modify the [archive](#archive) p | `-l` | `--language` | Audio locale which should be downloaded. Can be used multiple times. | | `-d` | `--directory` | Directory to download the video(s) to. | | `-o` | `--output` | Name of the output file. | -| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Cli#archive) for more information. | +| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli#archive) for more information. | | `-c` | `--compress` | If is set, all output will be compresses into an archive. This flag sets the name of the compressed output file and the file ending specifies the compression algorithm (gzip, tar, zip are supported). | | `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | | `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | @@ -200,18 +204,6 @@ These flags you can use across every sub-command: | `-v` | Shows additional debug output. | | `-p` | Use a proxy to hide your ip / redirect your traffic. | -# ๐Ÿ“š Library - -Download the library via `go get` - -```shell -$ go get github.com/ByteDream/crunchyroll-go/v2 -``` - -The documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/ByteDream/crunchyroll-go/v2). - -Examples how to use the library and some features of it are described in the [wiki](https://github.com/ByteDream/crunchyroll-go/wiki/Library). - # โ˜๏ธ Disclaimer This tool is **ONLY** meant to be used for private purposes. To use this tool you need crunchyroll premium anyway, so there is no reason why rip and share the episodes. @@ -220,4 +212,4 @@ This tool is **ONLY** meant to be used for private purposes. To use this tool yo # โš– License -This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the [LICENSE](LICENSE) file for more details. +This project is licensed under the GNU General Public License v3.0 (GPL-3.0) - see the [LICENSE](LICENSE) file for more details. From 781e52059145adeceb24e2dd9b764f80e3407920 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 29 Jun 2022 20:39:38 +0200 Subject: [PATCH 144/630] Update cobra version to 1.5.0 --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 988f612..2c0abc0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c github.com/grafov/m3u8 v0.11.1 - github.com/spf13/cobra v1.4.0 + github.com/spf13/cobra v1.5.0 ) require ( diff --git a/go.sum b/go.sum index 31a2cd9..626a2c5 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c h1:jPabd/Zl/zdoSo8ZGtZLm43+42nIFHIJABvrvdMOYtY= github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c/go.mod h1:L4M1sOPjJ4ui0YXFnpVUb4AzQIa+D/i/B0QG5iz9op4= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 303689ecbb1b303dde81856350991eb30d2c3b3f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 30 Jun 2022 16:08:08 +0200 Subject: [PATCH 145/630] Move and refactor files and some more changes :3 --- Makefile | 8 +- {commands => cli/commands/archive}/archive.go | 324 ++++-------- cli/commands/archive/compress.go | 136 +++++ .../commands/download}/download.go | 144 +++--- cli/commands/info/info.go | 40 ++ {commands => cli/commands}/logger.go | 59 ++- cli/commands/login/login.go | 158 ++++++ {commands => cli/commands}/unix.go | 2 +- {commands => cli/commands/update}/update.go | 35 +- cli/commands/utils.go | 125 +++++ {commands => cli/commands}/windows.go | 2 +- cli/root.go | 85 +++ commands/info.go | 40 -- commands/login.go | 206 -------- commands/root.go | 78 --- commands/utils.go | 485 ------------------ crunchy-cli.1 | 2 +- go.mod | 2 +- go.sum | 2 + main.go | 6 +- utils/extract.go | 94 ++++ utils/file.go | 49 ++ utils/format.go | 63 +++ utils/http.go | 51 ++ utils/locale.go | 59 +++ utils/logger.go | 12 + utils/save.go | 177 +++++++ utils/system.go | 7 + utils/vars.go | 14 + 29 files changed, 1305 insertions(+), 1160 deletions(-) rename {commands => cli/commands/archive}/archive.go (67%) create mode 100644 cli/commands/archive/compress.go rename {commands => cli/commands/download}/download.go (64%) create mode 100644 cli/commands/info/info.go rename {commands => cli/commands}/logger.go (82%) create mode 100644 cli/commands/login/login.go rename {commands => cli/commands}/unix.go (95%) rename {commands => cli/commands/update}/update.go (67%) create mode 100644 cli/commands/utils.go rename {commands => cli/commands}/windows.go (94%) create mode 100644 cli/root.go delete mode 100644 commands/info.go delete mode 100644 commands/login.go delete mode 100644 commands/root.go delete mode 100644 commands/utils.go create mode 100644 utils/extract.go create mode 100644 utils/file.go create mode 100644 utils/format.go create mode 100644 utils/http.go create mode 100644 utils/locale.go create mode 100644 utils/logger.go create mode 100644 utils/save.go create mode 100644 utils/system.go create mode 100644 utils/vars.go diff --git a/Makefile b/Makefile index 4266a91..7836af9 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DESTDIR= PREFIX=/usr build: - go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(BINARY_NAME) . + go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(BINARY_NAME) . clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* @@ -24,8 +24,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchy-cli/LICENSE release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux . - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe . - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/commands.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin . + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe . + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin . strip $(VERSION_BINARY_NAME)_linux diff --git a/commands/archive.go b/cli/commands/archive/archive.go similarity index 67% rename from commands/archive.go rename to cli/commands/archive/archive.go index 5ad6900..29ee836 100644 --- a/commands/archive.go +++ b/cli/commands/archive/archive.go @@ -1,15 +1,14 @@ -package commands +package archive import ( - "archive/tar" - "archive/zip" "bufio" "bytes" - "compress/gzip" "context" "fmt" + "github.com/ByteDream/crunchy-cli/cli/commands" + "github.com/ByteDream/crunchy-cli/utils" "github.com/ByteDream/crunchyroll-go/v3" - "github.com/ByteDream/crunchyroll-go/v3/utils" + crunchyUtils "github.com/ByteDream/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "io" @@ -23,8 +22,6 @@ import ( "sort" "strconv" "strings" - "sync" - "time" ) var ( @@ -42,39 +39,39 @@ var ( archiveGoroutinesFlag int ) -var archiveCmd = &cobra.Command{ +var Cmd = &cobra.Command{ Use: "archive", Short: "Stores the given videos with all subtitles and multiple audios in a .mkv file", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { - out.Debug("Validating arguments") + utils.Log.Debug("Validating arguments") - if !hasFFmpeg() { + if !utils.HasFFmpeg() { return fmt.Errorf("ffmpeg is needed to run this command correctly") } - out.Debug("FFmpeg detected") + utils.Log.Debug("FFmpeg detected") if filepath.Ext(archiveOutputFlag) != ".mkv" { return fmt.Errorf("currently only matroska / .mkv files are supported") } for _, locale := range archiveLanguagesFlag { - if !utils.ValidateLocale(crunchyroll.LOCALE(locale)) { + if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(locale)) { // if locale is 'all', match all known locales if locale == "all" { - archiveLanguagesFlag = allLocalesAsStrings() + archiveLanguagesFlag = utils.LocalesAsStrings() break } - return fmt.Errorf("%s is not a valid locale. Choose from: %s", locale, strings.Join(allLocalesAsStrings(), ", ")) + return fmt.Errorf("%s is not a valid locale. Choose from: %s", locale, strings.Join(utils.LocalesAsStrings(), ", ")) } } - out.Debug("Using following audio locales: %s", strings.Join(archiveLanguagesFlag, ", ")) + utils.Log.Debug("Using following audio locales: %s", strings.Join(archiveLanguagesFlag, ", ")) var found bool for _, mode := range []string{"auto", "audio", "video"} { if archiveMergeFlag == mode { - out.Debug("Using %s merge behavior", archiveMergeFlag) + utils.Log.Debug("Using %s merge behavior", archiveMergeFlag) found = true break } @@ -87,7 +84,7 @@ var archiveCmd = &cobra.Command{ found = false for _, algo := range []string{".tar", ".tar.gz", ".tgz", ".zip"} { if strings.HasSuffix(archiveCompressFlag, algo) { - out.Debug("Using %s compression", algo) + utils.Log.Debug("Using %s compression", algo) found = true break } @@ -100,8 +97,8 @@ var archiveCmd = &cobra.Command{ switch archiveResolutionFlag { case "1080p", "720p", "480p", "360p": - intRes, _ := strconv.ParseFloat(strings.TrimSuffix(downloadResolutionFlag, "p"), 84) - archiveResolutionFlag = fmt.Sprintf("%.0fx%s", math.Ceil(intRes*(float64(16)/float64(9))), strings.TrimSuffix(downloadResolutionFlag, "p")) + intRes, _ := strconv.ParseFloat(strings.TrimSuffix(archiveResolutionFlag, "p"), 84) + archiveResolutionFlag = fmt.Sprintf("%.0fx%s", math.Ceil(intRes*(float64(16)/float64(9))), strings.TrimSuffix(archiveResolutionFlag, "p")) case "240p": // 240p would round up to 427x240 if used in the case statement above, so it has to be handled separately archiveResolutionFlag = "428x240" @@ -109,31 +106,33 @@ var archiveCmd = &cobra.Command{ default: return fmt.Errorf("'%s' is not a valid resolution", archiveResolutionFlag) } - out.Debug("Using resolution '%s'", archiveResolutionFlag) + utils.Log.Debug("Using resolution '%s'", archiveResolutionFlag) return nil }, RunE: func(cmd *cobra.Command, args []string) error { - loadCrunchy() + if err := commands.LoadCrunchy(); err != nil { + return err + } return archive(args) }, } func init() { - archiveCmd.Flags().StringSliceVarP(&archiveLanguagesFlag, + Cmd.Flags().StringSliceVarP(&archiveLanguagesFlag, "language", "l", - []string{string(systemLocale(false)), string(crunchyroll.JP)}, + []string{string(utils.SystemLocale(false)), string(crunchyroll.JP)}, "Audio locale which should be downloaded. Can be used multiple times") cwd, _ := os.Getwd() - archiveCmd.Flags().StringVarP(&archiveDirectoryFlag, + Cmd.Flags().StringVarP(&archiveDirectoryFlag, "directory", "d", cwd, "The directory to store the files into") - archiveCmd.Flags().StringVarP(&archiveOutputFlag, + Cmd.Flags().StringVarP(&archiveOutputFlag, "output", "o", "{title}.mkv", @@ -148,13 +147,13 @@ func init() { "\t{audio} ยป Audio locale of the video\n"+ "\t{subtitle} ยป Subtitle locale of the video") - archiveCmd.Flags().StringVarP(&archiveMergeFlag, + Cmd.Flags().StringVarP(&archiveMergeFlag, "merge", "m", "auto", "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'") - archiveCmd.Flags().StringVarP(&archiveCompressFlag, + Cmd.Flags().StringVarP(&archiveCompressFlag, "compress", "c", "", @@ -162,7 +161,7 @@ func init() { "This flag sets the name of the compressed output file. The file ending specifies the compression algorithm. "+ "The following algorithms are supported: gzip, tar, zip") - archiveCmd.Flags().StringVarP(&archiveResolutionFlag, + Cmd.Flags().StringVarP(&archiveResolutionFlag, "resolution", "r", "best", @@ -171,51 +170,49 @@ func init() { "\tAvailable abbreviations: 1080p, 720p, 480p, 360p, 240p\n"+ "\tAvailable common-use words: best (best available resolution), worst (worst available resolution)") - archiveCmd.Flags().IntVarP(&archiveGoroutinesFlag, + Cmd.Flags().IntVarP(&archiveGoroutinesFlag, "goroutines", "g", runtime.NumCPU(), "Number of parallel segment downloads") - - rootCmd.AddCommand(archiveCmd) } func archive(urls []string) error { for i, url := range urls { - out.SetProgress("Parsing url %d", i+1) + utils.Log.SetProcess("Parsing url %d", i+1) episodes, err := archiveExtractEpisodes(url) if err != nil { - out.StopProgress("Failed to parse url %d", i+1) - if crunchy.Config.Premium { - out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + + utils.Log.StopProcess("Failed to parse url %d", i+1) + if utils.Crunchy.Config.Premium { + utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchy-cli/issues/22 for more information") } return err } - out.StopProgress("Parsed url %d", i+1) + utils.Log.StopProcess("Parsed url %d", i+1) var compressFile *os.File - var c compress + var c Compress if archiveCompressFlag != "" { - compressFile, err = os.Create(generateFilename(archiveCompressFlag, "")) + compressFile, err = os.Create(utils.GenerateFilename(archiveCompressFlag, "")) if err != nil { return fmt.Errorf("failed to create archive file: %v", err) } if strings.HasSuffix(archiveCompressFlag, ".tar") { - c = newTarCompress(compressFile) + c = NewTarCompress(compressFile) } else if strings.HasSuffix(archiveCompressFlag, ".tar.gz") || strings.HasSuffix(archiveCompressFlag, ".tgz") { - c = newGzipCompress(compressFile) + c = NewGzipCompress(compressFile) } else if strings.HasSuffix(archiveCompressFlag, ".zip") { - c = newZipCompress(compressFile) + c = NewZipCompress(compressFile) } } for _, season := range episodes { - out.Info("%s Season %d", season[0].SeriesName, season[0].SeasonNumber) + utils.Log.Info("%s Season %d", season[0].SeriesName, season[0].SeasonNumber) for j, info := range season { - out.Info("\t%d. %s ยป %spx, %.2f FPS (S%02dE%02d)", + utils.Log.Info("\t%d. %s ยป %spx, %.2f FPS (S%02dE%02d)", j+1, info.Title, info.Resolution, @@ -224,26 +221,26 @@ func archive(urls []string) error { info.EpisodeNumber) } } - out.Empty() + utils.Log.Empty() for j, season := range episodes { for k, info := range season { var filename string var writeCloser io.WriteCloser if c != nil { - filename = info.Format(archiveOutputFlag) + filename = info.FormatString(archiveOutputFlag) writeCloser, err = c.NewFile(info) if err != nil { return fmt.Errorf("failed to pre generate new archive file: %v", err) } } else { - dir := info.Format(archiveDirectoryFlag) + dir := info.FormatString(archiveDirectoryFlag) if _, err = os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0777); err != nil { return fmt.Errorf("error while creating directory: %v", err) } } - filename = generateFilename(info.Format(archiveOutputFlag), dir) + filename = utils.GenerateFilename(info.FormatString(archiveOutputFlag), dir) writeCloser, err = os.Create(filename) if err != nil { return fmt.Errorf("failed to create new file: %v", err) @@ -264,7 +261,7 @@ func archive(urls []string) error { writeCloser.Close() if i != len(urls)-1 || j != len(episodes)-1 || k != len(season)-1 { - out.Empty() + utils.Log.Empty() } } } @@ -278,8 +275,8 @@ func archive(urls []string) error { return nil } -func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename string) error { - out.Debug("Entering season %d, episode %d with %d additional formats", info.SeasonNumber, info.EpisodeNumber, len(info.additionalFormats)) +func archiveInfo(info utils.FormatInformation, writeCloser io.WriteCloser, filename string) error { + utils.Log.Debug("Entering season %d, episode %d with %d additional formats", info.SeasonNumber, info.EpisodeNumber, len(info.AdditionalFormats)) dp, err := createArchiveProgress(info) if err != nil { @@ -307,7 +304,7 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st return nil } - if out.IsDev() { + if utils.Log.IsDev() { dp.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) } else { dp.Update() @@ -325,8 +322,8 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st select { case <-sig: signal.Stop(sig) - out.Exit("Exiting... (may take a few seconds)") - out.Exit("To force exit press ctrl+c (again)") + utils.Log.Err("Exiting... (may take a few seconds)") + utils.Log.Err("To force exit press ctrl+c (again)") cancel() // os.Exit(1) is not called since an immediate exit after the cancel function does not let // the download process enough time to stop gratefully. A result of this is that the temporary @@ -335,15 +332,15 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st // this is just here to end the goroutine and prevent it from running forever without a reason } }() - out.Debug("Set up signal catcher") + utils.Log.Debug("Set up signal catcher") var additionalDownloaderOpts []string var mergeMessage string switch archiveMergeFlag { case "auto": additionalDownloaderOpts = []string{"-vn"} - for _, format := range info.additionalFormats { - if format.Video.Bandwidth != info.format.Video.Bandwidth { + for _, format := range info.AdditionalFormats { + if format.Video.Bandwidth != info.Format.Video.Bandwidth { // revoke the changed FFmpegOpts above additionalDownloaderOpts = []string{} break @@ -361,12 +358,12 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st mergeMessage = "merging video for additional formats" } - out.Info("Downloading episode `%s` to `%s` (%s)", info.Title, filepath.Base(filename), mergeMessage) - out.Info("\tEpisode: S%02dE%02d", info.SeasonNumber, info.EpisodeNumber) - out.Info("\tAudio: %s", info.Audio) - out.Info("\tSubtitle: %s", info.Subtitle) - out.Info("\tResolution: %spx", info.Resolution) - out.Info("\tFPS: %.2f", info.FPS) + utils.Log.Info("Downloading episode `%s` to `%s` (%s)", info.Title, filepath.Base(filename), mergeMessage) + utils.Log.Info("\tEpisode: S%02dE%02d", info.SeasonNumber, info.EpisodeNumber) + utils.Log.Info("\tAudio: %s", info.Audio) + utils.Log.Info("\tSubtitle: %s", info.Subtitle) + utils.Log.Info("\tResolution: %spx", info.Resolution) + utils.Log.Info("\tFPS: %.2f", info.FPS) var videoFiles, audioFiles, subtitleFiles []string defer func() { @@ -376,7 +373,7 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st }() var f []string - if f, err = archiveDownloadVideos(downloader, filepath.Base(filename), true, info.format); err != nil { + if f, err = archiveDownloadVideos(downloader, filepath.Base(filename), true, info.Format); err != nil { if err != ctx.Err() { return fmt.Errorf("error while downloading: %v", err) } @@ -387,29 +384,29 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st if len(additionalDownloaderOpts) == 0 { var videos []string downloader.FFmpegOpts = additionalDownloaderOpts - if videos, err = archiveDownloadVideos(downloader, filepath.Base(filename), true, info.additionalFormats...); err != nil { + if videos, err = archiveDownloadVideos(downloader, filepath.Base(filename), true, info.AdditionalFormats...); err != nil { return fmt.Errorf("error while downloading additional videos: %v", err) } downloader.FFmpegOpts = []string{} videoFiles = append(videoFiles, videos...) } else { var audios []string - if audios, err = archiveDownloadVideos(downloader, filepath.Base(filename), false, info.additionalFormats...); err != nil { + if audios, err = archiveDownloadVideos(downloader, filepath.Base(filename), false, info.AdditionalFormats...); err != nil { return fmt.Errorf("error while downloading additional videos: %v", err) } audioFiles = append(audioFiles, audios...) } - sort.Sort(utils.SubtitlesByLocale(info.format.Subtitles)) + sort.Sort(crunchyUtils.SubtitlesByLocale(info.Format.Subtitles)) sortSubtitles, _ := strconv.ParseBool(os.Getenv("SORT_SUBTITLES")) if sortSubtitles && len(archiveLanguagesFlag) > 0 { // this sort the subtitle locales after the languages which were specified // with the `archiveLanguagesFlag` flag for _, language := range archiveLanguagesFlag { - for i, subtitle := range info.format.Subtitles { + for i, subtitle := range info.Format.Subtitles { if subtitle.Locale == crunchyroll.LOCALE(language) { - info.format.Subtitles = append([]*crunchyroll.Subtitle{subtitle}, append(info.format.Subtitles[:i], info.format.Subtitles[i+1:]...)...) + info.Format.Subtitles = append([]*crunchyroll.Subtitle{subtitle}, append(info.Format.Subtitles[:i], info.Format.Subtitles[i+1:]...)...) break } } @@ -417,7 +414,7 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st } var subtitles []string - if subtitles, err = archiveDownloadSubtitles(filepath.Base(filename), info.format.Subtitles...); err != nil { + if subtitles, err = archiveDownloadSubtitles(filepath.Base(filename), info.Format.Subtitles...); err != nil { return fmt.Errorf("error while downloading subtitles: %v", err) } subtitleFiles = append(subtitleFiles, subtitles...) @@ -429,22 +426,22 @@ func archiveInfo(info formatInformation, writeCloser io.WriteCloser, filename st dp.UpdateMessage("Download finished", false) signal.Stop(sig) - out.Debug("Stopped signal catcher") + utils.Log.Debug("Stopped signal catcher") - out.Empty() + utils.Log.Empty() return nil } -func createArchiveProgress(info formatInformation) (*downloadProgress, error) { +func createArchiveProgress(info utils.FormatInformation) (*commands.DownloadProgress, error) { var progressCount int - if err := info.format.InitVideo(); err != nil { + if err := info.Format.InitVideo(); err != nil { return nil, fmt.Errorf("error while initializing a video: %v", err) } // + number of segments a video has +1 is for merging - progressCount += int(info.format.Video.Chunklist.Count()) + 1 - for _, f := range info.additionalFormats { - if f == info.format { + progressCount += int(info.Format.Video.Chunklist.Count()) + 1 + for _, f := range info.AdditionalFormats { + if f == info.Format { continue } @@ -455,16 +452,16 @@ func createArchiveProgress(info formatInformation) (*downloadProgress, error) { progressCount += int(f.Video.Chunklist.Count()) + 1 } - dp := &downloadProgress{ - Prefix: out.InfoLog.Prefix(), + dp := &commands.DownloadProgress{ + Prefix: utils.Log.(*commands.Logger).InfoLog.Prefix(), Message: "Downloading video", // number of segments a video +1 is for the success message Total: progressCount + 1, - Dev: out.IsDev(), - Quiet: out.IsQuiet(), + Dev: utils.Log.IsDev(), + Quiet: utils.Log.(*commands.Logger).IsQuiet(), } - if out.IsDev() { - dp.Prefix = out.DebugLog.Prefix() + if utils.Log.IsDev() { + dp.Prefix = utils.Log.(*commands.Logger).DebugLog.Prefix() } return dp, nil @@ -497,7 +494,7 @@ func archiveDownloadVideos(downloader crunchyroll.Downloader, filename string, v } f.Close() - out.Debug("Downloaded '%s' video", format.AudioLocale) + utils.Log.Debug("Downloaded '%s' video", format.AudioLocale) } return files, nil @@ -522,7 +519,7 @@ func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitl } f.Close() - out.Debug("Downloaded '%s' subtitles", subtitle.Locale) + utils.Log.Debug("Downloaded '%s' subtitles", subtitle.Locale) } return files, nil @@ -537,9 +534,9 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s maps = append(maps, "-map", strconv.Itoa(i)) locale := crunchyroll.LOCALE(re.FindStringSubmatch(video)[1]) metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) } for i, audio := range audioFiles { @@ -547,7 +544,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles))) locale := crunchyroll.LOCALE(re.FindStringSubmatch(audio)[1]) metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) } for i, subtitle := range subtitleFiles { @@ -555,7 +552,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles)+len(audioFiles))) locale := crunchyroll.LOCALE(re.FindStringSubmatch(subtitle)[1]) metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("title=%s", utils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) } commandOptions := []string{"-y"} @@ -577,7 +574,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s commandOptions = append(commandOptions, "-disposition:s:0", "0", "-c", "copy", "-f", "matroska", file.Name()) // just a little nicer debug output to copy and paste the ffmpeg for debug reasons - if out.IsDev() { + if utils.Log.IsDev() { var debugOptions []string for _, option := range commandOptions { @@ -591,7 +588,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s debugOptions = append(debugOptions, option) } } - out.Debug("FFmpeg merge command: ffmpeg %s", strings.Join(debugOptions, " ")) + utils.Log.Debug("FFmpeg merge command: ffmpeg %s", strings.Join(debugOptions, " ")) } var errBuf bytes.Buffer @@ -611,7 +608,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s return err } -func archiveExtractEpisodes(url string) ([][]formatInformation, error) { +func archiveExtractEpisodes(url string) ([][]utils.FormatInformation, error) { var hasJapanese bool languagesAsLocale := []crunchyroll.LOCALE{crunchyroll.JP} for _, language := range archiveLanguagesFlag { @@ -623,7 +620,7 @@ func archiveExtractEpisodes(url string) ([][]formatInformation, error) { } } - episodes, err := extractEpisodes(url, languagesAsLocale...) + episodes, err := utils.ExtractEpisodes(url, languagesAsLocale...) if err != nil { return nil, err } @@ -634,9 +631,9 @@ func archiveExtractEpisodes(url string) ([][]formatInformation, error) { for i, eps := range episodes { if len(eps) == 0 { - out.SetProgress("%s has no matching episodes", languagesAsLocale[i]) + utils.Log.SetProcess("%s has no matching episodes", languagesAsLocale[i]) } else if len(episodes[0]) > len(eps) { - out.SetProgress("%s has %d less episodes than existing in japanese (%d)", languagesAsLocale[i], len(episodes[0])-len(eps), len(episodes[0])) + utils.Log.SetProcess("%s has %d less episodes than existing in japanese (%d)", languagesAsLocale[i], len(episodes[0])-len(eps), len(episodes[0])) } } @@ -644,11 +641,11 @@ func archiveExtractEpisodes(url string) ([][]formatInformation, error) { episodes = episodes[1:] } - eps := make(map[int]map[int]*formatInformation) + eps := make(map[int]map[int]*utils.FormatInformation) for _, lang := range episodes { - for _, season := range utils.SortEpisodesBySeason(lang) { + for _, season := range crunchyUtils.SortEpisodesBySeason(lang) { if _, ok := eps[season[0].SeasonNumber]; !ok { - eps[season[0].SeasonNumber] = map[int]*formatInformation{} + eps[season[0].SeasonNumber] = map[int]*utils.FormatInformation{} } for _, episode := range season { format, err := episode.GetFormat(archiveResolutionFlag, "", false) @@ -657,9 +654,9 @@ func archiveExtractEpisodes(url string) ([][]formatInformation, error) { } if _, ok := eps[episode.SeasonNumber][episode.EpisodeNumber]; !ok { - eps[episode.SeasonNumber][episode.EpisodeNumber] = &formatInformation{ - format: format, - additionalFormats: make([]*crunchyroll.Format, 0), + eps[episode.SeasonNumber][episode.EpisodeNumber] = &utils.FormatInformation{ + Format: format, + AdditionalFormats: make([]*crunchyroll.Format, 0), Title: episode.Title, SeriesName: episode.SeriesTitle, @@ -671,15 +668,15 @@ func archiveExtractEpisodes(url string) ([][]formatInformation, error) { Audio: format.AudioLocale, } } else { - eps[episode.SeasonNumber][episode.EpisodeNumber].additionalFormats = append(eps[episode.SeasonNumber][episode.EpisodeNumber].additionalFormats, format) + eps[episode.SeasonNumber][episode.EpisodeNumber].AdditionalFormats = append(eps[episode.SeasonNumber][episode.EpisodeNumber].AdditionalFormats, format) } } } } - var infoFormat [][]formatInformation + var infoFormat [][]utils.FormatInformation for _, e := range eps { - var tmpFormatInfo []formatInformation + var tmpFormatInfo []utils.FormatInformation var keys []int for episodeNumber := range e { @@ -696,124 +693,3 @@ func archiveExtractEpisodes(url string) ([][]formatInformation, error) { return infoFormat, nil } - -type compress interface { - io.Closer - - NewFile(information formatInformation) (io.WriteCloser, error) -} - -func newGzipCompress(file *os.File) *tarCompress { - gw := gzip.NewWriter(file) - return &tarCompress{ - parent: gw, - dst: tar.NewWriter(gw), - } -} - -func newTarCompress(file *os.File) *tarCompress { - return &tarCompress{ - dst: tar.NewWriter(file), - } -} - -type tarCompress struct { - compress - - wg sync.WaitGroup - - parent *gzip.Writer - dst *tar.Writer -} - -func (tc *tarCompress) Close() error { - // we have to wait here in case the actual content isn't copied completely into the - // writer yet - tc.wg.Wait() - - var err, err2 error - if tc.parent != nil { - err2 = tc.parent.Close() - } - err = tc.dst.Close() - - if err != nil && err2 != nil { - // best way to show double errors at once that I've found - return fmt.Errorf("%v\n%v", err, err2) - } else if err == nil && err2 != nil { - err = err2 - } - - return err -} - -func (tc *tarCompress) NewFile(information formatInformation) (io.WriteCloser, error) { - rp, wp := io.Pipe() - go func() { - tc.wg.Add(1) - defer tc.wg.Done() - var buf bytes.Buffer - io.Copy(&buf, rp) - - header := &tar.Header{ - Name: filepath.Join(fmt.Sprintf("S%2d", information.SeasonNumber), information.Title), - ModTime: time.Now(), - Mode: 0644, - Typeflag: tar.TypeReg, - // fun fact: I did not set the size for quiet some time because I thought that it isn't - // required. well because of this I debugged this part for multiple hours because without - // proper size information only a tiny amount gets copied into the tar (or zip) writer. - // this is also the reason why the file content is completely copied into a buffer before - // writing it to the writer. I could bypass this and save some memory but this requires - // some rewriting and im nearly at the (planned) finish for version 2 so nah in the future - // maybe - Size: int64(buf.Len()), - } - tc.dst.WriteHeader(header) - io.Copy(tc.dst, &buf) - }() - return wp, nil -} - -func newZipCompress(file *os.File) *zipCompress { - return &zipCompress{ - dst: zip.NewWriter(file), - } -} - -type zipCompress struct { - compress - - wg sync.WaitGroup - - dst *zip.Writer -} - -func (zc *zipCompress) Close() error { - zc.wg.Wait() - return zc.dst.Close() -} - -func (zc *zipCompress) NewFile(information formatInformation) (io.WriteCloser, error) { - rp, wp := io.Pipe() - go func() { - zc.wg.Add(1) - defer zc.wg.Done() - - var buf bytes.Buffer - io.Copy(&buf, rp) - - header := &zip.FileHeader{ - Name: filepath.Join(fmt.Sprintf("S%2d", information.SeasonNumber), information.Title), - Modified: time.Now(), - Method: zip.Deflate, - UncompressedSize64: uint64(buf.Len()), - } - header.SetMode(0644) - - hw, _ := zc.dst.CreateHeader(header) - io.Copy(hw, &buf) - }() - - return wp, nil -} diff --git a/cli/commands/archive/compress.go b/cli/commands/archive/compress.go new file mode 100644 index 0000000..d6aa492 --- /dev/null +++ b/cli/commands/archive/compress.go @@ -0,0 +1,136 @@ +package archive + +import ( + "archive/tar" + "archive/zip" + "bytes" + "compress/gzip" + "fmt" + "github.com/ByteDream/crunchy-cli/utils" + "io" + "os" + "path/filepath" + "sync" + "time" +) + +type Compress interface { + io.Closer + + NewFile(information utils.FormatInformation) (io.WriteCloser, error) +} + +func NewGzipCompress(file *os.File) *TarCompress { + gw := gzip.NewWriter(file) + return &TarCompress{ + parent: gw, + dst: tar.NewWriter(gw), + } +} + +func NewTarCompress(file *os.File) *TarCompress { + return &TarCompress{ + dst: tar.NewWriter(file), + } +} + +type TarCompress struct { + Compress + + wg sync.WaitGroup + + parent *gzip.Writer + dst *tar.Writer +} + +func (tc *TarCompress) Close() error { + // we have to wait here in case the actual content isn't copied completely into the + // writer yet + tc.wg.Wait() + + var err, err2 error + if tc.parent != nil { + err2 = tc.parent.Close() + } + err = tc.dst.Close() + + if err != nil && err2 != nil { + // best way to show double errors at once that I've found + return fmt.Errorf("%v\n%v", err, err2) + } else if err == nil && err2 != nil { + err = err2 + } + + return err +} + +func (tc *TarCompress) NewFile(information utils.FormatInformation) (io.WriteCloser, error) { + rp, wp := io.Pipe() + go func() { + tc.wg.Add(1) + defer tc.wg.Done() + var buf bytes.Buffer + io.Copy(&buf, rp) + + header := &tar.Header{ + Name: filepath.Join(fmt.Sprintf("S%2d", information.SeasonNumber), information.Title), + ModTime: time.Now(), + Mode: 0644, + Typeflag: tar.TypeReg, + // fun fact: I did not set the size for quiet some time because I thought that it isn't + // required. well because of this I debugged this part for multiple hours because without + // proper size information only a tiny amount gets copied into the tar (or zip) writer. + // this is also the reason why the file content is completely copied into a buffer before + // writing it to the writer. I could bypass this and save some memory but this requires + // some rewriting and im nearly at the (planned) finish for version 2 so nah in the future + // maybe + Size: int64(buf.Len()), + } + tc.dst.WriteHeader(header) + io.Copy(tc.dst, &buf) + }() + return wp, nil +} + +func NewZipCompress(file *os.File) *ZipCompress { + return &ZipCompress{ + dst: zip.NewWriter(file), + } +} + +type ZipCompress struct { + Compress + + wg sync.WaitGroup + + dst *zip.Writer +} + +func (zc *ZipCompress) Close() error { + zc.wg.Wait() + return zc.dst.Close() +} + +func (zc *ZipCompress) NewFile(information utils.FormatInformation) (io.WriteCloser, error) { + rp, wp := io.Pipe() + go func() { + zc.wg.Add(1) + defer zc.wg.Done() + + var buf bytes.Buffer + io.Copy(&buf, rp) + + header := &zip.FileHeader{ + Name: filepath.Join(fmt.Sprintf("S%2d", information.SeasonNumber), information.Title), + Modified: time.Now(), + Method: zip.Deflate, + UncompressedSize64: uint64(buf.Len()), + } + header.SetMode(0644) + + hw, _ := zc.dst.CreateHeader(header) + io.Copy(hw, &buf) + }() + + return wp, nil +} diff --git a/commands/download.go b/cli/commands/download/download.go similarity index 64% rename from commands/download.go rename to cli/commands/download/download.go index 82b68bf..4e0ef66 100644 --- a/commands/download.go +++ b/cli/commands/download/download.go @@ -1,10 +1,12 @@ -package commands +package download import ( "context" "fmt" + "github.com/ByteDream/crunchy-cli/cli/commands" + "github.com/ByteDream/crunchy-cli/utils" "github.com/ByteDream/crunchyroll-go/v3" - "github.com/ByteDream/crunchyroll-go/v3/utils" + crunchyUtils "github.com/ByteDream/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "math" @@ -29,29 +31,29 @@ var ( downloadGoroutinesFlag int ) -var downloadCmd = &cobra.Command{ +var Cmd = &cobra.Command{ Use: "download", Short: "Download a video", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { - out.Debug("Validating arguments") + utils.Log.Debug("Validating arguments") if filepath.Ext(downloadOutputFlag) != ".ts" { - if !hasFFmpeg() { + if !utils.HasFFmpeg() { return fmt.Errorf("the file ending for the output file (%s) is not `.ts`. "+ "Install ffmpeg (https://ffmpeg.org/download.html) to use other media file endings (e.g. `.mp4`)", downloadOutputFlag) } else { - out.Debug("Custom file ending '%s' (ffmpeg is installed)", filepath.Ext(downloadOutputFlag)) + utils.Log.Debug("Custom file ending '%s' (ffmpeg is installed)", filepath.Ext(downloadOutputFlag)) } } - if !utils.ValidateLocale(crunchyroll.LOCALE(downloadAudioFlag)) { - return fmt.Errorf("%s is not a valid audio locale. Choose from: %s", downloadAudioFlag, strings.Join(allLocalesAsStrings(), ", ")) - } else if downloadSubtitleFlag != "" && !utils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { - return fmt.Errorf("%s is not a valid subtitle locale. Choose from: %s", downloadSubtitleFlag, strings.Join(allLocalesAsStrings(), ", ")) + if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadAudioFlag)) { + return fmt.Errorf("%s is not a valid audio locale. Choose from: %s", downloadAudioFlag, strings.Join(utils.LocalesAsStrings(), ", ")) + } else if downloadSubtitleFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { + return fmt.Errorf("%s is not a valid subtitle locale. Choose from: %s", downloadSubtitleFlag, strings.Join(utils.LocalesAsStrings(), ", ")) } - out.Debug("Locales: audio: %s / subtitle: %s", downloadAudioFlag, downloadSubtitleFlag) + utils.Log.Debug("Locales: audio: %s / subtitle: %s", downloadAudioFlag, downloadSubtitleFlag) switch downloadResolutionFlag { case "1080p", "720p", "480p", "360p": @@ -64,35 +66,37 @@ var downloadCmd = &cobra.Command{ default: return fmt.Errorf("'%s' is not a valid resolution", downloadResolutionFlag) } - out.Debug("Using resolution '%s'", downloadResolutionFlag) + utils.Log.Debug("Using resolution '%s'", downloadResolutionFlag) return nil }, RunE: func(cmd *cobra.Command, args []string) error { - loadCrunchy() + if err := commands.LoadCrunchy(); err != nil { + return err + } return download(args) }, } func init() { - downloadCmd.Flags().StringVarP(&downloadAudioFlag, "audio", + Cmd.Flags().StringVarP(&downloadAudioFlag, "audio", "a", - string(systemLocale(false)), - "The locale of the audio. Available locales: "+strings.Join(allLocalesAsStrings(), ", ")) - downloadCmd.Flags().StringVarP(&downloadSubtitleFlag, + string(utils.SystemLocale(false)), + "The locale of the audio. Available locales: "+strings.Join(utils.LocalesAsStrings(), ", ")) + Cmd.Flags().StringVarP(&downloadSubtitleFlag, "subtitle", "s", "", - "The locale of the subtitle. Available locales: "+strings.Join(allLocalesAsStrings(), ", ")) + "The locale of the subtitle. Available locales: "+strings.Join(utils.LocalesAsStrings(), ", ")) cwd, _ := os.Getwd() - downloadCmd.Flags().StringVarP(&downloadDirectoryFlag, + Cmd.Flags().StringVarP(&downloadDirectoryFlag, "directory", "d", cwd, "The directory to download the file(s) into") - downloadCmd.Flags().StringVarP(&downloadOutputFlag, + Cmd.Flags().StringVarP(&downloadOutputFlag, "output", "o", "{title}.ts", @@ -108,7 +112,7 @@ func init() { "\t{audio} ยป Audio locale of the video\n"+ "\t{subtitle} ยป Subtitle locale of the video") - downloadCmd.Flags().StringVarP(&downloadResolutionFlag, + Cmd.Flags().StringVarP(&downloadResolutionFlag, "resolution", "r", "best", @@ -117,34 +121,32 @@ func init() { "\tAvailable abbreviations: 1080p, 720p, 480p, 360p, 240p\n"+ "\tAvailable common-use words: best (best available resolution), worst (worst available resolution)") - downloadCmd.Flags().IntVarP(&downloadGoroutinesFlag, + Cmd.Flags().IntVarP(&downloadGoroutinesFlag, "goroutines", "g", runtime.NumCPU(), "Sets how many parallel segment downloads should be used") - - rootCmd.AddCommand(downloadCmd) } func download(urls []string) error { for i, url := range urls { - out.SetProgress("Parsing url %d", i+1) + utils.Log.SetProcess("Parsing url %d", i+1) episodes, err := downloadExtractEpisodes(url) if err != nil { - out.StopProgress("Failed to parse url %d", i+1) - if crunchy.Config.Premium { - out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + + utils.Log.StopProcess("Failed to parse url %d", i+1) + if utils.Crunchy.Config.Premium { + utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchy-cli/issues/22 for more information") } return err } - out.StopProgress("Parsed url %d", i+1) + utils.Log.StopProcess("Parsed url %d", i+1) for _, season := range episodes { - out.Info("%s Season %d", season[0].SeriesName, season[0].SeasonNumber) + utils.Log.Info("%s Season %d", season[0].SeriesName, season[0].SeasonNumber) for j, info := range season { - out.Info("\t%d. %s ยป %spx, %.2f FPS (S%02dE%02d)", + utils.Log.Info("\t%d. %s ยป %spx, %.2f FPS (S%02dE%02d)", j+1, info.Title, info.Resolution, @@ -153,17 +155,17 @@ func download(urls []string) error { info.EpisodeNumber) } } - out.Empty() + utils.Log.Empty() for j, season := range episodes { for k, info := range season { - dir := info.Format(downloadDirectoryFlag) + dir := info.FormatString(downloadDirectoryFlag) if _, err = os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0777); err != nil { return fmt.Errorf("error while creating directory: %v", err) } } - file, err := os.Create(generateFilename(info.Format(downloadOutputFlag), dir)) + file, err := os.Create(utils.GenerateFilename(info.FormatString(downloadOutputFlag), dir)) if err != nil { return fmt.Errorf("failed to create output file: %v", err) } @@ -176,7 +178,7 @@ func download(urls []string) error { file.Close() if i != len(urls)-1 || j != len(episodes)-1 || k != len(season)-1 { - out.Empty() + utils.Log.Empty() } } } @@ -184,23 +186,23 @@ func download(urls []string) error { return nil } -func downloadInfo(info formatInformation, file *os.File) error { - out.Debug("Entering season %d, episode %d", info.SeasonNumber, info.EpisodeNumber) +func downloadInfo(info utils.FormatInformation, file *os.File) error { + utils.Log.Debug("Entering season %d, episode %d", info.SeasonNumber, info.EpisodeNumber) - if err := info.format.InitVideo(); err != nil { + if err := info.Format.InitVideo(); err != nil { return fmt.Errorf("error while initializing the video: %v", err) } - dp := &downloadProgress{ - Prefix: out.InfoLog.Prefix(), + dp := &commands.DownloadProgress{ + Prefix: utils.Log.(*commands.Logger).InfoLog.Prefix(), Message: "Downloading video", // number of segments a video has +2 is for merging and the success message - Total: int(info.format.Video.Chunklist.Count()) + 2, - Dev: out.IsDev(), - Quiet: out.IsQuiet(), + Total: int(info.Format.Video.Chunklist.Count()) + 2, + Dev: utils.Log.IsDev(), + Quiet: utils.Log.(*commands.Logger).IsQuiet(), } - if out.IsDev() { - dp.Prefix = out.DebugLog.Prefix() + if utils.Log.IsDev() { + dp.Prefix = utils.Log.(*commands.Logger).DebugLog.Prefix() } defer func() { if dp.Total != dp.Current { @@ -217,7 +219,7 @@ func downloadInfo(info formatInformation, file *os.File) error { return nil } - if out.IsDev() { + if utils.Log.IsDev() { dp.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) } else { dp.Update() @@ -228,7 +230,7 @@ func downloadInfo(info formatInformation, file *os.File) error { } return nil }) - if hasFFmpeg() { + if utils.HasFFmpeg() { downloader.FFmpegOpts = make([]string, 0) } @@ -238,8 +240,8 @@ func downloadInfo(info formatInformation, file *os.File) error { select { case <-sig: signal.Stop(sig) - out.Exit("Exiting... (may take a few seconds)") - out.Exit("To force exit press ctrl+c (again)") + utils.Log.Err("Exiting... (may take a few seconds)") + utils.Log.Err("To force exit press ctrl+c (again)") cancel() // os.Exit(1) is not called because an immediate exit after the cancel function does not let // the download process enough time to stop gratefully. A result of this is that the temporary @@ -248,38 +250,38 @@ func downloadInfo(info formatInformation, file *os.File) error { // this is just here to end the goroutine and prevent it from running forever without a reason } }() - out.Debug("Set up signal catcher") + utils.Log.Debug("Set up signal catcher") - out.Info("Downloading episode `%s` to `%s`", info.Title, filepath.Base(file.Name())) - out.Info("\tEpisode: S%02dE%02d", info.SeasonNumber, info.EpisodeNumber) - out.Info("\tAudio: %s", info.Audio) - out.Info("\tSubtitle: %s", info.Subtitle) - out.Info("\tResolution: %spx", info.Resolution) - out.Info("\tFPS: %.2f", info.FPS) - if err := info.format.Download(downloader); err != nil { + utils.Log.Info("Downloading episode `%s` to `%s`", info.Title, filepath.Base(file.Name())) + utils.Log.Info("\tEpisode: S%02dE%02d", info.SeasonNumber, info.EpisodeNumber) + utils.Log.Info("\tAudio: %s", info.Audio) + utils.Log.Info("\tSubtitle: %s", info.Subtitle) + utils.Log.Info("\tResolution: %spx", info.Resolution) + utils.Log.Info("\tFPS: %.2f", info.FPS) + if err := info.Format.Download(downloader); err != nil { return fmt.Errorf("error while downloading: %v", err) } dp.UpdateMessage("Download finished", false) signal.Stop(sig) - out.Debug("Stopped signal catcher") + utils.Log.Debug("Stopped signal catcher") - out.Empty() + utils.Log.Empty() return nil } -func downloadExtractEpisodes(url string) ([][]formatInformation, error) { - episodes, err := extractEpisodes(url, crunchyroll.JP, crunchyroll.LOCALE(downloadAudioFlag)) +func downloadExtractEpisodes(url string) ([][]utils.FormatInformation, error) { + episodes, err := utils.ExtractEpisodes(url, crunchyroll.JP, crunchyroll.LOCALE(downloadAudioFlag)) if err != nil { return nil, err } japanese := episodes[0] custom := episodes[1] - sort.Sort(utils.EpisodesByNumber(japanese)) - sort.Sort(utils.EpisodesByNumber(custom)) + sort.Sort(crunchyUtils.EpisodesByNumber(japanese)) + sort.Sort(crunchyUtils.EpisodesByNumber(custom)) var errMessages []string @@ -303,25 +305,25 @@ func downloadExtractEpisodes(url string) ([][]formatInformation, error) { if len(errMessages) > 10 { for _, msg := range errMessages[:10] { - out.SetProgress(msg) + utils.Log.SetProcess(msg) } - out.SetProgress("... and %d more", len(errMessages)-10) + utils.Log.SetProcess("... and %d more", len(errMessages)-10) } else { for _, msg := range errMessages { - out.SetProgress(msg) + utils.Log.SetProcess(msg) } } - var infoFormat [][]formatInformation - for _, season := range utils.SortEpisodesBySeason(final) { - tmpFormatInformation := make([]formatInformation, 0) + var infoFormat [][]utils.FormatInformation + for _, season := range crunchyUtils.SortEpisodesBySeason(final) { + tmpFormatInformation := make([]utils.FormatInformation, 0) for _, episode := range season { format, err := episode.GetFormat(downloadResolutionFlag, crunchyroll.LOCALE(downloadSubtitleFlag), true) if err != nil { return nil, fmt.Errorf("error while receiving format for %s: %v", episode.Title, err) } - tmpFormatInformation = append(tmpFormatInformation, formatInformation{ - format: format, + tmpFormatInformation = append(tmpFormatInformation, utils.FormatInformation{ + Format: format, Title: episode.Title, SeriesName: episode.SeriesTitle, diff --git a/cli/commands/info/info.go b/cli/commands/info/info.go new file mode 100644 index 0000000..a9a6ef0 --- /dev/null +++ b/cli/commands/info/info.go @@ -0,0 +1,40 @@ +package info + +import ( + "fmt" + "github.com/ByteDream/crunchy-cli/cli/commands" + "github.com/ByteDream/crunchy-cli/utils" + crunchyUtils "github.com/ByteDream/crunchyroll-go/v3/utils" + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "info", + Short: "Shows information about the logged in user", + Args: cobra.MinimumNArgs(0), + + RunE: func(cmd *cobra.Command, args []string) error { + if err := commands.LoadCrunchy(); err != nil { + return err + } + + return info() + }, +} + +func info() error { + account, err := utils.Crunchy.Account() + if err != nil { + return err + } + + fmt.Println("Username: ", account.Username) + fmt.Println("Email: ", account.Email) + fmt.Println("Premium: ", utils.Crunchy.Config.Premium) + fmt.Println("Interface language:", crunchyUtils.LocaleLanguage(account.PreferredCommunicationLanguage)) + fmt.Println("Subtitle language: ", crunchyUtils.LocaleLanguage(account.PreferredContentSubtitleLanguage)) + fmt.Println("Created: ", account.Created) + fmt.Println("Account ID: ", account.AccountID) + + return nil +} diff --git a/commands/logger.go b/cli/commands/logger.go similarity index 82% rename from commands/logger.go rename to cli/commands/logger.go index fab8515..2f3ae37 100644 --- a/commands/logger.go +++ b/cli/commands/logger.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "github.com/ByteDream/crunchy-cli/utils" "io" "log" "os" @@ -35,19 +36,7 @@ type progress struct { stop bool } -type logger struct { - DebugLog *log.Logger - InfoLog *log.Logger - ErrLog *log.Logger - - devView bool - - progress chan progress - done chan interface{} - lock sync.Mutex -} - -func newLogger(debug, info, err bool) *logger { +func NewLogger(debug, info, err bool) *Logger { initPrefixBecauseWindowsSucksBallsHard() debugLog, infoLog, errLog := log.New(io.Discard, prefix+" ", 0), log.New(io.Discard, prefix+" ", 0), log.New(io.Discard, prefix+" ", 0) @@ -68,7 +57,7 @@ func newLogger(debug, info, err bool) *logger { errLog = log.New(errLog.Writer(), "[err] ", 0) } - return &logger{ + return &Logger{ DebugLog: debugLog, InfoLog: infoLog, ErrLog: errLog, @@ -77,38 +66,52 @@ func newLogger(debug, info, err bool) *logger { } } -func (l *logger) IsDev() bool { +type Logger struct { + utils.Logger + + DebugLog *log.Logger + InfoLog *log.Logger + ErrLog *log.Logger + + devView bool + + progress chan progress + done chan interface{} + lock sync.Mutex +} + +func (l *Logger) IsDev() bool { return l.devView } -func (l *logger) IsQuiet() bool { +func (l *Logger) IsQuiet() bool { return l.DebugLog.Writer() == io.Discard && l.InfoLog.Writer() == io.Discard && l.ErrLog.Writer() == io.Discard } -func (l *logger) Debug(format string, v ...interface{}) { +func (l *Logger) Debug(format string, v ...interface{}) { l.DebugLog.Printf(format, v...) } -func (l *logger) Info(format string, v ...interface{}) { +func (l *Logger) Info(format string, v ...interface{}) { l.InfoLog.Printf(format, v...) } -func (l *logger) Err(format string, v ...interface{}) { +func (l *Logger) Warn(format string, v ...interface{}) { + l.Err(format, v...) +} + +func (l *Logger) Err(format string, v ...interface{}) { l.ErrLog.Printf(format, v...) } -func (l *logger) Exit(format string, v ...interface{}) { - fmt.Fprintln(l.ErrLog.Writer(), fmt.Sprintf(format, v...)) -} - -func (l *logger) Empty() { +func (l *Logger) Empty() { if !l.devView && l.InfoLog.Writer() != io.Discard { fmt.Println("") } } -func (l *logger) SetProgress(format string, v ...interface{}) { - if out.InfoLog.Writer() == io.Discard { +func (l *Logger) SetProcess(format string, v ...interface{}) { + if l.InfoLog.Writer() == io.Discard { return } else if l.devView { l.Debug(format, v...) @@ -175,8 +178,8 @@ func (l *logger) SetProgress(format string, v ...interface{}) { }() } -func (l *logger) StopProgress(format string, v ...interface{}) { - if out.InfoLog.Writer() == io.Discard { +func (l *Logger) StopProcess(format string, v ...interface{}) { + if l.InfoLog.Writer() == io.Discard { return } else if l.devView { l.Debug(format, v...) diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go new file mode 100644 index 0000000..31155d8 --- /dev/null +++ b/cli/commands/login/login.go @@ -0,0 +1,158 @@ +package login + +import ( + "bytes" + "fmt" + "github.com/ByteDream/crunchy-cli/cli/commands" + "github.com/ByteDream/crunchy-cli/utils" + "github.com/ByteDream/crunchyroll-go/v3" + "github.com/spf13/cobra" + "os" +) + +var ( + loginPersistentFlag bool + loginEncryptFlag bool + + loginSessionIDFlag bool + loginEtpRtFlag bool +) + +var Cmd = &cobra.Command{ + Use: "login", + Short: "Login to crunchyroll", + Args: cobra.RangeArgs(1, 2), + + RunE: func(cmd *cobra.Command, args []string) error { + if loginSessionIDFlag { + return loginSessionID(args[0]) + } else if loginEtpRtFlag { + return loginEtpRt(args[0]) + } else { + return loginCredentials(args[0], args[1]) + } + }, +} + +func init() { + Cmd.Flags().BoolVar(&loginPersistentFlag, + "persistent", + false, + "If the given credential should be stored persistent") + Cmd.Flags().BoolVar(&loginEncryptFlag, + "encrypt", + false, + "Encrypt the given credentials (won't do anything if --session-id is given or --persistent is not given)") + + Cmd.Flags().BoolVar(&loginSessionIDFlag, + "session-id", + false, + "Use a session id to login instead of username and password") + Cmd.Flags().BoolVar(&loginEtpRtFlag, + "etp-rt", + false, + "Use a etp rt cookie to login instead of username and password") + + Cmd.MarkFlagsMutuallyExclusive("session-id", "etp-rt") +} + +func loginCredentials(user, password string) error { + utils.Log.Debug("Logging in via credentials") + c, err := crunchyroll.LoginWithCredentials(user, password, utils.SystemLocale(false), utils.Client) + if err != nil { + return err + } + + if loginPersistentFlag { + var passwd []byte + if loginEncryptFlag { + for { + fmt.Print("Enter password: ") + passwd, err = commands.ReadLineSilent() + if err != nil { + return err + } + fmt.Println() + + fmt.Print("Enter password again: ") + repasswd, err := commands.ReadLineSilent() + if err != nil { + return err + } + fmt.Println() + + if bytes.Equal(passwd, repasswd) { + break + } + fmt.Println("Passwords does not match, try again") + } + } + if err = utils.SaveCredentialsPersistent(user, password, passwd); err != nil { + return err + } + if !loginEncryptFlag { + utils.Log.Warn("The login information will be stored permanently UNENCRYPTED on your drive. " + + "To encrypt it, use the `--encrypt` flag") + } + } + if err = utils.SaveSession(c); err != nil { + return err + } + + if !loginPersistentFlag { + utils.Log.Info("Due to security reasons, you have to login again on the next reboot") + } + + return nil +} + +func loginSessionID(sessionID string) error { + utils.Log.Debug("Logging in via session id") + var c *crunchyroll.Crunchyroll + var err error + if c, err = crunchyroll.LoginWithSessionID(sessionID, utils.SystemLocale(false), utils.Client); err != nil { + return err + } + + if loginPersistentFlag { + if err = utils.SaveSessionPersistent(c); err != nil { + return err + } + utils.Log.Warn("The login information will be stored permanently UNENCRYPTED on your drive") + } + if err = utils.SaveSession(c); err != nil { + return err + } + + if !loginPersistentFlag { + utils.Log.Info("Due to security reasons, you have to login again on the next reboot") + } + + return nil +} + +func loginEtpRt(etpRt string) error { + utils.Log.Debug("Logging in via etp rt") + var c *crunchyroll.Crunchyroll + var err error + if c, err = crunchyroll.LoginWithEtpRt(etpRt, utils.SystemLocale(false), utils.Client); err != nil { + utils.Log.Err(err.Error()) + os.Exit(1) + } + + if loginPersistentFlag { + if err = utils.SaveSessionPersistent(c); err != nil { + return err + } + utils.Log.Warn("The login information will be stored permanently UNENCRYPTED on your drive") + } + if err = utils.SaveSession(c); err != nil { + return err + } + + if !loginPersistentFlag { + utils.Log.Info("Due to security reasons, you have to login again on the next reboot") + } + + return nil +} diff --git a/commands/unix.go b/cli/commands/unix.go similarity index 95% rename from commands/unix.go rename to cli/commands/unix.go index 955695c..ec69180 100644 --- a/commands/unix.go +++ b/cli/commands/unix.go @@ -19,7 +19,7 @@ func init() { } } -func readLineSilent() ([]byte, error) { +func ReadLineSilent() ([]byte, error) { pid, err := setEcho(false) if err != nil { return nil, err diff --git a/commands/update.go b/cli/commands/update/update.go similarity index 67% rename from commands/update.go rename to cli/commands/update/update.go index fb4c1e9..6587ca2 100644 --- a/commands/update.go +++ b/cli/commands/update/update.go @@ -1,8 +1,9 @@ -package commands +package update import ( "encoding/json" "fmt" + "github.com/ByteDream/crunchy-cli/utils" "github.com/spf13/cobra" "io" "os" @@ -15,7 +16,7 @@ var ( updateInstallFlag bool ) -var updateCmd = &cobra.Command{ +var Cmd = &cobra.Command{ Use: "update", Short: "Check if updates are available", Args: cobra.MaximumNArgs(0), @@ -26,19 +27,17 @@ var updateCmd = &cobra.Command{ } func init() { - updateCmd.Flags().BoolVarP(&updateInstallFlag, + Cmd.Flags().BoolVarP(&updateInstallFlag, "install", "i", false, "If set and a new version is available, the new version gets installed") - - rootCmd.AddCommand(updateCmd) } func update() error { var release map[string]interface{} - resp, err := client.Get("https://api.github.com/repos/ByteDream/crunchy-cli/releases/latest") + resp, err := utils.Client.Get("https://api.github.com/repos/ByteDream/crunchy-cli/releases/latest") if err != nil { return err } @@ -48,8 +47,8 @@ func update() error { } releaseVersion := strings.TrimPrefix(release["tag_name"].(string), "v") - if Version == "development" { - out.Info("Development version, update service not available") + if utils.Version == "development" { + utils.Log.Info("Development version, update service not available") return nil } @@ -58,17 +57,17 @@ func update() error { return fmt.Errorf("latest tag name (%s) is not parsable", releaseVersion) } - internalVersion := strings.SplitN(Version, ".", 4) + internalVersion := strings.SplitN(utils.Version, ".", 4) if len(internalVersion) != 3 { - return fmt.Errorf("internal version (%s) is not parsable", Version) + return fmt.Errorf("internal version (%s) is not parsable", utils.Version) } - out.Info("Installed version is %s", Version) + utils.Log.Info("Installed version is %s", utils.Version) var hasUpdate bool for i := 0; i < 3; i++ { if latestRelease[i] < internalVersion[i] { - out.Info("Local version is newer than version in latest release (%s)", releaseVersion) + utils.Log.Info("Local version is newer than version in latest release (%s)", releaseVersion) return nil } else if latestRelease[i] > internalVersion[i] { hasUpdate = true @@ -76,11 +75,11 @@ func update() error { } if !hasUpdate { - out.Info("Version is up-to-date") + utils.Log.Info("Version is up-to-date") return nil } - out.Info("A new version is available (%s): https://github.com/ByteDream/crunchy-cli/releases/tag/v%s", releaseVersion, releaseVersion) + utils.Log.Info("A new version is available (%s): https://github.com/ByteDream/crunchy-cli/releases/tag/v%s", releaseVersion, releaseVersion) if updateInstallFlag { if runtime.GOARCH != "amd64" { @@ -93,7 +92,7 @@ func update() error { case "linux": yayCommand := exec.Command("pacman -Q crunchy-cli") if yayCommand.Run() == nil && yayCommand.ProcessState.Success() { - out.Info("crunchy-cli was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") + utils.Log.Info("crunchy-cli was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") return nil } downloadFile = fmt.Sprintf("crunchy-v%s_linux", releaseVersion) @@ -106,7 +105,7 @@ func update() error { "You have to update manually (https://github.com/ByteDream/crunchy-cli", runtime.GOOS) } - out.SetProgress("Updating executable %s", os.Args[0]) + utils.Log.SetProcess("Updating executable %s", os.Args[0]) perms, err := os.Stat(os.Args[0]) if err != nil { @@ -119,7 +118,7 @@ func update() error { } defer executeFile.Close() - resp, err := client.Get(fmt.Sprintf("https://github.com/ByteDream/crunchy-cli/releases/download/v%s/%s", releaseVersion, downloadFile)) + resp, err := utils.Client.Get(fmt.Sprintf("https://github.com/ByteDream/crunchy-cli/releases/download/v%s/%s", releaseVersion, downloadFile)) if err != nil { return err } @@ -129,7 +128,7 @@ func update() error { return err } - out.StopProgress("Updated executable %s", os.Args[0]) + utils.Log.StopProcess("Updated executable %s", os.Args[0]) } return nil diff --git a/cli/commands/utils.go b/cli/commands/utils.go new file mode 100644 index 0000000..924cd4d --- /dev/null +++ b/cli/commands/utils.go @@ -0,0 +1,125 @@ +package commands + +import ( + "fmt" + "github.com/ByteDream/crunchy-cli/utils" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "sync" +) + +type DownloadProgress struct { + Prefix string + Message string + + Total int + Current int + + Dev bool + Quiet bool + + lock sync.Mutex +} + +func (dp *DownloadProgress) Update() { + dp.update("", false) +} + +func (dp *DownloadProgress) UpdateMessage(msg string, permanent bool) { + dp.update(msg, permanent) +} + +func (dp *DownloadProgress) update(msg string, permanent bool) { + if dp.Quiet { + return + } + + if dp.Current >= dp.Total { + return + } + + dp.lock.Lock() + defer dp.lock.Unlock() + dp.Current++ + + if msg == "" { + msg = dp.Message + } + if permanent { + dp.Message = msg + } + + if dp.Dev { + fmt.Printf("%s%s\n", dp.Prefix, msg) + return + } + + percentage := float32(dp.Current) / float32(dp.Total) * 100 + + pre := fmt.Sprintf("%s%s [", dp.Prefix, msg) + post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total) + + // I don't really know why +2 is needed here but without it the Printf below would not print to the line end + progressWidth := terminalWidth() - len(pre) - len(post) + 2 + repeatCount := int(percentage / float32(100) * float32(progressWidth)) + // it can be lower than zero when the terminal is very tiny + if repeatCount < 0 { + repeatCount = 0 + } + progressPercentage := strings.Repeat("=", repeatCount) + if dp.Current != dp.Total { + progressPercentage += ">" + } + + fmt.Printf("\r%s%-"+fmt.Sprint(progressWidth)+"s%s", pre, progressPercentage, post) +} + +func terminalWidth() int { + if runtime.GOOS != "windows" { + cmd := exec.Command("stty", "size") + cmd.Stdin = os.Stdin + res, err := cmd.Output() + if err != nil { + return 60 + } + // on alpine linux the command `stty size` does not respond the terminal size + // but something like "stty: standard input". this may also apply to other systems + splitOutput := strings.SplitN(strings.ReplaceAll(string(res), "\n", ""), " ", 2) + if len(splitOutput) == 1 { + return 60 + } + width, err := strconv.Atoi(splitOutput[1]) + if err != nil { + return 60 + } + return width + } + return 60 +} + +func LoadCrunchy() error { + var encryptionKey []byte + + if utils.IsTempSession() { + encryptionKey = nil + } else { + if encrypted, err := utils.IsSavedSessionEncrypted(); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("to use this command, login first. Type `%s login -h` to get help", os.Args[0]) + } + return err + } else if encrypted { + encryptionKey, err = ReadLineSilent() + if err != nil { + return fmt.Errorf("failed to read password") + } + } + } + + var err error + utils.Crunchy, err = utils.LoadSession(encryptionKey) + return err +} diff --git a/commands/windows.go b/cli/commands/windows.go similarity index 94% rename from commands/windows.go rename to cli/commands/windows.go index 2ae47b5..a9bce74 100644 --- a/commands/windows.go +++ b/cli/commands/windows.go @@ -9,7 +9,7 @@ import ( ) // https://github.com/bgentry/speakeasy/blob/master/speakeasy_windows.go -func readLineSilent() ([]byte, error) { +func ReadLineSilent() ([]byte, error) { var oldMode uint32 if err := syscall.GetConsoleMode(syscall.Stdin, &oldMode); err != nil { diff --git a/cli/root.go b/cli/root.go new file mode 100644 index 0000000..ebb5079 --- /dev/null +++ b/cli/root.go @@ -0,0 +1,85 @@ +package cli + +import ( + "context" + "fmt" + "github.com/ByteDream/crunchy-cli/cli/commands" + "github.com/ByteDream/crunchy-cli/cli/commands/archive" + "github.com/ByteDream/crunchy-cli/cli/commands/download" + "github.com/ByteDream/crunchy-cli/cli/commands/info" + "github.com/ByteDream/crunchy-cli/cli/commands/login" + "github.com/ByteDream/crunchy-cli/cli/commands/update" + "github.com/ByteDream/crunchy-cli/utils" + "github.com/spf13/cobra" + "os" + "runtime/debug" + "strings" +) + +var ( + quietFlag bool + verboseFlag bool + + proxyFlag string + + useragentFlag string +) + +var RootCmd = &cobra.Command{ + Use: "crunchy-cli", + Version: utils.Version, + Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchy-cli/wiki", + + SilenceErrors: true, + SilenceUsage: true, + + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + if verboseFlag { + utils.Log = commands.NewLogger(true, true, true) + } else if quietFlag { + utils.Log = commands.NewLogger(false, false, false) + } + + utils.Log.Debug("Executing `%s` command with %d arg(s)", cmd.Name(), len(args)) + + utils.Client, err = utils.CreateOrDefaultClient(proxyFlag, useragentFlag) + return + }, +} + +func init() { + RootCmd.PersistentFlags().BoolVarP(&quietFlag, "quiet", "q", false, "Disable all output") + RootCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Adds debug messages to the normal output") + + RootCmd.PersistentFlags().StringVarP(&proxyFlag, "proxy", "p", "", "Proxy to use") + + RootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchy-cli/%s", utils.Version), "Useragent to do all request with") + + RootCmd.AddCommand(archive.Cmd) + RootCmd.AddCommand(download.Cmd) + RootCmd.AddCommand(info.Cmd) + RootCmd.AddCommand(login.Cmd) + RootCmd.AddCommand(update.Cmd) + + utils.Log = commands.NewLogger(false, true, true) +} + +func Execute() { + RootCmd.CompletionOptions.HiddenDefaultCmd = true + defer func() { + if r := recover(); r != nil { + if utils.Log.IsDev() { + utils.Log.Err("%v: %s", r, debug.Stack()) + } else { + utils.Log.Err("Unexpected error: %v", r) + } + os.Exit(1) + } + }() + if err := RootCmd.Execute(); err != nil { + if !strings.HasSuffix(err.Error(), context.Canceled.Error()) { + utils.Log.Err("An error occurred: %v", err) + } + os.Exit(1) + } +} diff --git a/commands/info.go b/commands/info.go deleted file mode 100644 index fe9693d..0000000 --- a/commands/info.go +++ /dev/null @@ -1,40 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/ByteDream/crunchyroll-go/v3/utils" - "github.com/spf13/cobra" -) - -var infoCmd = &cobra.Command{ - Use: "info", - Short: "Shows information about the logged in user", - Args: cobra.MinimumNArgs(0), - - RunE: func(cmd *cobra.Command, args []string) error { - loadCrunchy() - - return info() - }, -} - -func init() { - rootCmd.AddCommand(infoCmd) -} - -func info() error { - account, err := crunchy.Account() - if err != nil { - return err - } - - fmt.Println("Username: ", account.Username) - fmt.Println("Email: ", account.Email) - fmt.Println("Premium: ", crunchy.Config.Premium) - fmt.Println("Interface language:", utils.LocaleLanguage(account.PreferredCommunicationLanguage)) - fmt.Println("Subtitle language: ", utils.LocaleLanguage(account.PreferredContentSubtitleLanguage)) - fmt.Println("Created: ", account.Created) - fmt.Println("Account ID: ", account.AccountID) - - return nil -} diff --git a/commands/login.go b/commands/login.go deleted file mode 100644 index a710bb6..0000000 --- a/commands/login.go +++ /dev/null @@ -1,206 +0,0 @@ -package commands - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "fmt" - "github.com/ByteDream/crunchyroll-go/v3" - "github.com/spf13/cobra" - "io" - "os" - "path/filepath" -) - -var ( - loginPersistentFlag bool - loginEncryptFlag bool - - loginSessionIDFlag bool - loginEtpRtFlag bool -) - -var loginCmd = &cobra.Command{ - Use: "login", - Short: "Login to crunchyroll", - Args: cobra.RangeArgs(1, 2), - - RunE: func(cmd *cobra.Command, args []string) error { - if loginSessionIDFlag { - return loginSessionID(args[0]) - } else if loginEtpRtFlag { - return loginEtpRt(args[0]) - } else { - return loginCredentials(args[0], args[1]) - } - }, -} - -func init() { - loginCmd.Flags().BoolVar(&loginPersistentFlag, - "persistent", - false, - "If the given credential should be stored persistent") - loginCmd.Flags().BoolVar(&loginEncryptFlag, - "encrypt", - false, - "Encrypt the given credentials (won't do anything if --session-id is given or --persistent is not given)") - - loginCmd.Flags().BoolVar(&loginSessionIDFlag, - "session-id", - false, - "Use a session id to login instead of username and password") - loginCmd.Flags().BoolVar(&loginEtpRtFlag, - "etp-rt", - false, - "Use a etp rt cookie to login instead of username and password") - - rootCmd.AddCommand(loginCmd) -} - -func loginCredentials(user, password string) error { - out.Debug("Logging in via credentials") - c, err := crunchyroll.LoginWithCredentials(user, password, systemLocale(false), client) - if err != nil { - return err - } - - if loginPersistentFlag { - if configDir, err := os.UserConfigDir(); err != nil { - return fmt.Errorf("could not save credentials persistent: %w", err) - } else { - var credentials []byte - - if loginEncryptFlag { - var passwd []byte - - for { - fmt.Print("Enter password: ") - passwd, err = readLineSilent() - if err != nil { - return err - } - fmt.Println() - - fmt.Print("Enter password again: ") - repasswd, err := readLineSilent() - if err != nil { - return err - } - fmt.Println() - - if !bytes.Equal(passwd, repasswd) { - fmt.Println("Passwords does not match, try again") - continue - } - - hashedPassword := sha256.Sum256(passwd) - block, err := aes.NewCipher(hashedPassword[:]) - if err != nil { - out.Err("Failed to create block: %w", err) - os.Exit(1) - } - gcm, err := cipher.NewGCM(block) - if err != nil { - out.Err("Failed to create gcm: %w", err) - os.Exit(1) - } - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - out.Err("Failed to fill nonce: %w", err) - os.Exit(1) - } - - b := gcm.Seal(nonce, nonce, []byte(fmt.Sprintf("%s\n%s", user, password)), nil) - credentials = append([]byte("aes:"), b...) - - break - } - } else { - credentials = []byte(fmt.Sprintf("%s\n%s", user, password)) - } - - os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchy-cli", "crunchy"), credentials, 0600); err != nil { - return err - } - if !loginEncryptFlag { - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s). "+ - "To encrypt it, use the `--encrypt` flag", filepath.Join(configDir, "crunchy-cli", "crunchy")) - } - } - } - - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { - return err - } - - if !loginPersistentFlag { - out.Info("Due to security reasons, you have to login again on the next reboot") - } - - return nil -} - -func loginSessionID(sessionID string) error { - out.Debug("Logging in via session id") - var c *crunchyroll.Crunchyroll - var err error - if c, err = crunchyroll.LoginWithSessionID(sessionID, systemLocale(false), client); err != nil { - out.Err(err.Error()) - os.Exit(1) - } - - if loginPersistentFlag { - if configDir, err := os.UserConfigDir(); err != nil { - return fmt.Errorf("could not save credentials persistent: %w", err) - } else { - os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchy-cli", "crunchy"), []byte(c.EtpRt), 0600); err != nil { - return err - } - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchy-cli", "crunchy")) - } - } - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(c.EtpRt), 0600); err != nil { - return err - } - - if !loginPersistentFlag { - out.Info("Due to security reasons, you have to login again on the next reboot") - } - - return nil -} - -func loginEtpRt(etpRt string) error { - out.Debug("Logging in via etp rt") - if _, err := crunchyroll.LoginWithEtpRt(etpRt, systemLocale(false), client); err != nil { - out.Err(err.Error()) - os.Exit(1) - } - - var err error - if loginPersistentFlag { - if configDir, err := os.UserConfigDir(); err != nil { - return fmt.Errorf("could not save credentials persistent: %w", err) - } else { - os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755) - if err = os.WriteFile(filepath.Join(configDir, "crunchy-cli", "crunchy"), []byte(etpRt), 0600); err != nil { - return err - } - out.Info("The login information will be stored permanently UNENCRYPTED on your drive (%s)", filepath.Join(configDir, "crunchy-cli", "crunchy")) - } - } - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(etpRt), 0600); err != nil { - return err - } - - if !loginPersistentFlag { - out.Info("Due to security reasons, you have to login again on the next reboot") - } - - return nil -} diff --git a/commands/root.go b/commands/root.go deleted file mode 100644 index ffcf35a..0000000 --- a/commands/root.go +++ /dev/null @@ -1,78 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "github.com/ByteDream/crunchyroll-go/v3" - "github.com/spf13/cobra" - "net/http" - "os" - "runtime/debug" - "strings" -) - -var Version = "development" - -var ( - client *http.Client - crunchy *crunchyroll.Crunchyroll - out = newLogger(false, true, true) - - quietFlag bool - verboseFlag bool - - proxyFlag string - - useragentFlag string -) - -var rootCmd = &cobra.Command{ - Use: "crunchy-cli", - Version: Version, - Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchy-cli/wiki", - - SilenceErrors: true, - SilenceUsage: true, - - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - if verboseFlag { - out = newLogger(true, true, true) - } else if quietFlag { - out = newLogger(false, false, false) - } - - out.Debug("Executing `%s` command with %d arg(s)", cmd.Name(), len(args)) - - client, err = createOrDefaultClient(proxyFlag, useragentFlag) - return - }, -} - -func init() { - rootCmd.PersistentFlags().BoolVarP(&quietFlag, "quiet", "q", false, "Disable all output") - rootCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Adds debug messages to the normal output") - - rootCmd.PersistentFlags().StringVarP(&proxyFlag, "proxy", "p", "", "Proxy to use") - - rootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchy-cli/%s", Version), "Useragent to do all request with") -} - -func Execute() { - rootCmd.CompletionOptions.HiddenDefaultCmd = true - defer func() { - if r := recover(); r != nil { - if out.IsDev() { - out.Err("%v: %s", r, debug.Stack()) - } else { - out.Err("Unexpected error: %v", r) - } - os.Exit(1) - } - }() - if err := rootCmd.Execute(); err != nil { - if !strings.HasSuffix(err.Error(), context.Canceled.Error()) { - out.Exit("An error occurred: %v", err) - } - os.Exit(1) - } -} diff --git a/commands/utils.go b/commands/utils.go deleted file mode 100644 index 486c61f..0000000 --- a/commands/utils.go +++ /dev/null @@ -1,485 +0,0 @@ -package commands - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/sha256" - "fmt" - "github.com/ByteDream/crunchyroll-go/v3" - "github.com/ByteDream/crunchyroll-go/v3/utils" - "net/http" - "net/url" - "os" - "os/exec" - "path/filepath" - "reflect" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" -) - -var ( - // ahh i love windows :))) - invalidWindowsChars = []string{"\\", "<", ">", ":", "\"", "/", "|", "?", "*"} - invalidNotWindowsChars = []string{"/"} -) - -var urlFilter = regexp.MustCompile(`(S(\d+))?(E(\d+))?((-)(S(\d+))?(E(\d+))?)?(,|$)`) - -// systemLocale receives the system locale -// https://stackoverflow.com/questions/51829386/golang-get-system-language/51831590#51831590 -func systemLocale(verbose bool) crunchyroll.LOCALE { - if runtime.GOOS != "windows" { - if lang, ok := os.LookupEnv("LANG"); ok { - var l crunchyroll.LOCALE - if preSuffix := strings.Split(strings.Split(lang, ".")[0], "_"); len(preSuffix) == 1 { - l = crunchyroll.LOCALE(preSuffix[0]) - } else { - prefix := strings.Split(lang, "_")[0] - l = crunchyroll.LOCALE(fmt.Sprintf("%s-%s", prefix, preSuffix[1])) - } - if !utils.ValidateLocale(l) { - if verbose { - out.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) - } - l = crunchyroll.US - } - return l - } - } else { - cmd := exec.Command("powershell", "Get-Culture | select -exp Name") - if output, err := cmd.Output(); err == nil { - l := crunchyroll.LOCALE(strings.Trim(string(output), "\r\n")) - if !utils.ValidateLocale(l) { - if verbose { - out.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) - } - l = crunchyroll.US - } - return l - } - } - if verbose { - out.Err("Failed to get locale, using %s", crunchyroll.US) - } - return crunchyroll.US -} - -func allLocalesAsStrings() (locales []string) { - for _, locale := range utils.AllLocales { - locales = append(locales, string(locale)) - } - sort.Strings(locales) - return -} - -type headerRoundTripper struct { - http.RoundTripper - header map[string]string -} - -func (rht headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { - resp, err := rht.RoundTripper.RoundTrip(r) - if err != nil { - return nil, err - } - for k, v := range rht.header { - resp.Header.Set(k, v) - } - return resp, nil -} - -func createOrDefaultClient(proxy, useragent string) (*http.Client, error) { - if proxy == "" { - return http.DefaultClient, nil - } else { - out.Info("Using custom proxy %s", proxy) - proxyURL, err := url.Parse(proxy) - if err != nil { - return nil, err - } - - var rt http.RoundTripper = &http.Transport{ - DisableCompression: true, - Proxy: http.ProxyURL(proxyURL), - } - if useragent != "" { - rt = headerRoundTripper{ - RoundTripper: rt, - header: map[string]string{"User-Agent": useragent}, - } - } - - client := &http.Client{ - Transport: rt, - Timeout: 30 * time.Second, - } - return client, nil - } -} - -func freeFileName(filename string) (string, bool) { - ext := filepath.Ext(filename) - base := strings.TrimSuffix(filename, ext) - // checks if a .tar stands before the "actual" file ending - if extraExt := filepath.Ext(base); extraExt == ".tar" { - ext = extraExt + ext - base = strings.TrimSuffix(base, extraExt) - } - j := 0 - for ; ; j++ { - if _, stat := os.Stat(filename); stat != nil && !os.IsExist(stat) { - break - } - filename = fmt.Sprintf("%s (%d)%s", base, j+1, ext) - } - return filename, j != 0 -} - -func loadCrunchy() { - out.SetProgress("Logging in") - - tmpFilePath := filepath.Join(os.TempDir(), ".crunchy") - if _, statErr := os.Stat(tmpFilePath); !os.IsNotExist(statErr) { - body, err := os.ReadFile(tmpFilePath) - if err != nil { - out.StopProgress("Failed to read login information: %v", err) - os.Exit(1) - } - if crunchy, err = crunchyroll.LoginWithEtpRt(string(body), systemLocale(true), client); err != nil { - out.Debug("Failed to login with temp etp rt cookie: %v", err) - } else { - out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) - - out.StopProgress("Logged in") - return - } - } - - if configDir, err := os.UserConfigDir(); err == nil { - persistentFilePath := filepath.Join(configDir, "crunchy-cli", "crunchy") - if _, statErr := os.Stat(persistentFilePath); statErr == nil { - body, err := os.ReadFile(persistentFilePath) - if err != nil { - out.StopProgress("Failed to read login information: %v", err) - os.Exit(1) - } - split := strings.SplitN(string(body), "\n", 2) - if len(split) == 1 || split[1] == "" { - if strings.HasPrefix(split[0], "aes:") { - encrypted := body[4:] - - out.StopProgress("Credentials are encrypted") - fmt.Print("Enter password to encrypt it: ") - passwd, err := readLineSilent() - fmt.Println() - if err != nil { - out.Err("Failed to read password; %w", err) - os.Exit(1) - } - out.SetProgress("Logging in") - - hashedPassword := sha256.Sum256(passwd) - block, err := aes.NewCipher(hashedPassword[:]) - if err != nil { - out.Err("Failed to create block: %w", err) - os.Exit(1) - } - gcm, err := cipher.NewGCM(block) - if err != nil { - out.Err("Failed to create gcm: %w", err) - os.Exit(1) - } - nonce, c := encrypted[:gcm.NonceSize()], encrypted[gcm.NonceSize():] - - b, err := gcm.Open(nil, nonce, c, nil) - if err != nil { - out.StopProgress("Invalid password") - os.Exit(1) - } - split = strings.SplitN(string(b), "\n", 2) - } - } - - if len(split) == 2 { - if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) - } - out.Debug("Logged in with credentials") - } else { - if crunchy, err = crunchyroll.LoginWithEtpRt(split[0], systemLocale(true), client); err != nil { - out.StopProgress(err.Error()) - os.Exit(1) - } - out.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) - } - - // the etp rt is written to a temp file to reduce the amount of re-logging in. - // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time - os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600) - - out.StopProgress("Logged in") - return - } - } - - out.StopProgress("To use this command, login first. Type `%s login -h` to get help", os.Args[0]) - os.Exit(1) -} - -func hasFFmpeg() bool { - return exec.Command("ffmpeg", "-h").Run() == nil -} - -func terminalWidth() int { - if runtime.GOOS != "windows" { - cmd := exec.Command("stty", "size") - cmd.Stdin = os.Stdin - res, err := cmd.Output() - if err != nil { - return 60 - } - // on alpine linux the command `stty size` does not respond the terminal size - // but something like "stty: standard input". this may also apply to other systems - splitOutput := strings.SplitN(strings.ReplaceAll(string(res), "\n", ""), " ", 2) - if len(splitOutput) == 1 { - return 60 - } - width, err := strconv.Atoi(splitOutput[1]) - if err != nil { - return 60 - } - return width - } - return 60 -} - -func generateFilename(name, directory string) string { - if runtime.GOOS != "windows" { - for _, char := range invalidNotWindowsChars { - name = strings.ReplaceAll(name, char, "") - } - out.Debug("Replaced invalid characters (not windows)") - } else { - for _, char := range invalidWindowsChars { - name = strings.ReplaceAll(name, char, "") - } - out.Debug("Replaced invalid characters (windows)") - } - - filename, changed := freeFileName(filepath.Join(directory, name)) - if changed { - out.Debug("File `%s` already exists, changing name to `%s`", filepath.Base(name), filepath.Base(filename)) - } - - return filename -} - -func extractEpisodes(url string, locales ...crunchyroll.LOCALE) ([][]*crunchyroll.Episode, error) { - var matches [][]string - - lastOpen := strings.LastIndex(url, "[") - if strings.HasSuffix(url, "]") && lastOpen != -1 && lastOpen < len(url) { - matches = urlFilter.FindAllStringSubmatch(url[lastOpen+1:len(url)-1], -1) - - var all string - for _, match := range matches { - all += match[0] - } - if all != url[lastOpen+1:len(url)-1] { - return nil, fmt.Errorf("invalid episode filter") - } - url = url[:lastOpen] - } - - final := make([][]*crunchyroll.Episode, len(locales)) - episodes, err := crunchy.ExtractEpisodesFromUrl(url, locales...) - if err != nil { - return nil, fmt.Errorf("failed to get episodes: %v", err) - } - - if len(episodes) == 0 { - return nil, fmt.Errorf("no episodes found") - } - - if matches != nil { - for _, match := range matches { - fromSeason, fromEpisode, toSeason, toEpisode := -1, -1, -1, -1 - if match[2] != "" { - fromSeason, _ = strconv.Atoi(match[2]) - } - if match[4] != "" { - fromEpisode, _ = strconv.Atoi(match[4]) - } - if match[8] != "" { - toSeason, _ = strconv.Atoi(match[8]) - } - if match[10] != "" { - toEpisode, _ = strconv.Atoi(match[10]) - } - - if match[6] != "-" { - toSeason = fromSeason - toEpisode = fromEpisode - } - - tmpEps := make([]*crunchyroll.Episode, 0) - for _, episode := range episodes { - if fromSeason != -1 && (episode.SeasonNumber < fromSeason || (fromEpisode != -1 && episode.EpisodeNumber < fromEpisode)) { - continue - } else if fromSeason == -1 && fromEpisode != -1 && episode.EpisodeNumber < fromEpisode { - continue - } else if toSeason != -1 && (episode.SeasonNumber > toSeason || (toEpisode != -1 && episode.EpisodeNumber > toEpisode)) { - continue - } else if toSeason == -1 && toEpisode != -1 && episode.EpisodeNumber > toEpisode { - continue - } else { - tmpEps = append(tmpEps, episode) - } - } - - if len(tmpEps) == 0 { - return nil, fmt.Errorf("no episodes are matching the given filter") - } - - episodes = tmpEps - } - } - - localeSorted, err := utils.SortEpisodesByAudio(episodes) - if err != nil { - return nil, fmt.Errorf("failed to get audio locale: %v", err) - } - for i, locale := range locales { - final[i] = append(final[i], localeSorted[locale]...) - } - - return final, nil -} - -type formatInformation struct { - // the format to download - format *crunchyroll.Format - - // additional formats which are only used by archive.go - additionalFormats []*crunchyroll.Format - - Title string `json:"title"` - SeriesName string `json:"series_name"` - SeasonName string `json:"season_name"` - SeasonNumber int `json:"season_number"` - EpisodeNumber int `json:"episode_number"` - Resolution string `json:"resolution"` - FPS float64 `json:"fps"` - Audio crunchyroll.LOCALE `json:"audio"` - Subtitle crunchyroll.LOCALE `json:"subtitle"` -} - -func (fi formatInformation) Format(source string) string { - fields := reflect.TypeOf(fi) - values := reflect.ValueOf(fi) - - for i := 0; i < fields.NumField(); i++ { - var valueAsString string - switch value := values.Field(i); value.Kind() { - case reflect.String: - valueAsString = value.String() - case reflect.Int: - valueAsString = fmt.Sprintf("%02d", value.Int()) - case reflect.Float64: - valueAsString = fmt.Sprintf("%.2f", value.Float()) - case reflect.Bool: - valueAsString = fields.Field(i).Tag.Get("json") - if !value.Bool() { - valueAsString = "no " + valueAsString - } - } - - if runtime.GOOS != "windows" { - for _, char := range invalidNotWindowsChars { - valueAsString = strings.ReplaceAll(valueAsString, char, "") - } - out.Debug("Replaced invalid characters (not windows)") - } else { - for _, char := range invalidWindowsChars { - valueAsString = strings.ReplaceAll(valueAsString, char, "") - } - out.Debug("Replaced invalid characters (windows)") - } - - source = strings.ReplaceAll(source, "{"+fields.Field(i).Tag.Get("json")+"}", valueAsString) - } - - return source -} - -type downloadProgress struct { - Prefix string - Message string - - Total int - Current int - - Dev bool - Quiet bool - - lock sync.Mutex -} - -func (dp *downloadProgress) Update() { - dp.update("", false) -} - -func (dp *downloadProgress) UpdateMessage(msg string, permanent bool) { - dp.update(msg, permanent) -} - -func (dp *downloadProgress) update(msg string, permanent bool) { - if dp.Quiet { - return - } - - if dp.Current >= dp.Total { - return - } - - dp.lock.Lock() - defer dp.lock.Unlock() - dp.Current++ - - if msg == "" { - msg = dp.Message - } - if permanent { - dp.Message = msg - } - - if dp.Dev { - fmt.Printf("%s%s\n", dp.Prefix, msg) - return - } - - percentage := float32(dp.Current) / float32(dp.Total) * 100 - - pre := fmt.Sprintf("%s%s [", dp.Prefix, msg) - post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total) - - // I don't really know why +2 is needed here but without it the Printf below would not print to the line end - progressWidth := terminalWidth() - len(pre) - len(post) + 2 - repeatCount := int(percentage / float32(100) * float32(progressWidth)) - // it can be lower than zero when the terminal is very tiny - if repeatCount < 0 { - repeatCount = 0 - } - progressPercentage := strings.Repeat("=", repeatCount) - if dp.Current != dp.Total { - progressPercentage += ">" - } - - fmt.Printf("\r%s%-"+fmt.Sprint(progressWidth)+"s%s", pre, progressPercentage, post) -} diff --git a/crunchy-cli.1 b/crunchy-cli.1 index d9b1075..ebadb9c 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -61,7 +61,7 @@ NOTE: The credentials are stored in plain text and if you not use \fB--session-i Login via a session id (which can be extracted from a crunchyroll browser cookie) instead of using username and password. .SH DOWNLOAD COMMAND -A command to simply download videos. The output file is stored as a \fI.ts\fR file. \fIffmpeg\fR has to be installed if you want to change the format the videos are stored in. +A command to simply download videos. The output file is stored as a \fI.ts\fR file. \fIffmpeg\fR has to be installed if you want to change the Format the videos are stored in. .TP \fB-a, --audio AUDIO\fR diff --git a/go.mod b/go.mod index 2c0abc0..909378c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ByteDream/crunchy-cli go 1.18 require ( - github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c + github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220630135625-ed58b3fe8cc1 github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 626a2c5..5603301 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c h1:jPabd/Zl/zdoSo8ZGtZLm43+42nIFHIJABvrvdMOYtY= github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c/go.mod h1:L4M1sOPjJ4ui0YXFnpVUb4AzQIa+D/i/B0QG5iz9op4= +github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220630135625-ed58b3fe8cc1 h1:hOL4xzDc6oCcrpf6GrrdUgvqwsQo6dI2zL4nA8rl9hg= +github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220630135625-ed58b3fe8cc1/go.mod h1:L4M1sOPjJ4ui0YXFnpVUb4AzQIa+D/i/B0QG5iz9op4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= diff --git a/main.go b/main.go index f9c3a90..7275a13 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main -import "github.com/ByteDream/crunchy-cli/commands" +import ( + "github.com/ByteDream/crunchy-cli/cli" +) func main() { - commands.Execute() + cli.Execute() } diff --git a/utils/extract.go b/utils/extract.go new file mode 100644 index 0000000..ba52242 --- /dev/null +++ b/utils/extract.go @@ -0,0 +1,94 @@ +package utils + +import ( + "fmt" + "github.com/ByteDream/crunchyroll-go/v3" + "github.com/ByteDream/crunchyroll-go/v3/utils" + "regexp" + "strconv" + "strings" +) + +var urlFilter = regexp.MustCompile(`(S(\d+))?(E(\d+))?((-)(S(\d+))?(E(\d+))?)?(,|$)`) + +func ExtractEpisodes(url string, locales ...crunchyroll.LOCALE) ([][]*crunchyroll.Episode, error) { + var matches [][]string + + lastOpen := strings.LastIndex(url, "[") + if strings.HasSuffix(url, "]") && lastOpen != -1 && lastOpen < len(url) { + matches = urlFilter.FindAllStringSubmatch(url[lastOpen+1:len(url)-1], -1) + + var all string + for _, match := range matches { + all += match[0] + } + if all != url[lastOpen+1:len(url)-1] { + return nil, fmt.Errorf("invalid episode filter") + } + url = url[:lastOpen] + } + + final := make([][]*crunchyroll.Episode, len(locales)) + episodes, err := Crunchy.ExtractEpisodesFromUrl(url, locales...) + if err != nil { + return nil, fmt.Errorf("failed to get episodes: %v", err) + } + + if len(episodes) == 0 { + return nil, fmt.Errorf("no episodes found") + } + + if matches != nil { + for _, match := range matches { + fromSeason, fromEpisode, toSeason, toEpisode := -1, -1, -1, -1 + if match[2] != "" { + fromSeason, _ = strconv.Atoi(match[2]) + } + if match[4] != "" { + fromEpisode, _ = strconv.Atoi(match[4]) + } + if match[8] != "" { + toSeason, _ = strconv.Atoi(match[8]) + } + if match[10] != "" { + toEpisode, _ = strconv.Atoi(match[10]) + } + + if match[6] != "-" { + toSeason = fromSeason + toEpisode = fromEpisode + } + + tmpEps := make([]*crunchyroll.Episode, 0) + for _, episode := range episodes { + if fromSeason != -1 && (episode.SeasonNumber < fromSeason || (fromEpisode != -1 && episode.EpisodeNumber < fromEpisode)) { + continue + } else if fromSeason == -1 && fromEpisode != -1 && episode.EpisodeNumber < fromEpisode { + continue + } else if toSeason != -1 && (episode.SeasonNumber > toSeason || (toEpisode != -1 && episode.EpisodeNumber > toEpisode)) { + continue + } else if toSeason == -1 && toEpisode != -1 && episode.EpisodeNumber > toEpisode { + continue + } else { + tmpEps = append(tmpEps, episode) + } + } + + if len(tmpEps) == 0 { + return nil, fmt.Errorf("no episodes are matching the given filter") + } + + episodes = tmpEps + } + } + + localeSorted, err := utils.SortEpisodesByAudio(episodes) + if err != nil { + return nil, fmt.Errorf("failed to get audio locale: %v", err) + } + for i, locale := range locales { + final[i] = append(final[i], localeSorted[locale]...) + } + + return final, nil +} diff --git a/utils/file.go b/utils/file.go new file mode 100644 index 0000000..183b06f --- /dev/null +++ b/utils/file.go @@ -0,0 +1,49 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" +) + +func FreeFileName(filename string) (string, bool) { + ext := filepath.Ext(filename) + base := strings.TrimSuffix(filename, ext) + // checks if a .tar stands before the "actual" file ending + if extraExt := filepath.Ext(base); extraExt == ".tar" { + ext = extraExt + ext + base = strings.TrimSuffix(base, extraExt) + } + j := 0 + for ; ; j++ { + if _, stat := os.Stat(filename); stat != nil && !os.IsExist(stat) { + break + } + filename = fmt.Sprintf("%s (%d)%s", base, j+1, ext) + } + return filename, j != 0 +} + +func GenerateFilename(name, directory string) string { + if runtime.GOOS != "windows" { + for _, char := range []string{"/"} { + name = strings.ReplaceAll(name, char, "") + } + Log.Debug("Replaced invalid characters (not windows)") + } else { + // ahh i love windows :))) + for _, char := range []string{"\\", "<", ">", ":", "\"", "/", "|", "?", "*"} { + name = strings.ReplaceAll(name, char, "") + } + Log.Debug("Replaced invalid characters (windows)") + } + + filename, changed := FreeFileName(filepath.Join(directory, name)) + if changed { + Log.Debug("File `%s` already exists, changing name to `%s`", filepath.Base(name), filepath.Base(filename)) + } + + return filename +} diff --git a/utils/format.go b/utils/format.go new file mode 100644 index 0000000..9642a31 --- /dev/null +++ b/utils/format.go @@ -0,0 +1,63 @@ +package utils + +import ( + "fmt" + "github.com/ByteDream/crunchyroll-go/v3" + "reflect" + "runtime" + "strings" +) + +type FormatInformation struct { + // the Format to download + Format *crunchyroll.Format + + // additional formats which are only used by archive.go + AdditionalFormats []*crunchyroll.Format + + Title string `json:"title"` + SeriesName string `json:"series_name"` + SeasonName string `json:"season_name"` + SeasonNumber int `json:"season_number"` + EpisodeNumber int `json:"episode_number"` + Resolution string `json:"resolution"` + FPS float64 `json:"fps"` + Audio crunchyroll.LOCALE `json:"audio"` + Subtitle crunchyroll.LOCALE `json:"subtitle"` +} + +func (fi FormatInformation) FormatString(source string) string { + fields := reflect.TypeOf(fi) + values := reflect.ValueOf(fi) + + for i := 0; i < fields.NumField(); i++ { + var valueAsString string + switch value := values.Field(i); value.Kind() { + case reflect.String: + valueAsString = value.String() + case reflect.Int: + valueAsString = fmt.Sprintf("%02d", value.Int()) + case reflect.Float64: + valueAsString = fmt.Sprintf("%.2f", value.Float()) + case reflect.Bool: + valueAsString = fields.Field(i).Tag.Get("json") + if !value.Bool() { + valueAsString = "no " + valueAsString + } + } + + if runtime.GOOS != "windows" { + for _, char := range []string{"/"} { + valueAsString = strings.ReplaceAll(valueAsString, char, "") + } + } else { + for _, char := range []string{"\\", "<", ">", ":", "\"", "/", "|", "?", "*"} { + valueAsString = strings.ReplaceAll(valueAsString, char, "") + } + } + + source = strings.ReplaceAll(source, "{"+fields.Field(i).Tag.Get("json")+"}", valueAsString) + } + + return source +} diff --git a/utils/http.go b/utils/http.go new file mode 100644 index 0000000..f572bd9 --- /dev/null +++ b/utils/http.go @@ -0,0 +1,51 @@ +package utils + +import ( + "net/http" + "net/url" + "time" +) + +type headerRoundTripper struct { + http.RoundTripper + header map[string]string +} + +func (rht headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + resp, err := rht.RoundTripper.RoundTrip(r) + if err != nil { + return nil, err + } + for k, v := range rht.header { + resp.Header.Set(k, v) + } + return resp, nil +} + +func CreateOrDefaultClient(proxy, useragent string) (*http.Client, error) { + if proxy == "" { + return http.DefaultClient, nil + } else { + proxyURL, err := url.Parse(proxy) + if err != nil { + return nil, err + } + + var rt http.RoundTripper = &http.Transport{ + DisableCompression: true, + Proxy: http.ProxyURL(proxyURL), + } + if useragent != "" { + rt = headerRoundTripper{ + RoundTripper: rt, + header: map[string]string{"User-Agent": useragent}, + } + } + + client := &http.Client{ + Transport: rt, + Timeout: 30 * time.Second, + } + return client, nil + } +} diff --git a/utils/locale.go b/utils/locale.go new file mode 100644 index 0000000..e1517bb --- /dev/null +++ b/utils/locale.go @@ -0,0 +1,59 @@ +package utils + +import ( + "fmt" + "github.com/ByteDream/crunchyroll-go/v3" + "github.com/ByteDream/crunchyroll-go/v3/utils" + "os" + "os/exec" + "runtime" + "sort" + "strings" +) + +// SystemLocale receives the system locale +// https://stackoverflow.com/questions/51829386/golang-get-system-language/51831590#51831590 +func SystemLocale(verbose bool) crunchyroll.LOCALE { + if runtime.GOOS != "windows" { + if lang, ok := os.LookupEnv("LANG"); ok { + var l crunchyroll.LOCALE + if preSuffix := strings.Split(strings.Split(lang, ".")[0], "_"); len(preSuffix) == 1 { + l = crunchyroll.LOCALE(preSuffix[0]) + } else { + prefix := strings.Split(lang, "_")[0] + l = crunchyroll.LOCALE(fmt.Sprintf("%s-%s", prefix, preSuffix[1])) + } + if !utils.ValidateLocale(l) { + if verbose { + Log.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) + } + l = crunchyroll.US + } + return l + } + } else { + cmd := exec.Command("powershell", "Get-Culture | select -exp Name") + if output, err := cmd.Output(); err == nil { + l := crunchyroll.LOCALE(strings.Trim(string(output), "\r\n")) + if !utils.ValidateLocale(l) { + if verbose { + Log.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) + } + l = crunchyroll.US + } + return l + } + } + if verbose { + Log.Err("Failed to get locale, using %s", crunchyroll.US) + } + return crunchyroll.US +} + +func LocalesAsStrings() (locales []string) { + for _, locale := range utils.AllLocales { + locales = append(locales, string(locale)) + } + sort.Strings(locales) + return +} diff --git a/utils/logger.go b/utils/logger.go new file mode 100644 index 0000000..27ea344 --- /dev/null +++ b/utils/logger.go @@ -0,0 +1,12 @@ +package utils + +type Logger interface { + IsDev() bool + Debug(format string, v ...any) + Info(format string, v ...any) + Warn(format string, v ...any) + Err(format string, v ...any) + Empty() + SetProcess(format string, v ...any) + StopProcess(format string, v ...any) +} diff --git a/utils/save.go b/utils/save.go new file mode 100644 index 0000000..43eeda7 --- /dev/null +++ b/utils/save.go @@ -0,0 +1,177 @@ +package utils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "fmt" + "github.com/ByteDream/crunchyroll-go/v3" + "io" + "os" + "path/filepath" + "strings" +) + +func SaveSession(crunchy *crunchyroll.Crunchyroll) error { + file := filepath.Join(os.TempDir(), ".crunchy") + return os.WriteFile(file, []byte(crunchy.EtpRt), 0600) +} + +func SaveCredentialsPersistent(user, password string, encryptionKey []byte) error { + configDir, err := os.UserConfigDir() + if err != nil { + return err + } + file := filepath.Join(configDir, "crunchy-cli", "crunchy") + + var credentials []byte + if encryptionKey != nil { + hashedEncryptionKey := sha256.Sum256(encryptionKey) + block, err := aes.NewCipher(hashedEncryptionKey[:]) + if err != nil { + return err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return err + } + b := gcm.Seal(nonce, nonce, []byte(fmt.Sprintf("%s\n%s", user, password)), nil) + credentials = append([]byte("aes:"), b...) + } else { + credentials = []byte(fmt.Sprintf("%s\n%s", user, password)) + } + + if err = os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755); err != nil { + return err + } + return os.WriteFile(file, credentials, 0600) +} + +func SaveSessionPersistent(crunchy *crunchyroll.Crunchyroll) error { + configDir, err := os.UserConfigDir() + if err != nil { + return err + } + file := filepath.Join(configDir, "crunchy-cli", "crunchy") + + if err = os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755); err != nil { + return err + } + return os.WriteFile(file, []byte(crunchy.EtpRt), 0600) +} + +func IsTempSession() bool { + file := filepath.Join(os.TempDir(), ".crunchy") + if _, err := os.Stat(file); !os.IsNotExist(err) { + return true + } + return false +} + +func IsSavedSessionEncrypted() (bool, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return false, err + } + file := filepath.Join(configDir, "crunchy-cli", "crunchy") + body, err := os.ReadFile(file) + if err != nil { + return false, err + } + return strings.HasPrefix(string(body), "aes:"), nil +} + +func LoadSession(encryptionKey []byte) (*crunchyroll.Crunchyroll, error) { + file := filepath.Join(os.TempDir(), ".crunchy") + crunchy, err := loadTempSession(file) + if err != nil { + return nil, err + } + if crunchy != nil { + return crunchy, nil + } + + configDir, err := os.UserConfigDir() + if err != nil { + return nil, err + } + file = filepath.Join(configDir, "crunchy-cli", "crunchy") + crunchy, err = loadPersistentSession(file, encryptionKey) + if err != nil { + return nil, err + } + if crunchy != nil { + return crunchy, nil + } + + return nil, fmt.Errorf("not logged in") +} + +func loadTempSession(file string) (*crunchyroll.Crunchyroll, error) { + if _, err := os.Stat(file); !os.IsNotExist(err) { + body, err := os.ReadFile(file) + if err != nil { + return nil, err + } + crunchy, err := crunchyroll.LoginWithEtpRt(string(body), SystemLocale(true), Client) + if err != nil { + Log.Debug("Failed to login with temp etp rt cookie: %v", err) + } else { + Log.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) + return crunchy, nil + } + } + return nil, nil +} + +func loadPersistentSession(file string, encryptionKey []byte) (crunchy *crunchyroll.Crunchyroll, err error) { + if _, err = os.Stat(file); !os.IsNotExist(err) { + body, err := os.ReadFile(file) + if err != nil { + return nil, err + } + split := strings.SplitN(string(body), "\n", 2) + if len(split) == 1 || split[1] == "" && strings.HasPrefix(split[0], "aes:") { + encrypted := body[4:] + hashedEncryptionKey := sha256.Sum256(encryptionKey) + block, err := aes.NewCipher(hashedEncryptionKey[:]) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonce, cypherText := encrypted[:gcm.NonceSize()], encrypted[gcm.NonceSize():] + b, err := gcm.Open(nil, nonce, cypherText, nil) + if err != nil { + return nil, err + } + split = strings.SplitN(string(b), "\n", 2) + } + if len(split) == 2 { + if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], SystemLocale(true), Client); err != nil { + return nil, err + } + Log.Debug("Logged in with credentials") + } else { + if crunchy, err = crunchyroll.LoginWithEtpRt(split[0], SystemLocale(true), Client); err != nil { + return nil, err + } + Log.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + } + + // the etp rt is written to a temp file to reduce the amount of re-logging in. + // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600); err != nil { + return nil, err + } + } + + return +} diff --git a/utils/system.go b/utils/system.go new file mode 100644 index 0000000..ac97706 --- /dev/null +++ b/utils/system.go @@ -0,0 +1,7 @@ +package utils + +import "os/exec" + +func HasFFmpeg() bool { + return exec.Command("ffmpeg", "-h").Run() == nil +} diff --git a/utils/vars.go b/utils/vars.go new file mode 100644 index 0000000..caca63e --- /dev/null +++ b/utils/vars.go @@ -0,0 +1,14 @@ +package utils + +import ( + "github.com/ByteDream/crunchyroll-go/v3" + "net/http" +) + +var Version = "development" + +var ( + Crunchy *crunchyroll.Crunchyroll + Client *http.Client + Log Logger +) From f1a41d6d3b66744b0f9df52dd9da712232f18cd4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 7 Jul 2022 21:21:44 +0200 Subject: [PATCH 146/630] Change name due to organization move --- Makefile | 8 +++--- README.md | 42 +++++++++++++++--------------- cmd/crunchyroll-go/cmd/archive.go | 6 ++--- cmd/crunchyroll-go/cmd/download.go | 6 ++--- cmd/crunchyroll-go/cmd/login.go | 2 +- cmd/crunchyroll-go/cmd/root.go | 4 +-- cmd/crunchyroll-go/cmd/utils.go | 4 +-- cmd/crunchyroll-go/main.go | 2 +- crunchyroll-go.1 | 6 ++--- crunchyroll.go | 2 +- go.mod | 2 +- utils/locale.go | 4 +-- utils/sort.go | 2 +- 13 files changed, 44 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 3dd5c3e..44662f1 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DESTDIR= PREFIX=/usr build: - go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go + go build -ldflags "-X 'github.com/crunchy-labs/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(BINARY_NAME) cmd/crunchyroll-go/main.go clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* @@ -24,8 +24,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchyroll-go/LICENSE release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe cmd/crunchyroll-go/main.go + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchyroll-go/v2/cmd/crunchyroll-go/cmd.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin cmd/crunchyroll-go/main.go strip $(VERSION_BINARY_NAME)_linux diff --git a/README.md b/README.md index b6d960f..df75ec8 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,26 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account to for full (api) access. <p align="center"> - <a href="https://github.com/ByteDream/crunchy-cli"> - <img src="https://img.shields.io/github/languages/code-size/ByteDream/crunchy-cli?style=flat-square" alt="Code size"> + <a href="https://github.com/crunchy-labs/crunchy-cli"> + <img src="https://img.shields.io/github/languages/code-size/crunchy-labs/crunchy-cli?style=flat-square" alt="Code size"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> - <img src="https://img.shields.io/github/downloads/ByteDream/crunchy-cli/total?style=flat-square" alt="Download Badge"> + <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/downloads/crunchy-labs/crunchy-cli/total?style=flat-square" alt="Download Badge"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/blob/master/LICENSE"> - <img src="https://img.shields.io/github/license/ByteDream/crunchy-cli?style=flat-square" alt="License"> + <a href="https://github.com/crunchy-labs/crunchy-cli/blob/master/LICENSE"> + <img src="https://img.shields.io/github/license/crunchy-labs/crunchy-cli?style=flat-square" alt="License"> </a> <a href="https://golang.org"> - <img src="https://img.shields.io/github/go-mod/go-version/ByteDream/crunchy-cli?style=flat-square" alt="Go version"> + <img src="https://img.shields.io/github/go-mod/go-version/crunchy-labs/crunchy-cli?style=flat-square" alt="Go version"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> - <img src="https://img.shields.io/github/v/release/ByteDream/crunchy-cli?style=flat-square" alt="Release"> + <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> </a> <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/actions/workflows/ci.yml"> - <img src="https://github.com/ByteDream/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> + <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> + <img src="https://github.com/crunchy-labs/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> </a> </p> @@ -35,7 +35,7 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crun </p> _This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility. -See [#39](https://github.com/ByteDream/crunchy-cli/issues/39) for more information._ +See [#39](https://github.com/crunchy-labs/crunchy-cli/issues/39) for more information._ # ๐Ÿ–ฅ๏ธ CLI @@ -47,10 +47,10 @@ See [#39](https://github.com/ByteDream/crunchy-cli/issues/39) for more informati ## ๐Ÿ’พ Get the executable -- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchy-cli/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_darwin) +- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/crunchy-labs/crunchy-cli/releases/latest) or get it from below: + - [Linux (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell $ yay -S crunchyroll-go @@ -65,24 +65,24 @@ See [#39](https://github.com/ByteDream/crunchy-cli/issues/39) for more informati </del> <i>Currently not working because the repo got renamed!</i> -- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): +- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/crunchy-labs/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): - use `make` (requires `go` to be installed): ```shell - $ git clone https://github.com/ByteDream/crunchy-cli + $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli $ make $ sudo make install # <- only if you want to install it on your system ``` - use `go`: ```shell - $ git clone https://github.com/ByteDream/crunchy-cli + $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli $ go build -o crunchy . ``` ## ๐Ÿ“ Examples -_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli), further usages and options are described there. +_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli), further usages and options are described there. ### Login @@ -169,7 +169,7 @@ The following flags can be (optional) passed to modify the [archive](#archive) p | `-l` | `--language` | Audio locale which should be downloaded. Can be used multiple times. | | `-d` | `--directory` | Directory to download the video(s) to. | | `-o` | `--output` | Name of the output file. | -| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli#archive) for more information. | +| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#archive) for more information. | | `-c` | `--compress` | If is set, all output will be compresses into an archive. This flag sets the name of the compressed output file and the file ending specifies the compression algorithm (gzip, tar, zip are supported). | | `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | | `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | diff --git a/cmd/crunchyroll-go/cmd/archive.go b/cmd/crunchyroll-go/cmd/archive.go index 6dcf61b..baa90e5 100644 --- a/cmd/crunchyroll-go/cmd/archive.go +++ b/cmd/crunchyroll-go/cmd/archive.go @@ -8,8 +8,8 @@ import ( "compress/gzip" "context" "fmt" - "github.com/ByteDream/crunchyroll-go/v2" - "github.com/ByteDream/crunchyroll-go/v2/utils" + "github.com/crunchy-labs/crunchyroll-go/v2" + "github.com/crunchy-labs/crunchyroll-go/v2/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "io" @@ -187,7 +187,7 @@ func archive(urls []string) error { if err != nil { out.StopProgress("Failed to parse url %d", i+1) out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchyroll-go/issues/22 for more information") return err } out.StopProgress("Parsed url %d", i+1) diff --git a/cmd/crunchyroll-go/cmd/download.go b/cmd/crunchyroll-go/cmd/download.go index 92b6027..063a2f7 100644 --- a/cmd/crunchyroll-go/cmd/download.go +++ b/cmd/crunchyroll-go/cmd/download.go @@ -3,8 +3,8 @@ package cmd import ( "context" "fmt" - "github.com/ByteDream/crunchyroll-go/v2" - "github.com/ByteDream/crunchyroll-go/v2/utils" + "github.com/crunchy-labs/crunchyroll-go/v2" + "github.com/crunchy-labs/crunchyroll-go/v2/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "math" @@ -133,7 +133,7 @@ func download(urls []string) error { if err != nil { out.StopProgress("Failed to parse url %d", i+1) out.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information") + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchyroll-go/issues/22 for more information") return err } out.StopProgress("Parsed url %d", i+1) diff --git a/cmd/crunchyroll-go/cmd/login.go b/cmd/crunchyroll-go/cmd/login.go index c9fc923..d5cc993 100644 --- a/cmd/crunchyroll-go/cmd/login.go +++ b/cmd/crunchyroll-go/cmd/login.go @@ -2,7 +2,7 @@ package cmd import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/crunchy-labs/crunchyroll-go/v2" "github.com/spf13/cobra" "os" "path/filepath" diff --git a/cmd/crunchyroll-go/cmd/root.go b/cmd/crunchyroll-go/cmd/root.go index 82ee133..8acdefe 100644 --- a/cmd/crunchyroll-go/cmd/root.go +++ b/cmd/crunchyroll-go/cmd/root.go @@ -3,7 +3,7 @@ package cmd import ( "context" "fmt" - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/crunchy-labs/crunchyroll-go/v2" "github.com/spf13/cobra" "net/http" "os" @@ -29,7 +29,7 @@ var ( var rootCmd = &cobra.Command{ Use: "crunchyroll-go", Version: Version, - Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchyroll-go/wiki", + Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/crunchy-labs/crunchyroll-go/wiki", SilenceErrors: true, SilenceUsage: true, diff --git a/cmd/crunchyroll-go/cmd/utils.go b/cmd/crunchyroll-go/cmd/utils.go index 2a632ff..8df50f1 100644 --- a/cmd/crunchyroll-go/cmd/utils.go +++ b/cmd/crunchyroll-go/cmd/utils.go @@ -2,8 +2,8 @@ package cmd import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v2" - "github.com/ByteDream/crunchyroll-go/v2/utils" + "github.com/crunchy-labs/crunchyroll-go/v2" + "github.com/crunchy-labs/crunchyroll-go/v2/utils" "net/http" "net/url" "os" diff --git a/cmd/crunchyroll-go/main.go b/cmd/crunchyroll-go/main.go index a502afb..48a4088 100644 --- a/cmd/crunchyroll-go/main.go +++ b/cmd/crunchyroll-go/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/ByteDream/crunchyroll-go/v2/cmd/crunchyroll-go/cmd" + "github.com/crunchy-labs/crunchyroll-go/v2/cmd/crunchyroll-go/cmd" ) func main() { diff --git a/crunchyroll-go.1 b/crunchyroll-go.1 index 558aa23..e6141f4 100644 --- a/crunchyroll-go.1 +++ b/crunchyroll-go.1 @@ -184,12 +184,12 @@ $ crunchyroll-go archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY If you notice any bug or want an enhancement, feel free to create a new issue or pull request in the GitHub repository. .SH AUTHOR -ByteDream +Crunchy Labs .br -Source: https://github.com/ByteDream/crunchyroll-go +Source: https://github.com/crunchy-labs/crunchyroll-go .SH COPYRIGHT -Copyright (C) 2022 ByteDream +Copyright (C) 2022 Crunchy Labs This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/crunchyroll.go b/crunchyroll.go index dcfc375..6f221f6 100644 --- a/crunchyroll.go +++ b/crunchyroll.go @@ -337,7 +337,7 @@ func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, // // Deprecated: Use Search instead. The first result sometimes isn't the correct one // so this function is inaccurate in some cases. -// See https://github.com/ByteDream/crunchyroll-go/issues/22 for more information. +// See https://github.com/crunchy-labs/crunchyroll-go/issues/22 for more information. func (c *Crunchyroll) FindVideoByName(seriesName string) (Video, error) { s, m, err := c.Search(seriesName, 1) if err != nil { diff --git a/go.mod b/go.mod index 99877f2..15cf1e1 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ByteDream/crunchyroll-go/v2 +module github.com/crunchy-labs/crunchyroll-go/v2 go 1.18 diff --git a/utils/locale.go b/utils/locale.go index 537b165..fbedd6a 100644 --- a/utils/locale.go +++ b/utils/locale.go @@ -1,8 +1,6 @@ package utils -import ( - "github.com/ByteDream/crunchyroll-go/v2" -) +import "github.com/crunchy-labs/crunchyroll-go/v2" // AllLocales is an array of all available locales. var AllLocales = []crunchyroll.LOCALE{ diff --git a/utils/sort.go b/utils/sort.go index a44717d..a98f858 100644 --- a/utils/sort.go +++ b/utils/sort.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/ByteDream/crunchyroll-go/v2" + "github.com/crunchy-labs/crunchyroll-go/v2" "sort" "strconv" "strings" From 680db83c59588b90a2de5067ae2cbe64722cb170 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 9 Jul 2022 01:03:09 +0200 Subject: [PATCH 147/630] Reactivate scoop install instructions --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index df75ec8..467b6ff 100644 --- a/README.md +++ b/README.md @@ -55,15 +55,11 @@ See [#39](https://github.com/crunchy-labs/crunchy-cli/issues/39) for more inform ```shell $ yay -S crunchyroll-go ``` -- <del> - - On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): +- On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): ```shell $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` - - </del> <i>Currently not working because the repo got renamed!</i> - ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/crunchy-labs/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): - use `make` (requires `go` to be installed): From 136591061038d38d040992227d5f4e9f9fcbd3be Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 9 Jul 2022 01:25:00 +0200 Subject: [PATCH 148/630] Change discord invite link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 467b6ff..141207d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crun <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> </a> - <a href="https://discord.gg/gUWwekeNNg"> + <a href="https://discord.gg/PXGPGpQxgk"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> From 5b59662e29de060fad38274621eba399b7eb06c2 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 9 Jul 2022 01:25:59 +0200 Subject: [PATCH 149/630] Change discord shield server --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 141207d..0c0b329 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crun <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> </a> <a href="https://discord.gg/PXGPGpQxgk"> - <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> + <img src="https://img.shields.io/discord/994882878125121596?label=discord&style=flat-square" alt="Discord"> </a> <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> <img src="https://github.com/crunchy-labs/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> From fd502446c62c2c0bd2c0dabf85e4aebcbf0964cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=81=E3=81=AF=E3=83=AC=E3=82=AA=E3=83=B3=E3=81=A7?= =?UTF-8?q?=E3=81=99?= <50367411+IchBinLeoon@users.noreply.github.com> Date: Mon, 11 Jul 2022 22:03:02 +0200 Subject: [PATCH 150/630] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c0b329..6374ae0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # crunchy-cli -A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account to for full (api) access. +A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account for full (api) access. <p align="center"> <a href="https://github.com/crunchy-labs/crunchy-cli"> From 5b4c228b60cae297760f6401ad8c6ea6ebc84ebc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 25 Jul 2022 10:53:29 +0200 Subject: [PATCH 151/630] Change crunchyroll-go dependency name --- cli/commands/archive/archive.go | 4 ++-- cli/commands/download/download.go | 4 ++-- cli/commands/info/info.go | 2 +- cli/commands/login/login.go | 2 +- go.mod | 2 +- go.sum | 6 ++---- utils/extract.go | 4 ++-- utils/format.go | 2 +- utils/locale.go | 4 ++-- utils/save.go | 2 +- utils/vars.go | 2 +- 11 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 29ee836..99cb77f 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/ByteDream/crunchy-cli/cli/commands" "github.com/ByteDream/crunchy-cli/utils" - "github.com/ByteDream/crunchyroll-go/v3" - crunchyUtils "github.com/ByteDream/crunchyroll-go/v3/utils" + "github.com/crunchy-labs/crunchyroll-go/v3" + crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "io" diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go index 4e0ef66..2f2ec9f 100644 --- a/cli/commands/download/download.go +++ b/cli/commands/download/download.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/ByteDream/crunchy-cli/cli/commands" "github.com/ByteDream/crunchy-cli/utils" - "github.com/ByteDream/crunchyroll-go/v3" - crunchyUtils "github.com/ByteDream/crunchyroll-go/v3/utils" + "github.com/crunchy-labs/crunchyroll-go/v3" + crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" "github.com/spf13/cobra" "math" diff --git a/cli/commands/info/info.go b/cli/commands/info/info.go index a9a6ef0..9b816fd 100644 --- a/cli/commands/info/info.go +++ b/cli/commands/info/info.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/ByteDream/crunchy-cli/cli/commands" "github.com/ByteDream/crunchy-cli/utils" - crunchyUtils "github.com/ByteDream/crunchyroll-go/v3/utils" + crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/spf13/cobra" ) diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go index 31155d8..7fb135c 100644 --- a/cli/commands/login/login.go +++ b/cli/commands/login/login.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/ByteDream/crunchy-cli/cli/commands" "github.com/ByteDream/crunchy-cli/utils" - "github.com/ByteDream/crunchyroll-go/v3" + "github.com/crunchy-labs/crunchyroll-go/v3" "github.com/spf13/cobra" "os" ) diff --git a/go.mod b/go.mod index 909378c..ce09935 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ByteDream/crunchy-cli go 1.18 require ( - github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220630135625-ed58b3fe8cc1 + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220724174415-375eaf9007bd github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 5603301..34683e1 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ -github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c h1:jPabd/Zl/zdoSo8ZGtZLm43+42nIFHIJABvrvdMOYtY= -github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220627201246-98185d763c0c/go.mod h1:L4M1sOPjJ4ui0YXFnpVUb4AzQIa+D/i/B0QG5iz9op4= -github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220630135625-ed58b3fe8cc1 h1:hOL4xzDc6oCcrpf6GrrdUgvqwsQo6dI2zL4nA8rl9hg= -github.com/ByteDream/crunchyroll-go/v3 v3.0.0-20220630135625-ed58b3fe8cc1/go.mod h1:L4M1sOPjJ4ui0YXFnpVUb4AzQIa+D/i/B0QG5iz9op4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220724174415-375eaf9007bd h1:wxMjLwhvilxwTnYCOXmBPenMNymhC63m4aFzQ5C7ipQ= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220724174415-375eaf9007bd/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= diff --git a/utils/extract.go b/utils/extract.go index ba52242..60ced1d 100644 --- a/utils/extract.go +++ b/utils/extract.go @@ -2,8 +2,8 @@ package utils import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v3" - "github.com/ByteDream/crunchyroll-go/v3/utils" + "github.com/crunchy-labs/crunchyroll-go/v3" + "github.com/crunchy-labs/crunchyroll-go/v3/utils" "regexp" "strconv" "strings" diff --git a/utils/format.go b/utils/format.go index 9642a31..6d40142 100644 --- a/utils/format.go +++ b/utils/format.go @@ -2,7 +2,7 @@ package utils import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v3" + "github.com/crunchy-labs/crunchyroll-go/v3" "reflect" "runtime" "strings" diff --git a/utils/locale.go b/utils/locale.go index e1517bb..9769940 100644 --- a/utils/locale.go +++ b/utils/locale.go @@ -2,8 +2,8 @@ package utils import ( "fmt" - "github.com/ByteDream/crunchyroll-go/v3" - "github.com/ByteDream/crunchyroll-go/v3/utils" + "github.com/crunchy-labs/crunchyroll-go/v3" + "github.com/crunchy-labs/crunchyroll-go/v3/utils" "os" "os/exec" "runtime" diff --git a/utils/save.go b/utils/save.go index 43eeda7..ef6d0ec 100644 --- a/utils/save.go +++ b/utils/save.go @@ -6,7 +6,7 @@ import ( "crypto/rand" "crypto/sha256" "fmt" - "github.com/ByteDream/crunchyroll-go/v3" + "github.com/crunchy-labs/crunchyroll-go/v3" "io" "os" "path/filepath" diff --git a/utils/vars.go b/utils/vars.go index caca63e..d2893a5 100644 --- a/utils/vars.go +++ b/utils/vars.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/ByteDream/crunchyroll-go/v3" + "github.com/crunchy-labs/crunchyroll-go/v3" "net/http" ) From 2773445050c45d262d18834c9b1f9cf2394e524a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 26 Jul 2022 12:47:23 +0200 Subject: [PATCH 152/630] Change author names and links to crunchy-labs --- Makefile | 8 +++---- README.md | 40 +++++++++++++++---------------- cli/commands/archive/archive.go | 6 ++--- cli/commands/archive/compress.go | 2 +- cli/commands/download/download.go | 6 ++--- cli/commands/info/info.go | 4 ++-- cli/commands/logger.go | 2 +- cli/commands/login/login.go | 4 ++-- cli/commands/update/update.go | 12 +++++----- cli/commands/utils.go | 2 +- cli/root.go | 16 ++++++------- crunchy-cli.1 | 6 ++--- go.mod | 2 +- main.go | 2 +- 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index 7836af9..966872b 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DESTDIR= PREFIX=/usr build: - go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(BINARY_NAME) . + go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(BINARY_NAME) . clean: rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* @@ -24,8 +24,8 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchy-cli/LICENSE release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux . - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe . - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/ByteDream/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin . + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe . + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin . strip $(VERSION_BINARY_NAME)_linux diff --git a/README.md b/README.md index bce05b9..09cd829 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,26 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account to for full (api) access. <p align="center"> - <a href="https://github.com/ByteDream/crunchy-cli"> - <img src="https://img.shields.io/github/languages/code-size/ByteDream/crunchy-cli?style=flat-square" alt="Code size"> + <a href="https://github.com/crunchy-labs/crunchy-cli"> + <img src="https://img.shields.io/github/languages/code-size/crunchy-labs/crunchy-cli?style=flat-square" alt="Code size"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> - <img src="https://img.shields.io/github/downloads/ByteDream/crunchy-cli/total?style=flat-square" alt="Download Badge"> + <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/downloads/crunchy-labs/crunchy-cli/total?style=flat-square" alt="Download Badge"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/blob/master/LICENSE"> - <img src="https://img.shields.io/github/license/ByteDream/crunchy-cli?style=flat-square" alt="License"> + <a href="https://github.com/crunchy-labs/crunchy-cli/blob/master/LICENSE"> + <img src="https://img.shields.io/github/license/crunchy-labs/crunchy-cli?style=flat-square" alt="License"> </a> <a href="https://golang.org"> - <img src="https://img.shields.io/github/go-mod/go-version/ByteDream/crunchy-cli?style=flat-square" alt="Go version"> + <img src="https://img.shields.io/github/go-mod/go-version/crunchy-labs/crunchy-cli?style=flat-square" alt="Go version"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/releases/latest"> - <img src="https://img.shields.io/github/v/release/ByteDream/crunchy-cli?style=flat-square" alt="Release"> + <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> + <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> </a> <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> - <a href="https://github.com/ByteDream/crunchy-cli/actions/workflows/ci.yml"> - <img src="https://github.com/ByteDream/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> + <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> + <img src="https://github.com/crunchy-labs/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> </a> </p> @@ -47,10 +47,10 @@ See #39 for more information._ ## ๐Ÿ’พ Get the executable -- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/ByteDream/crunchy-cli/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.bytedream.org/github/ByteDream/crunchy-cli/crunchy-{tag}_darwin) +- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/crunchy-labs/crunchy-cli/releases/latest) or get it from below: + - [Linux (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell $ yay -S crunchyroll-go @@ -65,24 +65,24 @@ See #39 for more information._ </del> <i>Currently not working because the repo got renamed!</i> -- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/ByteDream/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): +- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/crunchy-labs/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): - use `make` (requires `go` to be installed): ```shell - $ git clone https://github.com/ByteDream/crunchy-cli + $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli $ make $ sudo make install # <- only if you want to install it on your system ``` - use `go`: ```shell - $ git clone https://github.com/ByteDream/crunchy-cli + $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli $ go build -o crunchy . ``` ## ๐Ÿ“ Examples -_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli), further usages and options are described there. +_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli), further usages and options are described there. ### Login @@ -169,7 +169,7 @@ The following flags can be (optional) passed to modify the [archive](#archive) p | `-l` | `--language` | Audio locale which should be downloaded. Can be used multiple times. | | `-d` | `--directory` | Directory to download the video(s) to. | | `-o` | `--output` | Name of the output file. | -| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/ByteDream/crunchy-cli/wiki/Cli#archive) for more information. | +| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#archive) for more information. | | `-c` | `--compress` | If is set, all output will be compresses into an archive. This flag sets the name of the compressed output file and the file ending specifies the compression algorithm (gzip, tar, zip are supported). | | `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | | `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 99cb77f..8e09f5e 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -5,8 +5,8 @@ import ( "bytes" "context" "fmt" - "github.com/ByteDream/crunchy-cli/cli/commands" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/cli/commands" + "github.com/crunchy-labs/crunchy-cli/utils" "github.com/crunchy-labs/crunchyroll-go/v3" crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" @@ -185,7 +185,7 @@ func archive(urls []string) error { utils.Log.StopProcess("Failed to parse url %d", i+1) if utils.Crunchy.Config.Premium { utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchy-cli/issues/22 for more information") + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchy-cli/issues/22 for more information") } return err } diff --git a/cli/commands/archive/compress.go b/cli/commands/archive/compress.go index d6aa492..e0b9ad4 100644 --- a/cli/commands/archive/compress.go +++ b/cli/commands/archive/compress.go @@ -6,7 +6,7 @@ import ( "bytes" "compress/gzip" "fmt" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/utils" "io" "os" "path/filepath" diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go index 2f2ec9f..2976e2d 100644 --- a/cli/commands/download/download.go +++ b/cli/commands/download/download.go @@ -3,8 +3,8 @@ package download import ( "context" "fmt" - "github.com/ByteDream/crunchy-cli/cli/commands" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/cli/commands" + "github.com/crunchy-labs/crunchy-cli/utils" "github.com/crunchy-labs/crunchyroll-go/v3" crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/grafov/m3u8" @@ -136,7 +136,7 @@ func download(urls []string) error { utils.Log.StopProcess("Failed to parse url %d", i+1) if utils.Crunchy.Config.Premium { utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/ByteDream/crunchy-cli/issues/22 for more information") + "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchy-cli/issues/22 for more information") } return err } diff --git a/cli/commands/info/info.go b/cli/commands/info/info.go index 9b816fd..db909c1 100644 --- a/cli/commands/info/info.go +++ b/cli/commands/info/info.go @@ -2,8 +2,8 @@ package info import ( "fmt" - "github.com/ByteDream/crunchy-cli/cli/commands" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/cli/commands" + "github.com/crunchy-labs/crunchy-cli/utils" crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/spf13/cobra" ) diff --git a/cli/commands/logger.go b/cli/commands/logger.go index 2f3ae37..89a4717 100644 --- a/cli/commands/logger.go +++ b/cli/commands/logger.go @@ -2,7 +2,7 @@ package commands import ( "fmt" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/utils" "io" "log" "os" diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go index 7fb135c..eb1b4dc 100644 --- a/cli/commands/login/login.go +++ b/cli/commands/login/login.go @@ -3,8 +3,8 @@ package login import ( "bytes" "fmt" - "github.com/ByteDream/crunchy-cli/cli/commands" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/cli/commands" + "github.com/crunchy-labs/crunchy-cli/utils" "github.com/crunchy-labs/crunchyroll-go/v3" "github.com/spf13/cobra" "os" diff --git a/cli/commands/update/update.go b/cli/commands/update/update.go index 6587ca2..831621d 100644 --- a/cli/commands/update/update.go +++ b/cli/commands/update/update.go @@ -3,7 +3,7 @@ package update import ( "encoding/json" "fmt" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/utils" "github.com/spf13/cobra" "io" "os" @@ -37,7 +37,7 @@ func init() { func update() error { var release map[string]interface{} - resp, err := utils.Client.Get("https://api.github.com/repos/ByteDream/crunchy-cli/releases/latest") + resp, err := utils.Client.Get("https://api.github.com/repos/crunchy-labs/crunchy-cli/releases/latest") if err != nil { return err } @@ -79,12 +79,12 @@ func update() error { return nil } - utils.Log.Info("A new version is available (%s): https://github.com/ByteDream/crunchy-cli/releases/tag/v%s", releaseVersion, releaseVersion) + utils.Log.Info("A new version is available (%s): https://github.com/crunchy-labs/crunchy-cli/releases/tag/v%s", releaseVersion, releaseVersion) if updateInstallFlag { if runtime.GOARCH != "amd64" { return fmt.Errorf("invalid architecture found (%s), only amd64 is currently supported for automatic updating. "+ - "You have to update manually (https://github.com/ByteDream/crunchy-cli)", runtime.GOARCH) + "You have to update manually (https://github.com/crunchy-labs/crunchy-cli)", runtime.GOARCH) } var downloadFile string @@ -102,7 +102,7 @@ func update() error { downloadFile = fmt.Sprintf("crunchy-v%s_windows.exe", releaseVersion) default: return fmt.Errorf("invalid operation system found (%s), only linux, windows and darwin / macos are currently supported. "+ - "You have to update manually (https://github.com/ByteDream/crunchy-cli", runtime.GOOS) + "You have to update manually (https://github.com/crunchy-labs/crunchy-cli", runtime.GOOS) } utils.Log.SetProcess("Updating executable %s", os.Args[0]) @@ -118,7 +118,7 @@ func update() error { } defer executeFile.Close() - resp, err := utils.Client.Get(fmt.Sprintf("https://github.com/ByteDream/crunchy-cli/releases/download/v%s/%s", releaseVersion, downloadFile)) + resp, err := utils.Client.Get(fmt.Sprintf("https://github.com/crunchy-labs/crunchy-cli/releases/download/v%s/%s", releaseVersion, downloadFile)) if err != nil { return err } diff --git a/cli/commands/utils.go b/cli/commands/utils.go index 924cd4d..53e51f2 100644 --- a/cli/commands/utils.go +++ b/cli/commands/utils.go @@ -2,7 +2,7 @@ package commands import ( "fmt" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/utils" "os" "os/exec" "runtime" diff --git a/cli/root.go b/cli/root.go index ebb5079..b589a03 100644 --- a/cli/root.go +++ b/cli/root.go @@ -3,13 +3,13 @@ package cli import ( "context" "fmt" - "github.com/ByteDream/crunchy-cli/cli/commands" - "github.com/ByteDream/crunchy-cli/cli/commands/archive" - "github.com/ByteDream/crunchy-cli/cli/commands/download" - "github.com/ByteDream/crunchy-cli/cli/commands/info" - "github.com/ByteDream/crunchy-cli/cli/commands/login" - "github.com/ByteDream/crunchy-cli/cli/commands/update" - "github.com/ByteDream/crunchy-cli/utils" + "github.com/crunchy-labs/crunchy-cli/cli/commands" + "github.com/crunchy-labs/crunchy-cli/cli/commands/archive" + "github.com/crunchy-labs/crunchy-cli/cli/commands/download" + "github.com/crunchy-labs/crunchy-cli/cli/commands/info" + "github.com/crunchy-labs/crunchy-cli/cli/commands/login" + "github.com/crunchy-labs/crunchy-cli/cli/commands/update" + "github.com/crunchy-labs/crunchy-cli/utils" "github.com/spf13/cobra" "os" "runtime/debug" @@ -28,7 +28,7 @@ var ( var RootCmd = &cobra.Command{ Use: "crunchy-cli", Version: utils.Version, - Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/ByteDream/crunchy-cli/wiki", + Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/crunchy-labs/crunchy-cli/wiki", SilenceErrors: true, SilenceUsage: true, diff --git a/crunchy-cli.1 b/crunchy-cli.1 index ebadb9c..6f20dba 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -193,12 +193,12 @@ $ crunchy-cli archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VE If you notice any bug or want an enhancement, feel free to create a new issue or pull request in the GitHub repository. .SH AUTHOR -ByteDream +Crunchy Labs Maintainers .br -Source: https://github.com/ByteDream/crunchy-cli +Source: https://github.com/crunchy-labs/crunchy-cli .SH COPYRIGHT -Copyright (C) 2022 ByteDream +Copyright (C) 2022 Crunchy Labs Maintainers This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License diff --git a/go.mod b/go.mod index ce09935..37a4e42 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ByteDream/crunchy-cli +module github.com/crunchy-labs/crunchy-cli go 1.18 diff --git a/main.go b/main.go index 7275a13..72ec83b 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/ByteDream/crunchy-cli/cli" + "github.com/crunchy-labs/crunchy-cli/cli" ) func main() { From 8942ea574bf1bf76ba59cfa3fa8e2e768625b192 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 27 Jul 2022 21:25:03 +0200 Subject: [PATCH 153/630] Add v3 notice --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 09cd829..e8a363d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crun _This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility. See #39 for more information._ +> This tool relies on the [crunchyroll-go](https://github.com/crunchy-labs/crunchyroll-go) library to communicate with crunchyroll. +> The library enters maintenance mode (only small fixes, no new features) with version v3 in favor of rewriting it completely in Rust. +> **crunchy-cli** follows it (also beginning with version v3) and won't have major updates until the Rust rewrite of the library reaches a good usable state. + # ๐Ÿ–ฅ๏ธ CLI ## โœจ Features From b62769ccfd90d66f4774577337e729c8bea0d940 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 27 Jul 2022 21:53:03 +0200 Subject: [PATCH 154/630] Update next version number --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8a363d..85e1bf5 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ See #39 for more information._ > This tool relies on the [crunchyroll-go](https://github.com/crunchy-labs/crunchyroll-go) library to communicate with crunchyroll. > The library enters maintenance mode (only small fixes, no new features) with version v3 in favor of rewriting it completely in Rust. -> **crunchy-cli** follows it (also beginning with version v3) and won't have major updates until the Rust rewrite of the library reaches a good usable state. +> **crunchy-cli** follows it (with version v2.3.0) and won't have major updates until the Rust rewrite of the library reaches a good usable state. # ๐Ÿ–ฅ๏ธ CLI From fbb90f907953546ab5282f295a401bda9d6babc7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 29 Jul 2022 18:23:38 +0200 Subject: [PATCH 155/630] Fix info spacing --- cli/commands/info/info.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/commands/info/info.go b/cli/commands/info/info.go index db909c1..650d8c7 100644 --- a/cli/commands/info/info.go +++ b/cli/commands/info/info.go @@ -28,13 +28,13 @@ func info() error { return err } - fmt.Println("Username: ", account.Username) - fmt.Println("Email: ", account.Email) - fmt.Println("Premium: ", utils.Crunchy.Config.Premium) - fmt.Println("Interface language:", crunchyUtils.LocaleLanguage(account.PreferredCommunicationLanguage)) - fmt.Println("Subtitle language: ", crunchyUtils.LocaleLanguage(account.PreferredContentSubtitleLanguage)) - fmt.Println("Created: ", account.Created) - fmt.Println("Account ID: ", account.AccountID) + fmt.Println("Username: ", account.Username) + fmt.Println("Email: ", account.Email) + fmt.Println("Premium: ", utils.Crunchy.Config.Premium) + fmt.Println("Interface language: ", crunchyUtils.LocaleLanguage(account.PreferredCommunicationLanguage)) + fmt.Println("Subtitle language: ", crunchyUtils.LocaleLanguage(account.PreferredContentSubtitleLanguage)) + fmt.Println("Created: ", account.Created) + fmt.Println("Account ID: ", account.AccountID) return nil } From 81946c5092f53a37f5d4ceeca7af370bcc277fb5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 31 Jul 2022 13:50:59 +0200 Subject: [PATCH 156/630] Remove CI badge --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 85e1bf5..1147f1e 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,6 @@ A [go](https://golang.org) written cli client for [crunchyroll](https://www.crun <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> - <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> - <img src="https://github.com/crunchy-labs/crunchy-cli/workflows/CI/badge.svg?style=flat" alt="CI"> - </a> </p> <p align="center"> From b5f4882601ad03c4f01e3db180b19c437cfcd3ed Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 1 Aug 2022 00:18:14 +0200 Subject: [PATCH 157/630] Update issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 3cbda16..0c62d66 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -10,14 +10,8 @@ assignees: '' **Describe the bug** A clear and concise description of what the bug is. -**Bug target** -Set a `x` between the square brackets if your bug occurred while using the CLI or the library - -- [ ] CLI (command line client) -- [ ] Library - **To Reproduce** -Steps to reproduce the behavior: +Steps / command to reproduce the behavior: ``` $ crunchy ... ``` From caeb734b2c03fa432e8395ff93c65d3d3f3f7a71 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 1 Aug 2022 00:41:09 +0200 Subject: [PATCH 158/630] Add login session id warning --- cli/commands/login/login.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go index eb1b4dc..20ceb6a 100644 --- a/cli/commands/login/login.go +++ b/cli/commands/login/login.go @@ -108,6 +108,7 @@ func loginCredentials(user, password string) error { func loginSessionID(sessionID string) error { utils.Log.Debug("Logging in via session id") + utils.Log.Warn("Logging in with session id is deprecated and not very reliable. Consider choosing another option (if it fails)") var c *crunchyroll.Crunchyroll var err error if c, err = crunchyroll.LoginWithSessionID(sessionID, utils.SystemLocale(false), utils.Client); err != nil { From a64981930b3e08629752490b2fc31b85e1cc2dce Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 2 Aug 2022 12:07:43 +0200 Subject: [PATCH 159/630] Add option to change temp dir --- cli/commands/archive/archive.go | 7 +++++++ cli/commands/download/download.go | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 8e09f5e..b2be970 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -29,6 +29,7 @@ var ( archiveDirectoryFlag string archiveOutputFlag string + archiveTempDirFlag string archiveMergeFlag string @@ -146,6 +147,10 @@ func init() { "\t{fps} ยป Frame Rate of the video\n"+ "\t{audio} ยป Audio locale of the video\n"+ "\t{subtitle} ยป Subtitle locale of the video") + Cmd.Flags().StringVar(&archiveTempDirFlag, + "temp", + os.TempDir(), + "Directory to store temporary files in") Cmd.Flags().StringVarP(&archiveMergeFlag, "merge", @@ -315,6 +320,8 @@ func archiveInfo(info utils.FormatInformation, writeCloser io.WriteCloser, filen } return nil }) + tmp, _ := os.MkdirTemp(archiveTempDirFlag, "crunchy_") + downloader.TempDir = tmp sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go index 2976e2d..60e975e 100644 --- a/cli/commands/download/download.go +++ b/cli/commands/download/download.go @@ -25,6 +25,7 @@ var ( downloadDirectoryFlag string downloadOutputFlag string + downloadTempDirFlag string downloadResolutionFlag string @@ -111,6 +112,10 @@ func init() { "\t{fps} ยป Frame Rate of the video\n"+ "\t{audio} ยป Audio locale of the video\n"+ "\t{subtitle} ยป Subtitle locale of the video") + Cmd.Flags().StringVar(&downloadTempDirFlag, + "temp", + os.TempDir(), + "Directory to store temporary files in") Cmd.Flags().StringVarP(&downloadResolutionFlag, "resolution", @@ -230,6 +235,8 @@ func downloadInfo(info utils.FormatInformation, file *os.File) error { } return nil }) + tmp, _ := os.MkdirTemp(downloadTempDirFlag, "crunchy_") + downloader.TempDir = tmp if utils.HasFFmpeg() { downloader.FFmpegOpts = make([]string, 0) } From 6239d10d220b947cd0c0370996832b522f4d0ca0 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Aug 2022 22:20:25 +0200 Subject: [PATCH 160/630] Fix crunchyroll api changes --- cli/commands/archive/archive.go | 6 ++++ cli/commands/download/download.go | 52 +++---------------------------- utils/extract.go | 19 ++++++----- 3 files changed, 22 insertions(+), 55 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index b2be970..a15822c 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -627,6 +627,12 @@ func archiveExtractEpisodes(url string) ([][]utils.FormatInformation, error) { } } + if _, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { + return nil, fmt.Errorf("archiving episodes by url is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") + } else if _, _, _, _, ok := crunchyroll.ParseEpisodeURL(url); ok { + return nil, fmt.Errorf("archiving episodes by url is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") + } + episodes, err := utils.ExtractEpisodes(url, languagesAsLocale...) if err != nil { return nil, err diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go index 60e975e..b7f3153 100644 --- a/cli/commands/download/download.go +++ b/cli/commands/download/download.go @@ -14,13 +14,11 @@ import ( "os/signal" "path/filepath" "runtime" - "sort" "strconv" "strings" ) var ( - downloadAudioFlag string downloadSubtitleFlag string downloadDirectoryFlag string @@ -49,12 +47,10 @@ var Cmd = &cobra.Command{ } } - if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadAudioFlag)) { - return fmt.Errorf("%s is not a valid audio locale. Choose from: %s", downloadAudioFlag, strings.Join(utils.LocalesAsStrings(), ", ")) - } else if downloadSubtitleFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { + if downloadSubtitleFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { return fmt.Errorf("%s is not a valid subtitle locale. Choose from: %s", downloadSubtitleFlag, strings.Join(utils.LocalesAsStrings(), ", ")) } - utils.Log.Debug("Locales: audio: %s / subtitle: %s", downloadAudioFlag, downloadSubtitleFlag) + utils.Log.Debug("Subtitle locale: %s", downloadSubtitleFlag) switch downloadResolutionFlag { case "1080p", "720p", "480p", "360p": @@ -81,10 +77,6 @@ var Cmd = &cobra.Command{ } func init() { - Cmd.Flags().StringVarP(&downloadAudioFlag, "audio", - "a", - string(utils.SystemLocale(false)), - "The locale of the audio. Available locales: "+strings.Join(utils.LocalesAsStrings(), ", ")) Cmd.Flags().StringVarP(&downloadSubtitleFlag, "subtitle", "s", @@ -280,49 +272,13 @@ func downloadInfo(info utils.FormatInformation, file *os.File) error { } func downloadExtractEpisodes(url string) ([][]utils.FormatInformation, error) { - episodes, err := utils.ExtractEpisodes(url, crunchyroll.JP, crunchyroll.LOCALE(downloadAudioFlag)) + episodes, err := utils.ExtractEpisodes(url) if err != nil { return nil, err } - japanese := episodes[0] - custom := episodes[1] - - sort.Sort(crunchyUtils.EpisodesByNumber(japanese)) - sort.Sort(crunchyUtils.EpisodesByNumber(custom)) - - var errMessages []string - - var final []*crunchyroll.Episode - if len(japanese) == 0 || len(japanese) == len(custom) { - final = custom - } else { - for _, jp := range japanese { - before := len(final) - for _, episode := range custom { - if jp.SeasonNumber == episode.SeasonNumber && jp.EpisodeNumber == episode.EpisodeNumber { - final = append(final, episode) - } - } - if before == len(final) { - errMessages = append(errMessages, fmt.Sprintf("%s has no %s audio, using %s as fallback", jp.Title, crunchyroll.LOCALE(downloadAudioFlag), crunchyroll.JP)) - final = append(final, jp) - } - } - } - - if len(errMessages) > 10 { - for _, msg := range errMessages[:10] { - utils.Log.SetProcess(msg) - } - utils.Log.SetProcess("... and %d more", len(errMessages)-10) - } else { - for _, msg := range errMessages { - utils.Log.SetProcess(msg) - } - } var infoFormat [][]utils.FormatInformation - for _, season := range crunchyUtils.SortEpisodesBySeason(final) { + for _, season := range crunchyUtils.SortEpisodesBySeason(episodes[0]) { tmpFormatInformation := make([]utils.FormatInformation, 0) for _, episode := range season { format, err := episode.GetFormat(downloadResolutionFlag, crunchyroll.LOCALE(downloadSubtitleFlag), true) diff --git a/utils/extract.go b/utils/extract.go index 60ced1d..fa2d650 100644 --- a/utils/extract.go +++ b/utils/extract.go @@ -28,7 +28,6 @@ func ExtractEpisodes(url string, locales ...crunchyroll.LOCALE) ([][]*crunchyrol url = url[:lastOpen] } - final := make([][]*crunchyroll.Episode, len(locales)) episodes, err := Crunchy.ExtractEpisodesFromUrl(url, locales...) if err != nil { return nil, fmt.Errorf("failed to get episodes: %v", err) @@ -82,12 +81,18 @@ func ExtractEpisodes(url string, locales ...crunchyroll.LOCALE) ([][]*crunchyrol } } - localeSorted, err := utils.SortEpisodesByAudio(episodes) - if err != nil { - return nil, fmt.Errorf("failed to get audio locale: %v", err) - } - for i, locale := range locales { - final[i] = append(final[i], localeSorted[locale]...) + var final [][]*crunchyroll.Episode + if len(locales) > 0 { + final = make([][]*crunchyroll.Episode, len(locales)) + localeSorted, err := utils.SortEpisodesByAudio(episodes) + if err != nil { + return nil, fmt.Errorf("failed to get audio locale: %v", err) + } + for i, locale := range locales { + final[i] = append(final[i], localeSorted[locale]...) + } + } else { + final = [][]*crunchyroll.Episode{episodes} } return final, nil From f7a21fbfb2806779b975779afb70724333e357cc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Aug 2022 01:04:46 +0200 Subject: [PATCH 161/630] Change all etp rt related stuff to refresh token --- cli/commands/login/login.go | 22 +++++++++++----------- go.mod | 2 +- go.sum | 4 ++-- utils/save.go | 18 +++++++++--------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go index 20ceb6a..9fce181 100644 --- a/cli/commands/login/login.go +++ b/cli/commands/login/login.go @@ -14,8 +14,8 @@ var ( loginPersistentFlag bool loginEncryptFlag bool - loginSessionIDFlag bool - loginEtpRtFlag bool + loginSessionIDFlag bool + loginRefreshTokenFlag bool ) var Cmd = &cobra.Command{ @@ -26,8 +26,8 @@ var Cmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { if loginSessionIDFlag { return loginSessionID(args[0]) - } else if loginEtpRtFlag { - return loginEtpRt(args[0]) + } else if loginRefreshTokenFlag { + return loginRefreshToken(args[0]) } else { return loginCredentials(args[0], args[1]) } @@ -48,12 +48,12 @@ func init() { "session-id", false, "Use a session id to login instead of username and password") - Cmd.Flags().BoolVar(&loginEtpRtFlag, - "etp-rt", + Cmd.Flags().BoolVar(&loginRefreshTokenFlag, + "refresh-token", false, - "Use a etp rt cookie to login instead of username and password") + "Use a refresh token to login instead of username and password. Can be obtained by copying the `etp-rt` cookie from beta.crunchyroll.com") - Cmd.MarkFlagsMutuallyExclusive("session-id", "etp-rt") + Cmd.MarkFlagsMutuallyExclusive("session-id", "refresh-token") } func loginCredentials(user, password string) error { @@ -132,11 +132,11 @@ func loginSessionID(sessionID string) error { return nil } -func loginEtpRt(etpRt string) error { - utils.Log.Debug("Logging in via etp rt") +func loginRefreshToken(refreshToken string) error { + utils.Log.Debug("Logging in via refresh token") var c *crunchyroll.Crunchyroll var err error - if c, err = crunchyroll.LoginWithEtpRt(etpRt, utils.SystemLocale(false), utils.Client); err != nil { + if c, err = crunchyroll.LoginWithRefreshToken(refreshToken, utils.SystemLocale(false), utils.Client); err != nil { utils.Log.Err(err.Error()) os.Exit(1) } diff --git a/go.mod b/go.mod index 37a4e42..d7f0eba 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/crunchy-labs/crunchy-cli go 1.18 require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220724174415-375eaf9007bd + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220808230013-bb434a0fba7a github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 34683e1..225fce4 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220724174415-375eaf9007bd h1:wxMjLwhvilxwTnYCOXmBPenMNymhC63m4aFzQ5C7ipQ= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220724174415-375eaf9007bd/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220808230013-bb434a0fba7a h1:q5M2xmCTu2njig5rlRAd83LJDaPANmweFJjYsnxi5zM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220808230013-bb434a0fba7a/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= diff --git a/utils/save.go b/utils/save.go index ef6d0ec..7660974 100644 --- a/utils/save.go +++ b/utils/save.go @@ -15,7 +15,7 @@ import ( func SaveSession(crunchy *crunchyroll.Crunchyroll) error { file := filepath.Join(os.TempDir(), ".crunchy") - return os.WriteFile(file, []byte(crunchy.EtpRt), 0600) + return os.WriteFile(file, []byte(crunchy.RefreshToken), 0600) } func SaveCredentialsPersistent(user, password string, encryptionKey []byte) error { @@ -62,7 +62,7 @@ func SaveSessionPersistent(crunchy *crunchyroll.Crunchyroll) error { if err = os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755); err != nil { return err } - return os.WriteFile(file, []byte(crunchy.EtpRt), 0600) + return os.WriteFile(file, []byte(crunchy.RefreshToken), 0600) } func IsTempSession() bool { @@ -118,11 +118,11 @@ func loadTempSession(file string) (*crunchyroll.Crunchyroll, error) { if err != nil { return nil, err } - crunchy, err := crunchyroll.LoginWithEtpRt(string(body), SystemLocale(true), Client) + crunchy, err := crunchyroll.LoginWithRefreshToken(string(body), SystemLocale(true), Client) if err != nil { - Log.Debug("Failed to login with temp etp rt cookie: %v", err) + Log.Debug("Failed to login with temp refresh token: %v", err) } else { - Log.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) + Log.Debug("Logged in with refresh token %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) return crunchy, nil } } @@ -160,15 +160,15 @@ func loadPersistentSession(file string, encryptionKey []byte) (crunchy *crunchyr } Log.Debug("Logged in with credentials") } else { - if crunchy, err = crunchyroll.LoginWithEtpRt(split[0], SystemLocale(true), Client); err != nil { + if crunchy, err = crunchyroll.LoginWithRefreshToken(split[0], SystemLocale(true), Client); err != nil { return nil, err } - Log.Debug("Logged in with etp rt cookie %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) + Log.Debug("Logged in with refresh token %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) } - // the etp rt is written to a temp file to reduce the amount of re-logging in. + // the refresh token is written to a temp file to reduce the amount of re-logging in. // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.EtpRt), 0600); err != nil { + if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.RefreshToken), 0600); err != nil { return nil, err } } From 441ec084af98ee21f4ed447411ca045c0b64dc8b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Aug 2022 01:20:34 +0200 Subject: [PATCH 162/630] Re-enable language choosing for series --- cli/commands/download/download.go | 76 ++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go index b7f3153..39273d0 100644 --- a/cli/commands/download/download.go +++ b/cli/commands/download/download.go @@ -14,11 +14,13 @@ import ( "os/signal" "path/filepath" "runtime" + "sort" "strconv" "strings" ) var ( + downloadAudioFlag string downloadSubtitleFlag string downloadDirectoryFlag string @@ -47,10 +49,12 @@ var Cmd = &cobra.Command{ } } - if downloadSubtitleFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { + if downloadAudioFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadAudioFlag)) { + return fmt.Errorf("%s is not a valid audio locale. Choose from: %s", downloadAudioFlag, strings.Join(utils.LocalesAsStrings(), ", ")) + } else if downloadSubtitleFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { return fmt.Errorf("%s is not a valid subtitle locale. Choose from: %s", downloadSubtitleFlag, strings.Join(utils.LocalesAsStrings(), ", ")) } - utils.Log.Debug("Subtitle locale: %s", downloadSubtitleFlag) + utils.Log.Debug("Locales: audio: %s / subtitle: %s", downloadAudioFlag, downloadSubtitleFlag) switch downloadResolutionFlag { case "1080p", "720p", "480p", "360p": @@ -77,6 +81,10 @@ var Cmd = &cobra.Command{ } func init() { + Cmd.Flags().StringVarP(&downloadAudioFlag, "audio", + "a", + "", + "The locale of the audio. Available locales: "+strings.Join(utils.LocalesAsStrings(), ", ")) Cmd.Flags().StringVarP(&downloadSubtitleFlag, "subtitle", "s", @@ -272,13 +280,69 @@ func downloadInfo(info utils.FormatInformation, file *os.File) error { } func downloadExtractEpisodes(url string) ([][]utils.FormatInformation, error) { - episodes, err := utils.ExtractEpisodes(url) - if err != nil { - return nil, err + var episodes [][]*crunchyroll.Episode + var final []*crunchyroll.Episode + + if downloadAudioFlag != "" { + if _, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { + return nil, fmt.Errorf("downloading episodes by url and specifying a language is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") + } else if _, _, _, _, ok := crunchyroll.ParseEpisodeURL(url); ok { + return nil, fmt.Errorf("downloading episodes by url and specifying a language is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") + } + + var err error + episodes, err = utils.ExtractEpisodes(url, crunchyroll.JP, crunchyroll.LOCALE(downloadAudioFlag)) + if err != nil { + return nil, err + } + japanese := episodes[0] + custom := episodes[1] + + sort.Sort(crunchyUtils.EpisodesByNumber(japanese)) + sort.Sort(crunchyUtils.EpisodesByNumber(custom)) + + var errMessages []string + + if len(japanese) == 0 || len(japanese) == len(custom) { + final = custom + } else { + for _, jp := range japanese { + before := len(final) + for _, episode := range custom { + if jp.SeasonNumber == episode.SeasonNumber && jp.EpisodeNumber == episode.EpisodeNumber { + final = append(final, episode) + } + } + if before == len(final) { + errMessages = append(errMessages, fmt.Sprintf("%s has no %s audio, using %s as fallback", jp.Title, crunchyroll.LOCALE(downloadAudioFlag), crunchyroll.JP)) + final = append(final, jp) + } + } + } + + if len(errMessages) > 10 { + for _, msg := range errMessages[:10] { + utils.Log.SetProcess(msg) + } + utils.Log.SetProcess("... and %d more", len(errMessages)-10) + } else { + for _, msg := range errMessages { + utils.Log.SetProcess(msg) + } + } + } else { + var err error + episodes, err = utils.ExtractEpisodes(url) + if err != nil { + return nil, err + } else if len(episodes) == 0 { + return nil, fmt.Errorf("cannot find any episode") + } + final = episodes[0] } var infoFormat [][]utils.FormatInformation - for _, season := range crunchyUtils.SortEpisodesBySeason(episodes[0]) { + for _, season := range crunchyUtils.SortEpisodesBySeason(final) { tmpFormatInformation := make([]utils.FormatInformation, 0) for _, episode := range season { format, err := episode.GetFormat(downloadResolutionFlag, crunchyroll.LOCALE(downloadSubtitleFlag), true) From ac876f674a77a030c82db589452977336721ed21 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Aug 2022 13:38:42 +0200 Subject: [PATCH 163/630] Update to newer crunchyroll-go version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d7f0eba..cb59580 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/crunchy-labs/crunchy-cli go 1.18 require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220808230013-bb434a0fba7a + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220812161741-903599bcbe60 github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 225fce4..d134d52 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220808230013-bb434a0fba7a h1:q5M2xmCTu2njig5rlRAd83LJDaPANmweFJjYsnxi5zM= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220808230013-bb434a0fba7a/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220812161741-903599bcbe60 h1:cvEKs8D8816yWJDXYl8V7bYLYsAcbNbGGcUZDUofwTI= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220812161741-903599bcbe60/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= From 4ae4345c4017e9f3287aa6143f857645f1493047 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Aug 2022 13:46:39 +0200 Subject: [PATCH 164/630] Fix smartrelease host url --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6374ae0..bb218a0 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ See [#39](https://github.com/crunchy-labs/crunchy-cli/issues/39) for more inform ## ๐Ÿ’พ Get the executable - ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/crunchy-labs/crunchy-cli/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) + - [Linux (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell $ yay -S crunchyroll-go From 416507c8a6406be5069df6975cf676ccd9318703 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 15 Aug 2022 00:27:34 +0200 Subject: [PATCH 165/630] Remove not working notice for scoop --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index bb218a0..ce53d41 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ See [#39](https://github.com/crunchy-labs/crunchy-cli/issues/39) for more inform $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` - <i>Currently not working because the repo got renamed!</i> - ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/crunchy-labs/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): - use `make` (requires `go` to be installed): ```shell From d13e5714f8aed8feb6b880efca8287aaf7cecb9d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 21 Aug 2022 19:40:05 +0200 Subject: [PATCH 166/630] Fix README details --- README.md | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 1147f1e..dfb3fa3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # crunchy-cli -A [go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account to for full (api) access. +A [Go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account for full access & features. <p align="center"> <a href="https://github.com/crunchy-labs/crunchy-cli"> @@ -56,16 +56,11 @@ See #39 for more information._ ```shell $ yay -S crunchyroll-go ``` -- <del> - - On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): +- On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): ```shell $ scoop bucket add extras # <- in case you haven't added the extra repository already $ scoop install crunchyroll-go ``` - - </del> - <i>Currently not working because the repo got renamed!</i> - ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/crunchy-labs/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): - use `make` (requires `go` to be installed): ```shell @@ -175,26 +170,6 @@ The following flags can be (optional) passed to modify the [archive](#archive) p | `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | | `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | -### Help - -- General help - ```shell - $ crunchy help - ``` -- Login help - ```shell - $ crunchy help login - ``` -- Download help - ```shell - $ crunchy help download - ``` - -- Archive help - ```shell - $ crunchy help archive - ``` - ### Global flags These flags you can use across every sub-command: From e5636df96931c2826921a6b32db15dbfcdd56a05 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 21 Aug 2022 22:07:46 +0200 Subject: [PATCH 167/630] Update crunchyroll-go version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cb59580..eae1163 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/crunchy-labs/crunchy-cli go 1.18 require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220812161741-903599bcbe60 + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.1 github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index d134d52..4f103cb 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220812161741-903599bcbe60 h1:cvEKs8D8816yWJDXYl8V7bYLYsAcbNbGGcUZDUofwTI= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.0-20220812161741-903599bcbe60/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.1 h1:mWCNqQJb9Vf8pkM2YYgbLYTn49//7VaCMSvFK3G7l7Q= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.1/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= From f974d5296b12036f50d3a9a01b05db7708ecdb24 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 22 Aug 2022 12:41:47 +0200 Subject: [PATCH 168/630] Remove rename issue link --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index dfb3fa3..16f9559 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,7 @@ A [Go](https://golang.org) written cli client for [crunchyroll](https://www.crun <a href="#-license">License โš–</a> </p> -_This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility. -See #39 for more information._ +_This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility._ > This tool relies on the [crunchyroll-go](https://github.com/crunchy-labs/crunchyroll-go) library to communicate with crunchyroll. > The library enters maintenance mode (only small fixes, no new features) with version v3 in favor of rewriting it completely in Rust. From 0371b31dcc894d496368567fb8b8dd06288844e6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 22 Aug 2022 13:06:05 +0200 Subject: [PATCH 169/630] Add info and update command examples --- README.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16f9559..9c22f1b 100644 --- a/README.md +++ b/README.md @@ -89,10 +89,10 @@ This can be performed via crunchyroll account email and password. $ crunchy login user@example.com password ``` -or via session id +or via refresh token / `etp_rt` cookie ```shell -$ crunchy login --session-id 8e9gs135defhga790dvrf2i0eris8gts +$ crunchy login --refresh-token 7578ce50-5712-3gef-b97e-01332d6b588c ``` ### Download @@ -169,6 +169,24 @@ The following flags can be (optional) passed to modify the [archive](#archive) p | `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | | `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | + +### Info + +The `info` displays some information about the account which is used for the cli. + +```shell +$ crunchy info +``` + +### Update + +If you want to update your local version of `crunchy-cli`, this command makes this easier. +It checks if a new version is available and if so, updates itself. + +```shell +$ crunchy update +``` + ### Global flags These flags you can use across every sub-command: From afc85350ab60800ad1206542ce0deb9bb47ea47a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 22 Aug 2022 13:31:33 +0200 Subject: [PATCH 170/630] Update arch install package name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c22f1b..3c7a7a0 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ _This repo was former known as **crunchyroll-go** (which still exists but now co - [MacOS (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell - $ yay -S crunchyroll-go + $ yay -S crunchy-cli ``` - On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): ```shell From 3f78101eb8ae190af1af7dadeb07ace490fbc598 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 3 Sep 2022 15:18:52 +0200 Subject: [PATCH 171/630] Fix unwanted video track and wrong labeled audio track in archive (#45) --- cli/commands/archive/archive.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index a15822c..c0a2486 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -548,10 +548,10 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s for i, audio := range audioFiles { input = append(input, "-i", audio) - maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles))) + maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles))+":1") locale := crunchyroll.LOCALE(re.FindStringSubmatch(audio)[1]) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i+len(videoFiles)), fmt.Sprintf("language=%s", locale)) + metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i+len(videoFiles)), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) } for i, subtitle := range subtitleFiles { From c02306ff9f2f19977bcd32ab179de070e7379c5c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 3 Sep 2022 15:47:25 +0200 Subject: [PATCH 172/630] Bump crunchyroll-go version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eae1163..7152a6a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/crunchy-labs/crunchy-cli go 1.18 require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.1 + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 4f103cb..51fe7a6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.1 h1:mWCNqQJb9Vf8pkM2YYgbLYTn49//7VaCMSvFK3G7l7Q= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.1/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 h1:PG5++Gje126/xRtzZwCowoFU1Dl3qKzFjd3lWhVXoso= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= From dc2309ab10868b7c62fbff913ad6e3a362107fec Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 3 Sep 2022 15:47:41 +0200 Subject: [PATCH 173/630] Bump locale names in docs --- crunchy-cli.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crunchy-cli.1 b/crunchy-cli.1 index 6f20dba..07d3422 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -65,11 +65,11 @@ A command to simply download videos. The output file is stored as a \fI.ts\fR fi .TP \fB-a, --audio AUDIO\fR -Forces to download videos with the given audio locale. If no video with this audio locale is available, nothing will be downloaded. Available locales are: ja-JP, en-US, es-419, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA. +Forces to download videos with the given audio locale. If no video with this audio locale is available, nothing will be downloaded. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. .TP \fB-s, --subtitle SUBTITLE\fR -Forces to download the videos with subtitles in the given locale / language. If no video with this subtitle locale is available, nothing will be downloaded. Available locales are: ja-JP, en-US, es-419, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA. +Forces to download the videos with subtitles in the given locale / language. If no video with this subtitle locale is available, nothing will be downloaded. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. .TP \fB-d, --directory DIRECTORY\fR @@ -104,7 +104,7 @@ This command behaves like \fBdownload\fR besides the fact that it requires \fIff .TP \fB-l, --language LANGUAGE\fR -Audio locales which should be downloaded. Can be used multiple times. Available locales are: ja-JP, en-US, es-419, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA. +Audio locales which should be downloaded. Can be used multiple times. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. .TP \fB-d, --directory DIRECTORY\fR From d53b20717ad97e203f7f77bc237e2b9a419515f7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 3 Sep 2022 18:22:34 +0200 Subject: [PATCH 174/630] Fix video length sometimes exceeds actual episode length (#32) --- cli/commands/archive/archive.go | 75 +++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index c0a2486..ce53b85 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -535,6 +535,10 @@ func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitl func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, subtitleFiles []string) error { var input, maps, metadata []string re := regexp.MustCompile(`(?m)_([a-z]{2}-([A-Z]{2}|[0-9]{3}))_(video|audio|subtitle)`) + // https://github.com/crunchy-labs/crunchy-cli/issues/32 + videoLength32Fix := regexp.MustCompile(`Duration:\s?(\d+):(\d+):(\d+).(\d+),`) + + videoLength := [4]int{0, 0, 0, 0} for i, video := range videoFiles { input = append(input, "-i", video) @@ -544,6 +548,27 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) + + var errBuf bytes.Buffer + cmd := exec.CommandContext(ctx, "ffmpeg", "-i", video) + cmd.Stderr = &errBuf + cmd.Run() + + matches := videoLength32Fix.FindStringSubmatch(errBuf.String()) + hours, _ := strconv.Atoi(matches[1]) + minutes, _ := strconv.Atoi(matches[2]) + seconds, _ := strconv.Atoi(matches[3]) + millis, _ := strconv.Atoi(matches[4]) + + if hours > videoLength[0] { + videoLength = [4]int{hours, minutes, seconds, millis} + } else if hours == videoLength[0] && minutes > videoLength[1] { + videoLength = [4]int{hours, minutes, seconds, millis} + } else if hours == videoLength[0] && minutes == videoLength[1] && seconds > videoLength[2] { + videoLength = [4]int{hours, minutes, seconds, millis} + } else if hours == videoLength[0] && minutes == videoLength[1] && seconds == videoLength[2] && millis > videoLength[3] { + videoLength = [4]int{hours, minutes, seconds, millis} + } } for i, audio := range audioFiles { @@ -611,6 +636,56 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s } defer file.Close() + errBuf.Reset() + cmd = exec.CommandContext(ctx, "ffmpeg", "-i", file.Name()) + cmd.Stderr = &errBuf + cmd.Run() + + matches := videoLength32Fix.FindStringSubmatch(errBuf.String()) + hours, _ := strconv.Atoi(matches[1]) + minutes, _ := strconv.Atoi(matches[2]) + seconds, _ := strconv.Atoi(matches[3]) + millis, _ := strconv.Atoi(matches[4]) + + var reencode bool + if hours > videoLength[0] { + reencode = true + } else if hours == videoLength[0] && minutes > videoLength[1] { + reencode = true + } else if hours == videoLength[0] && minutes == videoLength[1] && seconds > videoLength[2] { + reencode = true + } else if hours == videoLength[0] && minutes == videoLength[1] && seconds == videoLength[2] && millis > videoLength[3] { + reencode = true + } + + // very dirty solution to https://github.com/crunchy-labs/crunchy-cli/issues/32. + // this might get triggered when not needed but there is currently no easy way to + // bypass this unwanted triggering + if reencode { + utils.Log.Debug("Reencode to short video length") + + file.Close() + + tmpFile, _ := os.CreateTemp("", filepath.Base(file.Name())+"-32_fix") + tmpFile.Close() + + errBuf.Reset() + cmd = exec.CommandContext(ctx, "ffmpeg", "-y", "-i", file.Name(), "-c", "copy", "-t", fmt.Sprintf("%02d:%02d:%02d.%d", videoLength[0], videoLength[1], videoLength[2], videoLength[3]), "-f", "matroska", tmpFile.Name()) + cmd.Stderr = &errBuf + if err = cmd.Run(); err != nil { + return fmt.Errorf(errBuf.String()) + } + + os.Remove(file.Name()) + os.Rename(tmpFile.Name(), file.Name()) + + file, err = os.Open(file.Name()) + if err != nil { + return err + } + defer file.Close() + } + _, err = bufio.NewWriter(dst).ReadFrom(file) return err } From 610c4e29935fcb9213c025afe5a1914329d4a6d2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 4 Sep 2022 17:12:36 +0200 Subject: [PATCH 175/630] Bump go version to 1.19 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7152a6a..162e7a1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/crunchy-labs/crunchy-cli -go 1.18 +go 1.19 require ( github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 From 689bbcd9a4a3ba515edd45faf6af1d8c6de4ed27 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 4 Sep 2022 17:12:57 +0200 Subject: [PATCH 176/630] Remove go version badge --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 3c7a7a0..19dba21 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ A [Go](https://golang.org) written cli client for [crunchyroll](https://www.crun <a href="https://github.com/crunchy-labs/crunchy-cli/blob/master/LICENSE"> <img src="https://img.shields.io/github/license/crunchy-labs/crunchy-cli?style=flat-square" alt="License"> </a> - <a href="https://golang.org"> - <img src="https://img.shields.io/github/go-mod/go-version/crunchy-labs/crunchy-cli?style=flat-square" alt="Go version"> - </a> <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> </a> From 027047fc7e22f5a9d439fd6fa4467ad00b3f3a3c Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sun, 4 Sep 2022 20:33:52 +0200 Subject: [PATCH 177/630] Fix binary links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 19dba21..cbe6a37 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ _This repo was former known as **crunchyroll-go** (which still exists but now co ## ๐Ÿ’พ Get the executable - ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/crunchy-labs/crunchy-cli/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.crunchy-labs.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) + - [Linux (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) + - [Windows (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) + - [MacOS (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) - If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): ```shell $ yay -S crunchy-cli From 36c1423ff659f8369b227523d99b0d890520358b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 5 Sep 2022 00:20:29 +0200 Subject: [PATCH 178/630] Fix re-encode removes video, audio and subtitle tracks (#47) --- cli/commands/archive/archive.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index ce53b85..63eac77 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -662,7 +662,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s // this might get triggered when not needed but there is currently no easy way to // bypass this unwanted triggering if reencode { - utils.Log.Debug("Reencode to short video length") + utils.Log.Debug("Re-encode to short video length") file.Close() @@ -670,7 +670,7 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s tmpFile.Close() errBuf.Reset() - cmd = exec.CommandContext(ctx, "ffmpeg", "-y", "-i", file.Name(), "-c", "copy", "-t", fmt.Sprintf("%02d:%02d:%02d.%d", videoLength[0], videoLength[1], videoLength[2], videoLength[3]), "-f", "matroska", tmpFile.Name()) + cmd = exec.CommandContext(ctx, "ffmpeg", "-y", "-i", file.Name(), "-c", "copy", "-map", "0", "-t", fmt.Sprintf("%02d:%02d:%02d.%d", videoLength[0], videoLength[1], videoLength[2], videoLength[3]), "-f", "matroska", tmpFile.Name()) cmd.Stderr = &errBuf if err = cmd.Run(); err != nil { return fmt.Errorf(errBuf.String()) From 62938a500ff8ef70889451c9ec7534a3f18fb2a8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 5 Sep 2022 00:24:31 +0200 Subject: [PATCH 179/630] Disable subtitles by default on re-encode --- cli/commands/archive/archive.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 63eac77..841b33d 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -670,7 +670,15 @@ func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, s tmpFile.Close() errBuf.Reset() - cmd = exec.CommandContext(ctx, "ffmpeg", "-y", "-i", file.Name(), "-c", "copy", "-map", "0", "-t", fmt.Sprintf("%02d:%02d:%02d.%d", videoLength[0], videoLength[1], videoLength[2], videoLength[3]), "-f", "matroska", tmpFile.Name()) + cmd = exec.CommandContext(ctx, "ffmpeg", + "-y", + "-i", file.Name(), + "-map", "0", + "-c", "copy", + "-disposition:s:0", "0", + "-t", fmt.Sprintf("%02d:%02d:%02d.%d", videoLength[0], videoLength[1], videoLength[2], videoLength[3]), + "-f", "matroska", + tmpFile.Name()) cmd.Stderr = &errBuf if err = cmd.Run(); err != nil { return fmt.Errorf(errBuf.String()) From 97dd8011379717402ecce1b1041a60a2c13cd893 Mon Sep 17 00:00:00 2001 From: LordBex <lordibex@protonmail.com> Date: Mon, 5 Sep 2022 15:40:49 +0200 Subject: [PATCH 180/630] adding subtitle flag for archive (-s, --sublang) --- cli/commands/archive/archive.go | 52 ++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index ce53b85..385a13d 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -5,12 +5,6 @@ import ( "bytes" "context" "fmt" - "github.com/crunchy-labs/crunchy-cli/cli/commands" - "github.com/crunchy-labs/crunchy-cli/utils" - "github.com/crunchy-labs/crunchyroll-go/v3" - crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" - "github.com/grafov/m3u8" - "github.com/spf13/cobra" "io" "math" "os" @@ -22,10 +16,18 @@ import ( "sort" "strconv" "strings" + + "github.com/crunchy-labs/crunchy-cli/cli/commands" + "github.com/crunchy-labs/crunchy-cli/utils" + "github.com/crunchy-labs/crunchyroll-go/v3" + crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" + "github.com/grafov/m3u8" + "github.com/spf13/cobra" ) var ( - archiveLanguagesFlag []string + archiveLanguagesFlag []string + archiveSubLanguagesFlag []string archiveDirectoryFlag string archiveOutputFlag string @@ -69,6 +71,18 @@ var Cmd = &cobra.Command{ } utils.Log.Debug("Using following audio locales: %s", strings.Join(archiveLanguagesFlag, ", ")) + for _, locale := range archiveSubLanguagesFlag { + if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(locale)) { + // if locale is 'all', match all known locales + if locale == "all" { + archiveSubLanguagesFlag = utils.LocalesAsStrings() + break + } + return fmt.Errorf("%s is not a valid locale for Subtitels. Choose from: %s", locale, strings.Join(utils.LocalesAsStrings(), ", ")) + } + } + utils.Log.Debug("Using following subtitels locales: %s", strings.Join(archiveSubLanguagesFlag, ", ")) + var found bool for _, mode := range []string{"auto", "audio", "video"} { if archiveMergeFlag == mode { @@ -127,12 +141,20 @@ func init() { []string{string(utils.SystemLocale(false)), string(crunchyroll.JP)}, "Audio locale which should be downloaded. Can be used multiple times") + Cmd.Flags().StringSliceVarP(&archiveSubLanguagesFlag, + "sublang", + "s", + []string{}, + "Subtitles langs which should be downloaded. Can be used multiple times") + cwd, _ := os.Getwd() + Cmd.Flags().StringVarP(&archiveDirectoryFlag, "directory", "d", cwd, "The directory to store the files into") + Cmd.Flags().StringVarP(&archiveOutputFlag, "output", "o", @@ -147,6 +169,7 @@ func init() { "\t{fps} ยป Frame Rate of the video\n"+ "\t{audio} ยป Audio locale of the video\n"+ "\t{subtitle} ยป Subtitle locale of the video") + Cmd.Flags().StringVar(&archiveTempDirFlag, "temp", os.TempDir(), @@ -507,10 +530,25 @@ func archiveDownloadVideos(downloader crunchyroll.Downloader, filename string, v return files, nil } +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitle) ([]string, error) { var files []string for _, subtitle := range subtitles { + if len(archiveSubLanguagesFlag) > 0 { + if !stringInSlice(string(subtitle.Locale), archiveSubLanguagesFlag) { + continue + } + } + f, err := os.CreateTemp("", fmt.Sprintf("%s_%s_subtitle_*.ass", filename, subtitle.Locale)) if err != nil { return nil, err From 136d970feca4eb78adbf94410fd806c3cd343f55 Mon Sep 17 00:00:00 2001 From: LordBex <lordibex@protonmail.com> Date: Mon, 5 Sep 2022 21:30:41 +0200 Subject: [PATCH 181/630] move stringInSlice to separate as ElementInSlice --- utils/std.go | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 utils/std.go diff --git a/utils/std.go b/utils/std.go new file mode 100644 index 0000000..75b35f1 --- /dev/null +++ b/utils/std.go @@ -0,0 +1,10 @@ +package utils + +func ElementInSlice[T comparable](check T, toCheck []T) bool { + for _, item := range toCheck { + if check == item { + return true + } + } + return false +} From b42c87c9f8dcae7cf17c3c05c8c7baa3dcdd6497 Mon Sep 17 00:00:00 2001 From: LordBex <lordibex@protonmail.com> Date: Mon, 5 Sep 2022 21:59:00 +0200 Subject: [PATCH 182/630] remove stringInSlice and switch to ElementInSlice --- cli/commands/archive/archive.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 8ddc423..6a7cc8c 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -530,21 +530,12 @@ func archiveDownloadVideos(downloader crunchyroll.Downloader, filename string, v return files, nil } -func stringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitle) ([]string, error) { var files []string for _, subtitle := range subtitles { if len(archiveSubLanguagesFlag) > 0 { - if !stringInSlice(string(subtitle.Locale), archiveSubLanguagesFlag) { + if !utils.ElementInSlice(string(subtitle.Locale), archiveSubLanguagesFlag) { continue } } From 3f12cbae9599f6dc16dff01ce0ba69ae66d4aa64 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 5 Sep 2022 22:54:52 +0200 Subject: [PATCH 183/630] Change -s default value --- cli/commands/archive/archive.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 6a7cc8c..744635f 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -144,7 +144,7 @@ func init() { Cmd.Flags().StringSliceVarP(&archiveSubLanguagesFlag, "sublang", "s", - []string{}, + utils.LocalesAsStrings(), "Subtitles langs which should be downloaded. Can be used multiple times") cwd, _ := os.Getwd() @@ -534,10 +534,8 @@ func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitl var files []string for _, subtitle := range subtitles { - if len(archiveSubLanguagesFlag) > 0 { - if !utils.ElementInSlice(string(subtitle.Locale), archiveSubLanguagesFlag) { - continue - } + if !utils.ElementInSlice(string(subtitle.Locale), archiveSubLanguagesFlag) { + continue } f, err := os.CreateTemp("", fmt.Sprintf("%s_%s_subtitle_*.ass", filename, subtitle.Locale)) From 2f08aeac1abaddbe0a4a459424360ce96d83076e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 5 Sep 2022 22:55:27 +0200 Subject: [PATCH 184/630] Add -s flag documentation --- crunchy-cli.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crunchy-cli.1 b/crunchy-cli.1 index 07d3422..91ba30c 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -107,6 +107,10 @@ This command behaves like \fBdownload\fR besides the fact that it requires \fIff Audio locales which should be downloaded. Can be used multiple times. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. .TP +\fB-s, --sublang LANGUAGE\fR +Subtitle languages to use, by default all are included. Can be used multiple times. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. +.TP + \fB-d, --directory DIRECTORY\fR The directory to download all files to. .TP From d4bef511cbc32f15b99777511ef5889f6be6af85 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 11 Sep 2022 13:02:08 +0200 Subject: [PATCH 185/630] Fix update executable path --- cli/commands/update/update.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/cli/commands/update/update.go b/cli/commands/update/update.go index 831621d..6608f26 100644 --- a/cli/commands/update/update.go +++ b/cli/commands/update/update.go @@ -8,6 +8,7 @@ import ( "io" "os" "os/exec" + "path" "runtime" "strings" ) @@ -90,8 +91,8 @@ func update() error { var downloadFile string switch runtime.GOOS { case "linux": - yayCommand := exec.Command("pacman -Q crunchy-cli") - if yayCommand.Run() == nil && yayCommand.ProcessState.Success() { + pacmanCommand := exec.Command("pacman -Q crunchy-cli") + if pacmanCommand.Run() == nil && pacmanCommand.ProcessState.Success() { utils.Log.Info("crunchy-cli was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") return nil } @@ -105,14 +106,29 @@ func update() error { "You have to update manually (https://github.com/crunchy-labs/crunchy-cli", runtime.GOOS) } - utils.Log.SetProcess("Updating executable %s", os.Args[0]) + executePath := os.Args[0] + var perms os.FileInfo + // check if the path is relative, absolute or non (if so, the executable must be in PATH) + if strings.HasPrefix(executePath, "."+string(os.PathSeparator)) || path.IsAbs(executePath) { + if perms, err = os.Stat(os.Args[0]); err != nil { + return err + } + } else { + executePath, err = exec.LookPath(os.Args[0]) + if err != nil { + return err + } + if perms, err = os.Stat(executePath); err != nil { + return err + } + } - perms, err := os.Stat(os.Args[0]) - if err != nil { + utils.Log.SetProcess("Updating executable %s", executePath) + + if err = os.Remove(executePath); err != nil { return err } - os.Remove(os.Args[0]) - executeFile, err := os.OpenFile(os.Args[0], os.O_CREATE|os.O_WRONLY, perms.Mode()) + executeFile, err := os.OpenFile(executePath, os.O_CREATE|os.O_WRONLY, perms.Mode()) if err != nil { return err } @@ -128,7 +144,7 @@ func update() error { return err } - utils.Log.StopProcess("Updated executable %s", os.Args[0]) + utils.Log.StopProcess("Updated executable %s", executePath) } return nil From b4bc047b30c10786f4d156462220cb302979e73f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 02:01:02 +0000 Subject: [PATCH 186/630] Bump github.com/crunchy-labs/crunchyroll-go/v3 from 3.0.2 to 3.0.3 Bumps [github.com/crunchy-labs/crunchyroll-go/v3](https://github.com/crunchy-labs/crunchyroll-go) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/crunchy-labs/crunchyroll-go/releases) - [Changelog](https://github.com/crunchy-labs/crunchyroll-go/blob/master/news.go) - [Commits](https://github.com/crunchy-labs/crunchyroll-go/compare/v3.0.2...v3.0.3) --- updated-dependencies: - dependency-name: github.com/crunchy-labs/crunchyroll-go/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 162e7a1..19fb230 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/crunchy-labs/crunchy-cli go 1.19 require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3 github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 51fe7a6..ec82a63 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 h1:PG5++Gje126/xRtzZwCowoFU1Dl3qKzFjd3lWhVXoso= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3 h1:hkX7iSUnGt/6Lm/M28a6bAVQNfRFeowchtIh3OOH8Bg= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= From 36bdc76a48ceb60282e52c89796228b262e3074b Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Sat, 8 Oct 2022 02:05:29 +0200 Subject: [PATCH 187/630] Update discord invite link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cbe6a37..7605a61 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A [Go](https://golang.org) written cli client for [crunchyroll](https://www.crun <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> </a> - <a href="https://discord.gg/gUWwekeNNg"> + <a href="https://discord.gg/PXGPGpQxgk"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> </p> From eba2417f4ed94dbfef424ea24bdbcf90271ff623 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 01:48:15 +0000 Subject: [PATCH 188/630] Bump github.com/spf13/cobra from 1.5.0 to 1.6.0 Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 19fb230..b297fb0 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.19 require ( github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3 github.com/grafov/m3u8 v0.11.1 - github.com/spf13/cobra v1.5.0 + github.com/spf13/cobra v1.6.0 ) require ( - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/spf13/pflag v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index ec82a63..1295653 100644 --- a/go.sum +++ b/go.sum @@ -3,12 +3,12 @@ github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3 h1:hkX7iSUnGt/6Lm/M28a6bAVQNfRF github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e0d100b6274537950ac4e71c5940b580ddd50d25 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 20 Oct 2022 22:02:44 +0200 Subject: [PATCH 189/630] Update dependencies and fix #59 partially --- cli/commands/archive/archive.go | 8 +------- cli/commands/download/download.go | 8 +------- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 744635f..b46ea0f 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -211,10 +211,6 @@ func archive(urls []string) error { episodes, err := archiveExtractEpisodes(url) if err != nil { utils.Log.StopProcess("Failed to parse url %d", i+1) - if utils.Crunchy.Config.Premium { - utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchy-cli/issues/22 for more information") - } return err } utils.Log.StopProcess("Parsed url %d", i+1) @@ -737,9 +733,7 @@ func archiveExtractEpisodes(url string) ([][]utils.FormatInformation, error) { } } - if _, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { - return nil, fmt.Errorf("archiving episodes by url is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") - } else if _, _, _, _, ok := crunchyroll.ParseEpisodeURL(url); ok { + if _, ok := crunchyroll.ParseEpisodeURL(url); ok { return nil, fmt.Errorf("archiving episodes by url is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") } diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go index 39273d0..9102e7b 100644 --- a/cli/commands/download/download.go +++ b/cli/commands/download/download.go @@ -139,10 +139,6 @@ func download(urls []string) error { episodes, err := downloadExtractEpisodes(url) if err != nil { utils.Log.StopProcess("Failed to parse url %d", i+1) - if utils.Crunchy.Config.Premium { - utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchy-cli/issues/22 for more information") - } return err } utils.Log.StopProcess("Parsed url %d", i+1) @@ -284,9 +280,7 @@ func downloadExtractEpisodes(url string) ([][]utils.FormatInformation, error) { var final []*crunchyroll.Episode if downloadAudioFlag != "" { - if _, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { - return nil, fmt.Errorf("downloading episodes by url and specifying a language is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") - } else if _, _, _, _, ok := crunchyroll.ParseEpisodeURL(url); ok { + if _, ok := crunchyroll.ParseEpisodeURL(url); ok { return nil, fmt.Errorf("downloading episodes by url and specifying a language is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") } diff --git a/go.mod b/go.mod index b297fb0..e04d5ae 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/crunchy-labs/crunchy-cli go 1.19 require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3 + github.com/crunchy-labs/crunchyroll-go/v3 v3.0.4 github.com/grafov/m3u8 v0.11.1 github.com/spf13/cobra v1.6.0 ) diff --git a/go.sum b/go.sum index 1295653..9ca4b9c 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3 h1:hkX7iSUnGt/6Lm/M28a6bAVQNfRFeowchtIh3OOH8Bg= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.3/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.4 h1:QCHlk0PEfrm7uPgLm2RNtwXED3ACKlhD9xlrlGsPhDI= +github.com/crunchy-labs/crunchyroll-go/v3 v3.0.4/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= From dc7e5d564ea4ae7849bc2e593ab6493eb03db0f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 01:56:32 +0000 Subject: [PATCH 190/630] Bump github.com/spf13/cobra from 1.6.0 to 1.6.1 Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e04d5ae..b884b5a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/crunchy-labs/crunchyroll-go/v3 v3.0.4 github.com/grafov/m3u8 v0.11.1 - github.com/spf13/cobra v1.6.0 + github.com/spf13/cobra v1.6.1 ) require ( diff --git a/go.sum b/go.sum index 9ca4b9c..1881381 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 0572af4e07f9ec2bbefa79704a0833625095f630 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 28 Oct 2022 12:43:51 +0200 Subject: [PATCH 191/630] Change links from beta.crunchyroll.com to www.crunchyroll.com --- README.md | 12 ++++++------ cli/commands/login/login.go | 2 +- crunchy-cli.1 | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7605a61..6e757e1 100644 --- a/README.md +++ b/README.md @@ -98,13 +98,13 @@ By default, the cli tries to download the episode with your system language as a **If your system language is not supported, an error message will be displayed and en-US (american english) will be chosen as language.** ```shell -$ crunchy download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy download https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` With `-r best` the video(s) will have the best available resolution (mostly 1920x1080 / Full HD). ```shell -$ crunchy download -r best https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy download -r best https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` The file is by default saved as a `.ts` (mpeg transport stream) file. @@ -113,13 +113,13 @@ file, just name it `whatever.mp4`. **You need [ffmpeg](https://ffmpeg.org) to store the video in other file formats.** ```shell -$ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` With the `--audio` flag you can specify which audio the video should have and with `--subtitle` which subtitle it should have. Type `crunchy help download` to see all available locales. ```shell -$ crunchy download --audio ja-JP --subtitle de-DE https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx +$ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` ##### Flags @@ -143,13 +143,13 @@ the `--language` flag. Archive a file ```shell -$ crunchy archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy archive https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome ``` Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. ```shell -$ crunchy archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx +$ crunchy archive -c "ditf.tar.gz" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` ##### Flags diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go index 9fce181..47a595d 100644 --- a/cli/commands/login/login.go +++ b/cli/commands/login/login.go @@ -51,7 +51,7 @@ func init() { Cmd.Flags().BoolVar(&loginRefreshTokenFlag, "refresh-token", false, - "Use a refresh token to login instead of username and password. Can be obtained by copying the `etp-rt` cookie from beta.crunchyroll.com") + "Use a refresh token to login instead of username and password. Can be obtained by copying the `etp-rt` cookie from crunchyroll.com") Cmd.MarkFlagsMutuallyExclusive("session-id", "refresh-token") } diff --git a/crunchy-cli.1 b/crunchy-cli.1 index 91ba30c..eb3f16d 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -164,7 +164,7 @@ Use the list below to get a better overview what is possible ...[S3E4-] - Download all episodes from and including season 3, episode 4. ...[S1E4-S3] - Download all episodes from and including season 1, episode 4, until and including season 3. -In practise, it would look like this: \fIhttps://beta.crunchyroll.com/series/12345678/example[S1E5-S3E2]\fR. +In practise, it would look like this: \fIhttps://www.crunchyroll.com/series/12345678/example[S1E5-S3E2]\fR. The \fBS\fR, followed by the number indicates the season number, \fBE\fR, followed by the number indicates an episode number. It doesn't matter if \fBS\fR, \fBE\fR or both are missing. Theoretically \fB[-]\fR is a valid pattern too. Note that \fBS\fR must always stay before \fBE\fR when used. @@ -175,23 +175,23 @@ $ crunchy-cli login user@example.com 12345678 Download a episode normally. Your system locale will be used for the video's audio. .br -$ crunchy-cli download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy-cli download https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Download a episode with 720p and name it to 'darling.mp4'. Note that you need \fBffmpeg\fR to save files which do not have '.ts' as file extension. .br -$ crunchy-cli download -o "darling.mp4" -r 720p https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy-cli download -o "darling.mp4" -r 720p https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Download a episode with japanese audio and american subtitles. .br -$ crunchy-cli download -a ja-JP -s en-US https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E3-E5] +$ crunchy-cli download -a ja-JP -s en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E3-E5] Stores the episode in a .mkv file. .br -$ crunchy-cli archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome +$ crunchy-cli archive https://www.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. .br -$ crunchy-cli archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E2] +$ crunchy-cli archive -c "ditf.tar.gz" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E2] .SH BUGS If you notice any bug or want an enhancement, feel free to create a new issue or pull request in the GitHub repository. From 95b66c3ff51f7df23d9c4603400ce1356b917c6c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 31 Oct 2022 21:19:24 +0100 Subject: [PATCH 192/630] Fix subtitle styling and size (#66) --- cli/commands/archive/archive.go | 34 ++++++++++++++++++++++++++++++++- crunchy-cli.1 | 4 ++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index b46ea0f..03ec3ac 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -40,6 +40,8 @@ var ( archiveResolutionFlag string archiveGoroutinesFlag int + + archiveNoSubtitleOptimizations bool ) var Cmd = &cobra.Command{ @@ -203,6 +205,11 @@ func init() { "g", runtime.NumCPU(), "Number of parallel segment downloads") + + Cmd.Flags().BoolVar(&archiveNoSubtitleOptimizations, + "no-subtitle-optimizations", + false, + "Disable subtitle optimizations. See https://github.com/crunchy-labs/crunchy-cli/issues/66 for more information") } func archive(urls []string) error { @@ -540,13 +547,38 @@ func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitl } files = append(files, f.Name()) - if err := subtitle.Save(f); err != nil { + buffer := &bytes.Buffer{} + + if err := subtitle.Save(buffer); err != nil { f.Close() for _, file := range files { os.Remove(file) } return nil, err } + + if !archiveNoSubtitleOptimizations { + buffer2 := &bytes.Buffer{} + var scriptInfo bool + for _, line := range strings.Split(buffer.String(), "\n") { + if scriptInfo && strings.HasPrefix(strings.TrimSpace(line), "[") { + buffer2.WriteString("ScaledBorderAndShadows: yes\n") + scriptInfo = false + } else if strings.TrimSpace(line) == "[Script Info]" { + scriptInfo = true + } + buffer2.WriteString(line + "\n") + } + + if _, err = io.Copy(f, buffer2); err != nil { + return nil, err + } + } else { + if _, err = io.Copy(f, buffer); err != nil { + return nil, err + } + } + f.Close() utils.Log.Debug("Downloaded '%s' subtitles", subtitle.Locale) diff --git a/crunchy-cli.1 b/crunchy-cli.1 index eb3f16d..3032c79 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -146,6 +146,10 @@ The video resolution. Can either be specified via the pixels (e.g. 1920x1080), t \fB-g, --goroutines GOROUTINES\fR Sets the number of parallel downloads for the segments the final video is made of. Default is the number of cores the computer has. +.TP + +\fB--no-subtitle-optimizations DISABLE\fR +Disable subtitle optimizations which caused subtitle sizing and layout issues (https://github.com/crunchy-labs/crunchy-cli/issues/66). .SH UPDATE COMMAND Checks if a newer version is available. From 10617df834cf41f468eacc290f42454a781da2b7 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 31 Oct 2022 22:18:44 +0100 Subject: [PATCH 193/630] Fix archive sorting (#63) --- cli/commands/archive/archive.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go index 03ec3ac..4ac4f82 100644 --- a/cli/commands/archive/archive.go +++ b/cli/commands/archive/archive.go @@ -824,17 +824,22 @@ func archiveExtractEpisodes(url string) ([][]utils.FormatInformation, error) { } var infoFormat [][]utils.FormatInformation - for _, e := range eps { + var keys []int + for e := range eps { + keys = append(keys, e) + } + sort.Ints(keys) + + for _, k := range keys { var tmpFormatInfo []utils.FormatInformation - - var keys []int - for episodeNumber := range e { - keys = append(keys, episodeNumber) + var kkey []int + for ee := range eps[k] { + kkey = append(kkey, ee) } - sort.Ints(keys) + sort.Ints(kkey) - for _, key := range keys { - tmpFormatInfo = append(tmpFormatInfo, *e[key]) + for _, kk := range kkey { + tmpFormatInfo = append(tmpFormatInfo, *eps[k][kk]) } infoFormat = append(infoFormat, tmpFormatInfo) From 59e8793a2f4c08192af1c1dd7e3433a1ddc2e8f8 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 1 Nov 2022 22:17:19 +0100 Subject: [PATCH 194/630] Set option to modify locale used (#60) --- cli/root.go | 14 ++++++++++++++ crunchy-cli.1 | 8 ++++++++ utils/locale.go | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/cli/root.go b/cli/root.go index b589a03..c44ca23 100644 --- a/cli/root.go +++ b/cli/root.go @@ -10,6 +10,8 @@ import ( "github.com/crunchy-labs/crunchy-cli/cli/commands/login" "github.com/crunchy-labs/crunchy-cli/cli/commands/update" "github.com/crunchy-labs/crunchy-cli/utils" + "github.com/crunchy-labs/crunchyroll-go/v3" + crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" "github.com/spf13/cobra" "os" "runtime/debug" @@ -22,6 +24,8 @@ var ( proxyFlag string + langFlag string + useragentFlag string ) @@ -40,6 +44,14 @@ var RootCmd = &cobra.Command{ utils.Log = commands.NewLogger(false, false, false) } + if langFlag != "" { + if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(langFlag)) { + return fmt.Errorf("'%s' is not a valid language. Choose from %s", langFlag, strings.Join(utils.LocalesAsStrings(), ", ")) + } + + os.Setenv("CRUNCHY_LANG", langFlag) + } + utils.Log.Debug("Executing `%s` command with %d arg(s)", cmd.Name(), len(args)) utils.Client, err = utils.CreateOrDefaultClient(proxyFlag, useragentFlag) @@ -53,6 +65,8 @@ func init() { RootCmd.PersistentFlags().StringVarP(&proxyFlag, "proxy", "p", "", "Proxy to use") + RootCmd.PersistentFlags().StringVar(&langFlag, "lang", "", fmt.Sprintf("Set language to use. If not set, it's received from the system locale dynamically. Choose from: %s", strings.Join(utils.LocalesAsStrings(), ", "))) + RootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchy-cli/%s", utils.Version), "Useragent to do all request with") RootCmd.AddCommand(archive.Cmd) diff --git a/crunchy-cli.1 b/crunchy-cli.1 index 3032c79..af697f3 100644 --- a/crunchy-cli.1 +++ b/crunchy-cli.1 @@ -44,6 +44,14 @@ Disables all output. Shows verbose output. .TP +\fB--lang\fR +Set language to use. If not set, it's received from the system locale dynamically. Choose from: ar-ME, ar-SA, de-DE, en-US, es-419, es-ES, es-LA, fr-FR, it-IT, ja-JP, pt-BR, pt-PT, ru-RU, zh-CN. +.TP + +\fB--useragent\fR +Useragent to do all request with. +.TP + \fB--version\fR Shows the current cli version. diff --git a/utils/locale.go b/utils/locale.go index 9769940..1e28f6a 100644 --- a/utils/locale.go +++ b/utils/locale.go @@ -14,6 +14,10 @@ import ( // SystemLocale receives the system locale // https://stackoverflow.com/questions/51829386/golang-get-system-language/51831590#51831590 func SystemLocale(verbose bool) crunchyroll.LOCALE { + if lang, ok := os.LookupEnv("CRUNCHY_LANG"); ok { + return crunchyroll.LOCALE(lang) + } + if runtime.GOOS != "windows" { if lang, ok := os.LookupEnv("LANG"); ok { var l crunchyroll.LOCALE From 039d7cfb81684d9de6e0311af8ff4b5c86ab1bcc Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 20 Oct 2022 18:52:08 +0200 Subject: [PATCH 195/630] Rewrite it in Rust --- .github/dependabot.yml | 15 +- .github/workflows/ci.yml | 20 - .github/workflows/codeql-analysis.yml | 67 - Cargo.lock | 1645 +++++++++++++++++++++++++ Cargo.toml | 33 + Makefile | 31 - README.md | 272 ++-- build.rs | 112 ++ cli/commands/archive/archive.go | 818 ------------ cli/commands/archive/compress.go | 136 -- cli/commands/download/download.go | 368 ------ cli/commands/info/info.go | 40 - cli/commands/logger.go | 196 --- cli/commands/login/login.go | 159 --- cli/commands/unix.go | 48 - cli/commands/update/update.go | 151 --- cli/commands/utils.go | 125 -- cli/commands/windows.go | 41 - cli/root.go | 85 -- crunchy-cli-core/Cargo.toml | 29 + crunchy-cli-core/src/cli/archive.rs | 567 +++++++++ crunchy-cli-core/src/cli/download.rs | 452 +++++++ crunchy-cli-core/src/cli/log.rs | 197 +++ crunchy-cli-core/src/cli/login.rs | 39 + crunchy-cli-core/src/cli/mod.rs | 5 + crunchy-cli-core/src/cli/utils.rs | 178 +++ crunchy-cli-core/src/lib.rs | 196 +++ crunchy-cli-core/src/utils/clap.rs | 6 + crunchy-cli-core/src/utils/context.rs | 6 + crunchy-cli-core/src/utils/format.rs | 77 ++ crunchy-cli-core/src/utils/locale.rs | 15 + crunchy-cli-core/src/utils/log.rs | 19 + crunchy-cli-core/src/utils/mod.rs | 8 + crunchy-cli-core/src/utils/os.rs | 52 + crunchy-cli-core/src/utils/parse.rs | 170 +++ crunchy-cli-core/src/utils/sort.rs | 47 + crunchy-cli.1 | 219 ---- go.mod | 14 - go.sum | 14 - main.go | 9 - src/main.rs | 4 + utils/extract.go | 99 -- utils/file.go | 49 - utils/format.go | 63 - utils/http.go | 51 - utils/locale.go | 59 - utils/logger.go | 12 - utils/save.go | 177 --- utils/std.go | 10 - utils/system.go | 7 - utils/vars.go | 14 - 51 files changed, 4018 insertions(+), 3208 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 Makefile create mode 100644 build.rs delete mode 100644 cli/commands/archive/archive.go delete mode 100644 cli/commands/archive/compress.go delete mode 100644 cli/commands/download/download.go delete mode 100644 cli/commands/info/info.go delete mode 100644 cli/commands/logger.go delete mode 100644 cli/commands/login/login.go delete mode 100644 cli/commands/unix.go delete mode 100644 cli/commands/update/update.go delete mode 100644 cli/commands/utils.go delete mode 100644 cli/commands/windows.go delete mode 100644 cli/root.go create mode 100644 crunchy-cli-core/Cargo.toml create mode 100644 crunchy-cli-core/src/cli/archive.rs create mode 100644 crunchy-cli-core/src/cli/download.rs create mode 100644 crunchy-cli-core/src/cli/log.rs create mode 100644 crunchy-cli-core/src/cli/login.rs create mode 100644 crunchy-cli-core/src/cli/mod.rs create mode 100644 crunchy-cli-core/src/cli/utils.rs create mode 100644 crunchy-cli-core/src/lib.rs create mode 100644 crunchy-cli-core/src/utils/clap.rs create mode 100644 crunchy-cli-core/src/utils/context.rs create mode 100644 crunchy-cli-core/src/utils/format.rs create mode 100644 crunchy-cli-core/src/utils/locale.rs create mode 100644 crunchy-cli-core/src/utils/log.rs create mode 100644 crunchy-cli-core/src/utils/mod.rs create mode 100644 crunchy-cli-core/src/utils/os.rs create mode 100644 crunchy-cli-core/src/utils/parse.rs create mode 100644 crunchy-cli-core/src/utils/sort.rs delete mode 100644 crunchy-cli.1 delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 main.go create mode 100644 src/main.rs delete mode 100644 utils/extract.go delete mode 100644 utils/file.go delete mode 100644 utils/format.go delete mode 100644 utils/http.go delete mode 100644 utils/locale.go delete mode 100644 utils/logger.go delete mode 100644 utils/save.go delete mode 100644 utils/std.go delete mode 100644 utils/system.go delete mode 100644 utils/vars.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3938344..40d73b8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,17 @@ version: 2 updates: - - package-ecosystem: "gomod" + - package-ecosystem: "cargo" directory: "/" schedule: - interval: "daily" + interval: "weekly" + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-patch" ] + + - package-ecosystem: "cargo" + directory: "/crunchy-cli-core" + schedule: + interval: "weekly" + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-patch" ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b2409a0..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: CI - -on: [ push, pull_request ] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.18 - - - name: Build - run: go build -v . - - - name: Test - run: go test -v . diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index e0f5470..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,67 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - pull_request: - schedule: - - cron: '40 3 * * 2' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # โ„น๏ธ Command-line programs to run using the OS shell. - # ๐Ÿ“š https://git.io/JvXDl - - # โœ๏ธ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..05b2d65 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1645 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "async-channel" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-padding" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_complete" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0fba905b035a30d25c1b585bf1171690712fbb0ad3ac47214963aa4acc36c" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clap_mangen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa149477df7854a1497db0def32b8a65bf98f72a14d04ac75b01938285d83420" +dependencies = [ + "clap", + "roff", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy-cli" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "clap_complete", + "clap_mangen", + "crunchy-cli-core", + "tokio", +] + +[[package]] +name = "crunchy-cli-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "clap", + "crunchyroll-rs", + "ctrlc", + "dirs", + "isahc", + "log", + "num_cpus", + "regex", + "signal-hook", + "sys-locale", + "tempfile", + "terminal_size", + "tokio", +] + +[[package]] +name = "crunchyroll-rs" +version = "0.1.0" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +dependencies = [ + "aes", + "cbc", + "chrono", + "crunchyroll-rs-internal", + "http", + "isahc", + "m3u8-rs", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smart-default", + "tokio", +] + +[[package]] +name = "crunchyroll-rs-internal" +version = "0.1.0" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +dependencies = [ + "darling", + "quote", + "syn", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.59+curl-7.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + +[[package]] +name = "cxx" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" + +[[package]] +name = "isahc" +version = "1.7.0" +source = "git+https://github.com/sagebind/isahc?rev=34f158ef#34f158ef9f87b2387bed2c81936916a29c1eaad1" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "httpdate", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.7+1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "m3u8-rs" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d091887fd4a920417805283b7a838d0dcda68e8d632cd305a4439ee776d1ce" +dependencies = [ + "chrono", + "nom", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.24.0+1.1.1s" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +dependencies = [ + "autocfg", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "polling" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + +[[package]] +name = "rustix" +version = "0.35.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-locale" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3358acbb4acd4146138b9bda219e904a6bb5aaaa237f8eed06f4d6bc1580ecee" +dependencies = [ + "js-sys", + "libc", + "wasm-bindgen", + "web-sys", + "winapi", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" +dependencies = [ + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +dependencies = [ + "autocfg", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c99a2a9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "crunchy-cli" +version = "0.1.0" +edition = "2021" + +[features] +default = ["static-curl"] + +# Embed a static curl library into the binary instead of just linking it. +static-curl = ["crunchy-cli-core/static-curl"] +# Embed a static openssl library into the binary instead of just linking it. If you want to compile this project against +# musl and have openssl issues, this might solve these issues. +static-ssl = ["crunchy-cli-core/static-ssl"] + +[dependencies] +tokio = { version = "1.22", features = ["macros", "rt-multi-thread", "time"], default-features = false } + +crunchy-cli-core = { path = "./crunchy-cli-core" } + +[build-dependencies] +chrono = "0.4" +clap = { version = "4.0", features = ["string"] } +clap_complete = "4.0" +clap_mangen = "0.2" + +# The static-* features must be used here since build dependency features cannot be manipulated from the features +# specified in this Cargo.toml [features]. +crunchy-cli-core = { path = "./crunchy-cli-core", features = ["static-curl", "static-ssl"] } + +[profile.release] +strip = true +opt-level = "z" +lto = true diff --git a/Makefile b/Makefile deleted file mode 100644 index 966872b..0000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -VERSION=development -BINARY_NAME=crunchy -VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION) - -DESTDIR= -PREFIX=/usr - -build: - go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(BINARY_NAME) . - -clean: - rm -f $(BINARY_NAME) $(VERSION_BINARY_NAME)_* - -install: - install -Dm755 $(BINARY_NAME) $(DESTDIR)$(PREFIX)/bin/crunchy-cli - ln -sf ./crunchy-cli $(DESTDIR)$(PREFIX)/bin/crunchy - install -Dm644 crunchy-cli.1 $(DESTDIR)$(PREFIX)/share/man/man1/crunchy-cli.1 - install -Dm644 LICENSE $(DESTDIR)$(PREFIX)/share/licenses/crunchy-cli/LICENSE - -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/crunchy-cli - rm -f $(DESTDIR)$(PREFIX)/bin/crunchy - rm -f $(DESTDIR)$(PREFIX)/share/man/man1/crunchy-cli.1 - rm -f $(DESTDIR)$(PREFIX)/share/licenses/crunchy-cli/LICENSE - -release: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_linux . - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_windows.exe . - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/crunchy-labs/crunchy-cli/utils.Version=$(VERSION)'" -o $(VERSION_BINARY_NAME)_darwin . - - strip $(VERSION_BINARY_NAME)_linux diff --git a/README.md b/README.md index cbe6a37..2229739 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # crunchy-cli -A [Go](https://golang.org) written cli client for [crunchyroll](https://www.crunchyroll.com). To use it, you need a crunchyroll premium account for full access & features. +A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https://www.crunchyroll.com). <p align="center"> <a href="https://github.com/crunchy-labs/crunchy-cli"> @@ -21,178 +21,202 @@ A [Go](https://golang.org) written cli client for [crunchyroll](https://www.crun </p> <p align="center"> - <a href="#%EF%B8%8F-cli">CLI ๐Ÿ–ฅ๏ธ</a> + <a href="#%EF%B8%8F-usage">Usage ๐Ÿ–ฅ๏ธ</a> โ€ข <a href="#%EF%B8%8F-disclaimer">Disclaimer โ˜๏ธ</a> โ€ข <a href="#-license">License โš–</a> </p> -_This repo was former known as **crunchyroll-go** (which still exists but now contains only the library part) but got split up into two separate repositories to provide more flexibility._ - -> This tool relies on the [crunchyroll-go](https://github.com/crunchy-labs/crunchyroll-go) library to communicate with crunchyroll. -> The library enters maintenance mode (only small fixes, no new features) with version v3 in favor of rewriting it completely in Rust. -> **crunchy-cli** follows it (with version v2.3.0) and won't have major updates until the Rust rewrite of the library reaches a good usable state. - -# ๐Ÿ–ฅ๏ธ CLI +> We are in no way affiliated with, maintained, authorized, sponsored, or officially associated with Crunchyroll LLC or any of its subsidiaries or affiliates. +> The official Crunchyroll website can be found at https://crunchyroll.com/. ## โœจ Features -- Download single videos and entire series from [crunchyroll](https://www.crunchyroll.com). -- Archive episode or seasons in an `.mkv` file with multiple subtitles and audios and compress them to gzip or zip files. +- Download single videos and entire series from [Crunchyroll](https://www.crunchyroll.com). +- Archive episode or seasons in an `.mkv` file with multiple subtitles and audios. - Specify a range which episodes to download from an anime. ## ๐Ÿ’พ Get the executable -- ๐Ÿ“ฅ Download the latest binaries [here](https://github.com/crunchy-labs/crunchy-cli/releases/latest) or get it from below: - - [Linux (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_linux) - - [Windows (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_windows.exe) - - [MacOS (x64)](https://smartrelease.bytedream.org/github/crunchy-labs/crunchy-cli/crunchy-{tag}_darwin) -- If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/crunchyroll-go/): - ```shell - $ yay -S crunchy-cli - ``` -- On Windows [scoop](https://scoop.sh/) can be used to install it (added by [@AdmnJ](https://github.com/AdmnJ)): - ```shell - $ scoop bucket add extras # <- in case you haven't added the extra repository already - $ scoop install crunchyroll-go - ``` -- ๐Ÿ›  Build it yourself. Must be done if your target platform is not covered by the [provided binaries](https://github.com/crunchy-labs/crunchy-cli/releases/latest) (like Raspberry Pi or M1 Mac): - - use `make` (requires `go` to be installed): - ```shell - $ git clone https://github.com/crunchy-labs/crunchy-cli - $ cd crunchy-cli - $ make - $ sudo make install # <- only if you want to install it on your system - ``` - - use `go`: - ```shell - $ git clone https://github.com/crunchy-labs/crunchy-cli - $ cd crunchy-cli - $ go build -o crunchy . - ``` +### ๐Ÿ“ฅ Download the latest binaries -## ๐Ÿ“ Examples +Checkout the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the newest release. -_Before reading_: Because of the huge functionality not all cases can be covered in the README. Make sure to check the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli), further usages and options are described there. +### ๐Ÿ›  Build it yourself + +Since we do not support every platform and architecture you may have to build the project yourself. +This requires [git](https://git-scm.com/) and [Cargo](https://doc.rust-lang.org/cargo). +```shell +$ git clone https://github.com/crunchy-labs/crunchy-cli +$ cd crunchy-cli +$ cargo build --release +``` +After the binary has built successfully it is available in `target/release`. + +## ๐Ÿ–ฅ๏ธ Usage + +> All shown command are just examples + +Every command requires you to be logged in with an account. +It doesn't matter if this account is premium or not, both works (but as free user you do not have access to premium content). +You can pass your account via credentials (username & password) or refresh token. + +- Refresh Token + - To get the token you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. + The easiest way to get it is via a browser extension with lets you view your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox Store](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/); [Chrome Store](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). + If installed, search the `etp_rt` entry and extract the value. + - ```shell + $ crunchy --etp-rt "abcd1234-zyxw-9876-98zy-a1b2c3d4e5f6" + ``` +- Credentials + - Credentials must be provided as one single expression. + Username and password must be separated by a `:`. + - ```shell + $ crunchy --credentials "user:password" + ``` ### Login -Before you can do something, you have to log in first. - -This can be performed via crunchyroll account email and password. +If you do not want to provide your credentials every time you execute a command, they can be stored permanently on disk. +This can be done with the `login` subcommand. ```shell -$ crunchy login user@example.com password +$ crunchy --etp-rt "abcd1234-zyxw-9876-98zy-a1b2c3d4e5f6" login ``` -or via refresh token / `etp_rt` cookie - -```shell -$ crunchy login --refresh-token 7578ce50-5712-3gef-b97e-01332d6b588c -``` +Once set, you do not need to provide `--etp-rt` / `--credentials` anymore when using the cli. ### Download -By default, the cli tries to download the episode with your system language as audio. If no streams with your system language are available, the video will be downloaded with japanese audio and hardsubbed subtitles in your system language. -**If your system language is not supported, an error message will be displayed and en-US (american english) will be chosen as language.** +**Supported urls** +- Single episode + ```shell + $ crunchy download https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` +- Episode range + + If you want only specific episodes / seasons of an anime you can easily provide the series url along with a _filter_. + The filter has to be attached to the url. See the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter) for more information + ```shell + $ crunchy download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1] + ``` +- Series + ```shell + $ crunchy download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + +**Options** +- Audio language -```shell -$ crunchy download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome -``` + Which audio the episode(s) should be can be set via the `-a` / `--audio` flag. + This only works if the url points to a series since episode urls are language specific. + ```shell + $ crunchy download -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is your system language. If not supported by Crunchyroll, `en-US` (American English) is the default. -With `-r best` the video(s) will have the best available resolution (mostly 1920x1080 / Full HD). +- Subtitle language -```shell -$ crunchy download -r best https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome -``` + Besides the audio, it's also possible to specify which language the subtitles should have with the `-s` / `--subtitle` flag. + The subtitle will be hardsubbed (burned into the video) and thus, can't be turned off or on. + ```shell + $ crunchy download -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is no subtitle. -The file is by default saved as a `.ts` (mpeg transport stream) file. -`.ts` files may can't be played or are looking very weird (it depends on the video player you are using). With the `-o` flag, you can change the name (and file ending) of the output file. So if you want to save it as, for example, `mp4` -file, just name it `whatever.mp4`. -**You need [ffmpeg](https://ffmpeg.org) to store the video in other file formats.** +- Output filename -```shell -$ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome -``` + You can specify the name of the output file with the `-o` / `--output` flag. + If you want to use any other file format than [`.ts`](https://en.wikipedia.org/wiki/MPEG_transport_stream) you need [ffmpeg](https://ffmpeg.org/). + ```shell + $ crunchy download -o "ditf.ts" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + Default is `{title}.ts`. -With the `--audio` flag you can specify which audio the video should have and with `--subtitle` which subtitle it should have. Type `crunchy help download` to see all available locales. +- Resolution -```shell -$ crunchy download --audio ja-JP --subtitle de-DE https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx -``` - -##### Flags - -The following flags can be (optional) passed to modify the [download](#download) process. - -| Short | Extended | Description | -|-------|----------------|--------------------------------------------------------------------------------| -| `-a` | `--audio` | Forces audio of the video(s). | -| `-s` | `--subtitle` | Forces subtitle of the video(s). | -| `-d` | `--directory` | Directory to download the video(s) to. | -| `-o` | `--output` | Name of the output file. | -| `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | -| `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | + The resolution for videos can be set via the `-r` / `--resolution` flag. + ```shell + $ crunchy download -r worst https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + Default is `best`. ### Archive -Archive works just like [download](#download). It downloads the given videos as `.mkv` files and stores all (soft) subtitles in it. Default audio locales are japanese and your system language (if available) but you can set more or less with -the `--language` flag. +**Supported urls** +- Series -Archive a file + Only series urls are supported since single episode urls are (audio) language locked. + ```shell + $ crunchy archive https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + +**Options** +- Audio languages -```shell -$ crunchy archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome -``` + Which audios the episode(s) should be can be set via the `-a` / `--audio` flag. + ```shell + $ crunchy archive -a ja-JP -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Can be used multiple times. + Default is your system language (if not supported by Crunchyroll, `en-US` (American English) is the default) + `ja-JP` (Japanese). -Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. +- Subtitle languages -```shell -$ crunchy archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx -``` + Besides the audio, it's also possible to specify which languages the subtitles should have with the `-s` / `--subtitle` flag. + ```shell + $ crunchy archive -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is all subtitles. -##### Flags +- Output filename -The following flags can be (optional) passed to modify the [archive](#archive) process. + You can specify the name of the output file with the `-o` / `--output` flag. + The only supported file / container format is [`.mkv`](https://en.wikipedia.org/wiki/Matroska) since it stores / can store multiple audio, video and subtitle streams. + ```shell + $ crunchy archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is `{title}.mkv`. -| Short | Extended | Description | -|-------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `-l` | `--language` | Audio locale which should be downloaded. Can be used multiple times. | -| `-d` | `--directory` | Directory to download the video(s) to. | -| `-o` | `--output` | Name of the output file. | -| `-m` | `--merge` | Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. See the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#archive) for more information. | -| `-c` | `--compress` | If is set, all output will be compresses into an archive. This flag sets the name of the compressed output file and the file ending specifies the compression algorithm (gzip, tar, zip are supported). | -| `-r` | `--resolution` | The resolution of the video(s). `best` for best resolution, `worst` for worst. | -| `-g` | `--goroutines` | Sets how many parallel segment downloads should be used. | +- Resolution + The resolution for videos can be set via the `-r` / `--resolution` flag. + ```shell + $ crunchy archive -r worst https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is `best`. -### Info +- Merge behavior -The `info` displays some information about the account which is used for the cli. + Because of local restrictions (or other reasons) some episodes with different languages does not have the same length (e.g. when some scenes were cut out). + The ideal state, when multiple audios & subtitles used, would be if only one _video_ has to be stored and all other languages can be stored as audio-only. + But, as said, this is not always the case. + With the `-m` / `--merge` flag you can set what you want to do if some video lengths differ. + Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` else like `audio`. + Subtitles will always match to the first / primary audio and video. + ```shell + $ crunchy archive -m audio https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is `auto`. -```shell -$ crunchy info -``` +- Default subtitle -### Update + `--default_subtitle` set which subtitle language should be set as default / auto appear when starting the downloaded video(s). + ```shell + $ crunchy archive --default_subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is none. -If you want to update your local version of `crunchy-cli`, this command makes this easier. -It checks if a new version is available and if so, updates itself. +- No subtitle optimizations -```shell -$ crunchy update -``` - -### Global flags - -These flags you can use across every sub-command: - -| Flag | Description | -|------|------------------------------------------------------| -| `-q` | Disables all output. | -| `-v` | Shows additional debug output. | -| `-p` | Use a proxy to hide your ip / redirect your traffic. | + Subtitles, as Crunchyroll delivers them, look weird in some video players (#66). + This can be fixed by adding a specific entry to the subtitles. + But since this entry is only a de-factor standard and not represented in the official specification of the subtitle format ([`.ass`](https://en.wikipedia.org/wiki/SubStation_Alpha)) it could cause issues with some video players (but no issue got reported so far, so it's relatively safe to use). + `--no_subtitle_optimizations` can disable these optimizations. + ```shell + $ crunchy archive --no_subtitle_optimizations https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` # โ˜๏ธ Disclaimer diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..cc6a0cc --- /dev/null +++ b/build.rs @@ -0,0 +1,112 @@ +use clap::{Command, CommandFactory}; +use clap_complete::shells; +use std::path::{Path, PathBuf}; + +// this build file generates completions for various shells as well as manual pages + +fn main() -> std::io::Result<()> { + // do not generate anything when building non release + if cfg!(debug_assertions) { + return Ok(()); + } + + // note that we're using an anti-pattern here / violate the rust conventions. build script are + // not supposed to write outside of 'OUT_DIR'. to have the generated files in the build "root" + // (the same directory where the output binary lives) is much simpler than in 'OUT_DIR' since + // its nested in sub directories and is difficult to find (at least more difficult than in the + // build root) + let unconventional_out_dir = + std::path::PathBuf::from(std::env::var_os("OUT_DIR").ok_or(std::io::ErrorKind::NotFound)?) + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .to_path_buf(); + + let completions_dir = exist_or_create_dir(unconventional_out_dir.join("completions"))?; + let manpage_dir = exist_or_create_dir(unconventional_out_dir.join("manpages"))?; + + generate_completions(completions_dir)?; + generate_manpages(manpage_dir)?; + + Ok(()) +} + +fn exist_or_create_dir(path: PathBuf) -> std::io::Result<PathBuf> { + if !path.exists() { + std::fs::create_dir(path.clone())? + } + Ok(path) +} + +fn generate_completions(out_dir: PathBuf) -> std::io::Result<()> { + let mut command: Command = crunchy_cli_core::Cli::command(); + + clap_complete::generate_to( + shells::Bash, + &mut command.clone(), + "crunchy-cli", + out_dir.clone(), + )?; + clap_complete::generate_to( + shells::Elvish, + &mut command.clone(), + "crunchy-cli", + out_dir.clone(), + )?; + println!( + "{}", + clap_complete::generate_to( + shells::Fish, + &mut command.clone(), + "crunchy-cli", + out_dir.clone(), + )? + .to_string_lossy() + ); + clap_complete::generate_to( + shells::PowerShell, + &mut command.clone(), + "crunchy-cli", + out_dir.clone(), + )?; + clap_complete::generate_to(shells::Zsh, &mut command, "crunchy-cli", out_dir)?; + + Ok(()) +} + +fn generate_manpages(out_dir: PathBuf) -> std::io::Result<()> { + fn generate_command_manpage( + mut command: Command, + base_path: &Path, + sub_name: &str, + ) -> std::io::Result<()> { + let (file_name, title) = if sub_name.is_empty() { + command = command.name("crunchy-cli"); + ("crunchy-cli.1".to_string(), "crunchy-cli".to_string()) + } else { + command = command.name(format!("crunchy-cli {}", sub_name)); + ( + format!("crunchy-cli-{}.1", sub_name), + format!("crunchy-cli-{}", sub_name), + ) + }; + + let mut command_buf = vec![]; + let man = clap_mangen::Man::new(command) + .title(title) + .date(chrono::Utc::now().format("%b %d, %Y").to_string()); + man.render(&mut command_buf)?; + + std::fs::write(base_path.join(file_name), command_buf) + } + + generate_command_manpage(crunchy_cli_core::Cli::command(), &out_dir, "")?; + generate_command_manpage(crunchy_cli_core::Archive::command(), &out_dir, "archive")?; + generate_command_manpage(crunchy_cli_core::Download::command(), &out_dir, "download")?; + generate_command_manpage(crunchy_cli_core::Login::command(), &out_dir, "login")?; + + Ok(()) +} diff --git a/cli/commands/archive/archive.go b/cli/commands/archive/archive.go deleted file mode 100644 index 744635f..0000000 --- a/cli/commands/archive/archive.go +++ /dev/null @@ -1,818 +0,0 @@ -package archive - -import ( - "bufio" - "bytes" - "context" - "fmt" - "io" - "math" - "os" - "os/exec" - "os/signal" - "path/filepath" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - - "github.com/crunchy-labs/crunchy-cli/cli/commands" - "github.com/crunchy-labs/crunchy-cli/utils" - "github.com/crunchy-labs/crunchyroll-go/v3" - crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" - "github.com/grafov/m3u8" - "github.com/spf13/cobra" -) - -var ( - archiveLanguagesFlag []string - archiveSubLanguagesFlag []string - - archiveDirectoryFlag string - archiveOutputFlag string - archiveTempDirFlag string - - archiveMergeFlag string - - archiveCompressFlag string - - archiveResolutionFlag string - - archiveGoroutinesFlag int -) - -var Cmd = &cobra.Command{ - Use: "archive", - Short: "Stores the given videos with all subtitles and multiple audios in a .mkv file", - Args: cobra.MinimumNArgs(1), - - PreRunE: func(cmd *cobra.Command, args []string) error { - utils.Log.Debug("Validating arguments") - - if !utils.HasFFmpeg() { - return fmt.Errorf("ffmpeg is needed to run this command correctly") - } - utils.Log.Debug("FFmpeg detected") - - if filepath.Ext(archiveOutputFlag) != ".mkv" { - return fmt.Errorf("currently only matroska / .mkv files are supported") - } - - for _, locale := range archiveLanguagesFlag { - if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(locale)) { - // if locale is 'all', match all known locales - if locale == "all" { - archiveLanguagesFlag = utils.LocalesAsStrings() - break - } - return fmt.Errorf("%s is not a valid locale. Choose from: %s", locale, strings.Join(utils.LocalesAsStrings(), ", ")) - } - } - utils.Log.Debug("Using following audio locales: %s", strings.Join(archiveLanguagesFlag, ", ")) - - for _, locale := range archiveSubLanguagesFlag { - if !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(locale)) { - // if locale is 'all', match all known locales - if locale == "all" { - archiveSubLanguagesFlag = utils.LocalesAsStrings() - break - } - return fmt.Errorf("%s is not a valid locale for Subtitels. Choose from: %s", locale, strings.Join(utils.LocalesAsStrings(), ", ")) - } - } - utils.Log.Debug("Using following subtitels locales: %s", strings.Join(archiveSubLanguagesFlag, ", ")) - - var found bool - for _, mode := range []string{"auto", "audio", "video"} { - if archiveMergeFlag == mode { - utils.Log.Debug("Using %s merge behavior", archiveMergeFlag) - found = true - break - } - } - if !found { - return fmt.Errorf("'%s' is no valid merge flag. Use 'auto', 'audio' or 'video'", archiveMergeFlag) - } - - if archiveCompressFlag != "" { - found = false - for _, algo := range []string{".tar", ".tar.gz", ".tgz", ".zip"} { - if strings.HasSuffix(archiveCompressFlag, algo) { - utils.Log.Debug("Using %s compression", algo) - found = true - break - } - } - if !found { - return fmt.Errorf("'%s' is no valid compress algorithm. Valid algorithms / file endings are '.tar', '.tar.gz', '.zip'", - archiveCompressFlag) - } - } - - switch archiveResolutionFlag { - case "1080p", "720p", "480p", "360p": - intRes, _ := strconv.ParseFloat(strings.TrimSuffix(archiveResolutionFlag, "p"), 84) - archiveResolutionFlag = fmt.Sprintf("%.0fx%s", math.Ceil(intRes*(float64(16)/float64(9))), strings.TrimSuffix(archiveResolutionFlag, "p")) - case "240p": - // 240p would round up to 427x240 if used in the case statement above, so it has to be handled separately - archiveResolutionFlag = "428x240" - case "1920x1080", "1280x720", "640x480", "480x360", "428x240", "best", "worst": - default: - return fmt.Errorf("'%s' is not a valid resolution", archiveResolutionFlag) - } - utils.Log.Debug("Using resolution '%s'", archiveResolutionFlag) - - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - if err := commands.LoadCrunchy(); err != nil { - return err - } - - return archive(args) - }, -} - -func init() { - Cmd.Flags().StringSliceVarP(&archiveLanguagesFlag, - "language", - "l", - []string{string(utils.SystemLocale(false)), string(crunchyroll.JP)}, - "Audio locale which should be downloaded. Can be used multiple times") - - Cmd.Flags().StringSliceVarP(&archiveSubLanguagesFlag, - "sublang", - "s", - utils.LocalesAsStrings(), - "Subtitles langs which should be downloaded. Can be used multiple times") - - cwd, _ := os.Getwd() - - Cmd.Flags().StringVarP(&archiveDirectoryFlag, - "directory", - "d", - cwd, - "The directory to store the files into") - - Cmd.Flags().StringVarP(&archiveOutputFlag, - "output", - "o", - "{title}.mkv", - "Name of the output file. If you use the following things in the name, the will get replaced:\n"+ - "\t{title} ยป Title of the video\n"+ - "\t{series_name} ยป Name of the series\n"+ - "\t{season_name} ยป Name of the season\n"+ - "\t{season_number} ยป Number of the season\n"+ - "\t{episode_number} ยป Number of the episode\n"+ - "\t{resolution} ยป Resolution of the video\n"+ - "\t{fps} ยป Frame Rate of the video\n"+ - "\t{audio} ยป Audio locale of the video\n"+ - "\t{subtitle} ยป Subtitle locale of the video") - - Cmd.Flags().StringVar(&archiveTempDirFlag, - "temp", - os.TempDir(), - "Directory to store temporary files in") - - Cmd.Flags().StringVarP(&archiveMergeFlag, - "merge", - "m", - "auto", - "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'") - - Cmd.Flags().StringVarP(&archiveCompressFlag, - "compress", - "c", - "", - "If is set, all output will be compresses into an archive (every url generates a new one). "+ - "This flag sets the name of the compressed output file. The file ending specifies the compression algorithm. "+ - "The following algorithms are supported: gzip, tar, zip") - - Cmd.Flags().StringVarP(&archiveResolutionFlag, - "resolution", - "r", - "best", - "The video resolution. Can either be specified via the pixels, the abbreviation for pixels, or 'common-use' words\n"+ - "\tAvailable pixels: 1920x1080, 1280x720, 640x480, 480x360, 428x240\n"+ - "\tAvailable abbreviations: 1080p, 720p, 480p, 360p, 240p\n"+ - "\tAvailable common-use words: best (best available resolution), worst (worst available resolution)") - - Cmd.Flags().IntVarP(&archiveGoroutinesFlag, - "goroutines", - "g", - runtime.NumCPU(), - "Number of parallel segment downloads") -} - -func archive(urls []string) error { - for i, url := range urls { - utils.Log.SetProcess("Parsing url %d", i+1) - episodes, err := archiveExtractEpisodes(url) - if err != nil { - utils.Log.StopProcess("Failed to parse url %d", i+1) - if utils.Crunchy.Config.Premium { - utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchy-cli/issues/22 for more information") - } - return err - } - utils.Log.StopProcess("Parsed url %d", i+1) - - var compressFile *os.File - var c Compress - - if archiveCompressFlag != "" { - compressFile, err = os.Create(utils.GenerateFilename(archiveCompressFlag, "")) - if err != nil { - return fmt.Errorf("failed to create archive file: %v", err) - } - if strings.HasSuffix(archiveCompressFlag, ".tar") { - c = NewTarCompress(compressFile) - } else if strings.HasSuffix(archiveCompressFlag, ".tar.gz") || strings.HasSuffix(archiveCompressFlag, ".tgz") { - c = NewGzipCompress(compressFile) - } else if strings.HasSuffix(archiveCompressFlag, ".zip") { - c = NewZipCompress(compressFile) - } - } - - for _, season := range episodes { - utils.Log.Info("%s Season %d", season[0].SeriesName, season[0].SeasonNumber) - - for j, info := range season { - utils.Log.Info("\t%d. %s ยป %spx, %.2f FPS (S%02dE%02d)", - j+1, - info.Title, - info.Resolution, - info.FPS, - info.SeasonNumber, - info.EpisodeNumber) - } - } - utils.Log.Empty() - - for j, season := range episodes { - for k, info := range season { - var filename string - var writeCloser io.WriteCloser - if c != nil { - filename = info.FormatString(archiveOutputFlag) - writeCloser, err = c.NewFile(info) - if err != nil { - return fmt.Errorf("failed to pre generate new archive file: %v", err) - } - } else { - dir := info.FormatString(archiveDirectoryFlag) - if _, err = os.Stat(dir); os.IsNotExist(err) { - if err = os.MkdirAll(dir, 0777); err != nil { - return fmt.Errorf("error while creating directory: %v", err) - } - } - filename = utils.GenerateFilename(info.FormatString(archiveOutputFlag), dir) - writeCloser, err = os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create new file: %v", err) - } - } - - if err = archiveInfo(info, writeCloser, filename); err != nil { - writeCloser.Close() - if f, ok := writeCloser.(*os.File); ok { - os.Remove(f.Name()) - } else { - c.Close() - compressFile.Close() - os.RemoveAll(compressFile.Name()) - } - return err - } - writeCloser.Close() - - if i != len(urls)-1 || j != len(episodes)-1 || k != len(season)-1 { - utils.Log.Empty() - } - } - } - if c != nil { - c.Close() - } - if compressFile != nil { - compressFile.Close() - } - } - return nil -} - -func archiveInfo(info utils.FormatInformation, writeCloser io.WriteCloser, filename string) error { - utils.Log.Debug("Entering season %d, episode %d with %d additional formats", info.SeasonNumber, info.EpisodeNumber, len(info.AdditionalFormats)) - - dp, err := createArchiveProgress(info) - if err != nil { - return fmt.Errorf("error while setting up downloader: %v", err) - } - defer func() { - if dp.Total != dp.Current { - fmt.Println() - } - }() - - rootFile, err := os.CreateTemp("", fmt.Sprintf("%s_*.ts", strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename)))) - if err != nil { - return fmt.Errorf("failed to create temp file: %v", err) - } - defer os.Remove(rootFile.Name()) - defer rootFile.Close() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - downloader := crunchyroll.NewDownloader(ctx, rootFile, archiveGoroutinesFlag, func(segment *m3u8.MediaSegment, current, total int, file *os.File) error { - // check if the context was cancelled. - // must be done in to not print any progress messages if ctrl+c was pressed - if ctx.Err() != nil { - return nil - } - - if utils.Log.IsDev() { - dp.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) - } else { - dp.Update() - } - - if current == total { - dp.UpdateMessage("Merging segments", false) - } - return nil - }) - tmp, _ := os.MkdirTemp(archiveTempDirFlag, "crunchy_") - downloader.TempDir = tmp - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt) - go func() { - select { - case <-sig: - signal.Stop(sig) - utils.Log.Err("Exiting... (may take a few seconds)") - utils.Log.Err("To force exit press ctrl+c (again)") - cancel() - // os.Exit(1) is not called since an immediate exit after the cancel function does not let - // the download process enough time to stop gratefully. A result of this is that the temporary - // directory where the segments are downloaded to will not be deleted - case <-ctx.Done(): - // this is just here to end the goroutine and prevent it from running forever without a reason - } - }() - utils.Log.Debug("Set up signal catcher") - - var additionalDownloaderOpts []string - var mergeMessage string - switch archiveMergeFlag { - case "auto": - additionalDownloaderOpts = []string{"-vn"} - for _, format := range info.AdditionalFormats { - if format.Video.Bandwidth != info.Format.Video.Bandwidth { - // revoke the changed FFmpegOpts above - additionalDownloaderOpts = []string{} - break - } - } - if len(additionalDownloaderOpts) > 0 { - mergeMessage = "merging audio for additional formats" - } else { - mergeMessage = "merging video for additional formats" - } - case "audio": - additionalDownloaderOpts = []string{"-vn"} - mergeMessage = "merging audio for additional formats" - case "video": - mergeMessage = "merging video for additional formats" - } - - utils.Log.Info("Downloading episode `%s` to `%s` (%s)", info.Title, filepath.Base(filename), mergeMessage) - utils.Log.Info("\tEpisode: S%02dE%02d", info.SeasonNumber, info.EpisodeNumber) - utils.Log.Info("\tAudio: %s", info.Audio) - utils.Log.Info("\tSubtitle: %s", info.Subtitle) - utils.Log.Info("\tResolution: %spx", info.Resolution) - utils.Log.Info("\tFPS: %.2f", info.FPS) - - var videoFiles, audioFiles, subtitleFiles []string - defer func() { - for _, f := range append(append(videoFiles, audioFiles...), subtitleFiles...) { - os.RemoveAll(f) - } - }() - - var f []string - if f, err = archiveDownloadVideos(downloader, filepath.Base(filename), true, info.Format); err != nil { - if err != ctx.Err() { - return fmt.Errorf("error while downloading: %v", err) - } - return err - } - videoFiles = append(videoFiles, f[0]) - - if len(additionalDownloaderOpts) == 0 { - var videos []string - downloader.FFmpegOpts = additionalDownloaderOpts - if videos, err = archiveDownloadVideos(downloader, filepath.Base(filename), true, info.AdditionalFormats...); err != nil { - return fmt.Errorf("error while downloading additional videos: %v", err) - } - downloader.FFmpegOpts = []string{} - videoFiles = append(videoFiles, videos...) - } else { - var audios []string - if audios, err = archiveDownloadVideos(downloader, filepath.Base(filename), false, info.AdditionalFormats...); err != nil { - return fmt.Errorf("error while downloading additional videos: %v", err) - } - audioFiles = append(audioFiles, audios...) - } - - sort.Sort(crunchyUtils.SubtitlesByLocale(info.Format.Subtitles)) - - sortSubtitles, _ := strconv.ParseBool(os.Getenv("SORT_SUBTITLES")) - if sortSubtitles && len(archiveLanguagesFlag) > 0 { - // this sort the subtitle locales after the languages which were specified - // with the `archiveLanguagesFlag` flag - for _, language := range archiveLanguagesFlag { - for i, subtitle := range info.Format.Subtitles { - if subtitle.Locale == crunchyroll.LOCALE(language) { - info.Format.Subtitles = append([]*crunchyroll.Subtitle{subtitle}, append(info.Format.Subtitles[:i], info.Format.Subtitles[i+1:]...)...) - break - } - } - } - } - - var subtitles []string - if subtitles, err = archiveDownloadSubtitles(filepath.Base(filename), info.Format.Subtitles...); err != nil { - return fmt.Errorf("error while downloading subtitles: %v", err) - } - subtitleFiles = append(subtitleFiles, subtitles...) - - if err = archiveFFmpeg(ctx, writeCloser, videoFiles, audioFiles, subtitleFiles); err != nil { - return fmt.Errorf("failed to merge files: %v", err) - } - - dp.UpdateMessage("Download finished", false) - - signal.Stop(sig) - utils.Log.Debug("Stopped signal catcher") - - utils.Log.Empty() - - return nil -} - -func createArchiveProgress(info utils.FormatInformation) (*commands.DownloadProgress, error) { - var progressCount int - if err := info.Format.InitVideo(); err != nil { - return nil, fmt.Errorf("error while initializing a video: %v", err) - } - // + number of segments a video has +1 is for merging - progressCount += int(info.Format.Video.Chunklist.Count()) + 1 - for _, f := range info.AdditionalFormats { - if f == info.Format { - continue - } - - if err := f.InitVideo(); err != nil { - return nil, err - } - // + number of segments a video has +1 is for merging - progressCount += int(f.Video.Chunklist.Count()) + 1 - } - - dp := &commands.DownloadProgress{ - Prefix: utils.Log.(*commands.Logger).InfoLog.Prefix(), - Message: "Downloading video", - // number of segments a video +1 is for the success message - Total: progressCount + 1, - Dev: utils.Log.IsDev(), - Quiet: utils.Log.(*commands.Logger).IsQuiet(), - } - if utils.Log.IsDev() { - dp.Prefix = utils.Log.(*commands.Logger).DebugLog.Prefix() - } - - return dp, nil -} - -func archiveDownloadVideos(downloader crunchyroll.Downloader, filename string, video bool, formats ...*crunchyroll.Format) ([]string, error) { - var files []string - - for _, format := range formats { - var name string - if video { - name = fmt.Sprintf("%s_%s_video_*.ts", filename, format.AudioLocale) - } else { - name = fmt.Sprintf("%s_%s_audio_*.aac", filename, format.AudioLocale) - } - - f, err := os.CreateTemp("", name) - if err != nil { - return nil, err - } - files = append(files, f.Name()) - - downloader.Writer = f - if err = format.Download(downloader); err != nil { - f.Close() - for _, file := range files { - os.Remove(file) - } - return nil, err - } - f.Close() - - utils.Log.Debug("Downloaded '%s' video", format.AudioLocale) - } - - return files, nil -} - -func archiveDownloadSubtitles(filename string, subtitles ...*crunchyroll.Subtitle) ([]string, error) { - var files []string - - for _, subtitle := range subtitles { - if !utils.ElementInSlice(string(subtitle.Locale), archiveSubLanguagesFlag) { - continue - } - - f, err := os.CreateTemp("", fmt.Sprintf("%s_%s_subtitle_*.ass", filename, subtitle.Locale)) - if err != nil { - return nil, err - } - files = append(files, f.Name()) - - if err := subtitle.Save(f); err != nil { - f.Close() - for _, file := range files { - os.Remove(file) - } - return nil, err - } - f.Close() - - utils.Log.Debug("Downloaded '%s' subtitles", subtitle.Locale) - } - - return files, nil -} - -func archiveFFmpeg(ctx context.Context, dst io.Writer, videoFiles, audioFiles, subtitleFiles []string) error { - var input, maps, metadata []string - re := regexp.MustCompile(`(?m)_([a-z]{2}-([A-Z]{2}|[0-9]{3}))_(video|audio|subtitle)`) - // https://github.com/crunchy-labs/crunchy-cli/issues/32 - videoLength32Fix := regexp.MustCompile(`Duration:\s?(\d+):(\d+):(\d+).(\d+),`) - - videoLength := [4]int{0, 0, 0, 0} - - for i, video := range videoFiles { - input = append(input, "-i", video) - maps = append(maps, "-map", strconv.Itoa(i)) - locale := crunchyroll.LOCALE(re.FindStringSubmatch(video)[1]) - metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:v:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) - - var errBuf bytes.Buffer - cmd := exec.CommandContext(ctx, "ffmpeg", "-i", video) - cmd.Stderr = &errBuf - cmd.Run() - - matches := videoLength32Fix.FindStringSubmatch(errBuf.String()) - hours, _ := strconv.Atoi(matches[1]) - minutes, _ := strconv.Atoi(matches[2]) - seconds, _ := strconv.Atoi(matches[3]) - millis, _ := strconv.Atoi(matches[4]) - - if hours > videoLength[0] { - videoLength = [4]int{hours, minutes, seconds, millis} - } else if hours == videoLength[0] && minutes > videoLength[1] { - videoLength = [4]int{hours, minutes, seconds, millis} - } else if hours == videoLength[0] && minutes == videoLength[1] && seconds > videoLength[2] { - videoLength = [4]int{hours, minutes, seconds, millis} - } else if hours == videoLength[0] && minutes == videoLength[1] && seconds == videoLength[2] && millis > videoLength[3] { - videoLength = [4]int{hours, minutes, seconds, millis} - } - } - - for i, audio := range audioFiles { - input = append(input, "-i", audio) - maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles))+":1") - locale := crunchyroll.LOCALE(re.FindStringSubmatch(audio)[1]) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i+len(videoFiles)), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:a:%d", i+len(videoFiles)), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) - } - - for i, subtitle := range subtitleFiles { - input = append(input, "-i", subtitle) - maps = append(maps, "-map", strconv.Itoa(i+len(videoFiles)+len(audioFiles))) - locale := crunchyroll.LOCALE(re.FindStringSubmatch(subtitle)[1]) - metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("language=%s", locale)) - metadata = append(metadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("title=%s", crunchyUtils.LocaleLanguage(locale))) - } - - commandOptions := []string{"-y"} - commandOptions = append(commandOptions, input...) - commandOptions = append(commandOptions, maps...) - commandOptions = append(commandOptions, metadata...) - // we have to create a temporary file here because it must be seekable - // for ffmpeg. - // ffmpeg could write to dst too, but this would require to re-encode - // the audio which results in much higher time and resource consumption - // (0-1 second with the temp file, ~20 seconds with re-encoding on my system) - file, err := os.CreateTemp("", "") - if err != nil { - return err - } - file.Close() - defer os.Remove(file.Name()) - - commandOptions = append(commandOptions, "-disposition:s:0", "0", "-c", "copy", "-f", "matroska", file.Name()) - - // just a little nicer debug output to copy and paste the ffmpeg for debug reasons - if utils.Log.IsDev() { - var debugOptions []string - - for _, option := range commandOptions { - if strings.HasPrefix(option, "title=") { - debugOptions = append(debugOptions, "title=\""+strings.TrimPrefix(option, "title=")+"\"") - } else if strings.HasPrefix(option, "language=") { - debugOptions = append(debugOptions, "language=\""+strings.TrimPrefix(option, "language=")+"\"") - } else if strings.Contains(option, " ") { - debugOptions = append(debugOptions, "\""+option+"\"") - } else { - debugOptions = append(debugOptions, option) - } - } - utils.Log.Debug("FFmpeg merge command: ffmpeg %s", strings.Join(debugOptions, " ")) - } - - var errBuf bytes.Buffer - cmd := exec.CommandContext(ctx, "ffmpeg", commandOptions...) - cmd.Stderr = &errBuf - if err = cmd.Run(); err != nil { - return fmt.Errorf(errBuf.String()) - } - - file, err = os.Open(file.Name()) - if err != nil { - return err - } - defer file.Close() - - errBuf.Reset() - cmd = exec.CommandContext(ctx, "ffmpeg", "-i", file.Name()) - cmd.Stderr = &errBuf - cmd.Run() - - matches := videoLength32Fix.FindStringSubmatch(errBuf.String()) - hours, _ := strconv.Atoi(matches[1]) - minutes, _ := strconv.Atoi(matches[2]) - seconds, _ := strconv.Atoi(matches[3]) - millis, _ := strconv.Atoi(matches[4]) - - var reencode bool - if hours > videoLength[0] { - reencode = true - } else if hours == videoLength[0] && minutes > videoLength[1] { - reencode = true - } else if hours == videoLength[0] && minutes == videoLength[1] && seconds > videoLength[2] { - reencode = true - } else if hours == videoLength[0] && minutes == videoLength[1] && seconds == videoLength[2] && millis > videoLength[3] { - reencode = true - } - - // very dirty solution to https://github.com/crunchy-labs/crunchy-cli/issues/32. - // this might get triggered when not needed but there is currently no easy way to - // bypass this unwanted triggering - if reencode { - utils.Log.Debug("Re-encode to short video length") - - file.Close() - - tmpFile, _ := os.CreateTemp("", filepath.Base(file.Name())+"-32_fix") - tmpFile.Close() - - errBuf.Reset() - cmd = exec.CommandContext(ctx, "ffmpeg", - "-y", - "-i", file.Name(), - "-map", "0", - "-c", "copy", - "-disposition:s:0", "0", - "-t", fmt.Sprintf("%02d:%02d:%02d.%d", videoLength[0], videoLength[1], videoLength[2], videoLength[3]), - "-f", "matroska", - tmpFile.Name()) - cmd.Stderr = &errBuf - if err = cmd.Run(); err != nil { - return fmt.Errorf(errBuf.String()) - } - - os.Remove(file.Name()) - os.Rename(tmpFile.Name(), file.Name()) - - file, err = os.Open(file.Name()) - if err != nil { - return err - } - defer file.Close() - } - - _, err = bufio.NewWriter(dst).ReadFrom(file) - return err -} - -func archiveExtractEpisodes(url string) ([][]utils.FormatInformation, error) { - var hasJapanese bool - languagesAsLocale := []crunchyroll.LOCALE{crunchyroll.JP} - for _, language := range archiveLanguagesFlag { - locale := crunchyroll.LOCALE(language) - if locale == crunchyroll.JP { - hasJapanese = true - } else { - languagesAsLocale = append(languagesAsLocale, locale) - } - } - - if _, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { - return nil, fmt.Errorf("archiving episodes by url is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") - } else if _, _, _, _, ok := crunchyroll.ParseEpisodeURL(url); ok { - return nil, fmt.Errorf("archiving episodes by url is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") - } - - episodes, err := utils.ExtractEpisodes(url, languagesAsLocale...) - if err != nil { - return nil, err - } - - if !hasJapanese && len(episodes[1:]) == 0 { - return nil, fmt.Errorf("no episodes found") - } - - for i, eps := range episodes { - if len(eps) == 0 { - utils.Log.SetProcess("%s has no matching episodes", languagesAsLocale[i]) - } else if len(episodes[0]) > len(eps) { - utils.Log.SetProcess("%s has %d less episodes than existing in japanese (%d)", languagesAsLocale[i], len(episodes[0])-len(eps), len(episodes[0])) - } - } - - if !hasJapanese { - episodes = episodes[1:] - } - - eps := make(map[int]map[int]*utils.FormatInformation) - for _, lang := range episodes { - for _, season := range crunchyUtils.SortEpisodesBySeason(lang) { - if _, ok := eps[season[0].SeasonNumber]; !ok { - eps[season[0].SeasonNumber] = map[int]*utils.FormatInformation{} - } - for _, episode := range season { - format, err := episode.GetFormat(archiveResolutionFlag, "", false) - if err != nil { - return nil, fmt.Errorf("error while receiving format for %s: %v", episode.Title, err) - } - - if _, ok := eps[episode.SeasonNumber][episode.EpisodeNumber]; !ok { - eps[episode.SeasonNumber][episode.EpisodeNumber] = &utils.FormatInformation{ - Format: format, - AdditionalFormats: make([]*crunchyroll.Format, 0), - - Title: episode.Title, - SeriesName: episode.SeriesTitle, - SeasonName: episode.SeasonTitle, - SeasonNumber: episode.SeasonNumber, - EpisodeNumber: episode.EpisodeNumber, - Resolution: format.Video.Resolution, - FPS: format.Video.FrameRate, - Audio: format.AudioLocale, - } - } else { - eps[episode.SeasonNumber][episode.EpisodeNumber].AdditionalFormats = append(eps[episode.SeasonNumber][episode.EpisodeNumber].AdditionalFormats, format) - } - } - } - } - - var infoFormat [][]utils.FormatInformation - for _, e := range eps { - var tmpFormatInfo []utils.FormatInformation - - var keys []int - for episodeNumber := range e { - keys = append(keys, episodeNumber) - } - sort.Ints(keys) - - for _, key := range keys { - tmpFormatInfo = append(tmpFormatInfo, *e[key]) - } - - infoFormat = append(infoFormat, tmpFormatInfo) - } - - return infoFormat, nil -} diff --git a/cli/commands/archive/compress.go b/cli/commands/archive/compress.go deleted file mode 100644 index e0b9ad4..0000000 --- a/cli/commands/archive/compress.go +++ /dev/null @@ -1,136 +0,0 @@ -package archive - -import ( - "archive/tar" - "archive/zip" - "bytes" - "compress/gzip" - "fmt" - "github.com/crunchy-labs/crunchy-cli/utils" - "io" - "os" - "path/filepath" - "sync" - "time" -) - -type Compress interface { - io.Closer - - NewFile(information utils.FormatInformation) (io.WriteCloser, error) -} - -func NewGzipCompress(file *os.File) *TarCompress { - gw := gzip.NewWriter(file) - return &TarCompress{ - parent: gw, - dst: tar.NewWriter(gw), - } -} - -func NewTarCompress(file *os.File) *TarCompress { - return &TarCompress{ - dst: tar.NewWriter(file), - } -} - -type TarCompress struct { - Compress - - wg sync.WaitGroup - - parent *gzip.Writer - dst *tar.Writer -} - -func (tc *TarCompress) Close() error { - // we have to wait here in case the actual content isn't copied completely into the - // writer yet - tc.wg.Wait() - - var err, err2 error - if tc.parent != nil { - err2 = tc.parent.Close() - } - err = tc.dst.Close() - - if err != nil && err2 != nil { - // best way to show double errors at once that I've found - return fmt.Errorf("%v\n%v", err, err2) - } else if err == nil && err2 != nil { - err = err2 - } - - return err -} - -func (tc *TarCompress) NewFile(information utils.FormatInformation) (io.WriteCloser, error) { - rp, wp := io.Pipe() - go func() { - tc.wg.Add(1) - defer tc.wg.Done() - var buf bytes.Buffer - io.Copy(&buf, rp) - - header := &tar.Header{ - Name: filepath.Join(fmt.Sprintf("S%2d", information.SeasonNumber), information.Title), - ModTime: time.Now(), - Mode: 0644, - Typeflag: tar.TypeReg, - // fun fact: I did not set the size for quiet some time because I thought that it isn't - // required. well because of this I debugged this part for multiple hours because without - // proper size information only a tiny amount gets copied into the tar (or zip) writer. - // this is also the reason why the file content is completely copied into a buffer before - // writing it to the writer. I could bypass this and save some memory but this requires - // some rewriting and im nearly at the (planned) finish for version 2 so nah in the future - // maybe - Size: int64(buf.Len()), - } - tc.dst.WriteHeader(header) - io.Copy(tc.dst, &buf) - }() - return wp, nil -} - -func NewZipCompress(file *os.File) *ZipCompress { - return &ZipCompress{ - dst: zip.NewWriter(file), - } -} - -type ZipCompress struct { - Compress - - wg sync.WaitGroup - - dst *zip.Writer -} - -func (zc *ZipCompress) Close() error { - zc.wg.Wait() - return zc.dst.Close() -} - -func (zc *ZipCompress) NewFile(information utils.FormatInformation) (io.WriteCloser, error) { - rp, wp := io.Pipe() - go func() { - zc.wg.Add(1) - defer zc.wg.Done() - - var buf bytes.Buffer - io.Copy(&buf, rp) - - header := &zip.FileHeader{ - Name: filepath.Join(fmt.Sprintf("S%2d", information.SeasonNumber), information.Title), - Modified: time.Now(), - Method: zip.Deflate, - UncompressedSize64: uint64(buf.Len()), - } - header.SetMode(0644) - - hw, _ := zc.dst.CreateHeader(header) - io.Copy(hw, &buf) - }() - - return wp, nil -} diff --git a/cli/commands/download/download.go b/cli/commands/download/download.go deleted file mode 100644 index 39273d0..0000000 --- a/cli/commands/download/download.go +++ /dev/null @@ -1,368 +0,0 @@ -package download - -import ( - "context" - "fmt" - "github.com/crunchy-labs/crunchy-cli/cli/commands" - "github.com/crunchy-labs/crunchy-cli/utils" - "github.com/crunchy-labs/crunchyroll-go/v3" - crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" - "github.com/grafov/m3u8" - "github.com/spf13/cobra" - "math" - "os" - "os/signal" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" -) - -var ( - downloadAudioFlag string - downloadSubtitleFlag string - - downloadDirectoryFlag string - downloadOutputFlag string - downloadTempDirFlag string - - downloadResolutionFlag string - - downloadGoroutinesFlag int -) - -var Cmd = &cobra.Command{ - Use: "download", - Short: "Download a video", - Args: cobra.MinimumNArgs(1), - - PreRunE: func(cmd *cobra.Command, args []string) error { - utils.Log.Debug("Validating arguments") - - if filepath.Ext(downloadOutputFlag) != ".ts" { - if !utils.HasFFmpeg() { - return fmt.Errorf("the file ending for the output file (%s) is not `.ts`. "+ - "Install ffmpeg (https://ffmpeg.org/download.html) to use other media file endings (e.g. `.mp4`)", downloadOutputFlag) - } else { - utils.Log.Debug("Custom file ending '%s' (ffmpeg is installed)", filepath.Ext(downloadOutputFlag)) - } - } - - if downloadAudioFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadAudioFlag)) { - return fmt.Errorf("%s is not a valid audio locale. Choose from: %s", downloadAudioFlag, strings.Join(utils.LocalesAsStrings(), ", ")) - } else if downloadSubtitleFlag != "" && !crunchyUtils.ValidateLocale(crunchyroll.LOCALE(downloadSubtitleFlag)) { - return fmt.Errorf("%s is not a valid subtitle locale. Choose from: %s", downloadSubtitleFlag, strings.Join(utils.LocalesAsStrings(), ", ")) - } - utils.Log.Debug("Locales: audio: %s / subtitle: %s", downloadAudioFlag, downloadSubtitleFlag) - - switch downloadResolutionFlag { - case "1080p", "720p", "480p", "360p": - intRes, _ := strconv.ParseFloat(strings.TrimSuffix(downloadResolutionFlag, "p"), 84) - downloadResolutionFlag = fmt.Sprintf("%.0fx%s", math.Ceil(intRes*(float64(16)/float64(9))), strings.TrimSuffix(downloadResolutionFlag, "p")) - case "240p": - // 240p would round up to 427x240 if used in the case statement above, so it has to be handled separately - downloadResolutionFlag = "428x240" - case "1920x1080", "1280x720", "640x480", "480x360", "428x240", "best", "worst": - default: - return fmt.Errorf("'%s' is not a valid resolution", downloadResolutionFlag) - } - utils.Log.Debug("Using resolution '%s'", downloadResolutionFlag) - - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - if err := commands.LoadCrunchy(); err != nil { - return err - } - - return download(args) - }, -} - -func init() { - Cmd.Flags().StringVarP(&downloadAudioFlag, "audio", - "a", - "", - "The locale of the audio. Available locales: "+strings.Join(utils.LocalesAsStrings(), ", ")) - Cmd.Flags().StringVarP(&downloadSubtitleFlag, - "subtitle", - "s", - "", - "The locale of the subtitle. Available locales: "+strings.Join(utils.LocalesAsStrings(), ", ")) - - cwd, _ := os.Getwd() - Cmd.Flags().StringVarP(&downloadDirectoryFlag, - "directory", - "d", - cwd, - "The directory to download the file(s) into") - Cmd.Flags().StringVarP(&downloadOutputFlag, - "output", - "o", - "{title}.ts", - "Name of the output file. "+ - "If you use the following things in the name, the will get replaced:\n"+ - "\t{title} ยป Title of the video\n"+ - "\t{series_name} ยป Name of the series\n"+ - "\t{season_name} ยป Name of the season\n"+ - "\t{season_number} ยป Number of the season\n"+ - "\t{episode_number} ยป Number of the episode\n"+ - "\t{resolution} ยป Resolution of the video\n"+ - "\t{fps} ยป Frame Rate of the video\n"+ - "\t{audio} ยป Audio locale of the video\n"+ - "\t{subtitle} ยป Subtitle locale of the video") - Cmd.Flags().StringVar(&downloadTempDirFlag, - "temp", - os.TempDir(), - "Directory to store temporary files in") - - Cmd.Flags().StringVarP(&downloadResolutionFlag, - "resolution", - "r", - "best", - "The video resolution. Can either be specified via the pixels, the abbreviation for pixels, or 'common-use' words\n"+ - "\tAvailable pixels: 1920x1080, 1280x720, 640x480, 480x360, 428x240\n"+ - "\tAvailable abbreviations: 1080p, 720p, 480p, 360p, 240p\n"+ - "\tAvailable common-use words: best (best available resolution), worst (worst available resolution)") - - Cmd.Flags().IntVarP(&downloadGoroutinesFlag, - "goroutines", - "g", - runtime.NumCPU(), - "Sets how many parallel segment downloads should be used") -} - -func download(urls []string) error { - for i, url := range urls { - utils.Log.SetProcess("Parsing url %d", i+1) - episodes, err := downloadExtractEpisodes(url) - if err != nil { - utils.Log.StopProcess("Failed to parse url %d", i+1) - if utils.Crunchy.Config.Premium { - utils.Log.Debug("If the error says no episodes could be found but the passed url is correct and a crunchyroll classic url, " + - "try the corresponding crunchyroll beta url instead and try again. See https://github.com/crunchy-labs/crunchy-cli/issues/22 for more information") - } - return err - } - utils.Log.StopProcess("Parsed url %d", i+1) - - for _, season := range episodes { - utils.Log.Info("%s Season %d", season[0].SeriesName, season[0].SeasonNumber) - - for j, info := range season { - utils.Log.Info("\t%d. %s ยป %spx, %.2f FPS (S%02dE%02d)", - j+1, - info.Title, - info.Resolution, - info.FPS, - info.SeasonNumber, - info.EpisodeNumber) - } - } - utils.Log.Empty() - - for j, season := range episodes { - for k, info := range season { - dir := info.FormatString(downloadDirectoryFlag) - if _, err = os.Stat(dir); os.IsNotExist(err) { - if err = os.MkdirAll(dir, 0777); err != nil { - return fmt.Errorf("error while creating directory: %v", err) - } - } - file, err := os.Create(utils.GenerateFilename(info.FormatString(downloadOutputFlag), dir)) - if err != nil { - return fmt.Errorf("failed to create output file: %v", err) - } - - if err = downloadInfo(info, file); err != nil { - file.Close() - os.Remove(file.Name()) - return err - } - file.Close() - - if i != len(urls)-1 || j != len(episodes)-1 || k != len(season)-1 { - utils.Log.Empty() - } - } - } - } - return nil -} - -func downloadInfo(info utils.FormatInformation, file *os.File) error { - utils.Log.Debug("Entering season %d, episode %d", info.SeasonNumber, info.EpisodeNumber) - - if err := info.Format.InitVideo(); err != nil { - return fmt.Errorf("error while initializing the video: %v", err) - } - - dp := &commands.DownloadProgress{ - Prefix: utils.Log.(*commands.Logger).InfoLog.Prefix(), - Message: "Downloading video", - // number of segments a video has +2 is for merging and the success message - Total: int(info.Format.Video.Chunklist.Count()) + 2, - Dev: utils.Log.IsDev(), - Quiet: utils.Log.(*commands.Logger).IsQuiet(), - } - if utils.Log.IsDev() { - dp.Prefix = utils.Log.(*commands.Logger).DebugLog.Prefix() - } - defer func() { - if dp.Total != dp.Current { - fmt.Println() - } - }() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - downloader := crunchyroll.NewDownloader(ctx, file, downloadGoroutinesFlag, func(segment *m3u8.MediaSegment, current, total int, file *os.File) error { - // check if the context was cancelled. - // must be done in to not print any progress messages if ctrl+c was pressed - if ctx.Err() != nil { - return nil - } - - if utils.Log.IsDev() { - dp.UpdateMessage(fmt.Sprintf("Downloading %d/%d (%.2f%%) ยป %s", current, total, float32(current)/float32(total)*100, segment.URI), false) - } else { - dp.Update() - } - - if current == total { - dp.UpdateMessage("Merging segments", false) - } - return nil - }) - tmp, _ := os.MkdirTemp(downloadTempDirFlag, "crunchy_") - downloader.TempDir = tmp - if utils.HasFFmpeg() { - downloader.FFmpegOpts = make([]string, 0) - } - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt) - go func() { - select { - case <-sig: - signal.Stop(sig) - utils.Log.Err("Exiting... (may take a few seconds)") - utils.Log.Err("To force exit press ctrl+c (again)") - cancel() - // os.Exit(1) is not called because an immediate exit after the cancel function does not let - // the download process enough time to stop gratefully. A result of this is that the temporary - // directory where the segments are downloaded to will not be deleted - case <-ctx.Done(): - // this is just here to end the goroutine and prevent it from running forever without a reason - } - }() - utils.Log.Debug("Set up signal catcher") - - utils.Log.Info("Downloading episode `%s` to `%s`", info.Title, filepath.Base(file.Name())) - utils.Log.Info("\tEpisode: S%02dE%02d", info.SeasonNumber, info.EpisodeNumber) - utils.Log.Info("\tAudio: %s", info.Audio) - utils.Log.Info("\tSubtitle: %s", info.Subtitle) - utils.Log.Info("\tResolution: %spx", info.Resolution) - utils.Log.Info("\tFPS: %.2f", info.FPS) - if err := info.Format.Download(downloader); err != nil { - return fmt.Errorf("error while downloading: %v", err) - } - - dp.UpdateMessage("Download finished", false) - - signal.Stop(sig) - utils.Log.Debug("Stopped signal catcher") - - utils.Log.Empty() - - return nil -} - -func downloadExtractEpisodes(url string) ([][]utils.FormatInformation, error) { - var episodes [][]*crunchyroll.Episode - var final []*crunchyroll.Episode - - if downloadAudioFlag != "" { - if _, ok := crunchyroll.ParseBetaEpisodeURL(url); ok { - return nil, fmt.Errorf("downloading episodes by url and specifying a language is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") - } else if _, _, _, _, ok := crunchyroll.ParseEpisodeURL(url); ok { - return nil, fmt.Errorf("downloading episodes by url and specifying a language is no longer supported (thx crunchyroll). use the series url instead and filter after the given episode (https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter)") - } - - var err error - episodes, err = utils.ExtractEpisodes(url, crunchyroll.JP, crunchyroll.LOCALE(downloadAudioFlag)) - if err != nil { - return nil, err - } - japanese := episodes[0] - custom := episodes[1] - - sort.Sort(crunchyUtils.EpisodesByNumber(japanese)) - sort.Sort(crunchyUtils.EpisodesByNumber(custom)) - - var errMessages []string - - if len(japanese) == 0 || len(japanese) == len(custom) { - final = custom - } else { - for _, jp := range japanese { - before := len(final) - for _, episode := range custom { - if jp.SeasonNumber == episode.SeasonNumber && jp.EpisodeNumber == episode.EpisodeNumber { - final = append(final, episode) - } - } - if before == len(final) { - errMessages = append(errMessages, fmt.Sprintf("%s has no %s audio, using %s as fallback", jp.Title, crunchyroll.LOCALE(downloadAudioFlag), crunchyroll.JP)) - final = append(final, jp) - } - } - } - - if len(errMessages) > 10 { - for _, msg := range errMessages[:10] { - utils.Log.SetProcess(msg) - } - utils.Log.SetProcess("... and %d more", len(errMessages)-10) - } else { - for _, msg := range errMessages { - utils.Log.SetProcess(msg) - } - } - } else { - var err error - episodes, err = utils.ExtractEpisodes(url) - if err != nil { - return nil, err - } else if len(episodes) == 0 { - return nil, fmt.Errorf("cannot find any episode") - } - final = episodes[0] - } - - var infoFormat [][]utils.FormatInformation - for _, season := range crunchyUtils.SortEpisodesBySeason(final) { - tmpFormatInformation := make([]utils.FormatInformation, 0) - for _, episode := range season { - format, err := episode.GetFormat(downloadResolutionFlag, crunchyroll.LOCALE(downloadSubtitleFlag), true) - if err != nil { - return nil, fmt.Errorf("error while receiving format for %s: %v", episode.Title, err) - } - tmpFormatInformation = append(tmpFormatInformation, utils.FormatInformation{ - Format: format, - - Title: episode.Title, - SeriesName: episode.SeriesTitle, - SeasonName: episode.SeasonTitle, - SeasonNumber: episode.SeasonNumber, - EpisodeNumber: episode.EpisodeNumber, - Resolution: format.Video.Resolution, - FPS: format.Video.FrameRate, - Audio: format.AudioLocale, - }) - } - infoFormat = append(infoFormat, tmpFormatInformation) - } - return infoFormat, nil -} diff --git a/cli/commands/info/info.go b/cli/commands/info/info.go deleted file mode 100644 index 650d8c7..0000000 --- a/cli/commands/info/info.go +++ /dev/null @@ -1,40 +0,0 @@ -package info - -import ( - "fmt" - "github.com/crunchy-labs/crunchy-cli/cli/commands" - "github.com/crunchy-labs/crunchy-cli/utils" - crunchyUtils "github.com/crunchy-labs/crunchyroll-go/v3/utils" - "github.com/spf13/cobra" -) - -var Cmd = &cobra.Command{ - Use: "info", - Short: "Shows information about the logged in user", - Args: cobra.MinimumNArgs(0), - - RunE: func(cmd *cobra.Command, args []string) error { - if err := commands.LoadCrunchy(); err != nil { - return err - } - - return info() - }, -} - -func info() error { - account, err := utils.Crunchy.Account() - if err != nil { - return err - } - - fmt.Println("Username: ", account.Username) - fmt.Println("Email: ", account.Email) - fmt.Println("Premium: ", utils.Crunchy.Config.Premium) - fmt.Println("Interface language: ", crunchyUtils.LocaleLanguage(account.PreferredCommunicationLanguage)) - fmt.Println("Subtitle language: ", crunchyUtils.LocaleLanguage(account.PreferredContentSubtitleLanguage)) - fmt.Println("Created: ", account.Created) - fmt.Println("Account ID: ", account.AccountID) - - return nil -} diff --git a/cli/commands/logger.go b/cli/commands/logger.go deleted file mode 100644 index 89a4717..0000000 --- a/cli/commands/logger.go +++ /dev/null @@ -1,196 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/crunchy-labs/crunchy-cli/utils" - "io" - "log" - "os" - "runtime" - "strings" - "sync" - "time" -) - -var prefix, progressDown, progressDownFinish string - -func initPrefixBecauseWindowsSucksBallsHard() { - // dear windows user, please change to a good OS, linux in the best case. - // MICROSHIT DOES NOT GET IT DONE TO SHOW THE SYMBOLS IN THE ELSE CLAUSE - // CORRECTLY. NOT IN THE CMD NOR POWERSHELL. WHY TF, IT IS ONE OF THE MOST - // PROFITABLE COMPANIES ON THIS PLANET AND CANNOT SHOW A PROPER UTF-8 SYMBOL - // IN THEIR OWN PRODUCT WHICH GETS USED MILLION TIMES A DAY - if runtime.GOOS == "windows" { - prefix = "=>" - progressDown = "|" - progressDownFinish = "->" - } else { - prefix = "โžž" - progressDown = "โ†“" - progressDownFinish = "โ†ณ" - } -} - -type progress struct { - message string - stop bool -} - -func NewLogger(debug, info, err bool) *Logger { - initPrefixBecauseWindowsSucksBallsHard() - - debugLog, infoLog, errLog := log.New(io.Discard, prefix+" ", 0), log.New(io.Discard, prefix+" ", 0), log.New(io.Discard, prefix+" ", 0) - - if debug { - debugLog.SetOutput(os.Stdout) - } - if info { - infoLog.SetOutput(os.Stdout) - } - if err { - errLog.SetOutput(os.Stderr) - } - - if debug { - debugLog = log.New(debugLog.Writer(), "[debug] ", 0) - infoLog = log.New(infoLog.Writer(), "[info] ", 0) - errLog = log.New(errLog.Writer(), "[err] ", 0) - } - - return &Logger{ - DebugLog: debugLog, - InfoLog: infoLog, - ErrLog: errLog, - - devView: debug, - } -} - -type Logger struct { - utils.Logger - - DebugLog *log.Logger - InfoLog *log.Logger - ErrLog *log.Logger - - devView bool - - progress chan progress - done chan interface{} - lock sync.Mutex -} - -func (l *Logger) IsDev() bool { - return l.devView -} - -func (l *Logger) IsQuiet() bool { - return l.DebugLog.Writer() == io.Discard && l.InfoLog.Writer() == io.Discard && l.ErrLog.Writer() == io.Discard -} - -func (l *Logger) Debug(format string, v ...interface{}) { - l.DebugLog.Printf(format, v...) -} - -func (l *Logger) Info(format string, v ...interface{}) { - l.InfoLog.Printf(format, v...) -} - -func (l *Logger) Warn(format string, v ...interface{}) { - l.Err(format, v...) -} - -func (l *Logger) Err(format string, v ...interface{}) { - l.ErrLog.Printf(format, v...) -} - -func (l *Logger) Empty() { - if !l.devView && l.InfoLog.Writer() != io.Discard { - fmt.Println("") - } -} - -func (l *Logger) SetProcess(format string, v ...interface{}) { - if l.InfoLog.Writer() == io.Discard { - return - } else if l.devView { - l.Debug(format, v...) - return - } - - initialMessage := fmt.Sprintf(format, v...) - - p := progress{ - message: initialMessage, - } - - l.lock.Lock() - if l.done != nil { - l.progress <- p - return - } else { - l.progress = make(chan progress, 1) - l.progress <- p - l.done = make(chan interface{}) - } - - go func() { - states := []string{"-", "\\", "|", "/"} - - var count int - - for i := 0; ; i++ { - select { - case p := <-l.progress: - if p.stop { - fmt.Printf("\r" + strings.Repeat(" ", len(prefix)+len(initialMessage))) - if count > 1 { - fmt.Printf("\r%s %s\n", progressDownFinish, p.message) - } else { - fmt.Printf("\r%s %s\n", prefix, p.message) - } - - if l.done != nil { - l.done <- nil - } - l.progress = nil - - l.lock.Unlock() - return - } else { - if count > 0 { - fmt.Printf("\r%s %s\n", progressDown, p.message) - } - l.progress = make(chan progress, 1) - - count++ - - fmt.Printf("\r%s %s", states[i/10%4], initialMessage) - l.lock.Unlock() - } - default: - if i%10 == 0 { - fmt.Printf("\r%s %s", states[i/10%4], initialMessage) - } - time.Sleep(35 * time.Millisecond) - } - } - }() -} - -func (l *Logger) StopProcess(format string, v ...interface{}) { - if l.InfoLog.Writer() == io.Discard { - return - } else if l.devView { - l.Debug(format, v...) - return - } - - l.lock.Lock() - l.progress <- progress{ - message: fmt.Sprintf(format, v...), - stop: true, - } - <-l.done - l.done = nil -} diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go deleted file mode 100644 index 9fce181..0000000 --- a/cli/commands/login/login.go +++ /dev/null @@ -1,159 +0,0 @@ -package login - -import ( - "bytes" - "fmt" - "github.com/crunchy-labs/crunchy-cli/cli/commands" - "github.com/crunchy-labs/crunchy-cli/utils" - "github.com/crunchy-labs/crunchyroll-go/v3" - "github.com/spf13/cobra" - "os" -) - -var ( - loginPersistentFlag bool - loginEncryptFlag bool - - loginSessionIDFlag bool - loginRefreshTokenFlag bool -) - -var Cmd = &cobra.Command{ - Use: "login", - Short: "Login to crunchyroll", - Args: cobra.RangeArgs(1, 2), - - RunE: func(cmd *cobra.Command, args []string) error { - if loginSessionIDFlag { - return loginSessionID(args[0]) - } else if loginRefreshTokenFlag { - return loginRefreshToken(args[0]) - } else { - return loginCredentials(args[0], args[1]) - } - }, -} - -func init() { - Cmd.Flags().BoolVar(&loginPersistentFlag, - "persistent", - false, - "If the given credential should be stored persistent") - Cmd.Flags().BoolVar(&loginEncryptFlag, - "encrypt", - false, - "Encrypt the given credentials (won't do anything if --session-id is given or --persistent is not given)") - - Cmd.Flags().BoolVar(&loginSessionIDFlag, - "session-id", - false, - "Use a session id to login instead of username and password") - Cmd.Flags().BoolVar(&loginRefreshTokenFlag, - "refresh-token", - false, - "Use a refresh token to login instead of username and password. Can be obtained by copying the `etp-rt` cookie from beta.crunchyroll.com") - - Cmd.MarkFlagsMutuallyExclusive("session-id", "refresh-token") -} - -func loginCredentials(user, password string) error { - utils.Log.Debug("Logging in via credentials") - c, err := crunchyroll.LoginWithCredentials(user, password, utils.SystemLocale(false), utils.Client) - if err != nil { - return err - } - - if loginPersistentFlag { - var passwd []byte - if loginEncryptFlag { - for { - fmt.Print("Enter password: ") - passwd, err = commands.ReadLineSilent() - if err != nil { - return err - } - fmt.Println() - - fmt.Print("Enter password again: ") - repasswd, err := commands.ReadLineSilent() - if err != nil { - return err - } - fmt.Println() - - if bytes.Equal(passwd, repasswd) { - break - } - fmt.Println("Passwords does not match, try again") - } - } - if err = utils.SaveCredentialsPersistent(user, password, passwd); err != nil { - return err - } - if !loginEncryptFlag { - utils.Log.Warn("The login information will be stored permanently UNENCRYPTED on your drive. " + - "To encrypt it, use the `--encrypt` flag") - } - } - if err = utils.SaveSession(c); err != nil { - return err - } - - if !loginPersistentFlag { - utils.Log.Info("Due to security reasons, you have to login again on the next reboot") - } - - return nil -} - -func loginSessionID(sessionID string) error { - utils.Log.Debug("Logging in via session id") - utils.Log.Warn("Logging in with session id is deprecated and not very reliable. Consider choosing another option (if it fails)") - var c *crunchyroll.Crunchyroll - var err error - if c, err = crunchyroll.LoginWithSessionID(sessionID, utils.SystemLocale(false), utils.Client); err != nil { - return err - } - - if loginPersistentFlag { - if err = utils.SaveSessionPersistent(c); err != nil { - return err - } - utils.Log.Warn("The login information will be stored permanently UNENCRYPTED on your drive") - } - if err = utils.SaveSession(c); err != nil { - return err - } - - if !loginPersistentFlag { - utils.Log.Info("Due to security reasons, you have to login again on the next reboot") - } - - return nil -} - -func loginRefreshToken(refreshToken string) error { - utils.Log.Debug("Logging in via refresh token") - var c *crunchyroll.Crunchyroll - var err error - if c, err = crunchyroll.LoginWithRefreshToken(refreshToken, utils.SystemLocale(false), utils.Client); err != nil { - utils.Log.Err(err.Error()) - os.Exit(1) - } - - if loginPersistentFlag { - if err = utils.SaveSessionPersistent(c); err != nil { - return err - } - utils.Log.Warn("The login information will be stored permanently UNENCRYPTED on your drive") - } - if err = utils.SaveSession(c); err != nil { - return err - } - - if !loginPersistentFlag { - utils.Log.Info("Due to security reasons, you have to login again on the next reboot") - } - - return nil -} diff --git a/cli/commands/unix.go b/cli/commands/unix.go deleted file mode 100644 index ec69180..0000000 --- a/cli/commands/unix.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos - -package commands - -import ( - "bufio" - "os" - "os/exec" - "syscall" -) - -// https://github.com/bgentry/speakeasy/blob/master/speakeasy_unix.go -var stty string - -func init() { - var err error - if stty, err = exec.LookPath("stty"); err != nil { - panic(err) - } -} - -func ReadLineSilent() ([]byte, error) { - pid, err := setEcho(false) - if err != nil { - return nil, err - } - defer setEcho(true) - - syscall.Wait4(pid, nil, 0, nil) - - l, _, err := bufio.NewReader(os.Stdin).ReadLine() - return l, err -} - -func setEcho(on bool) (pid int, err error) { - fds := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()} - - if on { - pid, err = syscall.ForkExec(stty, []string{"stty", "echo"}, &syscall.ProcAttr{Files: fds}) - } else { - pid, err = syscall.ForkExec(stty, []string{"stty", "-echo"}, &syscall.ProcAttr{Files: fds}) - } - - if err != nil { - return 0, err - } - return -} diff --git a/cli/commands/update/update.go b/cli/commands/update/update.go deleted file mode 100644 index 6608f26..0000000 --- a/cli/commands/update/update.go +++ /dev/null @@ -1,151 +0,0 @@ -package update - -import ( - "encoding/json" - "fmt" - "github.com/crunchy-labs/crunchy-cli/utils" - "github.com/spf13/cobra" - "io" - "os" - "os/exec" - "path" - "runtime" - "strings" -) - -var ( - updateInstallFlag bool -) - -var Cmd = &cobra.Command{ - Use: "update", - Short: "Check if updates are available", - Args: cobra.MaximumNArgs(0), - - RunE: func(cmd *cobra.Command, args []string) error { - return update() - }, -} - -func init() { - Cmd.Flags().BoolVarP(&updateInstallFlag, - "install", - "i", - false, - "If set and a new version is available, the new version gets installed") -} - -func update() error { - var release map[string]interface{} - - resp, err := utils.Client.Get("https://api.github.com/repos/crunchy-labs/crunchy-cli/releases/latest") - if err != nil { - return err - } - defer resp.Body.Close() - if err = json.NewDecoder(resp.Body).Decode(&release); err != nil { - return err - } - releaseVersion := strings.TrimPrefix(release["tag_name"].(string), "v") - - if utils.Version == "development" { - utils.Log.Info("Development version, update service not available") - return nil - } - - latestRelease := strings.SplitN(releaseVersion, ".", 4) - if len(latestRelease) != 3 { - return fmt.Errorf("latest tag name (%s) is not parsable", releaseVersion) - } - - internalVersion := strings.SplitN(utils.Version, ".", 4) - if len(internalVersion) != 3 { - return fmt.Errorf("internal version (%s) is not parsable", utils.Version) - } - - utils.Log.Info("Installed version is %s", utils.Version) - - var hasUpdate bool - for i := 0; i < 3; i++ { - if latestRelease[i] < internalVersion[i] { - utils.Log.Info("Local version is newer than version in latest release (%s)", releaseVersion) - return nil - } else if latestRelease[i] > internalVersion[i] { - hasUpdate = true - } - } - - if !hasUpdate { - utils.Log.Info("Version is up-to-date") - return nil - } - - utils.Log.Info("A new version is available (%s): https://github.com/crunchy-labs/crunchy-cli/releases/tag/v%s", releaseVersion, releaseVersion) - - if updateInstallFlag { - if runtime.GOARCH != "amd64" { - return fmt.Errorf("invalid architecture found (%s), only amd64 is currently supported for automatic updating. "+ - "You have to update manually (https://github.com/crunchy-labs/crunchy-cli)", runtime.GOARCH) - } - - var downloadFile string - switch runtime.GOOS { - case "linux": - pacmanCommand := exec.Command("pacman -Q crunchy-cli") - if pacmanCommand.Run() == nil && pacmanCommand.ProcessState.Success() { - utils.Log.Info("crunchy-cli was probably installed via an Arch Linux AUR helper (like yay). Updating via this AUR helper is recommended") - return nil - } - downloadFile = fmt.Sprintf("crunchy-v%s_linux", releaseVersion) - case "darwin": - downloadFile = fmt.Sprintf("crunchy-v%s_darwin", releaseVersion) - case "windows": - downloadFile = fmt.Sprintf("crunchy-v%s_windows.exe", releaseVersion) - default: - return fmt.Errorf("invalid operation system found (%s), only linux, windows and darwin / macos are currently supported. "+ - "You have to update manually (https://github.com/crunchy-labs/crunchy-cli", runtime.GOOS) - } - - executePath := os.Args[0] - var perms os.FileInfo - // check if the path is relative, absolute or non (if so, the executable must be in PATH) - if strings.HasPrefix(executePath, "."+string(os.PathSeparator)) || path.IsAbs(executePath) { - if perms, err = os.Stat(os.Args[0]); err != nil { - return err - } - } else { - executePath, err = exec.LookPath(os.Args[0]) - if err != nil { - return err - } - if perms, err = os.Stat(executePath); err != nil { - return err - } - } - - utils.Log.SetProcess("Updating executable %s", executePath) - - if err = os.Remove(executePath); err != nil { - return err - } - executeFile, err := os.OpenFile(executePath, os.O_CREATE|os.O_WRONLY, perms.Mode()) - if err != nil { - return err - } - defer executeFile.Close() - - resp, err := utils.Client.Get(fmt.Sprintf("https://github.com/crunchy-labs/crunchy-cli/releases/download/v%s/%s", releaseVersion, downloadFile)) - if err != nil { - return err - } - defer resp.Body.Close() - - if _, err = io.Copy(executeFile, resp.Body); err != nil { - return err - } - - utils.Log.StopProcess("Updated executable %s", executePath) - } - - return nil -} diff --git a/cli/commands/utils.go b/cli/commands/utils.go deleted file mode 100644 index 53e51f2..0000000 --- a/cli/commands/utils.go +++ /dev/null @@ -1,125 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/crunchy-labs/crunchy-cli/utils" - "os" - "os/exec" - "runtime" - "strconv" - "strings" - "sync" -) - -type DownloadProgress struct { - Prefix string - Message string - - Total int - Current int - - Dev bool - Quiet bool - - lock sync.Mutex -} - -func (dp *DownloadProgress) Update() { - dp.update("", false) -} - -func (dp *DownloadProgress) UpdateMessage(msg string, permanent bool) { - dp.update(msg, permanent) -} - -func (dp *DownloadProgress) update(msg string, permanent bool) { - if dp.Quiet { - return - } - - if dp.Current >= dp.Total { - return - } - - dp.lock.Lock() - defer dp.lock.Unlock() - dp.Current++ - - if msg == "" { - msg = dp.Message - } - if permanent { - dp.Message = msg - } - - if dp.Dev { - fmt.Printf("%s%s\n", dp.Prefix, msg) - return - } - - percentage := float32(dp.Current) / float32(dp.Total) * 100 - - pre := fmt.Sprintf("%s%s [", dp.Prefix, msg) - post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total) - - // I don't really know why +2 is needed here but without it the Printf below would not print to the line end - progressWidth := terminalWidth() - len(pre) - len(post) + 2 - repeatCount := int(percentage / float32(100) * float32(progressWidth)) - // it can be lower than zero when the terminal is very tiny - if repeatCount < 0 { - repeatCount = 0 - } - progressPercentage := strings.Repeat("=", repeatCount) - if dp.Current != dp.Total { - progressPercentage += ">" - } - - fmt.Printf("\r%s%-"+fmt.Sprint(progressWidth)+"s%s", pre, progressPercentage, post) -} - -func terminalWidth() int { - if runtime.GOOS != "windows" { - cmd := exec.Command("stty", "size") - cmd.Stdin = os.Stdin - res, err := cmd.Output() - if err != nil { - return 60 - } - // on alpine linux the command `stty size` does not respond the terminal size - // but something like "stty: standard input". this may also apply to other systems - splitOutput := strings.SplitN(strings.ReplaceAll(string(res), "\n", ""), " ", 2) - if len(splitOutput) == 1 { - return 60 - } - width, err := strconv.Atoi(splitOutput[1]) - if err != nil { - return 60 - } - return width - } - return 60 -} - -func LoadCrunchy() error { - var encryptionKey []byte - - if utils.IsTempSession() { - encryptionKey = nil - } else { - if encrypted, err := utils.IsSavedSessionEncrypted(); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("to use this command, login first. Type `%s login -h` to get help", os.Args[0]) - } - return err - } else if encrypted { - encryptionKey, err = ReadLineSilent() - if err != nil { - return fmt.Errorf("failed to read password") - } - } - } - - var err error - utils.Crunchy, err = utils.LoadSession(encryptionKey) - return err -} diff --git a/cli/commands/windows.go b/cli/commands/windows.go deleted file mode 100644 index a9bce74..0000000 --- a/cli/commands/windows.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build windows - -package commands - -import ( - "bufio" - "os" - "syscall" -) - -// https://github.com/bgentry/speakeasy/blob/master/speakeasy_windows.go -func ReadLineSilent() ([]byte, error) { - var oldMode uint32 - - if err := syscall.GetConsoleMode(syscall.Stdin, &oldMode); err != nil { - return nil, err - } - - newMode := oldMode &^ 0x0004 - - err := setConsoleMode(syscall.Stdin, newMode) - defer setConsoleMode(syscall.Stdin, oldMode) - - if err != nil { - return nil, err - } - - l, _, err := bufio.NewReader(os.Stdin).ReadLine() - if err != nil { - return nil, err - } - return l, err -} - -func setConsoleMode(console syscall.Handle, mode uint32) error { - dll := syscall.MustLoadDLL("kernel32") - proc := dll.MustFindProc("SetConsoleMode") - _, _, err := proc.Call(uintptr(console), uintptr(mode)) - - return err -} diff --git a/cli/root.go b/cli/root.go deleted file mode 100644 index b589a03..0000000 --- a/cli/root.go +++ /dev/null @@ -1,85 +0,0 @@ -package cli - -import ( - "context" - "fmt" - "github.com/crunchy-labs/crunchy-cli/cli/commands" - "github.com/crunchy-labs/crunchy-cli/cli/commands/archive" - "github.com/crunchy-labs/crunchy-cli/cli/commands/download" - "github.com/crunchy-labs/crunchy-cli/cli/commands/info" - "github.com/crunchy-labs/crunchy-cli/cli/commands/login" - "github.com/crunchy-labs/crunchy-cli/cli/commands/update" - "github.com/crunchy-labs/crunchy-cli/utils" - "github.com/spf13/cobra" - "os" - "runtime/debug" - "strings" -) - -var ( - quietFlag bool - verboseFlag bool - - proxyFlag string - - useragentFlag string -) - -var RootCmd = &cobra.Command{ - Use: "crunchy-cli", - Version: utils.Version, - Short: "Download crunchyroll videos with ease. See the wiki for details about the cli and library: https://github.com/crunchy-labs/crunchy-cli/wiki", - - SilenceErrors: true, - SilenceUsage: true, - - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - if verboseFlag { - utils.Log = commands.NewLogger(true, true, true) - } else if quietFlag { - utils.Log = commands.NewLogger(false, false, false) - } - - utils.Log.Debug("Executing `%s` command with %d arg(s)", cmd.Name(), len(args)) - - utils.Client, err = utils.CreateOrDefaultClient(proxyFlag, useragentFlag) - return - }, -} - -func init() { - RootCmd.PersistentFlags().BoolVarP(&quietFlag, "quiet", "q", false, "Disable all output") - RootCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Adds debug messages to the normal output") - - RootCmd.PersistentFlags().StringVarP(&proxyFlag, "proxy", "p", "", "Proxy to use") - - RootCmd.PersistentFlags().StringVar(&useragentFlag, "useragent", fmt.Sprintf("crunchy-cli/%s", utils.Version), "Useragent to do all request with") - - RootCmd.AddCommand(archive.Cmd) - RootCmd.AddCommand(download.Cmd) - RootCmd.AddCommand(info.Cmd) - RootCmd.AddCommand(login.Cmd) - RootCmd.AddCommand(update.Cmd) - - utils.Log = commands.NewLogger(false, true, true) -} - -func Execute() { - RootCmd.CompletionOptions.HiddenDefaultCmd = true - defer func() { - if r := recover(); r != nil { - if utils.Log.IsDev() { - utils.Log.Err("%v: %s", r, debug.Stack()) - } else { - utils.Log.Err("Unexpected error: %v", r) - } - os.Exit(1) - } - }() - if err := RootCmd.Execute(); err != nil { - if !strings.HasSuffix(err.Error(), context.Canceled.Error()) { - utils.Log.Err("An error occurred: %v", err) - } - os.Exit(1) - } -} diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml new file mode 100644 index 0000000..893b278 --- /dev/null +++ b/crunchy-cli-core/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "crunchy-cli-core" +version = "0.1.0" +edition = "2021" + +[features] +# Embed a static curl library into the binary instead of just linking it. +static-curl = ["crunchyroll-rs/static-curl"] +# Embed a static openssl library into the binary instead of just linking it. If you want to compile this project against +# musl and have openssl issues, this might solve these issues. +static-ssl = ["crunchyroll-rs/static-ssl"] + +[dependencies] +anyhow = "1.0" +async-trait = "0.1" +clap = { version = "4.0", features = ["derive"] } +chrono = "0.4" +crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["stream", "parse"] } +ctrlc = "3.2" +dirs = "4.0" +isahc = { git = "https://github.com/sagebind/isahc", rev = "34f158ef" } +log = { version = "0.4", features = ["std"] } +num_cpus = "1.13" +regex = "1.6" +signal-hook = "0.3" +tempfile = "3.3" +terminal_size = "0.2" +tokio = { version = "1.21", features = ["macros", "rt-multi-thread", "time"] } +sys-locale = "0.2" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs new file mode 100644 index 0000000..21ae13b --- /dev/null +++ b/crunchy-cli-core/src/cli/archive.rs @@ -0,0 +1,567 @@ +use crate::cli::log::tab_info; +use crate::cli::utils::{download_segments, find_resolution}; +use crate::utils::context::Context; +use crate::utils::format::{format_string, Format}; +use crate::utils::log::progress; +use crate::utils::os::{free_file, tempfile}; +use crate::utils::parse::{parse_url, UrlFilter}; +use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; +use crate::Execute; +use anyhow::{bail, Result}; +use crunchyroll_rs::media::{Resolution, StreamSubtitle}; +use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; +use log::{debug, error, info}; +use regex::Regex; +use std::collections::BTreeMap; +use std::io::Write; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use tempfile::TempPath; + +#[derive(Clone, Debug)] +pub enum MergeBehavior { + Auto, + Audio, + Video, +} + +fn parse_merge_behavior(s: &str) -> Result<MergeBehavior, String> { + Ok(match s.to_lowercase().as_str() { + "auto" => MergeBehavior::Auto, + "audio" => MergeBehavior::Audio, + "video" => MergeBehavior::Video, + _ => return Err(format!("'{}' is not a valid merge behavior", s)), + }) +} + +#[derive(Debug, clap::Parser)] +#[clap(about = "Archive a video")] +#[command(arg_required_else_help(true))] +#[command()] +pub struct Archive { + #[arg(help = format!("Audio languages. Can be used multiple times. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Audio languages. Can be used multiple times. \ + Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + #[arg(short, long, default_values_t = vec![crate::utils::locale::system_locale(), Locale::ja_JP])] + audio: Vec<Locale>, + #[arg(help = format!("Subtitle languages. Can be used multiple times. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(short, long, default_values_t = Locale::all())] + subtitle: Vec<Locale>, + + #[arg(help = "Name of the output file")] + #[arg(long_help = "Name of the output file.\ + If you use one of the following pattern they will get replaced:\n \ + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {season_number} โ†’ Number of the season\n \ + {episode_number} โ†’ Number of the episode\n \ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] + #[arg(short, long, default_value = "{title}.mkv")] + output: String, + + #[arg(help = "Video resolution")] + #[arg(long_help = "The video resolution.\ + Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ + Specifying the exact pixels is not recommended, use one of the other options instead. \ + Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ + The available common-use words are 'best' (choose the best resolution available) and 'worst' (worst resolution available)")] + #[arg(short, long, default_value = "best")] + #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] + resolution: Resolution, + + #[arg( + help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio' and 'video'" + )] + #[arg( + long_help = "Because of local restrictions (or other reasons) some episodes with different languages does not have the same length (e.g. when some scenes were cut out). \ + With this flag you can set the behavior when handling multiple language. + Valid options are 'audio' (stores one video and all other languages as audio only), 'video' (stores the video + audio for every language) and 'auto' (detects if videos differ in length: if so, behave like 'video' else like 'audio')" + )] + #[arg(short, long, default_value = "auto")] + #[arg(value_parser = parse_merge_behavior)] + merge: MergeBehavior, + + #[arg( + help = "Set which subtitle language should be set as default / auto shown when starting a video" + )] + #[arg(long)] + default_subtitle: Option<Locale>, + #[arg(help = "Disable subtitle optimizations")] + #[arg( + long_help = "By default, Crunchyroll delivers subtitles in a format which may cause issues in some video players. \ + These issues are fixed internally by setting a flag which is not part of the official specification of the subtitle format. \ + If you do not want this fixes or they cause more trouble than they solve (for you), it can be disabled with this flag" + )] + #[arg(long)] + no_subtitle_optimizations: bool, + + #[arg(help = "Crunchyroll series url(s)")] + urls: Vec<String>, +} + +#[async_trait::async_trait(?Send)] +impl Execute for Archive { + async fn execute(self, ctx: Context) -> Result<()> { + let mut parsed_urls = vec![]; + + for (i, url) in self.urls.iter().enumerate() { + let _progress_handler = progress!("Parsing url {}", i + 1); + match parse_url(&ctx.crunchy, url.clone(), true).await { + Ok((media_collection, url_filter)) => { + parsed_urls.push((media_collection, url_filter)); + info!("Parsed url {}", i + 1) + } + Err(e) => bail!("url {} could not be parsed: {}", url, e), + } + } + + for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { + let archive_formats = match media_collection { + MediaCollection::Series(series) => { + let _progress_handler = progress!("Fetching series details"); + formats_from_series(&self, series, &url_filter).await? + } + MediaCollection::Season(_) => bail!("Archiving a season is not supported"), + MediaCollection::Episode(episode) => bail!("Archiving a episode is not supported. Use url filtering instead to specify the episode (https://www.crunchyroll.com/series/{}/{}[S{}E{}])", episode.metadata.series_id, episode.metadata.series_slug_title, episode.metadata.season_number, episode.metadata.episode_number), + MediaCollection::MovieListing(_) => bail!("Archiving a movie listing is not supported"), + MediaCollection::Movie(_) => bail!("Archiving a movie is not supported") + }; + + if archive_formats.is_empty() { + info!("Skipping url {} (no matching episodes found)", i + 1); + continue; + } + info!("Loaded series information for url {}", i + 1); + + if log::max_level() == log::Level::Debug { + let seasons = sort_formats_after_seasons( + archive_formats + .clone() + .into_iter() + .map(|(a, _)| a.get(0).unwrap().clone()) + .collect(), + ); + debug!("Series has {} seasons", seasons.len()); + for (i, season) in seasons.into_iter().enumerate() { + info!("Season {} ({})", i + 1, season.get(0).unwrap().season_title); + for format in season { + info!( + "{}: {}px, {:.02} FPS (S{:02}E{:02})", + format.title, + format.stream.resolution, + format.stream.fps, + format.season_number, + format.number, + ) + } + } + } else { + for season in sort_formats_after_seasons( + archive_formats + .clone() + .into_iter() + .map(|(a, _)| a.get(0).unwrap().clone()) + .collect(), + ) { + let first = season.get(0).unwrap(); + info!( + "{} Season {} ({})", + first.series_name, first.season_number, first.season_title + ); + + for (i, format) in season.into_iter().enumerate() { + tab_info!( + "{}. {} ยป {}px, {:.2} FPS (S{:02}E{:02})", + i + 1, + format.title, + format.stream.resolution, + format.stream.fps, + format.season_number, + format.number + ) + } + } + } + + for (formats, subtitles) in archive_formats { + let (primary, additionally) = formats.split_first().unwrap(); + + let mut path = PathBuf::from(&self.output); + path = free_file( + path.with_file_name(format_string( + if let Some(fname) = path.file_name() { + fname.to_str().unwrap() + } else { + "{title}.mkv" + } + .to_string(), + primary, + )), + ) + .0; + + info!( + "Downloading {} to '{}'", + primary.title, + path.to_str().unwrap() + ); + tab_info!( + "Episode: S{:02}E{:02}", + primary.season_number, + primary.number + ); + tab_info!( + "Audio: {} (primary), {}", + primary.audio, + additionally + .iter() + .map(|a| a.audio.to_string()) + .collect::<Vec<String>>() + .join(", ") + ); + tab_info!( + "Subtitle: {}", + subtitles + .iter() + .map(|s| { + if let Some(default) = &self.default_subtitle { + if default == &s.locale { + return format!("{} (primary)", default); + } + } + s.locale.to_string() + }) + .collect::<Vec<String>>() + .join(", ") + ); + tab_info!("Resolution: {}", primary.stream.resolution); + tab_info!("FPS: {:.2}", primary.stream.fps); + + let mut video_paths = vec![]; + let mut audio_paths = vec![]; + let mut subtitle_paths = vec![]; + + video_paths.push((download_video(&ctx, primary, false).await?, primary)); + for additional in additionally { + let only_audio = match self.merge { + MergeBehavior::Auto => additionally + .iter() + .all(|a| a.stream.bandwidth == primary.stream.bandwidth), + MergeBehavior::Audio => true, + MergeBehavior::Video => false, + }; + let path = download_video(&ctx, additional, only_audio).await?; + if only_audio { + audio_paths.push((path, additional)) + } else { + video_paths.push((path, additional)) + } + } + + for subtitle in subtitles { + subtitle_paths + .push((download_subtitle(&self, subtitle.clone()).await?, subtitle)) + } + + generate_mkv(&self, path, video_paths, audio_paths, subtitle_paths)? + } + } + + Ok(()) + } +} + +async fn formats_from_series( + archive: &Archive, + series: Media<Series>, + url_filter: &UrlFilter, +) -> Result<Vec<(Vec<Format>, Vec<StreamSubtitle>)>> { + let mut seasons = series.seasons().await?; + + // filter any season out which does not contain the specified audio languages + for season in sort_seasons_after_number(seasons.clone()) { + // get all locales which are specified but not present in the current iterated season and + // print an error saying this + let not_present_audio = archive + .audio + .clone() + .into_iter() + .filter(|l| !season.iter().any(|s| &s.metadata.audio_locale == l)) + .collect::<Vec<Locale>>(); + for not_present in not_present_audio { + error!( + "Season {} of series {} is not available with {} audio", + season.first().unwrap().metadata.season_number, + series.title, + not_present + ) + } + + // remove all seasons with the wrong audio for the current iterated season number + seasons.retain(|s| { + s.metadata.season_number != season.first().unwrap().metadata.season_number + || archive.audio.contains(&s.metadata.audio_locale) + }) + } + + #[allow(clippy::type_complexity)] + let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<StreamSubtitle>)>> = + BTreeMap::new(); + for season in series.seasons().await? { + if !url_filter.is_season_valid(season.metadata.season_number) + || !archive.audio.contains(&season.metadata.audio_locale) + { + continue; + } + + for episode in season.episodes().await? { + if !url_filter.is_episode_valid( + episode.metadata.episode_number, + episode.metadata.season_number, + ) { + continue; + } + + let streams = episode.streams().await?; + let streaming_data = streams.streaming_data(None).await?; + let Some(stream) = find_resolution(streaming_data, &archive.resolution) else { + bail!( + "Resolution ({}x{}) is not available for episode {} ({}) of season {} ({}) of {}", + archive.resolution.width, + archive.resolution.height, + episode.metadata.episode_number, + episode.title, + episode.metadata.season_number, + episode.metadata.season_title, + episode.metadata.series_title + ) + }; + + let (ref mut formats, _) = result + .entry(season.metadata.season_number) + .or_insert_with(BTreeMap::new) + .entry(episode.metadata.episode_number) + .or_insert_with(|| { + let subtitles: Vec<StreamSubtitle> = archive + .subtitle + .iter() + .filter_map(|l| streams.subtitles.get(l).cloned()) + .collect(); + (vec![], subtitles) + }); + formats.push(Format::new_from_episode(episode, stream)); + } + } + + Ok(result.into_values().flat_map(|v| v.into_values()).collect()) +} + +async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Result<TempPath> { + let tempfile = if only_audio { + tempfile(".aac")? + } else { + tempfile(".ts")? + }; + let (_, path) = tempfile.into_parts(); + + let ffmpeg = Command::new("ffmpeg") + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .arg("-y") + .args(["-f", "mpegts", "-i", "pipe:"]) + .args(if only_audio { vec!["-vn"] } else { vec![] }) + .arg(path.to_str().unwrap()) + .spawn()?; + + download_segments( + ctx, + &mut ffmpeg.stdin.unwrap(), + Some(format!("Download {}", format.audio)), + format.stream.segments().await?, + ) + .await?; + + Ok(path) +} + +async fn download_subtitle(archive: &Archive, subtitle: StreamSubtitle) -> Result<TempPath> { + let tempfile = tempfile(".ass")?; + let (mut file, path) = tempfile.into_parts(); + + let mut buf = vec![]; + subtitle.write_to(&mut buf).await?; + if !archive.no_subtitle_optimizations { + buf = fix_subtitle(buf) + } + + file.write_all(buf.as_slice())?; + + Ok(path) +} + +/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video +/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) +/// for more information. +fn fix_subtitle(raw: Vec<u8>) -> Vec<u8> { + let mut script_info = false; + let mut new = String::new(); + + for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { + if line.trim().starts_with('[') && script_info { + new.push_str("ScaledBorderAndShadows: yes\n"); + script_info = false + } else if line.trim() == "[Script Info]" { + script_info = true + } + new.push_str(line); + new.push('\n') + } + + new.into_bytes() +} + +fn generate_mkv( + archive: &Archive, + target: PathBuf, + video_paths: Vec<(TempPath, &Format)>, + audio_paths: Vec<(TempPath, &Format)>, + subtitle_paths: Vec<(TempPath, StreamSubtitle)>, +) -> Result<()> { + let mut input = vec![]; + let mut maps = vec![]; + let mut metadata = vec![]; + + let mut video_length = (0, 0, 0, 0); + + for (i, (video_path, format)) in video_paths.iter().enumerate() { + input.extend(["-i".to_string(), video_path.to_string_lossy().to_string()]); + maps.extend(["-map".to_string(), i.to_string()]); + metadata.extend([ + format!("-metadata:s:v:{}", i), + format!("language={}", format.audio), + ]); + metadata.extend([ + format!("-metadata:s:v:{}", i), + format!("title={}", format.audio.to_human_readable()), + ]); + metadata.extend([ + format!("-metadata:s:a:{}", i), + format!("language={}", format.audio), + ]); + metadata.extend([ + format!("-metadata:s:a:{}", i), + format!("title={}", format.audio.to_human_readable()), + ]); + + let vid_len = get_video_length(video_path.to_path_buf())?; + if vid_len > video_length { + video_length = vid_len + } + } + for (i, (audio_path, format)) in audio_paths.iter().enumerate() { + input.extend(["-i".to_string(), audio_path.to_string_lossy().to_string()]); + maps.extend(["-map".to_string(), (i + video_paths.len()).to_string()]); + metadata.extend([ + format!("-metadata:s:a:{}", i + video_paths.len()), + format!("language={}", format.audio), + ]); + metadata.extend([ + format!("-metadata:s:a:{}", i + video_paths.len()), + format!("title={}", format.audio.to_human_readable()), + ]); + } + for (i, (subtitle_path, subtitle)) in subtitle_paths.iter().enumerate() { + input.extend([ + "-i".to_string(), + subtitle_path.to_string_lossy().to_string(), + ]); + maps.extend([ + "-map".to_string(), + (i + video_paths.len() + audio_paths.len()).to_string(), + ]); + metadata.extend([ + format!("-metadata:s:s:{}", i), + format!("language={}", subtitle.locale), + ]); + metadata.extend([ + format!("-metadata:s:s:{}", i), + format!("title={}", subtitle.locale.to_human_readable()), + ]); + } + + let mut command_args = vec!["-y".to_string()]; + command_args.extend(input); + command_args.extend(maps); + command_args.extend(metadata); + + // set default subtitle + if let Some(default_subtitle) = &archive.default_subtitle { + // if `--default_subtitle <locale>` is given set the default subtitle to the given locale + if let Some(position) = subtitle_paths + .into_iter() + .position(|s| &s.1.locale == default_subtitle) + { + command_args.push(format!("-disposition:s:{}", position)) + } else { + command_args.extend(["-disposition:s:0".to_string(), "0".to_string()]) + } + } else { + command_args.extend(["-disposition:s:0".to_string(), "0".to_string()]) + } + + command_args.extend([ + "-c".to_string(), + "copy".to_string(), + "-f".to_string(), + "matroska".to_string(), + target.to_string_lossy().to_string(), + ]); + + debug!("ffmpeg {}", command_args.join(" ")); + + let ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .args(command_args) + .output()?; + if !ffmpeg.status.success() { + bail!("{}", String::from_utf8_lossy(ffmpeg.stderr.as_slice())) + } + + Ok(()) +} + +/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry +/// long after the actual video ends with artificially extends the video length on some video players. +/// To prevent this, the video length must be hard set with ffmpeg. See +/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// information. +fn get_video_length(path: PathBuf) -> Result<(u32, u32, u32, u32)> { + let video_length = Regex::new(r"Duration:\s?(\d+):(\d+):(\d+).(\d+),")?; + + let ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .arg("-y") + .args(["-i", path.to_str().unwrap()]) + .output()?; + let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; + let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); + + Ok(( + caps[1].parse()?, + caps[2].parse()?, + caps[3].parse()?, + caps[4].parse()?, + )) +} diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs new file mode 100644 index 0000000..7f9e3ec --- /dev/null +++ b/crunchy-cli-core/src/cli/download.rs @@ -0,0 +1,452 @@ +use crate::cli::log::tab_info; +use crate::cli::utils::{download_segments, find_resolution}; +use crate::utils::context::Context; +use crate::utils::format::{format_string, Format}; +use crate::utils::log::progress; +use crate::utils::os::{free_file, has_ffmpeg}; +use crate::utils::parse::{parse_url, UrlFilter}; +use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; +use crate::Execute; +use anyhow::{bail, Result}; +use crunchyroll_rs::media::{Resolution, VariantSegment}; +use crunchyroll_rs::{ + Episode, Locale, Media, MediaCollection, Movie, MovieListing, Season, Series, +}; +use log::{debug, error, info}; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +#[derive(Debug, clap::Parser)] +#[clap(about = "Download a video")] +#[command(arg_required_else_help(true))] +pub struct Download { + #[arg(help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ + Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + #[arg(short, long, default_value_t = crate::utils::locale::system_locale())] + audio: Locale, + #[arg(help = format!("Subtitle language. Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Subtitle language. If set, the subtitle will be burned into the video and cannot be disabled. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(short, long)] + subtitle: Option<Locale>, + + #[arg(help = "Name of the output file")] + #[arg(long_help = "Name of the output file.\ + If you use one of the following pattern they will get replaced:\n \ + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {season_number} โ†’ Number of the season\n \ + {episode_number} โ†’ Number of the episode\n \ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] + #[arg(short, long, default_value = "{title}.ts")] + output: String, + + #[arg(help = "Video resolution")] + #[arg(long_help = "The video resolution.\ + Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ + Specifying the exact pixels is not recommended, use one of the other options instead. \ + Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ + The available common-use words are 'best' (choose the best resolution available) and 'worst' (worst resolution available)")] + #[arg(short, long, default_value = "best")] + #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] + resolution: Resolution, + + #[arg(help = "Url(s) to Crunchyroll episodes or series")] + urls: Vec<String>, +} + +#[async_trait::async_trait(?Send)] +impl Execute for Download { + async fn execute(self, ctx: Context) -> Result<()> { + let mut parsed_urls = vec![]; + + for (i, url) in self.urls.iter().enumerate() { + let _progress_handler = progress!("Parsing url {}", i + 1); + match parse_url(&ctx.crunchy, url.clone(), true).await { + Ok((media_collection, url_filter)) => { + parsed_urls.push((media_collection, url_filter)); + info!("Parsed url {}", i + 1) + } + Err(e) => bail!("url {} could not be parsed: {}", url, e), + } + } + + for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { + let _progress_handler = progress!("Fetching series details"); + let formats = match media_collection { + MediaCollection::Series(series) => { + debug!("Url {} is series ({})", i + 1, series.title); + formats_from_series(&self, series, &url_filter).await? + } + MediaCollection::Season(season) => { + debug!( + "Url {} is season {} ({})", + i + 1, + season.metadata.season_number, + season.title + ); + formats_from_season(&self, season, &url_filter).await? + } + MediaCollection::Episode(episode) => { + debug!( + "Url {} is episode {} ({}) of season {} ({}) of {}", + i + 1, + episode.metadata.episode_number, + episode.title, + episode.metadata.season_number, + episode.metadata.season_title, + episode.metadata.series_title + ); + format_from_episode(&self, episode, &url_filter, false) + .await? + .map(|fmt| vec![fmt]) + } + MediaCollection::MovieListing(movie_listing) => { + debug!("Url {} is movie listing ({})", i + 1, movie_listing.title); + format_from_movie_listing(&self, movie_listing, &url_filter).await? + } + MediaCollection::Movie(movie) => { + debug!("Url {} is movie ({})", i + 1, movie.title); + format_from_movie(&self, movie, &url_filter) + .await? + .map(|fmt| vec![fmt]) + } + }; + + let Some(formats) = formats else { + info!("Skipping url {} (no matching episodes found)", i + 1); + continue; + }; + info!("Loaded series information for url {}", i + 1); + drop(_progress_handler); + + if log::max_level() == log::Level::Debug { + let seasons = sort_formats_after_seasons(formats.clone()); + debug!("Series has {} seasons", seasons.len()); + for (i, season) in seasons.into_iter().enumerate() { + info!("Season {} ({})", i + 1, season.get(0).unwrap().season_title); + for format in season { + info!( + "{}: {}px, {:.02} FPS (S{:02}E{:02})", + format.title, + format.stream.resolution, + format.stream.fps, + format.season_number, + format.number, + ) + } + } + } else { + for season in sort_formats_after_seasons(formats.clone()) { + let first = season.get(0).unwrap(); + info!( + "{} Season {} ({})", + first.series_name, first.season_number, first.season_title + ); + + for (i, format) in season.into_iter().enumerate() { + tab_info!( + "{}. {} ยป {}px, {:.2} FPS (S{:02}E{:02})", + i + 1, + format.title, + format.stream.resolution, + format.stream.fps, + format.season_number, + format.number + ) + } + } + } + + for format in formats { + let mut path = PathBuf::from(&self.output); + path = free_file( + path.with_file_name(format_string( + if let Some(fname) = path.file_name() { + fname.to_str().unwrap() + } else { + "{title}.ts" + } + .to_string(), + &format, + )), + ) + .0; + + let use_ffmpeg = if let Some(extension) = path.extension() { + if extension != "ts" { + if !has_ffmpeg() { + bail!( + "File ending is not `.ts`, ffmpeg is required to convert the video" + ) + } + true + } else { + false + } + } else { + false + }; + + info!( + "Downloading {} to '{}'", + format.title, + path.file_name().unwrap().to_str().unwrap() + ); + tab_info!("Episode: S{:02}E{:02}", format.season_number, format.number); + tab_info!("Audio: {}", format.audio); + tab_info!( + "Subtitles: {}", + self.subtitle + .clone() + .map_or("None".to_string(), |l| l.to_string()) + ); + tab_info!("Resolution: {}", format.stream.resolution); + tab_info!("FPS: {:.2}", format.stream.fps); + + let segments = format.stream.segments().await?; + + if use_ffmpeg { + download_ffmpeg(&ctx, segments, path.as_path()).await?; + } else if path.to_str().unwrap() == "-" { + let mut stdout = std::io::stdout().lock(); + download_segments(&ctx, &mut stdout, None, segments).await?; + } else { + let mut file = File::options().create(true).write(true).open(&path)?; + download_segments(&ctx, &mut file, None, segments).await? + } + } + } + + Ok(()) + } +} + +async fn download_ffmpeg( + ctx: &Context, + segments: Vec<VariantSegment>, + target: &Path, +) -> Result<()> { + let ffmpeg = Command::new("ffmpeg") + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .arg("-y") + .args(["-f", "mpegts", "-i", "pipe:"]) + .args(["-safe", "0"]) + .args(["-c", "copy"]) + .arg(target.to_str().unwrap()) + .spawn()?; + + download_segments(ctx, &mut ffmpeg.stdin.unwrap(), None, segments).await?; + + Ok(()) +} + +async fn formats_from_series( + download: &Download, + series: Media<Series>, + url_filter: &UrlFilter, +) -> Result<Option<Vec<Format>>> { + if !series.metadata.audio_locales.is_empty() + && !series.metadata.audio_locales.contains(&download.audio) + { + error!( + "Series {} is not available with {} audio", + series.title, download.audio + ); + return Ok(None); + } + + let mut seasons = series.seasons().await?; + + // filter any season out which does not contain the specified audio language + for season in sort_seasons_after_number(seasons.clone()) { + // check if the current iterated season has the specified audio language + if !season + .iter() + .any(|s| s.metadata.audio_locale == download.audio) + { + error!( + "Season {} of series {} is not available with {} audio", + season.first().unwrap().metadata.season_number, + series.title, + download.audio + ); + } + + // remove all seasons with the wrong audio for the current iterated season number + seasons.retain(|s| { + s.metadata.season_number != season.first().unwrap().metadata.season_number + || s.metadata.audio_locale == download.audio + }) + } + + let mut formats = vec![]; + for season in seasons { + if let Some(fmts) = formats_from_season(download, season, url_filter).await? { + formats.extend(fmts) + } + } + + Ok(some_vec_or_none(formats)) +} + +async fn formats_from_season( + download: &Download, + season: Media<Season>, + url_filter: &UrlFilter, +) -> Result<Option<Vec<Format>>> { + if season.metadata.audio_locale != download.audio { + error!( + "Season {} ({}) is not available with {} audio", + season.metadata.season_number, season.title, download.audio + ); + return Ok(None); + } else if !url_filter.is_season_valid(season.metadata.season_number) { + return Ok(None); + } + + let mut formats = vec![]; + + for episode in season.episodes().await? { + if let Some(fmt) = format_from_episode(download, episode, url_filter, true).await? { + formats.push(fmt) + } + } + + Ok(some_vec_or_none(formats)) +} + +async fn format_from_episode( + download: &Download, + episode: Media<Episode>, + url_filter: &UrlFilter, + filter_audio: bool, +) -> Result<Option<Format>> { + if filter_audio && episode.metadata.audio_locale != download.audio { + error!( + "Episode {} ({}) of season {} ({}) of {} has no {} audio", + episode.metadata.episode_number, + episode.title, + episode.metadata.season_number, + episode.metadata.season_title, + episode.metadata.series_title, + download.audio + ); + return Ok(None); + } else if !url_filter.is_episode_valid( + episode.metadata.episode_number, + episode.metadata.season_number, + ) { + return Ok(None); + } + + let streams = episode.streams().await?; + let streaming_data = if let Some(subtitle) = &download.subtitle { + if !streams.subtitles.keys().cloned().any(|x| &x == subtitle) { + error!( + "Episode {} ({}) of season {} ({}) of {} has no {} subtitles", + episode.metadata.episode_number, + episode.title, + episode.metadata.season_number, + episode.metadata.season_title, + episode.metadata.series_title, + subtitle + ); + return Ok(None); + } + streams.streaming_data(Some(subtitle.clone())).await? + } else { + streams.streaming_data(None).await? + }; + + let Some(stream) = find_resolution(streaming_data, &download.resolution) else { + bail!( + "Resolution ({}x{}) is not available for episode {} ({}) of season {} ({}) of {}", + download.resolution.width, + download.resolution.height, + episode.metadata.episode_number, + episode.title, + episode.metadata.season_number, + episode.metadata.season_title, + episode.metadata.series_title + ) + }; + + Ok(Some(Format::new_from_episode(episode, stream))) +} + +async fn format_from_movie_listing( + download: &Download, + movie_listing: Media<MovieListing>, + url_filter: &UrlFilter, +) -> Result<Option<Vec<Format>>> { + let mut formats = vec![]; + + for movie in movie_listing.movies().await? { + if let Some(fmt) = format_from_movie(download, movie, url_filter).await? { + formats.push(fmt) + } + } + + Ok(some_vec_or_none(formats)) +} + +async fn format_from_movie( + download: &Download, + movie: Media<Movie>, + _: &UrlFilter, +) -> Result<Option<Format>> { + let streams = movie.streams().await?; + let mut streaming_data = if let Some(subtitle) = &download.subtitle { + if !streams.subtitles.keys().cloned().any(|x| &x == subtitle) { + error!("Movie {} has no {} subtitles", movie.title, subtitle); + return Ok(None); + } + streams.streaming_data(Some(subtitle.clone())).await? + } else { + streams.streaming_data(None).await? + }; + + streaming_data.sort_by(|a, b| a.resolution.width.cmp(&b.resolution.width).reverse()); + let stream = { + match download.resolution.height { + u64::MAX => streaming_data.into_iter().next().unwrap(), + u64::MIN => streaming_data.into_iter().last().unwrap(), + _ => { + if let Some(streaming_data) = streaming_data.into_iter().find(|v| { + download.resolution.height == u64::MAX + || v.resolution.height == download.resolution.height + }) { + streaming_data + } else { + bail!( + "Resolution ({}x{}) is not available for movie {}", + download.resolution.width, + download.resolution.height, + movie.title + ) + } + } + } + }; + + Ok(Some(Format::new_from_movie(movie, stream))) +} + +fn some_vec_or_none<T>(v: Vec<T>) -> Option<Vec<T>> { + if v.is_empty() { + None + } else { + Some(v) + } +} diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs new file mode 100644 index 0000000..d4bb1b3 --- /dev/null +++ b/crunchy-cli-core/src/cli/log.rs @@ -0,0 +1,197 @@ +use log::{ + set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, SetLoggerError, +}; +use std::io::{stdout, Write}; +use std::sync::{mpsc, Mutex}; +use std::thread; +use std::thread::JoinHandle; +use std::time::Duration; + +struct CliProgress { + handler: JoinHandle<()>, + sender: mpsc::SyncSender<(String, Level)>, +} + +impl CliProgress { + fn new(record: &Record) -> Self { + let (tx, rx) = mpsc::sync_channel(1); + + let init_message = format!("{}", record.args()); + let init_level = record.level(); + let handler = thread::spawn(move || { + let states = ["-", "\\", "|", "/"]; + + let mut old_message = init_message.clone(); + let mut latest_info_message = init_message; + let mut old_level = init_level; + for i in 0.. { + let (msg, level) = match rx.try_recv() { + Ok(payload) => payload, + Err(e) => match e { + mpsc::TryRecvError::Empty => (old_message.clone(), old_level), + mpsc::TryRecvError::Disconnected => break, + }, + }; + + // clear last line + // prefix (2), space (1), state (1), space (1), message(n) + let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len())); + + if old_level != level || old_message != msg { + if old_level <= Level::Warn { + let _ = writeln!(stdout(), "\r:: โ€ข {}", old_message); + } else if old_level == Level::Info && level <= Level::Warn { + let _ = writeln!(stdout(), "\r:: โ†’ {}", old_message); + } else if level == Level::Info { + latest_info_message = msg.clone(); + } + } + + let _ = write!( + stdout(), + "\r:: {} {}", + states[i / 2 % states.len()], + if level == Level::Info { + &msg + } else { + &latest_info_message + } + ); + let _ = stdout().flush(); + + old_message = msg; + old_level = level; + + thread::sleep(Duration::from_millis(100)); + } + + // clear last line + // prefix (2), space (1), state (1), space (1), message(n) + let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len())); + let _ = writeln!(stdout(), "\r:: โœ“ {}", old_message); + let _ = stdout().flush(); + }); + + Self { + handler, + sender: tx, + } + } + + fn send(&self, record: &Record) { + let _ = self + .sender + .send((format!("{}", record.args()), record.level())); + } + + fn stop(self) { + drop(self.sender); + let _ = self.handler.join(); + } +} + +#[allow(clippy::type_complexity)] +pub struct CliLogger { + level: LevelFilter, + progress: Mutex<Option<CliProgress>>, +} + +impl Log for CliLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) + || (record.target() != "progress" + && record.target() != "progress_end" + && !record.target().starts_with("crunchy_cli")) + { + return; + } + + if self.level >= LevelFilter::Debug { + self.extended(record); + return; + } + + match record.target() { + "progress" => self.progress(record, false), + "progress_end" => self.progress(record, true), + _ => { + if self.progress.lock().unwrap().is_some() { + self.progress(record, false); + } else if record.level() > Level::Warn { + self.normal(record) + } else { + self.error(record) + } + } + } + } + + fn flush(&self) { + let _ = stdout().flush(); + } +} + +impl CliLogger { + pub fn new(level: LevelFilter) -> Self { + Self { + level, + progress: Mutex::new(None), + } + } + + pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> { + set_max_level(level); + set_boxed_logger(Box::new(CliLogger::new(level))) + } + + fn extended(&self, record: &Record) { + println!( + "[{}] {} {} ({}) {}", + chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + // replace the 'progress' prefix if this function is invoked via 'progress!' + record + .target() + .replacen("progress", "crunchy_cli", 1) + .replacen("progress_end", "crunchy_cli", 1), + format!("{:?}", thread::current().id()) + .replace("ThreadId(", "") + .replace(')', ""), + record.args() + ) + } + + fn normal(&self, record: &Record) { + println!(":: {}", record.args()) + } + + fn error(&self, record: &Record) { + eprintln!(":: {}", record.args()) + } + + fn progress(&self, record: &Record, stop: bool) { + let mut progress_option = self.progress.lock().unwrap(); + if stop && progress_option.is_some() { + progress_option.take().unwrap().stop() + } else if let Some(p) = &*progress_option { + p.send(record); + } else { + *progress_option = Some(CliProgress::new(record)) + } + } +} + +macro_rules! tab_info { + ($($arg:tt)+) => { + if log::max_level() == log::LevelFilter::Debug { + info!($($arg)+) + } else { + info!("\t{}", format!($($arg)+)) + } + } +} +pub(crate) use tab_info; diff --git a/crunchy-cli-core/src/cli/login.rs b/crunchy-cli-core/src/cli/login.rs new file mode 100644 index 0000000..c6d876a --- /dev/null +++ b/crunchy-cli-core/src/cli/login.rs @@ -0,0 +1,39 @@ +use crate::utils::context::Context; +use crate::Execute; +use anyhow::bail; +use anyhow::Result; +use crunchyroll_rs::crunchyroll::SessionToken; +use std::fs; +use std::path::PathBuf; + +#[derive(Debug, clap::Parser)] +#[clap(about = "Save your login credentials persistent on disk")] +pub struct Login { + #[arg(help = "Remove your stored credentials (instead of save them)")] + #[arg(long)] + pub remove: bool, +} + +#[async_trait::async_trait(?Send)] +impl Execute for Login { + async fn execute(self, ctx: Context) -> Result<()> { + if let Some(login_file_path) = login_file_path() { + match ctx.crunchy.session_token().await { + SessionToken::RefreshToken(refresh_token) => Ok(fs::write( + login_file_path, + format!("refresh_token:{}", refresh_token), + )?), + SessionToken::EtpRt(etp_rt) => { + Ok(fs::write(login_file_path, format!("etp_rt:{}", etp_rt))?) + } + SessionToken::Anonymous => bail!("Anonymous login cannot be saved"), + } + } else { + bail!("Cannot find config path") + } + } +} + +pub fn login_file_path() -> Option<PathBuf> { + dirs::config_dir().map(|config_dir| config_dir.join(".crunchy-cli-core")) +} diff --git a/crunchy-cli-core/src/cli/mod.rs b/crunchy-cli-core/src/cli/mod.rs new file mode 100644 index 0000000..c28f8e0 --- /dev/null +++ b/crunchy-cli-core/src/cli/mod.rs @@ -0,0 +1,5 @@ +pub mod archive; +pub mod download; +pub mod log; +pub mod login; +mod utils; diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs new file mode 100644 index 0000000..59816d1 --- /dev/null +++ b/crunchy-cli-core/src/cli/utils.rs @@ -0,0 +1,178 @@ +use crate::utils::context::Context; +use anyhow::Result; +use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; +use isahc::AsyncReadResponseExt; +use log::{debug, LevelFilter}; +use std::borrow::{Borrow, BorrowMut}; +use std::collections::BTreeMap; +use std::io; +use std::io::Write; +use std::sync::{mpsc, Arc, Mutex}; +use std::time::Duration; +use tokio::task::JoinSet; + +pub fn find_resolution( + mut streaming_data: Vec<VariantData>, + resolution: &Resolution, +) -> Option<VariantData> { + streaming_data.sort_by(|a, b| a.resolution.width.cmp(&b.resolution.width).reverse()); + match resolution.height { + u64::MAX => Some(streaming_data.into_iter().next().unwrap()), + u64::MIN => Some(streaming_data.into_iter().last().unwrap()), + _ => streaming_data + .into_iter() + .find(|v| resolution.height == u64::MAX || v.resolution.height == resolution.height), + } +} + +pub async fn download_segments( + ctx: &Context, + writer: &mut impl Write, + message: Option<String>, + segments: Vec<VariantSegment>, +) -> Result<()> { + let total_segments = segments.len(); + + let client = Arc::new(ctx.client.clone()); + let count = Arc::new(Mutex::new(0)); + let amount = Arc::new(Mutex::new(0)); + + // only print progress when log level is info + let output_handler = if log::max_level() == LevelFilter::Info { + let output_count = count.clone(); + let output_amount = amount.clone(); + Some(tokio::spawn(async move { + let sleep_time_ms = 100; + let iter_per_sec = 1000f64 / sleep_time_ms as f64; + + let mut bytes_start = 0f64; + let mut speed = 0f64; + let mut percentage = 0f64; + + while *output_count.lock().unwrap() < total_segments || percentage < 100f64 { + let tmp_amount = *output_amount.lock().unwrap() as f64; + + let tmp_speed = (tmp_amount - bytes_start) / 1024f64 / 1024f64; + if *output_count.lock().unwrap() < 3 { + speed = tmp_speed; + } else { + let (old_speed_ratio, new_speed_ratio) = if iter_per_sec <= 1f64 { + (0f64, 1f64) + } else { + (1f64 - (1f64 / iter_per_sec), (1f64 / iter_per_sec)) + }; + + // calculate the average download speed "smoother" + speed = (speed * old_speed_ratio) + (tmp_speed * new_speed_ratio); + } + + percentage = + (*output_count.lock().unwrap() as f64 / total_segments as f64) * 100f64; + + let size = terminal_size::terminal_size() + .unwrap_or((terminal_size::Width(60), terminal_size::Height(0))) + .0 + .0 as usize; + + let progress_available = size + - if let Some(msg) = &message { + 35 + msg.len() + } else { + 33 + }; + let progress_done_count = + (progress_available as f64 * (percentage / 100f64)).ceil() as usize; + let progress_to_do_count = progress_available - progress_done_count; + + let _ = write!( + io::stdout(), + "\r:: {}{:>5.1} MiB {:>5.2} MiB/s [{}{}] {:>3}%", + message.clone().map_or("".to_string(), |msg| msg + " "), + tmp_amount / 1024f64 / 1024f64, + speed * iter_per_sec, + "#".repeat(progress_done_count), + "-".repeat(progress_to_do_count), + percentage as usize + ); + + bytes_start = tmp_amount; + + tokio::time::sleep(Duration::from_millis(sleep_time_ms)).await; + } + println!() + })) + } else { + None + }; + + let cpus = num_cpus::get(); + let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); + for _ in 0..cpus { + segs.push(vec![]) + } + for (i, segment) in segments.into_iter().enumerate() { + segs[i - ((i / cpus) * cpus)].push(segment); + } + + let (sender, receiver) = mpsc::channel(); + + let mut join_set: JoinSet<Result<()>> = JoinSet::new(); + for num in 0..cpus { + let thread_client = client.clone(); + let thread_sender = sender.clone(); + let thread_segments = segs.remove(0); + let thread_amount = amount.clone(); + let thread_count = count.clone(); + join_set.spawn(async move { + for (i, segment) in thread_segments.into_iter().enumerate() { + let mut response = thread_client.get_async(&segment.url).await?; + let mut buf = response.bytes().await?.to_vec(); + + *thread_amount.lock().unwrap() += buf.len(); + + buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); + debug!( + "Downloaded and decrypted segment {} ({})", + num + (i * cpus), + segment.url + ); + thread_sender.send((num + (i * cpus), buf))?; + + *thread_count.lock().unwrap() += 1; + } + + Ok(()) + }); + } + + let mut data_pos = 0usize; + let mut buf: BTreeMap<usize, Vec<u8>> = BTreeMap::new(); + loop { + // is always `Some` because `sender` does not get dropped when all threads are finished + let data = receiver.recv().unwrap(); + + if data_pos == data.0 { + writer.write_all(data.1.borrow())?; + data_pos += 1; + } else { + buf.insert(data.0, data.1); + } + while let Some(b) = buf.remove(&data_pos) { + writer.write_all(b.borrow())?; + data_pos += 1; + } + + if *count.lock().unwrap() >= total_segments { + break; + } + } + + while let Some(joined) = join_set.join_next().await { + joined?? + } + if let Some(handler) = output_handler { + handler.await? + } + + Ok(()) +} diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs new file mode 100644 index 0000000..faef927 --- /dev/null +++ b/crunchy-cli-core/src/lib.rs @@ -0,0 +1,196 @@ +use crate::cli::log::CliLogger; +use crate::utils::context::Context; +use crate::utils::locale::system_locale; +use crate::utils::log::progress; +use anyhow::bail; +use anyhow::Result; +use clap::{Parser, Subcommand}; +use crunchyroll_rs::{Crunchyroll, Locale}; +use log::{debug, error, info, LevelFilter}; +use std::{env, fs}; + +mod cli; +mod utils; + +pub use cli::{archive::Archive, download::Download, login::Login}; + +#[async_trait::async_trait(?Send)] +trait Execute { + async fn execute(self, ctx: Context) -> Result<()>; +} + +#[derive(Debug, Parser)] +#[clap(author, version, about)] +#[clap(name = "crunchy-cli")] +pub struct Cli { + #[clap(flatten)] + verbosity: Option<Verbosity>, + + #[arg(help = "Overwrite the language in which results are returned. Default is your system language")] + #[arg(long)] + lang: Option<Locale>, + + #[clap(flatten)] + login_method: LoginMethod, + + #[clap(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + Archive(Archive), + Download(Download), + Login(Login), +} + +#[derive(Debug, Parser)] +struct Verbosity { + #[arg(help = "Verbose output")] + #[arg(short)] + v: bool, + + #[arg(help = "Quiet output. Does not print anything unless it's a error")] + #[arg(long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout")] + #[arg(short)] + q: bool, +} + +#[derive(Debug, Parser)] +struct LoginMethod { + #[arg(help = "Login with credentials (username or email and password)")] + #[arg(long_help = "Login with credentials (username or email and password). Must be provided as user:password")] + #[arg(long)] + credentials: Option<String>, + #[arg(help = "Login with the etp-rt cookie")] + #[arg(long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there")] + #[arg(long)] + etp_rt: Option<String>, +} + +pub async fn cli_entrypoint() { + let cli: Cli = Cli::parse(); + + if let Some(verbosity) = &cli.verbosity { + if verbosity.v && verbosity.q { + eprintln!("Output cannot be verbose ('-v') and quiet ('-q') at the same time"); + std::process::exit(1) + } else if verbosity.v { + CliLogger::init(LevelFilter::Debug).unwrap() + } else if verbosity.q { + CliLogger::init(LevelFilter::Error).unwrap() + } + } else { + CliLogger::init(LevelFilter::Info).unwrap() + } + + debug!("cli input: {:?}", cli); + + let ctx = match create_ctx(&cli).await { + Ok(ctx) => ctx, + Err(e) => { + error!("{}", e); + std::process::exit(1) + } + }; + debug!("Created context"); + + ctrlc::set_handler(move || { + debug!("Ctrl-c detected"); + if let Ok(dir) = fs::read_dir(&env::temp_dir()) { + for file in dir.flatten() { + if file + .path() + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .starts_with(".crunchy-cli_") + { + let result = fs::remove_file(file.path()); + debug!( + "Ctrl-c removed temporary file {} {}", + file.path().to_string_lossy(), + if result.is_ok() { + "successfully" + } else { + "not successfully" + } + ) + } + } + } + std::process::exit(1) + }) + .unwrap(); + debug!("Created ctrl-c handler"); + + let result = match cli.command { + Command::Archive(archive) => archive.execute(ctx).await, + Command::Download(download) => download.execute(ctx).await, + Command::Login(login) => { + if login.remove { + Ok(()) + } else { + login.execute(ctx).await + } + } + }; + if let Err(err) = result { + error!("{}", err); + std::process::exit(1) + } +} + +async fn create_ctx(cli: &Cli) -> Result<Context> { + let crunchy = crunchyroll_session(cli).await?; + // TODO: Use crunchy.client() when it's possible + // currently crunchy.client() has a cloudflare bypass built-in to access crunchyroll. the servers + // where crunchy stores their videos can't handle this bypass and simply refuses to connect + let client = isahc::HttpClient::new().unwrap(); + + Ok(Context { crunchy, client }) +} + +async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { + let mut builder = Crunchyroll::builder(); + builder.locale(cli.lang.clone().unwrap_or_else(system_locale)); + + let _progress_handler = progress!("Logging in"); + if cli.login_method.credentials.is_none() && cli.login_method.etp_rt.is_none() { + if let Some(login_file_path) = cli::login::login_file_path() { + if login_file_path.exists() { + let session = fs::read_to_string(login_file_path)?; + if let Some((token_type, token)) = session.split_once(':') { + match token_type { + "refresh_token" => { + return Ok(builder.login_with_refresh_token(token).await?) + } + "etp_rt" => return Ok(builder.login_with_etp_rt(token).await?), + _ => (), + } + } + bail!("Could not read stored session ('{}')", session) + } + } + bail!("Please use a login method ('--credentials' or '--etp_rt')") + } else if cli.login_method.credentials.is_some() && cli.login_method.etp_rt.is_some() { + bail!("Please use only one login method ('--credentials' or '--etp_rt')") + } + + let crunchy = if let Some(credentials) = &cli.login_method.credentials { + if let Some((user, password)) = credentials.split_once(':') { + builder.login_with_credentials(user, password).await? + } else { + bail!("Invalid credentials format. Please provide your credentials as user:password") + } + } else if let Some(etp_rt) = &cli.login_method.etp_rt { + builder.login_with_etp_rt(etp_rt).await? + } else { + bail!("should never happen") + }; + + info!("Logged in"); + + Ok(crunchy) +} diff --git a/crunchy-cli-core/src/utils/clap.rs b/crunchy-cli-core/src/utils/clap.rs new file mode 100644 index 0000000..4e27a5e --- /dev/null +++ b/crunchy-cli-core/src/utils/clap.rs @@ -0,0 +1,6 @@ +use crate::utils::parse::parse_resolution; +use crunchyroll_rs::media::Resolution; + +pub fn clap_parse_resolution(s: &str) -> Result<Resolution, String> { + parse_resolution(s.to_string()).map_err(|e| e.to_string()) +} diff --git a/crunchy-cli-core/src/utils/context.rs b/crunchy-cli-core/src/utils/context.rs new file mode 100644 index 0000000..6a2fd21 --- /dev/null +++ b/crunchy-cli-core/src/utils/context.rs @@ -0,0 +1,6 @@ +use crunchyroll_rs::Crunchyroll; + +pub struct Context { + pub crunchy: Crunchyroll, + pub client: isahc::HttpClient, +} diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs new file mode 100644 index 0000000..3dd7f74 --- /dev/null +++ b/crunchy-cli-core/src/utils/format.rs @@ -0,0 +1,77 @@ +use crunchyroll_rs::media::VariantData; +use crunchyroll_rs::{Episode, Locale, Media, Movie}; +use std::time::Duration; + +#[derive(Clone)] +pub struct Format { + pub id: String, + pub title: String, + pub description: String, + pub number: u32, + pub audio: Locale, + + pub duration: Duration, + pub stream: VariantData, + + pub series_id: String, + pub series_name: String, + + pub season_id: String, + pub season_title: String, + pub season_number: u32, +} + +impl Format { + pub fn new_from_episode(episode: Media<Episode>, stream: VariantData) -> Self { + Self { + id: episode.id, + title: episode.title, + description: episode.description, + number: episode.metadata.episode_number, + audio: episode.metadata.audio_locale, + + duration: episode.metadata.duration.to_std().unwrap(), + stream, + + series_id: episode.metadata.series_id, + series_name: episode.metadata.series_title, + + season_id: episode.metadata.season_id, + season_title: episode.metadata.season_title, + season_number: episode.metadata.season_number, + } + } + + pub fn new_from_movie(movie: Media<Movie>, stream: VariantData) -> Self { + Self { + id: movie.id, + title: movie.title, + description: movie.description, + number: 1, + audio: Locale::ja_JP, + + duration: movie.metadata.duration.to_std().unwrap(), + stream, + + series_id: movie.metadata.movie_listing_id.clone(), + series_name: movie.metadata.movie_listing_title.clone(), + + season_id: movie.metadata.movie_listing_id, + season_title: movie.metadata.movie_listing_title, + season_number: 1, + } + } +} + +pub fn format_string(s: String, format: &Format) -> String { + s.replace("{title}", &format.title) + .replace("{series_name}", &format.series_name) + .replace("{season_name}", &format.season_title) + .replace("{audio}", &format.audio.to_string()) + .replace("{resolution}", &format.stream.resolution.to_string()) + .replace("{season_number}", &format.season_number.to_string()) + .replace("{episode_number}", &format.number.to_string()) + .replace("{series_id}", &format.series_id) + .replace("{season_id}", &format.season_id) + .replace("{episode_id}", &format.id) +} diff --git a/crunchy-cli-core/src/utils/locale.rs b/crunchy-cli-core/src/utils/locale.rs new file mode 100644 index 0000000..300204d --- /dev/null +++ b/crunchy-cli-core/src/utils/locale.rs @@ -0,0 +1,15 @@ +use crunchyroll_rs::Locale; + +/// Return the locale of the system. +pub fn system_locale() -> Locale { + if let Some(system_locale) = sys_locale::get_locale() { + let locale = Locale::from(system_locale); + if let Locale::Custom(_) = locale { + Locale::en_US + } else { + locale + } + } else { + Locale::en_US + } +} diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs new file mode 100644 index 0000000..b9fa939 --- /dev/null +++ b/crunchy-cli-core/src/utils/log.rs @@ -0,0 +1,19 @@ +use log::info; + +pub struct ProgressHandler; + +impl Drop for ProgressHandler { + fn drop(&mut self) { + info!(target: "progress_end", "") + } +} + +macro_rules! progress { + ($($arg:tt)+) => { + { + log::info!(target: "progress", $($arg)+); + $crate::utils::log::ProgressHandler{} + } + } +} +pub(crate) use progress; diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs new file mode 100644 index 0000000..5f7a5d2 --- /dev/null +++ b/crunchy-cli-core/src/utils/mod.rs @@ -0,0 +1,8 @@ +pub mod clap; +pub mod context; +pub mod format; +pub mod locale; +pub mod log; +pub mod os; +pub mod parse; +pub mod sort; diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs new file mode 100644 index 0000000..317381d --- /dev/null +++ b/crunchy-cli-core/src/utils/os.rs @@ -0,0 +1,52 @@ +use log::debug; +use std::io::ErrorKind; +use std::path::PathBuf; +use std::process::Command; +use std::{env, io}; +use tempfile::{Builder, NamedTempFile}; + +pub fn has_ffmpeg() -> bool { + if let Err(e) = Command::new("ffmpeg").spawn() { + if ErrorKind::NotFound != e.kind() { + debug!( + "unknown error occurred while checking if ffmpeg exists: {}", + e.kind() + ) + } + false + } else { + true + } +} + +/// Any tempfiles should be created with this function. The prefix and directory of every file +/// created with this method stays the same which is helpful to query all existing tempfiles and +/// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like +/// setting the wrong prefix if done manually. +pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { + let tempfile = Builder::default() + .prefix(".crunchy-cli_") + .suffix(suffix.as_ref()) + .tempfile_in(&env::temp_dir())?; + debug!( + "Created temporary file: {}", + tempfile.path().to_string_lossy() + ); + Ok(tempfile) +} + +/// Check if the given path exists and rename it until the new (renamed) file does not exist. +pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { + let mut i = 0; + while path.exists() { + i += 1; + + let ext = path.extension().unwrap().to_str().unwrap(); + let mut filename = path.file_name().unwrap().to_str().unwrap(); + + filename = &filename[0..filename.len() - ext.len() - 1]; + + path.set_file_name(format!("{} ({}).{}", filename, i, ext)) + } + (path, i != 0) +} diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs new file mode 100644 index 0000000..fbfea35 --- /dev/null +++ b/crunchy-cli-core/src/utils/parse.rs @@ -0,0 +1,170 @@ +use anyhow::{anyhow, bail, Result}; +use crunchyroll_rs::media::Resolution; +use crunchyroll_rs::{Crunchyroll, MediaCollection, UrlType}; +use log::debug; +use regex::Regex; + +/// Define a filter, based on season and episode number to filter episodes / movies. +/// If a struct instance equals the [`Default::default()`] it's considered that no filter is applied. +/// If `from_*` is [`None`] they're set to [`u32::MIN`]. +/// If `to_*` is [`None`] they're set to [`u32::MAX`]. +#[derive(Debug)] +pub struct InnerUrlFilter { + from_episode: Option<u32>, + to_episode: Option<u32>, + from_season: Option<u32>, + to_season: Option<u32>, +} + +#[derive(Debug, Default)] +pub struct UrlFilter { + inner: Vec<InnerUrlFilter>, +} + +impl UrlFilter { + pub fn is_season_valid(&self, season: u32) -> bool { + self.inner.iter().any(|f| { + let from_season = f.from_season.unwrap_or(u32::MIN); + let to_season = f.to_season.unwrap_or(u32::MAX); + + season >= from_season && season <= to_season + }) + } + + pub fn is_episode_valid(&self, episode: u32, season: u32) -> bool { + self.inner.iter().any(|f| { + let from_episode = f.from_episode.unwrap_or(u32::MIN); + let to_episode = f.to_episode.unwrap_or(u32::MAX); + let from_season = f.from_season.unwrap_or(u32::MIN); + let to_season = f.to_season.unwrap_or(u32::MAX); + + episode >= from_episode + && episode <= to_episode + && season >= from_season + && season <= to_season + }) + } +} + +/// Parse a url and return all [`crunchyroll_rs::Media<crunchyroll_rs::Episode>`] & +/// [`crunchyroll_rs::Media<crunchyroll_rs::Movie>`] which could be related to it. +/// +/// The `with_filter` arguments says if filtering should be enabled for the url. Filtering is a +/// specific pattern at the end of the url which declares which parts of the url content should be +/// returned / filtered (out). _This only works if the url points to a series_. +/// +/// Examples how filtering works: +/// - `...[E5]` - Download the fifth episode. +/// - `...[S1]` - Download the full first season. +/// - `...[-S2]` - Download all seasons up to and including season 2. +/// - `...[S3E4-]` - Download all episodes from and including season 3, episode 4. +/// - `...[S1E4-S3]` - Download all episodes from and including season 1, episode 4, until andincluding season 3. +/// - `...[S3,S5]` - Download episode 3 and 5. +/// - `...[S1-S3,S4E2-S4E6]` - Download season 1 to 3 and episode 2 to episode 6 of season 4. + +/// In practice, it would look like this: `https://beta.crunchyroll.com/series/12345678/example[S1E5-S3E2]`. +pub async fn parse_url( + crunchy: &Crunchyroll, + mut url: String, + with_filter: bool, +) -> Result<(MediaCollection, UrlFilter)> { + let url_filter = if with_filter { + debug!("Url may contain filters"); + + let open_index = url.rfind('[').unwrap_or(0); + let close_index = url.rfind(']').unwrap_or(0); + + let filter = if open_index < close_index { + let filter = url.as_str()[open_index + 1..close_index].to_string(); + url = url.as_str()[0..open_index].to_string(); + filter + } else { + "".to_string() + }; + + let filter_regex = Regex::new(r"((S(?P<from_season>\d+))?(E(?P<from_episode>\d+))?)(((?P<dash>-)((S(?P<to_season>\d+))?(E(?P<to_episode>\d+))?))?)(,|$)").unwrap(); + + let mut filters = vec![]; + + for capture in filter_regex.captures_iter(&filter) { + let dash = capture.name("dash").is_some(); + let from_episode = capture + .name("from_episode") + .map_or(anyhow::Ok(None), |fe| Ok(Some(fe.as_str().parse()?)))?; + let to_episode = capture + .name("to_episode") + .map_or(anyhow::Ok(if dash { None } else { from_episode }), |te| { + Ok(Some(te.as_str().parse()?)) + })?; + let from_season = capture + .name("from_season") + .map_or(anyhow::Ok(None), |fs| Ok(Some(fs.as_str().parse()?)))?; + let to_season = capture + .name("to_season") + .map_or(anyhow::Ok(if dash { None } else { from_season }), |ts| { + Ok(Some(ts.as_str().parse()?)) + })?; + + filters.push(InnerUrlFilter { + from_episode, + to_episode, + from_season, + to_season, + }) + } + + let url_filter = UrlFilter { inner: filters }; + + debug!("Url filter: {:?}", url_filter); + + url_filter + } else { + UrlFilter::default() + }; + + let parsed_url = crunchyroll_rs::parse_url(url).map_or(Err(anyhow!("Invalid url")), Ok)?; + debug!("Url type: {:?}", parsed_url); + let media_collection = match parsed_url { + UrlType::Series(id) | UrlType::MovieListing(id) | UrlType::EpisodeOrMovie(id) => { + crunchy.media_collection_from_id(id).await? + } + }; + + Ok((media_collection, url_filter)) +} + +/// Parse a resolution given as a [`String`] to a [`crunchyroll_rs::media::Resolution`]. +pub fn parse_resolution(mut resolution: String) -> Result<Resolution> { + resolution = resolution.to_lowercase(); + + if resolution == "best" { + Ok(Resolution { + width: u64::MAX, + height: u64::MAX, + }) + } else if resolution == "worst" { + Ok(Resolution { + width: u64::MIN, + height: u64::MIN, + }) + } else if resolution.ends_with('p') { + let without_p = resolution.as_str()[0..resolution.len() - 2] + .parse() + .map_err(|_| anyhow!("Could not parse resolution"))?; + Ok(Resolution { + width: without_p * 16 / 9, + height: without_p, + }) + } else if let Some((w, h)) = resolution.split_once('x') { + Ok(Resolution { + width: w + .parse() + .map_err(|_| anyhow!("Could not parse resolution"))?, + height: h + .parse() + .map_err(|_| anyhow!("Could not parse resolution"))?, + }) + } else { + bail!("Could not parse resolution") + } +} diff --git a/crunchy-cli-core/src/utils/sort.rs b/crunchy-cli-core/src/utils/sort.rs new file mode 100644 index 0000000..089fe18 --- /dev/null +++ b/crunchy-cli-core/src/utils/sort.rs @@ -0,0 +1,47 @@ +use crate::utils::format::Format; +use crunchyroll_rs::{Media, Season}; +use std::collections::BTreeMap; + +/// Sort seasons after their season number. Crunchyroll may have multiple seasons for one season +/// number. They generally store different language in individual seasons with the same season number. +/// E.g. series X has one official season but crunchy has translations for it in 3 different languages +/// so there exist 3 different "seasons" on Crunchyroll which are actual the same season but with +/// different audio. +pub fn sort_seasons_after_number(seasons: Vec<Media<Season>>) -> Vec<Vec<Media<Season>>> { + let mut as_map = BTreeMap::new(); + + for season in seasons { + as_map + .entry(season.metadata.season_number) + .or_insert_with(Vec::new); + as_map + .get_mut(&season.metadata.season_number) + .unwrap() + .push(season) + } + + as_map.into_values().collect() +} + +/// Sort formats after their seasons and episodes (inside it) ascending. Make sure to have only +/// episodes from one series and in one language as argument since the function does not handle those +/// differences which could then lead to a semi messed up result. +pub fn sort_formats_after_seasons(formats: Vec<Format>) -> Vec<Vec<Format>> { + let mut as_map = BTreeMap::new(); + + for format in formats { + as_map.entry(format.season_number).or_insert_with(Vec::new); + as_map.get_mut(&format.season_number).unwrap().push(format); + } + + let mut sorted = as_map + .into_iter() + .map(|(_, mut values)| { + values.sort_by(|a, b| a.number.cmp(&b.number)); + values + }) + .collect::<Vec<Vec<Format>>>(); + sorted.sort_by(|a, b| a[0].series_id.cmp(&b[0].series_id)); + + sorted +} diff --git a/crunchy-cli.1 b/crunchy-cli.1 deleted file mode 100644 index 91ba30c..0000000 --- a/crunchy-cli.1 +++ /dev/null @@ -1,219 +0,0 @@ -.TH crunchy-cli 1 "27 June 2022" "crunchy-cli" "Crunchyroll Cli Client" - -.SH NAME -crunchy-cli - A cli for downloading videos and entire series from crunchyroll. - -.SH SYNOPSIS -crunchy-cli [\fB-h\fR] [\fB-p\fR \fIPROXY\fR] [\fB-q\fR] [\fB-v\fR] -.br -crunchy-cli help -.br -crunchy-cli login [\fB--persistent\fR] [\fB--session-id\fR \fISESSION_ID\fR] [\fIusername\fR, \fIpassword\fR] -.br -crunchy-cli download [\fB-a\fR \fIAUDIO\fR] [\fB-s\fR \fISUBTITLE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR -.br -crunchy-cli archive [\fB-l\fR \fILANGUAGE\fR] [\fB-d\fR \fIDIRECTORY\fR] [\fB-o\fR \fIOUTPUT\fR] [\fB-m\fR \fIMERGE BEHAVIOR\fR] [\fB-c\fR \fICOMPRESS\fR] [\fB-r\fR \fIRESOLUTION\fR] [\fB-g\fR \fIGOROUTINES\fR] \fIURLs...\fR -.br -crunchy-cli update [\fB-i\fR \fIINSTALL\fR] - -.SH DESCRIPTION -.TP -With \fBcrunchy-cli\fR you can easily download video and series from crunchyroll. -.TP - -Note that you need an \fBcrunchyroll premium\fR account in order to use this tool! - -.SH GENERAL OPTIONS -.TP -This options can be passed to every action. -.TP - -\fB-h, --help\fR -Shows help. -.TP - -\fB-p, --proxy PROXY\fR -Sets a proxy through which all traffic will be routed. -.TP - -\fB-q, --quiet\fR -Disables all output. -.TP - -\fB-v, --verbose\fR -Shows verbose output. -.TP - -\fB--version\fR -Shows the current cli version. - -.SH LOGIN COMMAND -This command logs in to crunchyroll and stores the session id or credentials on the drive. This needs to be done before calling other commands since they need a valid login to operate. -.TP - -\fB--persistent\fR -Stores the given credentials permanent on the drive. The *nix path for it is $HOME/.config/crunchy. -.br -NOTE: The credentials are stored in plain text and if you not use \fB--session-id\fR your credentials are used (if you not use the \fB--persistent\fR flag only a session id gets stored regardless if you login with username/password or a session id). -.TP - -\fB--session-id SESSION_ID\fR -Login via a session id (which can be extracted from a crunchyroll browser cookie) instead of using username and password. - -.SH DOWNLOAD COMMAND -A command to simply download videos. The output file is stored as a \fI.ts\fR file. \fIffmpeg\fR has to be installed if you want to change the Format the videos are stored in. -.TP - -\fB-a, --audio AUDIO\fR -Forces to download videos with the given audio locale. If no video with this audio locale is available, nothing will be downloaded. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. -.TP - -\fB-s, --subtitle SUBTITLE\fR -Forces to download the videos with subtitles in the given locale / language. If no video with this subtitle locale is available, nothing will be downloaded. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. -.TP - -\fB-d, --directory DIRECTORY\fR -The directory to download all files to. -.TP - -\fB-o, --output OUTPUT\fR -Name of the output file. Formatting is also supported, so if the name contains one or more of the following things, they will get replaced. - {title} ยป Title of the video. - {series_name} ยป Name of the series. - {season_name} ยป Name of the season. - {season_number} ยป Number of the season. - {episode_number} ยป Number of the episode. - {resolution} ยป Resolution of the video. - {fps} ยป Frame Rate of the video. - {audio} ยป Audio locale of the video. - {subtitle} ยป Subtitle locale of the video. -.TP - -\fB-r, resolution RESOLUTION\fR -The video resolution. Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or "common-use" words (e.g. best). - Available pixels: 1920x1080, 1280x720, 640x480, 480x360, 426x240. - Available abbreviations: 1080p, 720p, 480p, 360p, 240p. - Available common-use words: best (best available resolution), worst (worst available resolution). -.TP - -\fB-g, --goroutines GOROUTINES\fR -Sets the number of parallel downloads for the segments the final video is made of. Default is the number of cores the computer has. - -.SH ARCHIVE COMMAND -This command behaves like \fBdownload\fR besides the fact that it requires \fIffmpeg\fR and stores the output only to .mkv files. -.TP - -\fB-l, --language LANGUAGE\fR -Audio locales which should be downloaded. Can be used multiple times. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. -.TP - -\fB-s, --sublang LANGUAGE\fR -Subtitle languages to use, by default all are included. Can be used multiple times. Available locales are: ja-JP, en-US, es-419, es-LA, es-ES, fr-FR, pt-PT, pt-BR, it-IT, de-DE, ru-RU, ar-SA, ar-ME. -.TP - -\fB-d, --directory DIRECTORY\fR -The directory to download all files to. -.TP - -\fB-o, --output OUTPUT\fR -Name of the output file. Formatting is also supported, so if the name contains one or more of the following things, they will get replaced. - {title} ยป Title of the video. - {series_name} ยป Name of the series. - {season_name} ยป Name of the season. - {season_number} ยป Number of the season. - {episode_number} ยป Number of the episode. - {resolution} ยป Resolution of the video. - {fps} ยป Frame Rate of the video. - {audio} ยป Audio locale of the video. - {subtitle} ยป Subtitle locale of the video. -.TP - -\fB-m, --merge MERGE BEHAVIOR\fR -Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio', 'video'. \fB--audio\fR stores one video and only the audio of all other languages, \fBvideo\fR stores all videos of the given languages and their audio, \fBauto\fR (which is the default) only behaves like video if the length of two videos are different (and only for the two videos), else like audio. -.TP - -\fB-c, --compress COMPRESS\fR -If is set, all output will be compresses into an archive (every url generates a new one). This flag sets the name of the compressed output file. The file ending specifies the compression algorithm. The following algorithms are supported: gzip, tar, zip. -Just like \fB--output\fR the name can be formatted. But the only option available here is \fI{series_name}\fR. -.TP - -\fB-r, resolution RESOLUTION\fR -The video resolution. Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or "common-use" words (e.g. best). - Available pixels: 1920x1080, 1280x720, 640x480, 480x360, 426x240. - Available abbreviations: 1080p, 720p, 480p, 360p, 240p. - Available common-use words: best (best available resolution), worst (worst available resolution). -.TP - -\fB-g, --goroutines GOROUTINES\fR -Sets the number of parallel downloads for the segments the final video is made of. Default is the number of cores the computer has. - -.SH UPDATE COMMAND -Checks if a newer version is available. -.TP - -\fB-i, --install INSTALL\fR -If given, the command tries to update the executable with the newer version (if a newer is available). - -.SH URL OPTIONS -If you want to download only specific episode of a series, you could either pass every single episode url to the downloader (which is fine for 1 - 3 episodes) or use filtering. -It works pretty simple, just put a specific pattern surrounded by square brackets at the end of the url from the anime you want to download. A season and / or episode as well as a range from where to where episodes should be downloaded can be specified. -Use the list below to get a better overview what is possible - ...[E5] - Download the fifth episode. - ...[S1] - Download the full first season. - ...[-S2] - Download all seasons up to and including season 2. - ...[S3E4-] - Download all episodes from and including season 3, episode 4. - ...[S1E4-S3] - Download all episodes from and including season 1, episode 4, until and including season 3. - -In practise, it would look like this: \fIhttps://beta.crunchyroll.com/series/12345678/example[S1E5-S3E2]\fR. - -The \fBS\fR, followed by the number indicates the season number, \fBE\fR, followed by the number indicates an episode number. It doesn't matter if \fBS\fR, \fBE\fR or both are missing. Theoretically \fB[-]\fR is a valid pattern too. Note that \fBS\fR must always stay before \fBE\fR when used. - -.SH EXAMPLES -Login via crunchyroll account email and password. -.br -$ crunchy-cli login user@example.com 12345678 - -Download a episode normally. Your system locale will be used for the video's audio. -.br -$ crunchy-cli download https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome - -Download a episode with 720p and name it to 'darling.mp4'. Note that you need \fBffmpeg\fR to save files which do not have '.ts' as file extension. -.br -$ crunchy-cli download -o "darling.mp4" -r 720p https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome - -Download a episode with japanese audio and american subtitles. -.br -$ crunchy-cli download -a ja-JP -s en-US https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E3-E5] - -Stores the episode in a .mkv file. -.br -$ crunchy-cli archive https://beta.crunchyroll.com/watch/GRDKJZ81Y/alone-and-lonesome - -Downloads the first two episode of Darling in the FranXX and stores it compressed in a file. -.br -$ crunchy-cli archive -c "ditf.tar.gz" https://beta.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E2] - -.SH BUGS -If you notice any bug or want an enhancement, feel free to create a new issue or pull request in the GitHub repository. - -.SH AUTHOR -Crunchy Labs Maintainers -.br -Source: https://github.com/crunchy-labs/crunchy-cli - -.SH COPYRIGHT -Copyright (C) 2022 Crunchy Labs Maintainers - -This program is free software: you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation, either version 3 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <https://www.gnu.org/licenses/>. - diff --git a/go.mod b/go.mod deleted file mode 100644 index 162e7a1..0000000 --- a/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/crunchy-labs/crunchy-cli - -go 1.19 - -require ( - github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 - github.com/grafov/m3u8 v0.11.1 - github.com/spf13/cobra v1.5.0 -) - -require ( - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 51fe7a6..0000000 --- a/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2 h1:PG5++Gje126/xRtzZwCowoFU1Dl3qKzFjd3lWhVXoso= -github.com/crunchy-labs/crunchyroll-go/v3 v3.0.2/go.mod h1:SjTQD3IX7Z+MLsMSd2fP5ttsJ4KtpXY6r08bHLwrOLM= -github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= -github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go deleted file mode 100644 index 72ec83b..0000000 --- a/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/crunchy-labs/crunchy-cli/cli" -) - -func main() { - cli.Execute() -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5e1d695 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() { + crunchy_cli_core::cli_entrypoint().await +} diff --git a/utils/extract.go b/utils/extract.go deleted file mode 100644 index fa2d650..0000000 --- a/utils/extract.go +++ /dev/null @@ -1,99 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/crunchy-labs/crunchyroll-go/v3" - "github.com/crunchy-labs/crunchyroll-go/v3/utils" - "regexp" - "strconv" - "strings" -) - -var urlFilter = regexp.MustCompile(`(S(\d+))?(E(\d+))?((-)(S(\d+))?(E(\d+))?)?(,|$)`) - -func ExtractEpisodes(url string, locales ...crunchyroll.LOCALE) ([][]*crunchyroll.Episode, error) { - var matches [][]string - - lastOpen := strings.LastIndex(url, "[") - if strings.HasSuffix(url, "]") && lastOpen != -1 && lastOpen < len(url) { - matches = urlFilter.FindAllStringSubmatch(url[lastOpen+1:len(url)-1], -1) - - var all string - for _, match := range matches { - all += match[0] - } - if all != url[lastOpen+1:len(url)-1] { - return nil, fmt.Errorf("invalid episode filter") - } - url = url[:lastOpen] - } - - episodes, err := Crunchy.ExtractEpisodesFromUrl(url, locales...) - if err != nil { - return nil, fmt.Errorf("failed to get episodes: %v", err) - } - - if len(episodes) == 0 { - return nil, fmt.Errorf("no episodes found") - } - - if matches != nil { - for _, match := range matches { - fromSeason, fromEpisode, toSeason, toEpisode := -1, -1, -1, -1 - if match[2] != "" { - fromSeason, _ = strconv.Atoi(match[2]) - } - if match[4] != "" { - fromEpisode, _ = strconv.Atoi(match[4]) - } - if match[8] != "" { - toSeason, _ = strconv.Atoi(match[8]) - } - if match[10] != "" { - toEpisode, _ = strconv.Atoi(match[10]) - } - - if match[6] != "-" { - toSeason = fromSeason - toEpisode = fromEpisode - } - - tmpEps := make([]*crunchyroll.Episode, 0) - for _, episode := range episodes { - if fromSeason != -1 && (episode.SeasonNumber < fromSeason || (fromEpisode != -1 && episode.EpisodeNumber < fromEpisode)) { - continue - } else if fromSeason == -1 && fromEpisode != -1 && episode.EpisodeNumber < fromEpisode { - continue - } else if toSeason != -1 && (episode.SeasonNumber > toSeason || (toEpisode != -1 && episode.EpisodeNumber > toEpisode)) { - continue - } else if toSeason == -1 && toEpisode != -1 && episode.EpisodeNumber > toEpisode { - continue - } else { - tmpEps = append(tmpEps, episode) - } - } - - if len(tmpEps) == 0 { - return nil, fmt.Errorf("no episodes are matching the given filter") - } - - episodes = tmpEps - } - } - - var final [][]*crunchyroll.Episode - if len(locales) > 0 { - final = make([][]*crunchyroll.Episode, len(locales)) - localeSorted, err := utils.SortEpisodesByAudio(episodes) - if err != nil { - return nil, fmt.Errorf("failed to get audio locale: %v", err) - } - for i, locale := range locales { - final[i] = append(final[i], localeSorted[locale]...) - } - } else { - final = [][]*crunchyroll.Episode{episodes} - } - - return final, nil -} diff --git a/utils/file.go b/utils/file.go deleted file mode 100644 index 183b06f..0000000 --- a/utils/file.go +++ /dev/null @@ -1,49 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" -) - -func FreeFileName(filename string) (string, bool) { - ext := filepath.Ext(filename) - base := strings.TrimSuffix(filename, ext) - // checks if a .tar stands before the "actual" file ending - if extraExt := filepath.Ext(base); extraExt == ".tar" { - ext = extraExt + ext - base = strings.TrimSuffix(base, extraExt) - } - j := 0 - for ; ; j++ { - if _, stat := os.Stat(filename); stat != nil && !os.IsExist(stat) { - break - } - filename = fmt.Sprintf("%s (%d)%s", base, j+1, ext) - } - return filename, j != 0 -} - -func GenerateFilename(name, directory string) string { - if runtime.GOOS != "windows" { - for _, char := range []string{"/"} { - name = strings.ReplaceAll(name, char, "") - } - Log.Debug("Replaced invalid characters (not windows)") - } else { - // ahh i love windows :))) - for _, char := range []string{"\\", "<", ">", ":", "\"", "/", "|", "?", "*"} { - name = strings.ReplaceAll(name, char, "") - } - Log.Debug("Replaced invalid characters (windows)") - } - - filename, changed := FreeFileName(filepath.Join(directory, name)) - if changed { - Log.Debug("File `%s` already exists, changing name to `%s`", filepath.Base(name), filepath.Base(filename)) - } - - return filename -} diff --git a/utils/format.go b/utils/format.go deleted file mode 100644 index 6d40142..0000000 --- a/utils/format.go +++ /dev/null @@ -1,63 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/crunchy-labs/crunchyroll-go/v3" - "reflect" - "runtime" - "strings" -) - -type FormatInformation struct { - // the Format to download - Format *crunchyroll.Format - - // additional formats which are only used by archive.go - AdditionalFormats []*crunchyroll.Format - - Title string `json:"title"` - SeriesName string `json:"series_name"` - SeasonName string `json:"season_name"` - SeasonNumber int `json:"season_number"` - EpisodeNumber int `json:"episode_number"` - Resolution string `json:"resolution"` - FPS float64 `json:"fps"` - Audio crunchyroll.LOCALE `json:"audio"` - Subtitle crunchyroll.LOCALE `json:"subtitle"` -} - -func (fi FormatInformation) FormatString(source string) string { - fields := reflect.TypeOf(fi) - values := reflect.ValueOf(fi) - - for i := 0; i < fields.NumField(); i++ { - var valueAsString string - switch value := values.Field(i); value.Kind() { - case reflect.String: - valueAsString = value.String() - case reflect.Int: - valueAsString = fmt.Sprintf("%02d", value.Int()) - case reflect.Float64: - valueAsString = fmt.Sprintf("%.2f", value.Float()) - case reflect.Bool: - valueAsString = fields.Field(i).Tag.Get("json") - if !value.Bool() { - valueAsString = "no " + valueAsString - } - } - - if runtime.GOOS != "windows" { - for _, char := range []string{"/"} { - valueAsString = strings.ReplaceAll(valueAsString, char, "") - } - } else { - for _, char := range []string{"\\", "<", ">", ":", "\"", "/", "|", "?", "*"} { - valueAsString = strings.ReplaceAll(valueAsString, char, "") - } - } - - source = strings.ReplaceAll(source, "{"+fields.Field(i).Tag.Get("json")+"}", valueAsString) - } - - return source -} diff --git a/utils/http.go b/utils/http.go deleted file mode 100644 index f572bd9..0000000 --- a/utils/http.go +++ /dev/null @@ -1,51 +0,0 @@ -package utils - -import ( - "net/http" - "net/url" - "time" -) - -type headerRoundTripper struct { - http.RoundTripper - header map[string]string -} - -func (rht headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { - resp, err := rht.RoundTripper.RoundTrip(r) - if err != nil { - return nil, err - } - for k, v := range rht.header { - resp.Header.Set(k, v) - } - return resp, nil -} - -func CreateOrDefaultClient(proxy, useragent string) (*http.Client, error) { - if proxy == "" { - return http.DefaultClient, nil - } else { - proxyURL, err := url.Parse(proxy) - if err != nil { - return nil, err - } - - var rt http.RoundTripper = &http.Transport{ - DisableCompression: true, - Proxy: http.ProxyURL(proxyURL), - } - if useragent != "" { - rt = headerRoundTripper{ - RoundTripper: rt, - header: map[string]string{"User-Agent": useragent}, - } - } - - client := &http.Client{ - Transport: rt, - Timeout: 30 * time.Second, - } - return client, nil - } -} diff --git a/utils/locale.go b/utils/locale.go deleted file mode 100644 index 9769940..0000000 --- a/utils/locale.go +++ /dev/null @@ -1,59 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/crunchy-labs/crunchyroll-go/v3" - "github.com/crunchy-labs/crunchyroll-go/v3/utils" - "os" - "os/exec" - "runtime" - "sort" - "strings" -) - -// SystemLocale receives the system locale -// https://stackoverflow.com/questions/51829386/golang-get-system-language/51831590#51831590 -func SystemLocale(verbose bool) crunchyroll.LOCALE { - if runtime.GOOS != "windows" { - if lang, ok := os.LookupEnv("LANG"); ok { - var l crunchyroll.LOCALE - if preSuffix := strings.Split(strings.Split(lang, ".")[0], "_"); len(preSuffix) == 1 { - l = crunchyroll.LOCALE(preSuffix[0]) - } else { - prefix := strings.Split(lang, "_")[0] - l = crunchyroll.LOCALE(fmt.Sprintf("%s-%s", prefix, preSuffix[1])) - } - if !utils.ValidateLocale(l) { - if verbose { - Log.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) - } - l = crunchyroll.US - } - return l - } - } else { - cmd := exec.Command("powershell", "Get-Culture | select -exp Name") - if output, err := cmd.Output(); err == nil { - l := crunchyroll.LOCALE(strings.Trim(string(output), "\r\n")) - if !utils.ValidateLocale(l) { - if verbose { - Log.Err("%s is not a supported locale, using %s as fallback", l, crunchyroll.US) - } - l = crunchyroll.US - } - return l - } - } - if verbose { - Log.Err("Failed to get locale, using %s", crunchyroll.US) - } - return crunchyroll.US -} - -func LocalesAsStrings() (locales []string) { - for _, locale := range utils.AllLocales { - locales = append(locales, string(locale)) - } - sort.Strings(locales) - return -} diff --git a/utils/logger.go b/utils/logger.go deleted file mode 100644 index 27ea344..0000000 --- a/utils/logger.go +++ /dev/null @@ -1,12 +0,0 @@ -package utils - -type Logger interface { - IsDev() bool - Debug(format string, v ...any) - Info(format string, v ...any) - Warn(format string, v ...any) - Err(format string, v ...any) - Empty() - SetProcess(format string, v ...any) - StopProcess(format string, v ...any) -} diff --git a/utils/save.go b/utils/save.go deleted file mode 100644 index 7660974..0000000 --- a/utils/save.go +++ /dev/null @@ -1,177 +0,0 @@ -package utils - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "fmt" - "github.com/crunchy-labs/crunchyroll-go/v3" - "io" - "os" - "path/filepath" - "strings" -) - -func SaveSession(crunchy *crunchyroll.Crunchyroll) error { - file := filepath.Join(os.TempDir(), ".crunchy") - return os.WriteFile(file, []byte(crunchy.RefreshToken), 0600) -} - -func SaveCredentialsPersistent(user, password string, encryptionKey []byte) error { - configDir, err := os.UserConfigDir() - if err != nil { - return err - } - file := filepath.Join(configDir, "crunchy-cli", "crunchy") - - var credentials []byte - if encryptionKey != nil { - hashedEncryptionKey := sha256.Sum256(encryptionKey) - block, err := aes.NewCipher(hashedEncryptionKey[:]) - if err != nil { - return err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return err - } - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return err - } - b := gcm.Seal(nonce, nonce, []byte(fmt.Sprintf("%s\n%s", user, password)), nil) - credentials = append([]byte("aes:"), b...) - } else { - credentials = []byte(fmt.Sprintf("%s\n%s", user, password)) - } - - if err = os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755); err != nil { - return err - } - return os.WriteFile(file, credentials, 0600) -} - -func SaveSessionPersistent(crunchy *crunchyroll.Crunchyroll) error { - configDir, err := os.UserConfigDir() - if err != nil { - return err - } - file := filepath.Join(configDir, "crunchy-cli", "crunchy") - - if err = os.MkdirAll(filepath.Join(configDir, "crunchy-cli"), 0755); err != nil { - return err - } - return os.WriteFile(file, []byte(crunchy.RefreshToken), 0600) -} - -func IsTempSession() bool { - file := filepath.Join(os.TempDir(), ".crunchy") - if _, err := os.Stat(file); !os.IsNotExist(err) { - return true - } - return false -} - -func IsSavedSessionEncrypted() (bool, error) { - configDir, err := os.UserConfigDir() - if err != nil { - return false, err - } - file := filepath.Join(configDir, "crunchy-cli", "crunchy") - body, err := os.ReadFile(file) - if err != nil { - return false, err - } - return strings.HasPrefix(string(body), "aes:"), nil -} - -func LoadSession(encryptionKey []byte) (*crunchyroll.Crunchyroll, error) { - file := filepath.Join(os.TempDir(), ".crunchy") - crunchy, err := loadTempSession(file) - if err != nil { - return nil, err - } - if crunchy != nil { - return crunchy, nil - } - - configDir, err := os.UserConfigDir() - if err != nil { - return nil, err - } - file = filepath.Join(configDir, "crunchy-cli", "crunchy") - crunchy, err = loadPersistentSession(file, encryptionKey) - if err != nil { - return nil, err - } - if crunchy != nil { - return crunchy, nil - } - - return nil, fmt.Errorf("not logged in") -} - -func loadTempSession(file string) (*crunchyroll.Crunchyroll, error) { - if _, err := os.Stat(file); !os.IsNotExist(err) { - body, err := os.ReadFile(file) - if err != nil { - return nil, err - } - crunchy, err := crunchyroll.LoginWithRefreshToken(string(body), SystemLocale(true), Client) - if err != nil { - Log.Debug("Failed to login with temp refresh token: %v", err) - } else { - Log.Debug("Logged in with refresh token %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", body) - return crunchy, nil - } - } - return nil, nil -} - -func loadPersistentSession(file string, encryptionKey []byte) (crunchy *crunchyroll.Crunchyroll, err error) { - if _, err = os.Stat(file); !os.IsNotExist(err) { - body, err := os.ReadFile(file) - if err != nil { - return nil, err - } - split := strings.SplitN(string(body), "\n", 2) - if len(split) == 1 || split[1] == "" && strings.HasPrefix(split[0], "aes:") { - encrypted := body[4:] - hashedEncryptionKey := sha256.Sum256(encryptionKey) - block, err := aes.NewCipher(hashedEncryptionKey[:]) - if err != nil { - return nil, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - nonce, cypherText := encrypted[:gcm.NonceSize()], encrypted[gcm.NonceSize():] - b, err := gcm.Open(nil, nonce, cypherText, nil) - if err != nil { - return nil, err - } - split = strings.SplitN(string(b), "\n", 2) - } - if len(split) == 2 { - if crunchy, err = crunchyroll.LoginWithCredentials(split[0], split[1], SystemLocale(true), Client); err != nil { - return nil, err - } - Log.Debug("Logged in with credentials") - } else { - if crunchy, err = crunchyroll.LoginWithRefreshToken(split[0], SystemLocale(true), Client); err != nil { - return nil, err - } - Log.Debug("Logged in with refresh token %s. BLANK THIS LINE OUT IF YOU'RE ASKED TO POST THE DEBUG OUTPUT SOMEWHERE", split[0]) - } - - // the refresh token is written to a temp file to reduce the amount of re-logging in. - // it seems like that crunchyroll has also a little cooldown if a user logs in too often in a short time - if err = os.WriteFile(filepath.Join(os.TempDir(), ".crunchy"), []byte(crunchy.RefreshToken), 0600); err != nil { - return nil, err - } - } - - return -} diff --git a/utils/std.go b/utils/std.go deleted file mode 100644 index 75b35f1..0000000 --- a/utils/std.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -func ElementInSlice[T comparable](check T, toCheck []T) bool { - for _, item := range toCheck { - if check == item { - return true - } - } - return false -} diff --git a/utils/system.go b/utils/system.go deleted file mode 100644 index ac97706..0000000 --- a/utils/system.go +++ /dev/null @@ -1,7 +0,0 @@ -package utils - -import "os/exec" - -func HasFFmpeg() bool { - return exec.Command("ffmpeg", "-h").Run() == nil -} diff --git a/utils/vars.go b/utils/vars.go deleted file mode 100644 index d2893a5..0000000 --- a/utils/vars.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import ( - "github.com/crunchy-labs/crunchyroll-go/v3" - "net/http" -) - -var Version = "development" - -var ( - Crunchy *crunchyroll.Crunchyroll - Client *http.Client - Log Logger -) From 502cb399238f848a2e82c310fc889c10a5df4223 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 24 Nov 2022 15:54:17 +0100 Subject: [PATCH 196/630] Fix download binary description --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2229739..db06b99 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,9 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: ### ๐Ÿ“ฅ Download the latest binaries -Checkout the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the newest release. +~~Checkout the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the newest release.~~ + +Currently, no pre-built binary of the rewrite / this branch is available. ### ๐Ÿ›  Build it yourself From 4fd98723ea4e344f2d904054b2594bda3b9f54fb Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 26 Nov 2022 22:37:58 +0100 Subject: [PATCH 197/630] Change archive flag name from audio to locale --- crunchy-cli-core/src/cli/archive.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 21ae13b..754d6cb 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -44,7 +44,7 @@ pub struct Archive { #[arg(long_help = format!("Audio languages. Can be used multiple times. \ Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_values_t = vec![crate::utils::locale::system_locale(), Locale::ja_JP])] - audio: Vec<Locale>, + locale: Vec<Locale>, #[arg(help = format!("Subtitle languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ @@ -292,7 +292,7 @@ async fn formats_from_series( // get all locales which are specified but not present in the current iterated season and // print an error saying this let not_present_audio = archive - .audio + .locale .clone() .into_iter() .filter(|l| !season.iter().any(|s| &s.metadata.audio_locale == l)) @@ -309,7 +309,7 @@ async fn formats_from_series( // remove all seasons with the wrong audio for the current iterated season number seasons.retain(|s| { s.metadata.season_number != season.first().unwrap().metadata.season_number - || archive.audio.contains(&s.metadata.audio_locale) + || archive.locale.contains(&s.metadata.audio_locale) }) } @@ -318,7 +318,7 @@ async fn formats_from_series( BTreeMap::new(); for season in series.seasons().await? { if !url_filter.is_season_valid(season.metadata.season_number) - || !archive.audio.contains(&season.metadata.audio_locale) + || !archive.locale.contains(&season.metadata.audio_locale) { continue; } From a7c2bbe807129e8844cd30817a2770f0879085c8 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 15:18:55 +0100 Subject: [PATCH 198/630] Add ci workflow --- .github/workflows/ci.yml | 133 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f9b30a5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,133 @@ +name: ci + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + toolchain: x86_64-unknown-linux-musl + - os: windows-latest + toolchain: x86_64-pc-windows-msvc + - os: macos-latest + toolchain: x86_64-apple-darwin + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: ${{ matrix.toolchain }} + default: true + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features + + build: + if: github.ref == 'refs/heads/master' + needs: + - test + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + toolchain: x86_64-unknown-linux-musl + ext: + output: crunchy_linux + - os: windows-latest + toolchain: x86_64-pc-windows-msvc + ext: .exe + output: crunchy_windows.exe + - os: macos-latest + toolchain: x86_64-apple-darwin + ext: + output: crunchy_darwin + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: ${{ matrix.toolchain }} + default: true + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features + + - name: Bundle manpages + uses: thedoctor0/zip-release@0.6 + with: + type: zip + filename: manpages.zip + path: ./target/release/manpages + + - name: Bundle completions + uses: thedoctor0/zip-release@0.6 + with: + type: zip + filename: completions.zip + path: ./target/release/completions + + - name: Upload binary artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.output }} + path: ./target/release/crunchy-cli${{ matrix.ext }} + if-no-files-found: error + + - name: Upload manpages artifact + uses: actions/upload-artifact@v3 + with: + name: + path: ./manpages.zip + if-no-files-found: error + + - name: Upload completions artifact + uses: actions/upload-artifact@v3 + with: + name: + path: ./completions.zip + if-no-files-found: error From 3f4ce3a0a92e4fa517b7dc03d66b014b5f7673dc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 17:11:26 +0100 Subject: [PATCH 199/630] Fix ci zip action version --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9b30a5..50a41e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,14 +98,14 @@ jobs: args: --release --all-features - name: Bundle manpages - uses: thedoctor0/zip-release@0.6 + uses: thedoctor0/zip-release@0:6 with: type: zip filename: manpages.zip path: ./target/release/manpages - name: Bundle completions - uses: thedoctor0/zip-release@0.6 + uses: thedoctor0/zip-release@0:6 with: type: zip filename: completions.zip From a7605884414afacf120a5ffdf94bf6d53de1a78a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 17:11:32 +0100 Subject: [PATCH 200/630] Add ci badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index db06b99..0dcbc63 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> + <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> + <img src="https://img.shields.io/github/workflow/status/crunchy-labs/crunchy-cli/ci?style=flat-square" alt="CI"> + </a> </p> <p align="center"> From 8d8333e41413546d6cf7ca66f2afa215d7c382b2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 17:16:12 +0100 Subject: [PATCH 201/630] Fix ci zip workflow (again) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50a41e7..c27195a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,14 +98,14 @@ jobs: args: --release --all-features - name: Bundle manpages - uses: thedoctor0/zip-release@0:6 + uses: TheDoctor0/zip-release@0.6.2 with: type: zip filename: manpages.zip path: ./target/release/manpages - name: Bundle completions - uses: thedoctor0/zip-release@0:6 + uses: TheDoctor0/zip-release@0.6.2 with: type: zip filename: completions.zip From 45c315e9bb1008e54f1e6a2bb03e42d5d8c4f134 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 17:27:12 +0100 Subject: [PATCH 202/630] Fix ci upload artifact action --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c27195a..01ad86f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,13 +121,13 @@ jobs: - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: - name: + name: manpages.zip path: ./manpages.zip if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: - name: + name: completions.zip path: ./completions.zip if-no-files-found: error From b118b74b994608a73a9495a8d8d2fcafb78db364 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 19:57:37 +0100 Subject: [PATCH 203/630] Update README --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0dcbc63..f62c988 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: <a href="https://github.com/crunchy-labs/crunchy-cli/blob/master/LICENSE"> <img src="https://img.shields.io/github/license/crunchy-labs/crunchy-cli?style=flat-square" alt="License"> </a> - <a href="https://github.com/crunchy-labs/crunchy-cli/releases/latest"> - <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?style=flat-square" alt="Release"> + <a href="https://github.com/crunchy-labs/crunchy-cli/releases"> + <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?include_prereleases&style=flat-square" alt="Release"> </a> <a href="https://discord.gg/gUWwekeNNg"> <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> @@ -44,9 +44,7 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: ### ๐Ÿ“ฅ Download the latest binaries -~~Checkout the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the newest release.~~ - -Currently, no pre-built binary of the rewrite / this branch is available. +Checkout the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the newest (pre-)release. ### ๐Ÿ›  Build it yourself From f3f41aa0a2c94b595eabfae119feed9cafa64045 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 19:58:14 +0100 Subject: [PATCH 204/630] Add crunchy-cli-core/Cargo.lock --- crunchy-cli-core/Cargo.lock | 1608 +++++++++++++++++++++++++++++++++++ 1 file changed, 1608 insertions(+) create mode 100644 crunchy-cli-core/Cargo.lock diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock new file mode 100644 index 0000000..61ea0ff --- /dev/null +++ b/crunchy-cli-core/Cargo.lock @@ -0,0 +1,1608 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "async-channel" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-padding" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy-cli-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "clap", + "crunchyroll-rs", + "ctrlc", + "dirs", + "isahc", + "log", + "num_cpus", + "regex", + "signal-hook", + "sys-locale", + "tempfile", + "terminal_size", + "tokio", +] + +[[package]] +name = "crunchyroll-rs" +version = "0.1.0" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +dependencies = [ + "aes", + "cbc", + "chrono", + "crunchyroll-rs-internal", + "http", + "isahc", + "m3u8-rs", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smart-default", + "tokio", +] + +[[package]] +name = "crunchyroll-rs-internal" +version = "0.1.0" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +dependencies = [ + "darling", + "quote", + "syn", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.59+curl-7.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + +[[package]] +name = "cxx" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" + +[[package]] +name = "isahc" +version = "1.7.0" +source = "git+https://github.com/sagebind/isahc?rev=34f158ef#34f158ef9f87b2387bed2c81936916a29c1eaad1" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "httpdate", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.7+1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "m3u8-rs" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d091887fd4a920417805283b7a838d0dcda68e8d632cd305a4439ee776d1ce" +dependencies = [ + "chrono", + "nom", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.24.0+1.1.1s" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +dependencies = [ + "autocfg", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "polling" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustix" +version = "0.35.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-locale" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3358acbb4acd4146138b9bda219e904a6bb5aaaa237f8eed06f4d6bc1580ecee" +dependencies = [ + "js-sys", + "libc", + "wasm-bindgen", + "web-sys", + "winapi", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" +dependencies = [ + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +dependencies = [ + "autocfg", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" From 9bfe6b0e54f996116e786cda2b59bdeba9204461 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 20:02:37 +0100 Subject: [PATCH 205/630] Update authors --- Cargo.toml | 3 ++- crunchy-cli-core/Cargo.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c99a2a9..b9adc07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "crunchy-cli" -version = "0.1.0" +authors = ["Crunchy Labs Maintainers"] +version = "3.0.0" edition = "2021" [features] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 893b278..e6bba13 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "crunchy-cli-core" +authors = ["Crunchy Labs Maintainers"] version = "0.1.0" edition = "2021" From 7fe587a89168222ef1a6be7a29049fddcf5bbcb9 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 20:22:59 +0100 Subject: [PATCH 206/630] Update version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05b2d65..4c3ba10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "0.1.0" +version = "3.0.0-beta.1" dependencies = [ "chrono", "clap", @@ -275,7 +275,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "0.1.0" +version = "3.0.0-beta.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index b9adc07..4cdf8dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0" +version = "3.0.0-beta.1" edition = "2021" [features] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 61ea0ff..1502091 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "0.1.0" +version = "3.0.0-beta.1" dependencies = [ "anyhow", "async-trait", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e6bba13..e0e83be 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "0.1.0" +version = "3.0.0-beta.1" edition = "2021" [features] From 1487ba222eeb10adb99f3c7536a18eaca1cb031b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 20:24:10 +0100 Subject: [PATCH 207/630] Add short commit sha to ci build artifacts --- .github/workflows/ci.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01ad86f..cdc3136 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,16 +58,16 @@ jobs: include: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl + platform: linux ext: - output: crunchy_linux - os: windows-latest toolchain: x86_64-pc-windows-msvc + platform: windows ext: .exe - output: crunchy_windows.exe - os: macos-latest toolchain: x86_64-apple-darwin + platform: darwin ext: - output: crunchy_darwin steps: - name: Checkout uses: actions/checkout@v3 @@ -98,36 +98,41 @@ jobs: args: --release --all-features - name: Bundle manpages - uses: TheDoctor0/zip-release@0.6.2 + uses: thedoctor0/zip-release@0.6 with: type: zip filename: manpages.zip path: ./target/release/manpages - name: Bundle completions - uses: TheDoctor0/zip-release@0.6.2 + uses: thedoctor0/zip-release@0.6 with: type: zip filename: completions.zip path: ./target/release/completions + - name: Get short commit SHA + id: short_commit_sha + shell: bash + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: ${{ matrix.output }} + name: crunchy-${{ short_commit_sha.sha_short }}_${{ matrix.platform }}${{ matrix.ext }} path: ./target/release/crunchy-cli${{ matrix.ext }} if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: - name: manpages.zip + name: manpages-${{ short_commit_sha.sha_short }}.zip path: ./manpages.zip if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: - name: completions.zip + name: completions-${{ short_commit_sha.sha_short }}.zip path: ./completions.zip if-no-files-found: error From 81931829b0859590512019a3147de447cc065001 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 20:43:13 +0100 Subject: [PATCH 208/630] Fix invalid ci file syntax --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdc3136..dcf1c56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,20 +119,20 @@ jobs: - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-${{ short_commit_sha.sha_short }}_${{ matrix.platform }}${{ matrix.ext }} + name: crunchy-${{ steps.short_commit_sha.sha_short }}_${{ matrix.platform }}${{ matrix.ext }} path: ./target/release/crunchy-cli${{ matrix.ext }} if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: - name: manpages-${{ short_commit_sha.sha_short }}.zip + name: manpages-${{ steps.short_commit_sha.sha_short }}.zip path: ./manpages.zip if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: - name: completions-${{ short_commit_sha.sha_short }}.zip + name: completions-${{ steps.short_commit_sha.sha_short }}.zip path: ./completions.zip if-no-files-found: error From c99eedd7a7c0b29d22131813c2c08d085daa8907 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 22:14:32 +0100 Subject: [PATCH 209/630] Re-fix ci zip workflow --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcf1c56..4e67182 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,14 +98,14 @@ jobs: args: --release --all-features - name: Bundle manpages - uses: thedoctor0/zip-release@0.6 + uses: thedoctor0/zip-release@0.6.2 with: type: zip filename: manpages.zip path: ./target/release/manpages - name: Bundle completions - uses: thedoctor0/zip-release@0.6 + uses: thedoctor0/zip-release@0.6.2 with: type: zip filename: completions.zip From 24fbedc7d7a52bf96dec9f40b2313345a53b908b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 27 Nov 2022 22:42:15 +0100 Subject: [PATCH 210/630] Fix ci short commit sha --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e67182..9c6dba9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,20 +119,20 @@ jobs: - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-${{ steps.short_commit_sha.sha_short }}_${{ matrix.platform }}${{ matrix.ext }} + name: crunchy-${{ steps.short_commit_sha.outputs.sha_short }}_${{ matrix.platform }}${{ matrix.ext }} path: ./target/release/crunchy-cli${{ matrix.ext }} if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: - name: manpages-${{ steps.short_commit_sha.sha_short }}.zip + name: manpages-${{ steps.short_commit_sha.outputs.sha_short }}.zip path: ./manpages.zip if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: - name: completions-${{ steps.short_commit_sha.sha_short }}.zip + name: completions-${{ steps.short_commit_sha.outputs.sha_short }}.zip path: ./completions.zip if-no-files-found: error From 59b5e3d239c2ad0596f67a21bea248cb90aa2f01 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 28 Nov 2022 10:11:20 +0100 Subject: [PATCH 211/630] Update version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c3ba10..26ffd7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-beta.1" +version = "3.0.0-dev.1" dependencies = [ "chrono", "clap", @@ -275,7 +275,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-beta.1" +version = "3.0.0-dev.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 4cdf8dc..7614202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-beta.1" +version = "3.0.0-dev.1" edition = "2021" [features] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 1502091..61bc6f6 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-beta.1" +version = "3.0.0-dev.1" dependencies = [ "anyhow", "async-trait", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e0e83be..067679b 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-beta.1" +version = "3.0.0-dev.1" edition = "2021" [features] From b1182d4f7b23adc8042998980afa53b4c5c3c5f4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 28 Nov 2022 11:54:04 +0100 Subject: [PATCH 212/630] Add (short) commit hash and build time to version hash --- build.rs | 7 ------- crunchy-cli-core/Cargo.toml | 5 ++++- crunchy-cli-core/build.rs | 33 +++++++++++++++++++++++++++++++++ crunchy-cli-core/src/lib.rs | 30 +++++++++++++++++++++++++----- 4 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 crunchy-cli-core/build.rs diff --git a/build.rs b/build.rs index cc6a0cc..1e4d71f 100644 --- a/build.rs +++ b/build.rs @@ -2,14 +2,7 @@ use clap::{Command, CommandFactory}; use clap_complete::shells; use std::path::{Path, PathBuf}; -// this build file generates completions for various shells as well as manual pages - fn main() -> std::io::Result<()> { - // do not generate anything when building non release - if cfg!(debug_assertions) { - return Ok(()); - } - // note that we're using an anti-pattern here / violate the rust conventions. build script are // not supposed to write outside of 'OUT_DIR'. to have the generated files in the build "root" // (the same directory where the output binary lives) is much simpler than in 'OUT_DIR' since diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 067679b..00635b0 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -14,7 +14,7 @@ static-ssl = ["crunchyroll-rs/static-ssl"] [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = { version = "4.0", features = ["derive"] } +clap = { version = "4.0", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["stream", "parse"] } ctrlc = "3.2" @@ -28,3 +28,6 @@ tempfile = "3.3" terminal_size = "0.2" tokio = { version = "1.21", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.2" + +[build-dependencies] +chrono = "0.4" diff --git a/crunchy-cli-core/build.rs b/crunchy-cli-core/build.rs new file mode 100644 index 0000000..f7d5974 --- /dev/null +++ b/crunchy-cli-core/build.rs @@ -0,0 +1,33 @@ +fn main() -> std::io::Result<()> { + println!( + "cargo:rustc-env=GIT_HASH={}", + get_short_commit_hash()?.unwrap_or_default() + ); + println!( + "cargo:rustc-env=BUILD_DATE={}", + chrono::Utc::now().format("%F") + ); + + Ok(()) +} + +fn get_short_commit_hash() -> std::io::Result<Option<String>> { + let git = std::process::Command::new("git") + .arg("rev-parse") + .arg("--short") + .arg("HEAD") + .output(); + + match git { + Ok(cmd) => Ok(Some( + String::from_utf8_lossy(cmd.stdout.as_slice()).to_string(), + )), + Err(e) => { + if e.kind() != std::io::ErrorKind::NotFound { + Err(e) + } else { + Ok(None) + } + } + } +} diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index faef927..4571337 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -20,13 +20,15 @@ trait Execute { } #[derive(Debug, Parser)] -#[clap(author, version, about)] +#[clap(author, version = version(), about)] #[clap(name = "crunchy-cli")] pub struct Cli { #[clap(flatten)] verbosity: Option<Verbosity>, - #[arg(help = "Overwrite the language in which results are returned. Default is your system language")] + #[arg( + help = "Overwrite the language in which results are returned. Default is your system language" + )] #[arg(long)] lang: Option<Locale>, @@ -37,6 +39,18 @@ pub struct Cli { command: Command, } +fn version() -> String { + let package_version = env!("CARGO_PKG_VERSION"); + let git_commit_hash = env!("GIT_HASH"); + let build_date = env!("BUILD_DATE"); + + if git_commit_hash.is_empty() { + format!("{}", package_version) + } else { + format!("{} ({} {})", package_version, git_commit_hash, build_date) + } +} + #[derive(Debug, Subcommand)] enum Command { Archive(Archive), @@ -51,7 +65,9 @@ struct Verbosity { v: bool, #[arg(help = "Quiet output. Does not print anything unless it's a error")] - #[arg(long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout")] + #[arg( + long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout" + )] #[arg(short)] q: bool, } @@ -59,11 +75,15 @@ struct Verbosity { #[derive(Debug, Parser)] struct LoginMethod { #[arg(help = "Login with credentials (username or email and password)")] - #[arg(long_help = "Login with credentials (username or email and password). Must be provided as user:password")] + #[arg( + long_help = "Login with credentials (username or email and password). Must be provided as user:password" + )] #[arg(long)] credentials: Option<String>, #[arg(help = "Login with the etp-rt cookie")] - #[arg(long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there")] + #[arg( + long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there" + )] #[arg(long)] etp_rt: Option<String>, } From 2d89a712039f314faf4a28774b3b9824278c5f04 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 28 Nov 2022 12:18:44 +0100 Subject: [PATCH 213/630] Remove commit sha in filename --- .github/workflows/ci.yml | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c6dba9..f5d01b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,42 +97,23 @@ jobs: command: build args: --release --all-features - - name: Bundle manpages - uses: thedoctor0/zip-release@0.6.2 - with: - type: zip - filename: manpages.zip - path: ./target/release/manpages - - - name: Bundle completions - uses: thedoctor0/zip-release@0.6.2 - with: - type: zip - filename: completions.zip - path: ./target/release/completions - - - name: Get short commit SHA - id: short_commit_sha - shell: bash - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-${{ steps.short_commit_sha.outputs.sha_short }}_${{ matrix.platform }}${{ matrix.ext }} + name: crunchy-cli_${{ matrix.platform }} path: ./target/release/crunchy-cli${{ matrix.ext }} if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: - name: manpages-${{ steps.short_commit_sha.outputs.sha_short }}.zip - path: ./manpages.zip + name: manpages + path: ./target/release/manpages if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: - name: completions-${{ steps.short_commit_sha.outputs.sha_short }}.zip - path: ./completions.zip + name: completions + path: ./target/release/completions if-no-files-found: error From 99002e606f11aca2f07d079ee60af555c4fc99f4 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 28 Nov 2022 22:14:57 +0100 Subject: [PATCH 214/630] Change windows ci toolchain to gnu --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5d01b6..9aba2cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl - os: windows-latest - toolchain: x86_64-pc-windows-msvc + toolchain: x86_64-pc-windows-gnu - os: macos-latest toolchain: x86_64-apple-darwin steps: @@ -61,7 +61,7 @@ jobs: platform: linux ext: - os: windows-latest - toolchain: x86_64-pc-windows-msvc + toolchain: x86_64-pc-windows-gnu platform: windows ext: .exe - os: macos-latest From 4095b80477f30788aa262a50655e29265adba9d0 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 19:04:01 +0100 Subject: [PATCH 215/630] Update dependencies --- Cargo.lock | 196 +++++++++++++++++++++++++----------- crunchy-cli-core/Cargo.lock | 188 ++++++++++++++++++++++++---------- crunchy-cli-core/Cargo.toml | 1 + 3 files changed, 277 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26ffd7f..144cff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "async-channel" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", @@ -50,32 +50,27 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bitflags" version = "1.3.2" @@ -103,12 +98,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "castaway" version = "0.1.2" @@ -164,14 +153,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.26" +version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -179,9 +168,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.0.5" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0fba905b035a30d25c1b585bf1171690712fbb0ad3ac47214963aa4acc36c" +checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da" dependencies = [ "clap", ] @@ -210,9 +199,9 @@ dependencies = [ [[package]] name = "clap_mangen" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa149477df7854a1497db0def32b8a65bf98f72a14d04ac75b01938285d83420" +checksum = "e503c3058af0a0854668ea01db55c622482a080092fede9dd2e00a00a9436504" dependencies = [ "clap", "roff", @@ -230,11 +219,21 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" dependencies = [ - "cache-padded", + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -298,27 +297,30 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" dependencies = [ "aes", "cbc", "chrono", "crunchyroll-rs-internal", + "curl-sys", "http", "isahc", "m3u8-rs", "regex", + "rustls-native-certs", "serde", "serde_json", "serde_urlencoded", "smart-default", + "static_vcruntime", "tokio", ] [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" dependencies = [ "darling", "quote", @@ -455,6 +457,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "dirs" version = "4.0.0" @@ -598,6 +606,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "http" version = "0.2.8" @@ -676,9 +693,25 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.5" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] [[package]] name = "isahc" @@ -690,6 +723,7 @@ dependencies = [ "crossbeam-utils", "curl", "curl-sys", + "data-encoding", "encoding_rs", "event-listener", "futures-lite", @@ -767,9 +801,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.0.46" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" [[package]] name = "log" @@ -855,7 +889,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -946,16 +980,16 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" -version = "2.4.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748" dependencies = [ "autocfg", "cfg-if", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1054,9 +1088,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.35.13" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" dependencies = [ "bitflags", "errno", @@ -1066,6 +1100,27 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.11" @@ -1089,19 +1144,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] -name = "serde" -version = "1.0.147" +name = "security-framework" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", @@ -1191,6 +1269,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "static_vcruntime" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "954e3e877803def9dc46075bf4060147c55cd70db97873077232eae0269dc89b" + [[package]] name = "strsim" version = "0.10.0" @@ -1199,9 +1283,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" dependencies = [ "proc-macro2", "quote", @@ -1246,9 +1330,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" +checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", "windows-sys 0.42.0", @@ -1276,9 +1360,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -1314,9 +1398,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 61bc6f6..3ee8448 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -39,9 +39,9 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "async-channel" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", @@ -50,32 +50,27 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bitflags" version = "1.3.2" @@ -103,12 +98,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "castaway" version = "0.1.2" @@ -164,14 +153,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.26" +version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -211,11 +200,21 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" dependencies = [ - "cache-padded", + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -267,27 +266,30 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" dependencies = [ "aes", "cbc", "chrono", "crunchyroll-rs-internal", + "curl-sys", "http", "isahc", "m3u8-rs", "regex", + "rustls-native-certs", "serde", "serde_json", "serde_urlencoded", "smart-default", + "static_vcruntime", "tokio", ] [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7aedfc6c9a91a42ef46639ba9e99adba63cd0dda" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" dependencies = [ "darling", "quote", @@ -424,6 +426,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "dirs" version = "4.0.0" @@ -567,6 +575,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "http" version = "0.2.8" @@ -645,9 +662,25 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.5" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] [[package]] name = "isahc" @@ -659,6 +692,7 @@ dependencies = [ "crossbeam-utils", "curl", "curl-sys", + "data-encoding", "encoding_rs", "event-listener", "futures-lite", @@ -736,9 +770,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.0.46" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" [[package]] name = "log" @@ -824,7 +858,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -915,16 +949,16 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" -version = "2.4.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748" dependencies = [ "autocfg", "cfg-if", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1017,9 +1051,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.13" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" dependencies = [ "bitflags", "errno", @@ -1029,6 +1063,27 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.11" @@ -1052,19 +1107,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] -name = "serde" -version = "1.0.147" +name = "security-framework" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", @@ -1154,6 +1232,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "static_vcruntime" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "954e3e877803def9dc46075bf4060147c55cd70db97873077232eae0269dc89b" + [[package]] name = "strsim" version = "0.10.0" @@ -1162,9 +1246,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" dependencies = [ "proc-macro2", "quote", @@ -1209,9 +1293,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" +checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", "windows-sys 0.42.0", @@ -1239,9 +1323,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -1277,9 +1361,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 00635b0..f8c5007 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" static-curl = ["crunchyroll-rs/static-curl"] # Embed a static openssl library into the binary instead of just linking it. If you want to compile this project against # musl and have openssl issues, this might solve these issues. +# Has no effect on Windows, the library is always statically linked there. static-ssl = ["crunchyroll-rs/static-ssl"] [dependencies] From e200aab8ab47d746659c3c1d4969fd637f6a26c1 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 19:04:11 +0100 Subject: [PATCH 216/630] Update ci --- .github/workflows/ci.yml | 58 ++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9aba2cd..1fa94a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: command: test args: --all-features - build: + build-nix: if: github.ref == 'refs/heads/master' needs: - test @@ -59,15 +59,9 @@ jobs: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl platform: linux - ext: - - os: windows-latest - toolchain: x86_64-pc-windows-gnu - platform: windows - ext: .exe - os: macos-latest toolchain: x86_64-apple-darwin platform: darwin - ext: steps: - name: Checkout uses: actions/checkout@v3 @@ -101,7 +95,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: crunchy-cli_${{ matrix.platform }} - path: ./target/release/crunchy-cli${{ matrix.ext }} + path: ./target/release/crunchy-cli if-no-files-found: error - name: Upload manpages artifact @@ -117,3 +111,51 @@ jobs: name: completions path: ./target/release/completions if-no-files-found: error + + build-windows: + if: github.ref == 'refs/heads/master' + needs: + - test + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install OpenSSL + run: vcpkg install openssl:x64-windows-static-md + + - name: Set env variables + shell: bash + run: echo "CFLAGS=-I$(echo $VCPKG_INSTALLATION_ROOT)\packages\openssl_x64-windows-static-md\include" >> $GITHUB_ENV + + - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: x86_64-pc-windows-msvc + default: true + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release + + - name: Upload binary artifact + uses: actions/upload-artifact@v3 + with: + name: crunchy-cli_windows.exe + path: ./target/release/crunchy-cli.exe + if-no-files-found: error From 6ecd23bcd0c8b87fd49f2321fe09d08f623af9b4 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 19:25:00 +0100 Subject: [PATCH 217/630] Fix ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fa94a0..277a591 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,6 +121,8 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - run: vcpkg integrate install + - name: Install OpenSSL run: vcpkg install openssl:x64-windows-static-md From 12d49a27e468632976f4c8d269c9e124e1e2a898 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 19:25:21 +0100 Subject: [PATCH 218/630] Add static vc runtime --- Cargo.lock | 6 +++--- Cargo.toml | 3 +++ build.rs | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 144cff4..2690925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,7 @@ dependencies = [ "clap_complete", "clap_mangen", "crunchy-cli-core", + "static_vcruntime", "tokio", ] @@ -297,7 +298,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3d455ebca34cfa83df39777f3e1414cee4f84002" dependencies = [ "aes", "cbc", @@ -313,14 +314,13 @@ dependencies = [ "serde_json", "serde_urlencoded", "smart-default", - "static_vcruntime", "tokio", ] [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3d455ebca34cfa83df39777f3e1414cee4f84002" dependencies = [ "darling", "quote", diff --git a/Cargo.toml b/Cargo.toml index 7614202..5e3b153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,9 @@ clap_mangen = "0.2" # specified in this Cargo.toml [features]. crunchy-cli-core = { path = "./crunchy-cli-core", features = ["static-curl", "static-ssl"] } +[target.'cfg(all(windows, target_env = "msvc"))'.build-dependencies] +static_vcruntime = "2.0" + [profile.release] strip = true opt-level = "z" diff --git a/build.rs b/build.rs index 1e4d71f..927f4dd 100644 --- a/build.rs +++ b/build.rs @@ -3,6 +3,9 @@ use clap_complete::shells; use std::path::{Path, PathBuf}; fn main() -> std::io::Result<()> { + #[cfg(all(windows, target_env = "msvc"))] + static_vcruntime::metabuild(); + // note that we're using an anti-pattern here / violate the rust conventions. build script are // not supposed to write outside of 'OUT_DIR'. to have the generated files in the build "root" // (the same directory where the output binary lives) is much simpler than in 'OUT_DIR' since From a3c717dc1a4e9bd27e2bf508bfac8f02143268e0 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 19:54:39 +0100 Subject: [PATCH 219/630] Split test job --- .github/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 277a591..9f55b5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,15 +8,13 @@ on: workflow_dispatch: jobs: - test: + test-nix: runs-on: ${{ matrix.os }} strategy: matrix: include: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl - - os: windows-latest - toolchain: x86_64-pc-windows-gnu - os: macos-latest toolchain: x86_64-apple-darwin steps: @@ -48,10 +46,50 @@ jobs: command: test args: --all-features + test-windows: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - run: vcpkg integrate install + + - name: Install OpenSSL + run: vcpkg install openssl:x64-windows-static-md + + - name: Set env variables + shell: bash + run: echo "CFLAGS=-I$(echo $VCPKG_INSTALLATION_ROOT)\packages\openssl_x64-windows-static-md\include" >> $GITHUB_ENV + + - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: x86_64-pc-windows-msvc + default: true + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: + build-nix: if: github.ref == 'refs/heads/master' needs: - - test + - test-nix runs-on: ${{ matrix.os }} strategy: matrix: @@ -115,7 +153,7 @@ jobs: build-windows: if: github.ref == 'refs/heads/master' needs: - - test + - test-windows runs-on: windows-latest steps: - name: Checkout @@ -161,3 +199,17 @@ jobs: name: crunchy-cli_windows.exe path: ./target/release/crunchy-cli.exe if-no-files-found: error + + - name: Upload manpages artifact + uses: actions/upload-artifact@v3 + with: + name: manpages + path: ./target/release/manpages + if-no-files-found: error + + - name: Upload completions artifact + uses: actions/upload-artifact@v3 + with: + name: completions + path: ./target/release/completions + if-no-files-found: error From 0bb20d24a2bbbfcc882791a5aa25c70d7217ebcc Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 21:34:20 +0100 Subject: [PATCH 220/630] Update dependency versions --- Cargo.lock | 59 +++++++++++++++++++++++++++++-------- Cargo.toml | 5 +--- crunchy-cli-core/Cargo.lock | 59 ++++++++++--------------------------- crunchy-cli-core/Cargo.toml | 10 +++---- 4 files changed, 68 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2690925..290b5d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cbc" version = "0.1.2" @@ -284,10 +293,11 @@ dependencies = [ "crunchyroll-rs", "ctrlc", "dirs", - "isahc", + "isahc 1.7.0 (git+https://github.com/sagebind/isahc?rev=c39f6f8)", "log", "num_cpus", "regex", + "rustls-native-certs", "signal-hook", "sys-locale", "tempfile", @@ -306,7 +316,7 @@ dependencies = [ "crunchyroll-rs-internal", "curl-sys", "http", - "isahc", + "isahc 1.7.0 (git+https://github.com/sagebind/isahc?rev=34f158ef)", "m3u8-rs", "regex", "rustls-native-certs", @@ -719,7 +729,7 @@ version = "1.7.0" source = "git+https://github.com/sagebind/isahc?rev=34f158ef#34f158ef9f87b2387bed2c81936916a29c1eaad1" dependencies = [ "async-channel", - "castaway", + "castaway 0.1.2", "crossbeam-utils", "curl", "curl-sys", @@ -741,6 +751,33 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "isahc" +version = "1.7.0" +source = "git+https://github.com/sagebind/isahc?rev=c39f6f8#c39f6f85aaa1f36c5857064c6c3c80f4d307d863" +dependencies = [ + "async-channel", + "castaway 0.2.2", + "crossbeam-utils", + "curl", + "curl-sys", + "data-encoding", + "encoding_rs", + "event-listener", + "futures-io", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "itoa" version = "1.0.4" @@ -905,15 +942,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "111.24.0+1.1.1s" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.78" @@ -923,7 +951,6 @@ dependencies = [ "autocfg", "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -1121,6 +1148,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + [[package]] name = "ryu" version = "1.0.11" diff --git a/Cargo.toml b/Cargo.toml index 5e3b153..d08c47f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,6 @@ default = ["static-curl"] # Embed a static curl library into the binary instead of just linking it. static-curl = ["crunchy-cli-core/static-curl"] -# Embed a static openssl library into the binary instead of just linking it. If you want to compile this project against -# musl and have openssl issues, this might solve these issues. -static-ssl = ["crunchy-cli-core/static-ssl"] [dependencies] tokio = { version = "1.22", features = ["macros", "rt-multi-thread", "time"], default-features = false } @@ -26,7 +23,7 @@ clap_mangen = "0.2" # The static-* features must be used here since build dependency features cannot be manipulated from the features # specified in this Cargo.toml [features]. -crunchy-cli-core = { path = "./crunchy-cli-core", features = ["static-curl", "static-ssl"] } +crunchy-cli-core = { path = "./crunchy-cli-core", features = ["static-curl"] } [target.'cfg(all(windows, target_env = "msvc"))'.build-dependencies] static_vcruntime = "2.0" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 3ee8448..cd16c1b 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -100,9 +100,12 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "castaway" -version = "0.1.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] [[package]] name = "cbc" @@ -256,6 +259,7 @@ dependencies = [ "log", "num_cpus", "regex", + "rustls-native-certs", "signal-hook", "sys-locale", "tempfile", @@ -266,7 +270,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#ffb054cbae4079bf2357a23e90a353ce875978af" dependencies = [ "aes", "cbc", @@ -282,14 +286,13 @@ dependencies = [ "serde_json", "serde_urlencoded", "smart-default", - "static_vcruntime", "tokio", ] [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3e9f7bfaab68649ecdb486aee0b15fe658d51917" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#ffb054cbae4079bf2357a23e90a353ce875978af" dependencies = [ "darling", "quote", @@ -530,13 +533,8 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ - "fastrand", "futures-core", - "futures-io", - "memchr", - "parking", "pin-project-lite", - "waker-fn", ] [[package]] @@ -685,7 +683,7 @@ dependencies = [ [[package]] name = "isahc" version = "1.7.0" -source = "git+https://github.com/sagebind/isahc?rev=34f158ef#34f158ef9f87b2387bed2c81936916a29c1eaad1" +source = "git+https://github.com/sagebind/isahc?rev=c39f6f8#c39f6f85aaa1f36c5857064c6c3c80f4d307d863" dependencies = [ "async-channel", "castaway", @@ -695,6 +693,7 @@ dependencies = [ "data-encoding", "encoding_rs", "event-listener", + "futures-io", "futures-lite", "http", "httpdate", @@ -702,7 +701,6 @@ dependencies = [ "mime", "once_cell", "polling", - "slab", "sluice", "tracing", "tracing-futures", @@ -874,15 +872,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "111.24.0+1.1.1s" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.78" @@ -892,7 +881,6 @@ dependencies = [ "autocfg", "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -903,12 +891,6 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "percent-encoding" version = "2.2.0" @@ -1084,6 +1066,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + [[package]] name = "ryu" version = "1.0.11" @@ -1191,15 +1179,6 @@ dependencies = [ "libc", ] -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - [[package]] name = "sluice" version = "0.5.5" @@ -1232,12 +1211,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "static_vcruntime" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954e3e877803def9dc46075bf4060147c55cd70db97873077232eae0269dc89b" - [[package]] name = "strsim" version = "0.10.0" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f8c5007..30e6d44 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -7,10 +7,6 @@ edition = "2021" [features] # Embed a static curl library into the binary instead of just linking it. static-curl = ["crunchyroll-rs/static-curl"] -# Embed a static openssl library into the binary instead of just linking it. If you want to compile this project against -# musl and have openssl issues, this might solve these issues. -# Has no effect on Windows, the library is always statically linked there. -static-ssl = ["crunchyroll-rs/static-ssl"] [dependencies] anyhow = "1.0" @@ -20,7 +16,7 @@ chrono = "0.4" crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["stream", "parse"] } ctrlc = "3.2" dirs = "4.0" -isahc = { git = "https://github.com/sagebind/isahc", rev = "34f158ef" } +isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8" } log = { version = "0.4", features = ["std"] } num_cpus = "1.13" regex = "1.6" @@ -30,5 +26,9 @@ terminal_size = "0.2" tokio = { version = "1.21", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.2" +[target.'cfg(all(windows, target_env = "msvc"))'.dependencies] +isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8", features = ["data-encoding"] } +rustls-native-certs = "0.6" + [build-dependencies] chrono = "0.4" From 64bb39362e9f937bb852ded778e7ee315de72483 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 21:34:30 +0100 Subject: [PATCH 221/630] Add Windows certificates --- crunchy-cli-core/src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 4571337..c0cfce5 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -167,7 +167,21 @@ async fn create_ctx(cli: &Cli) -> Result<Context> { // TODO: Use crunchy.client() when it's possible // currently crunchy.client() has a cloudflare bypass built-in to access crunchyroll. the servers // where crunchy stores their videos can't handle this bypass and simply refuses to connect + #[cfg(not(all(windows, target_env = "msvc")))] let client = isahc::HttpClient::new().unwrap(); + #[cfg(all(windows, target_env = "msvc"))] + let client = isahc::HttpClientBuilder::default() + .proxy_tls_config( + isahc::tls::TlsConfigBuilder::default().root_cert_store( + isahc::tls::RootCertStore::custom( + rustls_native_certs::load_native_certs() + .unwrap() + .into_iter() + .map(|l| isahc::tls::Certificate::from_der(l.0)), + ), + ), + ) + .build(); Ok(Context { crunchy, client }) } From f687969f0456323dbffe2146e02657b2afa45a85 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 21:53:23 +0100 Subject: [PATCH 222/630] Fix isahc tls config --- crunchy-cli-core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index c0cfce5..d64dae5 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -171,7 +171,7 @@ async fn create_ctx(cli: &Cli) -> Result<Context> { let client = isahc::HttpClient::new().unwrap(); #[cfg(all(windows, target_env = "msvc"))] let client = isahc::HttpClientBuilder::default() - .proxy_tls_config( + .tls_config( isahc::tls::TlsConfigBuilder::default().root_cert_store( isahc::tls::RootCertStore::custom( rustls_native_certs::load_native_certs() From f6d6c9435c7b6c91001cfad4f53dd3cb7819be15 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 22:28:51 +0100 Subject: [PATCH 223/630] Fix invalid Windows client builder --- crunchy-cli-core/src/lib.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index d64dae5..a1e8381 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -164,24 +164,27 @@ pub async fn cli_entrypoint() { async fn create_ctx(cli: &Cli) -> Result<Context> { let crunchy = crunchyroll_session(cli).await?; - // TODO: Use crunchy.client() when it's possible + // use crunchy.client() when it's possible // currently crunchy.client() has a cloudflare bypass built-in to access crunchyroll. the servers // where crunchy stores their videos can't handle this bypass and simply refuses to connect #[cfg(not(all(windows, target_env = "msvc")))] let client = isahc::HttpClient::new().unwrap(); #[cfg(all(windows, target_env = "msvc"))] + use isahc::config::Configurable; + #[cfg(all(windows, target_env = "msvc"))] let client = isahc::HttpClientBuilder::default() .tls_config( - isahc::tls::TlsConfigBuilder::default().root_cert_store( - isahc::tls::RootCertStore::custom( + isahc::tls::TlsConfigBuilder::default() + .root_cert_store(isahc::tls::RootCertStore::custom( rustls_native_certs::load_native_certs() .unwrap() .into_iter() .map(|l| isahc::tls::Certificate::from_der(l.0)), - ), - ), + )) + .build(), ) - .build(); + .build() + .unwrap(); Ok(Context { crunchy, client }) } From 474e9f5e31c0d07727b0ce39d8dc08373a7bd278 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 30 Nov 2022 23:43:57 +0100 Subject: [PATCH 224/630] Add very verbose output flag --- crunchy-cli-core/src/cli/log.rs | 10 ++++++---- crunchy-cli-core/src/lib.rs | 14 ++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs index d4bb1b3..3539733 100644 --- a/crunchy-cli-core/src/cli/log.rs +++ b/crunchy-cli-core/src/cli/log.rs @@ -92,6 +92,7 @@ impl CliProgress { #[allow(clippy::type_complexity)] pub struct CliLogger { + all: bool, level: LevelFilter, progress: Mutex<Option<CliProgress>>, } @@ -105,7 +106,7 @@ impl Log for CliLogger { if !self.enabled(record.metadata()) || (record.target() != "progress" && record.target() != "progress_end" - && !record.target().starts_with("crunchy_cli")) + && (!self.all && !record.target().starts_with("crunchy_cli"))) { return; } @@ -136,16 +137,17 @@ impl Log for CliLogger { } impl CliLogger { - pub fn new(level: LevelFilter) -> Self { + pub fn new(all: bool, level: LevelFilter) -> Self { Self { + all, level, progress: Mutex::new(None), } } - pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> { + pub fn init(all: bool, level: LevelFilter) -> Result<(), SetLoggerError> { set_max_level(level); - set_boxed_logger(Box::new(CliLogger::new(level))) + set_boxed_logger(Box::new(CliLogger::new(all, level))) } fn extended(&self, record: &Record) { diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index a1e8381..9b57859 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -64,6 +64,10 @@ struct Verbosity { #[arg(short)] v: bool, + #[arg(help = "Very verbose output. Generally not recommended, use '-v' instead")] + #[arg(long)] + vv: bool, + #[arg(help = "Quiet output. Does not print anything unless it's a error")] #[arg( long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout" @@ -92,16 +96,18 @@ pub async fn cli_entrypoint() { let cli: Cli = Cli::parse(); if let Some(verbosity) = &cli.verbosity { - if verbosity.v && verbosity.q { + if verbosity.v as u8 + verbosity.q as u8 + verbosity.vv as u8 > 1 { eprintln!("Output cannot be verbose ('-v') and quiet ('-q') at the same time"); std::process::exit(1) } else if verbosity.v { - CliLogger::init(LevelFilter::Debug).unwrap() + CliLogger::init(false, LevelFilter::Debug).unwrap() } else if verbosity.q { - CliLogger::init(LevelFilter::Error).unwrap() + CliLogger::init(false, LevelFilter::Error).unwrap() + } else if verbosity.vv { + CliLogger::init(true, LevelFilter::Debug).unwrap() } } else { - CliLogger::init(LevelFilter::Info).unwrap() + CliLogger::init(false, LevelFilter::Info).unwrap() } debug!("cli input: {:?}", cli); From 6aa4078be343c99503f62bd5e5179109a9706b2a Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 2 Dec 2022 17:28:54 +0100 Subject: [PATCH 225/630] Update dependencies --- Cargo.lock | 386 ++++++++++++++++++++++---- crunchy-cli-core/src/cli/archive.rs | 2 +- crunchy-cli-core/src/cli/download.rs | 14 +- crunchy-cli-core/src/cli/utils.rs | 7 +- crunchy-cli-core/src/lib.rs | 24 +- crunchy-cli-core/src/utils/context.rs | 1 - 6 files changed, 342 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 290b5d1..fddbc62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,12 +98,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "castaway" version = "0.2.2" @@ -293,7 +287,7 @@ dependencies = [ "crunchyroll-rs", "ctrlc", "dirs", - "isahc 1.7.0 (git+https://github.com/sagebind/isahc?rev=c39f6f8)", + "isahc", "log", "num_cpus", "regex", @@ -308,7 +302,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3d455ebca34cfa83df39777f3e1414cee4f84002" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#b05ab04af60547f4d5b8d008e3b3f829eb99aa0f" dependencies = [ "aes", "cbc", @@ -316,10 +310,10 @@ dependencies = [ "crunchyroll-rs-internal", "curl-sys", "http", - "isahc 1.7.0 (git+https://github.com/sagebind/isahc?rev=34f158ef)", + "isahc", "m3u8-rs", "regex", - "rustls-native-certs", + "reqwest", "serde", "serde_json", "serde_urlencoded", @@ -330,7 +324,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#3d455ebca34cfa83df39777f3e1414cee4f84002" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#b05ab04af60547f4d5b8d008e3b3f829eb99aa0f" dependencies = [ "darling", "quote", @@ -544,6 +538,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -553,6 +562,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.25" @@ -571,13 +589,35 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ - "fastrand", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ "futures-core", "futures-io", + "futures-task", "memchr", - "parking", "pin-project-lite", - "waker-fn", + "pin-utils", + "slab", ] [[package]] @@ -601,6 +641,31 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "heck" version = "0.4.0" @@ -636,12 +701,66 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + [[package]] name = "httpdate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -682,6 +801,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.3" @@ -711,6 +840,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "ipnet" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" + [[package]] name = "is-terminal" version = "0.4.1" @@ -723,41 +858,13 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "isahc" -version = "1.7.0" -source = "git+https://github.com/sagebind/isahc?rev=34f158ef#34f158ef9f87b2387bed2c81936916a29c1eaad1" -dependencies = [ - "async-channel", - "castaway 0.1.2", - "crossbeam-utils", - "curl", - "curl-sys", - "data-encoding", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "httpdate", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "isahc" version = "1.7.0" source = "git+https://github.com/sagebind/isahc?rev=c39f6f8#c39f6f85aaa1f36c5857064c6c3c80f4d307d863" dependencies = [ "async-channel", - "castaway 0.2.2", + "castaway", "crossbeam-utils", "curl", "curl-sys", @@ -767,6 +874,7 @@ dependencies = [ "futures-io", "futures-lite", "http", + "httpdate", "log", "mime", "once_cell", @@ -853,9 +961,9 @@ dependencies = [ [[package]] name = "m3u8-rs" -version = "5.0.2" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d091887fd4a920417805283b7a838d0dcda68e8d632cd305a4439ee776d1ce" +checksum = "2c3b9f971e885044cb57330d3c89a4f3bd45fe97d01c93b3dcec57096efacffc" dependencies = [ "chrono", "nom", @@ -880,10 +988,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "nix" -version = "0.25.0" +name = "mio" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ "autocfg", "bitflags", @@ -936,6 +1074,32 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "openssl" +version = "0.10.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -961,12 +1125,6 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "percent-encoding" version = "2.2.0" @@ -999,6 +1157,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.26" @@ -1107,6 +1271,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "roff" version = "0.2.1" @@ -1316,9 +1517,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -1424,9 +1625,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg", + "bytes", + "libc", + "memchr", + "mio", "num_cpus", "pin-project-lite", + "socket2", "tokio-macros", + "winapi", ] [[package]] @@ -1440,6 +1647,36 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -1483,6 +1720,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.15.0" @@ -1545,6 +1788,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1582,6 +1835,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -1760,3 +2025,12 @@ name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 754d6cb..a057aed 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -387,7 +387,7 @@ async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Res ctx, &mut ffmpeg.stdin.unwrap(), Some(format!("Download {}", format.audio)), - format.stream.segments().await?, + format.stream.clone() ) .await?; diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 7f9e3ec..8535869 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -8,7 +8,7 @@ use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::Execute; use anyhow::{bail, Result}; -use crunchyroll_rs::media::{Resolution, VariantSegment}; +use crunchyroll_rs::media::{Resolution, VariantData}; use crunchyroll_rs::{ Episode, Locale, Media, MediaCollection, Movie, MovieListing, Season, Series, }; @@ -212,16 +212,14 @@ impl Execute for Download { tab_info!("Resolution: {}", format.stream.resolution); tab_info!("FPS: {:.2}", format.stream.fps); - let segments = format.stream.segments().await?; - if use_ffmpeg { - download_ffmpeg(&ctx, segments, path.as_path()).await?; + download_ffmpeg(&ctx, format.stream, path.as_path()).await?; } else if path.to_str().unwrap() == "-" { let mut stdout = std::io::stdout().lock(); - download_segments(&ctx, &mut stdout, None, segments).await?; + download_segments(&ctx, &mut stdout, None, format.stream).await?; } else { let mut file = File::options().create(true).write(true).open(&path)?; - download_segments(&ctx, &mut file, None, segments).await? + download_segments(&ctx, &mut file, None, format.stream).await? } } } @@ -232,7 +230,7 @@ impl Execute for Download { async fn download_ffmpeg( ctx: &Context, - segments: Vec<VariantSegment>, + variant_data: VariantData, target: &Path, ) -> Result<()> { let ffmpeg = Command::new("ffmpeg") @@ -246,7 +244,7 @@ async fn download_ffmpeg( .arg(target.to_str().unwrap()) .spawn()?; - download_segments(ctx, &mut ffmpeg.stdin.unwrap(), None, segments).await?; + download_segments(ctx, &mut ffmpeg.stdin.unwrap(), None, variant_data).await?; Ok(()) } diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 59816d1..e513101 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -26,14 +26,15 @@ pub fn find_resolution( } pub async fn download_segments( - ctx: &Context, + _ctx: &Context, writer: &mut impl Write, message: Option<String>, - segments: Vec<VariantSegment>, + variant_data: VariantData, ) -> Result<()> { + let segments = variant_data.segments().await?; let total_segments = segments.len(); - let client = Arc::new(ctx.client.clone()); + let client = Arc::new(variant_data.download_client()); let count = Arc::new(Mutex::new(0)); let amount = Arc::new(Mutex::new(0)); diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 9b57859..083b401 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -170,29 +170,7 @@ pub async fn cli_entrypoint() { async fn create_ctx(cli: &Cli) -> Result<Context> { let crunchy = crunchyroll_session(cli).await?; - // use crunchy.client() when it's possible - // currently crunchy.client() has a cloudflare bypass built-in to access crunchyroll. the servers - // where crunchy stores their videos can't handle this bypass and simply refuses to connect - #[cfg(not(all(windows, target_env = "msvc")))] - let client = isahc::HttpClient::new().unwrap(); - #[cfg(all(windows, target_env = "msvc"))] - use isahc::config::Configurable; - #[cfg(all(windows, target_env = "msvc"))] - let client = isahc::HttpClientBuilder::default() - .tls_config( - isahc::tls::TlsConfigBuilder::default() - .root_cert_store(isahc::tls::RootCertStore::custom( - rustls_native_certs::load_native_certs() - .unwrap() - .into_iter() - .map(|l| isahc::tls::Certificate::from_der(l.0)), - )) - .build(), - ) - .build() - .unwrap(); - - Ok(Context { crunchy, client }) + Ok(Context { crunchy }) } async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { diff --git a/crunchy-cli-core/src/utils/context.rs b/crunchy-cli-core/src/utils/context.rs index 6a2fd21..f8df024 100644 --- a/crunchy-cli-core/src/utils/context.rs +++ b/crunchy-cli-core/src/utils/context.rs @@ -2,5 +2,4 @@ use crunchyroll_rs::Crunchyroll; pub struct Context { pub crunchy: Crunchyroll, - pub client: isahc::HttpClient, } From 3fcb512c187e968df987b4c954925e514c223d56 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 2 Dec 2022 18:19:13 +0100 Subject: [PATCH 226/630] Merge test and build ci --- .github/workflows/ci.yml | 96 ++++++---------------------------------- 1 file changed, 13 insertions(+), 83 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f55b5d..d0e5960 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,88 +8,8 @@ on: workflow_dispatch: jobs: - test-nix: - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: ubuntu-latest - toolchain: x86_64-unknown-linux-musl - - os: macos-latest - toolchain: x86_64-apple-darwin - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: ${{ matrix.toolchain }} - default: true - - - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features - - test-windows: - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - run: vcpkg integrate install - - - name: Install OpenSSL - run: vcpkg install openssl:x64-windows-static-md - - - name: Set env variables - shell: bash - run: echo "CFLAGS=-I$(echo $VCPKG_INSTALLATION_ROOT)\packages\openssl_x64-windows-static-md\include" >> $GITHUB_ENV - - - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: x86_64-pc-windows-msvc - default: true - - - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: - build-nix: if: github.ref == 'refs/heads/master' - needs: - - test-nix runs-on: ${{ matrix.os }} strategy: matrix: @@ -123,6 +43,12 @@ jobs: target: ${{ matrix.toolchain }} default: true + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --release --all-features + - name: Build uses: actions-rs/cargo@v1 with: @@ -152,8 +78,6 @@ jobs: build-windows: if: github.ref == 'refs/heads/master' - needs: - - test-windows runs-on: windows-latest steps: - name: Checkout @@ -187,11 +111,17 @@ jobs: target: x86_64-pc-windows-msvc default: true + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --release --all-features + - name: Build uses: actions-rs/cargo@v1 with: command: build - args: --release + args: --release --all-features - name: Upload binary artifact uses: actions/upload-artifact@v3 From 33e27504f2ba2b981caa90103f2c29badfd1b0ed Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 2 Dec 2022 19:41:50 +0100 Subject: [PATCH 227/630] Update dependencies --- Cargo.lock | 4 +- crunchy-cli-core/Cargo.lock | 347 ++++++++++++++++++++++++++++++++++-- 2 files changed, 339 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fddbc62..ffdd71b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,7 +302,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#b05ab04af60547f4d5b8d008e3b3f829eb99aa0f" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" dependencies = [ "aes", "cbc", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#b05ab04af60547f4d5b8d008e3b3f829eb99aa0f" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" dependencies = [ "darling", "quote", diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index cd16c1b..4fa983a 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#ffb054cbae4079bf2357a23e90a353ce875978af" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" dependencies = [ "aes", "cbc", @@ -281,7 +281,7 @@ dependencies = [ "isahc", "m3u8-rs", "regex", - "rustls-native-certs", + "reqwest", "serde", "serde_json", "serde_urlencoded", @@ -292,7 +292,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#ffb054cbae4079bf2357a23e90a353ce875978af" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" dependencies = [ "darling", "quote", @@ -506,6 +506,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -515,6 +530,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.25" @@ -537,6 +561,33 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -558,6 +609,31 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "heck" version = "0.4.0" @@ -593,12 +669,66 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + [[package]] name = "httpdate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -639,6 +769,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.3" @@ -668,6 +808,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "ipnet" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" + [[package]] name = "is-terminal" version = "0.4.1" @@ -783,9 +929,9 @@ dependencies = [ [[package]] name = "m3u8-rs" -version = "5.0.2" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d091887fd4a920417805283b7a838d0dcda68e8d632cd305a4439ee776d1ce" +checksum = "2c3b9f971e885044cb57330d3c89a4f3bd45fe97d01c93b3dcec57096efacffc" dependencies = [ "chrono", "nom", @@ -810,10 +956,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "nix" -version = "0.25.0" +name = "mio" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ "autocfg", "bitflags", @@ -866,6 +1042,32 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "openssl" +version = "0.10.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -923,6 +1125,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.26" @@ -1031,6 +1239,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustix" version = "0.36.4" @@ -1179,6 +1424,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + [[package]] name = "sluice" version = "0.5.5" @@ -1219,9 +1473,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -1327,9 +1581,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg", + "bytes", + "libc", + "memchr", + "mio", "num_cpus", "pin-project-lite", + "socket2", "tokio-macros", + "winapi", ] [[package]] @@ -1343,6 +1603,36 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -1386,6 +1676,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.15.0" @@ -1448,6 +1744,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1485,6 +1791,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -1663,3 +1981,12 @@ name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] From cd9c69baf1ecb47865da2db8902b580155c9278e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 2 Dec 2022 20:23:30 +0100 Subject: [PATCH 228/630] Fix windows artifact name --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0e5960..3900976 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli_windows.exe + name: crunchy-cli_windows path: ./target/release/crunchy-cli.exe if-no-files-found: error From e9b3088cde86aa8468a47602e2694b5799fdc081 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 2 Dec 2022 21:42:24 +0100 Subject: [PATCH 229/630] Fix windows ok output character --- crunchy-cli-core/src/cli/log.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs index 3539733..d1f0225 100644 --- a/crunchy-cli-core/src/cli/log.rs +++ b/crunchy-cli-core/src/cli/log.rs @@ -19,6 +19,12 @@ impl CliProgress { let init_message = format!("{}", record.args()); let init_level = record.level(); let handler = thread::spawn(move || { + #[cfg(not(windows))] + let ok = 'โœ”'; + #[cfg(windows)] + // windows does not support all unicode characters by default in their consoles, so + // we're using this (square root?) symbol instead. microsoft. + let ok = 'โˆš'; let states = ["-", "\\", "|", "/"]; let mut old_message = init_message.clone(); @@ -68,7 +74,7 @@ impl CliProgress { // clear last line // prefix (2), space (1), state (1), space (1), message(n) let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len())); - let _ = writeln!(stdout(), "\r:: โœ“ {}", old_message); + let _ = writeln!(stdout(), "\r:: {} {}", ok, old_message); let _ = stdout().flush(); }); From afab3826c945d5906aaaf07e0d6b43749e7a338d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 2 Dec 2022 22:06:08 +0100 Subject: [PATCH 230/630] Extend function to get free file --- Cargo.lock | 11 +++++++++++ crunchy-cli-core/Cargo.lock | 11 +++++++++++ crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/cli/archive.rs | 5 ++--- crunchy-cli-core/src/cli/download.rs | 9 ++------- crunchy-cli-core/src/utils/os.rs | 17 ++++++++++------- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffdd71b..ff1f3dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,6 +292,7 @@ dependencies = [ "num_cpus", "regex", "rustls-native-certs", + "sanitize-filename", "signal-hook", "sys-locale", "tempfile", @@ -1361,6 +1362,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "sanitize-filename" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "schannel" version = "0.1.20" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 4fa983a..b842f38 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -260,6 +260,7 @@ dependencies = [ "num_cpus", "regex", "rustls-native-certs", + "sanitize-filename", "signal-hook", "sys-locale", "tempfile", @@ -1323,6 +1324,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "sanitize-filename" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "schannel" version = "0.1.20" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 30e6d44..d797098 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -20,6 +20,7 @@ isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8" } log = { version = "0.4", features = ["std"] } num_cpus = "1.13" regex = "1.6" +sanitize-filename = "0.4" signal-hook = "0.3" tempfile = "3.3" terminal_size = "0.2" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index a057aed..e798e75 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -206,8 +206,7 @@ impl Execute for Archive { .to_string(), primary, )), - ) - .0; + ); info!( "Downloading {} to '{}'", @@ -387,7 +386,7 @@ async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Res ctx, &mut ffmpeg.stdin.unwrap(), Some(format!("Download {}", format.audio)), - format.stream.clone() + format.stream.clone(), ) .await?; diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 8535869..acb086b 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -178,8 +178,7 @@ impl Execute for Download { .to_string(), &format, )), - ) - .0; + ); let use_ffmpeg = if let Some(extension) = path.extension() { if extension != "ts" { @@ -228,11 +227,7 @@ impl Execute for Download { } } -async fn download_ffmpeg( - ctx: &Context, - variant_data: VariantData, - target: &Path, -) -> Result<()> { +async fn download_ffmpeg(ctx: &Context, variant_data: VariantData, target: &Path) -> Result<()> { let ffmpeg = Command::new("ffmpeg") .stdin(Stdio::piped()) .stdout(Stdio::null()) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 317381d..92e4e9f 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -19,7 +19,7 @@ pub fn has_ffmpeg() -> bool { } } -/// Any tempfiles should be created with this function. The prefix and directory of every file +/// Any tempfile should be created with this function. The prefix and directory of every file /// created with this method stays the same which is helpful to query all existing tempfiles and /// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like /// setting the wrong prefix if done manually. @@ -36,17 +36,20 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { } /// Check if the given path exists and rename it until the new (renamed) file does not exist. -pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { +pub fn free_file(mut path: PathBuf) -> PathBuf { let mut i = 0; while path.exists() { i += 1; - let ext = path.extension().unwrap().to_str().unwrap(); - let mut filename = path.file_name().unwrap().to_str().unwrap(); - - filename = &filename[0..filename.len() - ext.len() - 1]; + let ext = path.extension().unwrap().to_string_lossy(); + let filename = path.file_stem().unwrap().to_string_lossy(); path.set_file_name(format!("{} ({}).{}", filename, i, ext)) } - (path, i != 0) + sanitize_file(path) +} + +/// Sanitizes the given path to not contain any invalid file character. +pub fn sanitize_file(path: PathBuf) -> PathBuf { + path.with_file_name(sanitize_filename::sanitize(path.file_name().unwrap().to_string_lossy())) } From 9d45995e86c4c2b1c8979fa71e6e7abba14937fd Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 3 Dec 2022 00:26:52 +0100 Subject: [PATCH 231/630] Set progress width with message to use complete space --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index e513101..6197684 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -77,7 +77,7 @@ pub async fn download_segments( let progress_available = size - if let Some(msg) = &message { - 35 + msg.len() + 34 + msg.len() } else { 33 }; From 135d59ce8b3ac8230c385c3a9469564bd460ce5b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 3 Dec 2022 00:39:52 +0100 Subject: [PATCH 232/630] Add pre-check function --- crunchy-cli-core/src/cli/archive.rs | 12 +++++++++++- crunchy-cli-core/src/cli/download.rs | 27 +++++++++++---------------- crunchy-cli-core/src/lib.rs | 3 ++- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index e798e75..d4b0a5d 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -3,7 +3,7 @@ use crate::cli::utils::{download_segments, find_resolution}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; -use crate::utils::os::{free_file, tempfile}; +use crate::utils::os::{free_file, has_ffmpeg, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::Execute; @@ -110,6 +110,16 @@ pub struct Archive { #[async_trait::async_trait(?Send)] impl Execute for Archive { + fn pre_check(&self) -> Result<()> { + if !has_ffmpeg() { + bail!("FFmpeg is needed to run this command") + } else if PathBuf::from(&self.output).extension().unwrap_or_default().to_string_lossy() != "mkv" { + bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") + } + + Ok(()) + } + async fn execute(self, ctx: Context) -> Result<()> { let mut parsed_urls = vec![]; diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index acb086b..b0d0ffa 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -65,6 +65,16 @@ pub struct Download { #[async_trait::async_trait(?Send)] impl Execute for Download { + fn pre_check(&self) -> Result<()> { + if has_ffmpeg() { + debug!("FFmpeg detected") + } else if PathBuf::from(&self.output).extension().unwrap_or_default().to_string_lossy() != "ts" { + bail!("File extension is not '.ts'. If you want to use a custom file format, please install ffmpeg") + } + + Ok(()) + } + async fn execute(self, ctx: Context) -> Result<()> { let mut parsed_urls = vec![]; @@ -180,21 +190,6 @@ impl Execute for Download { )), ); - let use_ffmpeg = if let Some(extension) = path.extension() { - if extension != "ts" { - if !has_ffmpeg() { - bail!( - "File ending is not `.ts`, ffmpeg is required to convert the video" - ) - } - true - } else { - false - } - } else { - false - }; - info!( "Downloading {} to '{}'", format.title, @@ -211,7 +206,7 @@ impl Execute for Download { tab_info!("Resolution: {}", format.stream.resolution); tab_info!("FPS: {:.2}", format.stream.fps); - if use_ffmpeg { + if path.extension().unwrap_or_default().to_string_lossy() != "ts" { download_ffmpeg(&ctx, format.stream, path.as_path()).await?; } else if path.to_str().unwrap() == "-" { let mut stdout = std::io::stdout().lock(); diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 083b401..c895d51 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -16,6 +16,7 @@ pub use cli::{archive::Archive, download::Download, login::Login}; #[async_trait::async_trait(?Send)] trait Execute { + fn pre_check(&self) -> Result<()> { Ok(()) } async fn execute(self, ctx: Context) -> Result<()>; } @@ -163,7 +164,7 @@ pub async fn cli_entrypoint() { } }; if let Err(err) = result { - error!("{}", err); + error!("a unexpected error occurred: {}", err); std::process::exit(1) } } From 5826d95e6aa2964dfabf189a3b785bab89972c14 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 3 Dec 2022 01:04:29 +0100 Subject: [PATCH 233/630] Add progress width offset --- crunchy-cli-core/src/cli/utils.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 6197684..0bf6b45 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -75,11 +75,17 @@ pub async fn download_segments( .0 .0 as usize; + // there is a offset of 1 "length" (idk how to describe it), so removing 1 from + // `progress_available` would fill the terminal width completely. on multiple + // systems there is a bug that printing until the end of the line causes a newline + // even though technically there shouldn't be one. on my tests, this only happens on + // windows and mac machines and (at the addressed environments) only with release + // builds. so maybe an unwanted optimization? let progress_available = size - if let Some(msg) = &message { - 34 + msg.len() + 35 + msg.len() } else { - 33 + 34 }; let progress_done_count = (progress_available as f64 * (percentage / 100f64)).ceil() as usize; From cd1308426ee6611292fcdb6bfcf2d24da1a18ea0 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 3 Dec 2022 12:26:36 +0100 Subject: [PATCH 234/630] Re-add static-ssl feature --- Cargo.lock | 30 ++++++++++++++++++++---------- Cargo.toml | 3 +++ crunchy-cli-core/Cargo.lock | 30 ++++++++++++++++++++---------- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff1f3dd..720a1c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" dependencies = [ "cc", "cxxbridge-flags", @@ -397,9 +397,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" dependencies = [ "cc", "codespan-reporting", @@ -412,15 +412,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" [[package]] name = "cxxbridge-macro" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" dependencies = [ "proc-macro2", "quote", @@ -910,9 +910,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libnghttp2-sys" @@ -1107,6 +1107,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "111.24.0+1.1.1s" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.78" @@ -1116,6 +1125,7 @@ dependencies = [ "autocfg", "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/Cargo.toml b/Cargo.toml index d08c47f..1b815c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ default = ["static-curl"] # Embed a static curl library into the binary instead of just linking it. static-curl = ["crunchy-cli-core/static-curl"] +# Embed a static tls library into the binary instead of just linking it. +# has no effect on Windows, always activated there. +static-ssl = ["crunchy-cli-core/static-ssl"] [dependencies] tokio = { version = "1.22", features = ["macros", "rt-multi-thread", "time"], default-features = false } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index b842f38..2612b36 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" dependencies = [ "cc", "cxxbridge-flags", @@ -365,9 +365,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" dependencies = [ "cc", "codespan-reporting", @@ -380,15 +380,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" [[package]] name = "cxxbridge-macro" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" dependencies = [ "proc-macro2", "quote", @@ -878,9 +878,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libnghttp2-sys" @@ -1075,6 +1075,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "111.24.0+1.1.1s" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.78" @@ -1084,6 +1093,7 @@ dependencies = [ "autocfg", "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index d797098..395030a 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -5,8 +5,8 @@ version = "3.0.0-dev.1" edition = "2021" [features] -# Embed a static curl library into the binary instead of just linking it. static-curl = ["crunchyroll-rs/static-curl"] +static-ssl = ["isahc/native-tls-static"] [dependencies] anyhow = "1.0" From 64717fd405256daa3a764107da92c8bfad309138 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 3 Dec 2022 12:32:45 +0100 Subject: [PATCH 235/630] Update dependencies --- Cargo.lock | 4 ++-- crunchy-cli-core/Cargo.lock | 4 ++-- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 720a1c4..108a431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" dependencies = [ "aes", "cbc", @@ -325,7 +325,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" dependencies = [ "darling", "quote", diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 2612b36..d0f951a 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -271,7 +271,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" dependencies = [ "aes", "cbc", @@ -293,7 +293,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#66678d49bc071f49c83a7fa73ab280d1c1de5b2f" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" dependencies = [ "darling", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 395030a..a1d898e 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [features] static-curl = ["crunchyroll-rs/static-curl"] -static-ssl = ["isahc/native-tls-static"] +static-ssl = ["crunchyroll-rs/static-ssl"] [dependencies] anyhow = "1.0" From 7c3bbfc1737ecdaf291d25ca817f1d35eccd21e3 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 4 Dec 2022 18:54:19 +0100 Subject: [PATCH 236/630] Wait until buffer is empty when downloading --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 0bf6b45..758636d 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -169,7 +169,7 @@ pub async fn download_segments( data_pos += 1; } - if *count.lock().unwrap() >= total_segments { + if *count.lock().unwrap() >= total_segments && buf.is_empty() { break; } } From 285d27772c10b288022bda19f4b027505f96c284 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 4 Dec 2022 18:54:22 +0100 Subject: [PATCH 237/630] Add manually .ass file editing to fix #32 --- crunchy-cli-core/src/cli/archive.rs | 108 ++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index d4b0a5d..d586725 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -8,6 +8,7 @@ use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::Execute; use anyhow::{bail, Result}; +use chrono::{NaiveTime, Timelike}; use crunchyroll_rs::media::{Resolution, StreamSubtitle}; use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; use log::{debug, error, info}; @@ -113,7 +114,12 @@ impl Execute for Archive { fn pre_check(&self) -> Result<()> { if !has_ffmpeg() { bail!("FFmpeg is needed to run this command") - } else if PathBuf::from(&self.output).extension().unwrap_or_default().to_string_lossy() != "mkv" { + } else if PathBuf::from(&self.output) + .extension() + .unwrap_or_default() + .to_string_lossy() + != "mkv" + { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } @@ -276,9 +282,18 @@ impl Execute for Archive { } } + let (primary_video, _) = video_paths.get(0).unwrap(); + let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap(); for subtitle in subtitles { - subtitle_paths - .push((download_subtitle(&self, subtitle.clone()).await?, subtitle)) + subtitle_paths.push(( + download_subtitle( + &self, + subtitle.clone(), + primary_video_length + ) + .await?, + subtitle, + )) } generate_mkv(&self, path, video_paths, audio_paths, subtitle_paths)? @@ -403,15 +418,20 @@ async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Res Ok(path) } -async fn download_subtitle(archive: &Archive, subtitle: StreamSubtitle) -> Result<TempPath> { +async fn download_subtitle( + archive: &Archive, + subtitle: StreamSubtitle, + max_length: NaiveTime, +) -> Result<TempPath> { let tempfile = tempfile(".ass")?; let (mut file, path) = tempfile.into_parts(); let mut buf = vec![]; subtitle.write_to(&mut buf).await?; if !archive.no_subtitle_optimizations { - buf = fix_subtitle(buf) + buf = fix_subtitle_look_and_feel(buf) } + buf = fix_subtitle_length(buf, max_length); file.write_all(buf.as_slice())?; @@ -421,7 +441,7 @@ async fn download_subtitle(archive: &Archive, subtitle: StreamSubtitle) -> Resul /// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video /// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) /// for more information. -fn fix_subtitle(raw: Vec<u8>) -> Vec<u8> { +fn fix_subtitle_look_and_feel(raw: Vec<u8>) -> Vec<u8> { let mut script_info = false; let mut new = String::new(); @@ -439,6 +459,62 @@ fn fix_subtitle(raw: Vec<u8>) -> Vec<u8> { new.into_bytes() } +/// Fix the length of subtitles to a specified maximum amount. This is required because sometimes +/// subtitles have an unnecessary entry long after the actual video ends with artificially extends +/// the video length on some video players. To prevent this, the video length must be hard set. See +/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// information. +fn fix_subtitle_length(raw: Vec<u8>, max_length: NaiveTime) -> Vec<u8> { + let re = + Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) + .unwrap(); + + // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 + // digits so them have to be reduced manually to avoid the panic + fn format_naive_time(native_time: NaiveTime) -> String { + let formatted_time = native_time.format("%f").to_string(); + format!("{}.{}", native_time.format("%T"), if formatted_time.len() <= 2 { + native_time.format("%2f").to_string() + } else { + formatted_time.split_at(2).0.parse().unwrap() + }) + } + + let length_as_string = format_naive_time(max_length); + let mut new = String::new(); + + for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { + if let Some(capture) = re.captures(line) { + let start = capture.name("start").map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }); + let end = capture.name("end").map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }); + + if start > max_length { + continue + } else if end > max_length { + new.push_str(re.replace( + line, + format!( + "Dialogue: {},{},", + format_naive_time(start), + &length_as_string + ), + ).to_string().as_str()) + } else { + new.push_str(line) + } + } else { + new.push_str(line) + } + new.push('\n') + } + + new.into_bytes() +} + fn generate_mkv( archive: &Archive, target: PathBuf, @@ -450,8 +526,6 @@ fn generate_mkv( let mut maps = vec![]; let mut metadata = vec![]; - let mut video_length = (0, 0, 0, 0); - for (i, (video_path, format)) in video_paths.iter().enumerate() { input.extend(["-i".to_string(), video_path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), i.to_string()]); @@ -471,11 +545,6 @@ fn generate_mkv( format!("-metadata:s:a:{}", i), format!("title={}", format.audio.to_human_readable()), ]); - - let vid_len = get_video_length(video_path.to_path_buf())?; - if vid_len > video_length { - video_length = vid_len - } } for (i, (audio_path, format)) in audio_paths.iter().enumerate() { input.extend(["-i".to_string(), audio_path.to_string_lossy().to_string()]); @@ -552,11 +621,11 @@ fn generate_mkv( /// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry /// long after the actual video ends with artificially extends the video length on some video players. -/// To prevent this, the video length must be hard set with ffmpeg. See +/// To prevent this, the video length must be hard set. See /// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more /// information. -fn get_video_length(path: PathBuf) -> Result<(u32, u32, u32, u32)> { - let video_length = Regex::new(r"Duration:\s?(\d+):(\d+):(\d+).(\d+),")?; +fn get_video_length(path: PathBuf) -> Result<NaiveTime> { + let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; let ffmpeg = Command::new("ffmpeg") .stdout(Stdio::null()) @@ -567,10 +636,5 @@ fn get_video_length(path: PathBuf) -> Result<(u32, u32, u32, u32)> { let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); - Ok(( - caps[1].parse()?, - caps[2].parse()?, - caps[3].parse()?, - caps[4].parse()?, - )) + Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) } From 342cf23ae00e62c243e1907f602878c8f80209da Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 4 Dec 2022 18:57:54 +0100 Subject: [PATCH 238/630] Remove if condition from ci --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3900976..eddc7b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: jobs: build-nix: - if: github.ref == 'refs/heads/master' runs-on: ${{ matrix.os }} strategy: matrix: @@ -77,7 +76,6 @@ jobs: if-no-files-found: error build-windows: - if: github.ref == 'refs/heads/master' runs-on: windows-latest steps: - name: Checkout From faadd89fffb1832126de5bfdf89dc421409535bf Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 4 Dec 2022 18:54:19 +0100 Subject: [PATCH 239/630] Wait until buffer is empty when downloading --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 0bf6b45..758636d 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -169,7 +169,7 @@ pub async fn download_segments( data_pos += 1; } - if *count.lock().unwrap() >= total_segments { + if *count.lock().unwrap() >= total_segments && buf.is_empty() { break; } } From c383b4d3076585931d441ad81299b7bb0449cfd9 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 4 Dec 2022 19:10:43 +0100 Subject: [PATCH 240/630] Fix filename generation if file already exists --- crunchy-cli-core/src/utils/os.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 92e4e9f..4919c08 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -42,7 +42,11 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { i += 1; let ext = path.extension().unwrap().to_string_lossy(); - let filename = path.file_stem().unwrap().to_string_lossy(); + let mut filename = path.file_stem().unwrap().to_str().unwrap(); + + if filename.ends_with(&format!(" ({})", i-1)) { + filename = filename.strip_suffix(&format!(" ({})", i-1)).unwrap(); + } path.set_file_name(format!("{} ({}).{}", filename, i, ext)) } From 4cd46f19ac46077c11b494c26e858ce9e9b1140e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 6 Dec 2022 22:02:13 +0100 Subject: [PATCH 241/630] Fix high ffmpeg cpu consuming with archive --- crunchy-cli-core/src/cli/archive.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index d586725..f44c21a 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -8,7 +8,7 @@ use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::Execute; use anyhow::{bail, Result}; -use chrono::{NaiveTime, Timelike}; +use chrono::NaiveTime; use crunchyroll_rs::media::{Resolution, StreamSubtitle}; use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; use log::{debug, error, info}; @@ -402,7 +402,9 @@ async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Res .stdout(Stdio::null()) .stderr(Stdio::piped()) .arg("-y") - .args(["-f", "mpegts", "-i", "pipe:"]) + .args(["-f", "mpegts"]) + .args(["-i", "pipe:"]) + .args(["-c", "copy"]) .args(if only_audio { vec!["-vn"] } else { vec![] }) .arg(path.to_str().unwrap()) .spawn()?; From 91f8a82ca48f8cf288e974724656735a965c6d04 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 7 Dec 2022 00:39:32 +0100 Subject: [PATCH 242/630] Fmt --- crunchy-cli-core/src/cli/download.rs | 7 ++++++- crunchy-cli-core/src/lib.rs | 4 +++- crunchy-cli-core/src/utils/os.rs | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index b0d0ffa..df48d54 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -68,7 +68,12 @@ impl Execute for Download { fn pre_check(&self) -> Result<()> { if has_ffmpeg() { debug!("FFmpeg detected") - } else if PathBuf::from(&self.output).extension().unwrap_or_default().to_string_lossy() != "ts" { + } else if PathBuf::from(&self.output) + .extension() + .unwrap_or_default() + .to_string_lossy() + != "ts" + { bail!("File extension is not '.ts'. If you want to use a custom file format, please install ffmpeg") } diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index c895d51..e8cd52a 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -16,7 +16,9 @@ pub use cli::{archive::Archive, download::Download, login::Login}; #[async_trait::async_trait(?Send)] trait Execute { - fn pre_check(&self) -> Result<()> { Ok(()) } + fn pre_check(&self) -> Result<()> { + Ok(()) + } async fn execute(self, ctx: Context) -> Result<()>; } diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 92e4e9f..e5c00ac 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -51,5 +51,7 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { /// Sanitizes the given path to not contain any invalid file character. pub fn sanitize_file(path: PathBuf) -> PathBuf { - path.with_file_name(sanitize_filename::sanitize(path.file_name().unwrap().to_string_lossy())) + path.with_file_name(sanitize_filename::sanitize( + path.file_name().unwrap().to_string_lossy(), + )) } From 6832c69eaa5edb93c9218f26b18435fe7676d1bd Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 7 Dec 2022 01:08:47 +0100 Subject: [PATCH 243/630] Add ffmpeg encode presets --- crunchy-cli-core/src/cli/archive.rs | 204 ++++++++++++++++++++++++---- 1 file changed, 174 insertions(+), 30 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index f44c21a..a6e6d4e 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -26,13 +26,145 @@ pub enum MergeBehavior { Video, } -fn parse_merge_behavior(s: &str) -> Result<MergeBehavior, String> { - Ok(match s.to_lowercase().as_str() { - "auto" => MergeBehavior::Auto, - "audio" => MergeBehavior::Audio, - "video" => MergeBehavior::Video, - _ => return Err(format!("'{}' is not a valid merge behavior", s)), - }) +impl MergeBehavior { + fn parse(s: &str) -> Result<MergeBehavior, String> { + Ok(match s.to_lowercase().as_str() { + "auto" => MergeBehavior::Auto, + "audio" => MergeBehavior::Audio, + "video" => MergeBehavior::Video, + _ => return Err(format!("'{}' is not a valid merge behavior", s)), + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FFmpegPreset { + Nvidia, + + Av1, + H265, + H264, +} + +impl ToString for FFmpegPreset { + fn to_string(&self) -> String { + match self { + &FFmpegPreset::Nvidia => "nvidia", + &FFmpegPreset::Av1 => "av1", + &FFmpegPreset::H265 => "h265", + &FFmpegPreset::H264 => "h264", + } + .to_string() + } +} + +impl FFmpegPreset { + fn all() -> Vec<FFmpegPreset> { + vec![ + FFmpegPreset::Nvidia, + FFmpegPreset::Av1, + FFmpegPreset::H265, + FFmpegPreset::H264, + ] + } + + fn description(self) -> String { + match self { + FFmpegPreset::Nvidia => "If you're have a nvidia card, use hardware / gpu accelerated video processing if available", + FFmpegPreset::Av1 => "Encode the video(s) with the av1 codec. Hardware acceleration is currently not possible with this", + FFmpegPreset::H265 => "Encode the video(s) with the h265 codec", + FFmpegPreset::H264 => "Encode the video(s) with the h264 codec" + }.to_string() + } + + fn parse(s: &str) -> Result<FFmpegPreset, String> { + Ok(match s.to_lowercase().as_str() { + "nvidia" => FFmpegPreset::Nvidia, + "av1" => FFmpegPreset::Av1, + "h265" | "h.265" | "hevc" => FFmpegPreset::H265, + "h264" | "h.264" => FFmpegPreset::H264, + _ => return Err(format!("'{}' is not a valid ffmpeg preset", s)), + }) + } + + fn ffmpeg_presets(mut presets: Vec<FFmpegPreset>) -> Result<(Vec<String>, Vec<String>)> { + fn preset_check_remove(presets: &mut Vec<FFmpegPreset>, preset: FFmpegPreset) -> bool { + if let Some(i) = presets.iter().position(|p| p == &preset) { + presets.remove(i); + true + } else { + false + } + } + + let nvidia = preset_check_remove(&mut presets, FFmpegPreset::Nvidia); + if presets.len() > 1 { + bail!( + "Can only use one video codec, {} found: {}", + presets.len(), + presets + .iter() + .map(|p| p.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + + let (mut input, mut output) = (vec![], vec![]); + for preset in presets { + if nvidia { + match preset { + FFmpegPreset::Av1 => bail!("Hardware acceleration preset ('nvidia') is not available in combination with the 'av1' preset"), + FFmpegPreset::H265 => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "hevc_nvenc"]); + } + FFmpegPreset::H264 => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "h264_nvenc"]); + } + _ => () + } + } else { + match preset { + FFmpegPreset::Av1 => { + output.extend(["-c:v", "libaom-av1"]); + } + FFmpegPreset::H265 => { + output.extend(["-c:v", "libx265"]); + } + FFmpegPreset::H264 => { + output.extend(["-c:v", "libx264"]); + } + _ => (), + } + } + } + + if input.is_empty() && output.is_empty() { + let mut new_presets = vec![]; + if nvidia { + new_presets.push(FFmpegPreset::Nvidia) + } + + if !new_presets.is_empty() { + return FFmpegPreset::ffmpeg_presets(new_presets); + } else { + output.extend(["-c", "copy"]) + } + } else { + if output.is_empty() { + output.extend(["-c", "copy"]) + } else { + output.extend(["-c:a", "copy", "-c:s", "copy"]) + } + } + + Ok(( + input.into_iter().map(|i| i.to_string()).collect(), + output.into_iter().map(|o| o.to_string()).collect(), + )) + } } #[derive(Debug, clap::Parser)] @@ -88,9 +220,14 @@ pub struct Archive { Valid options are 'audio' (stores one video and all other languages as audio only), 'video' (stores the video + audio for every language) and 'auto' (detects if videos differ in length: if so, behave like 'video' else like 'audio')" )] #[arg(short, long, default_value = "auto")] - #[arg(value_parser = parse_merge_behavior)] + #[arg(value_parser = MergeBehavior::parse)] merge: MergeBehavior, + #[arg(help = format!("Presets for audio converting. Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + #[arg(long)] + #[arg(value_parser = FFmpegPreset::parse)] + ffmpeg_preset: Vec<FFmpegPreset>, + #[arg( help = "Set which subtitle language should be set as default / auto shown when starting a video" )] @@ -122,6 +259,7 @@ impl Execute for Archive { { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } + let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; Ok(()) } @@ -286,12 +424,7 @@ impl Execute for Archive { let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap(); for subtitle in subtitles { subtitle_paths.push(( - download_subtitle( - &self, - subtitle.clone(), - primary_video_length - ) - .await?, + download_subtitle(&self, subtitle.clone(), primary_video_length).await?, subtitle, )) } @@ -475,11 +608,15 @@ fn fix_subtitle_length(raw: Vec<u8>, max_length: NaiveTime) -> Vec<u8> { // digits so them have to be reduced manually to avoid the panic fn format_naive_time(native_time: NaiveTime) -> String { let formatted_time = native_time.format("%f").to_string(); - format!("{}.{}", native_time.format("%T"), if formatted_time.len() <= 2 { - native_time.format("%2f").to_string() - } else { - formatted_time.split_at(2).0.parse().unwrap() - }) + format!( + "{}.{}", + native_time.format("%T"), + if formatted_time.len() <= 2 { + native_time.format("%2f").to_string() + } else { + formatted_time.split_at(2).0.parse().unwrap() + } + ) } let length_as_string = format_naive_time(max_length); @@ -495,16 +632,20 @@ fn fix_subtitle_length(raw: Vec<u8>, max_length: NaiveTime) -> Vec<u8> { }); if start > max_length { - continue + continue; } else if end > max_length { - new.push_str(re.replace( - line, - format!( - "Dialogue: {},{},", - format_naive_time(start), - &length_as_string - ), - ).to_string().as_str()) + new.push_str( + re.replace( + line, + format!( + "Dialogue: {},{},", + format_naive_time(start), + &length_as_string + ), + ) + .to_string() + .as_str(), + ) } else { new.push_str(line) } @@ -579,7 +720,11 @@ fn generate_mkv( ]); } + let (input_presets, output_presets) = + FFmpegPreset::ffmpeg_presets(archive.ffmpeg_preset.clone())?; + let mut command_args = vec!["-y".to_string()]; + command_args.extend(input_presets); command_args.extend(input); command_args.extend(maps); command_args.extend(metadata); @@ -599,9 +744,8 @@ fn generate_mkv( command_args.extend(["-disposition:s:0".to_string(), "0".to_string()]) } + command_args.extend(output_presets); command_args.extend([ - "-c".to_string(), - "copy".to_string(), "-f".to_string(), "matroska".to_string(), target.to_string_lossy().to_string(), From 1b1756a0ae3684d1d7e1282f5cd347a144c9e449 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 7 Dec 2022 15:12:01 +0100 Subject: [PATCH 244/630] Add long help to ffmpeg preset --- crunchy-cli-core/src/cli/archive.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index a6e6d4e..057e7a8 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -223,7 +223,12 @@ pub struct Archive { #[arg(value_parser = MergeBehavior::parse)] merge: MergeBehavior, - #[arg(help = format!("Presets for audio converting. Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + #[arg(help = format!("Presets for audio converting. \ + Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + #[arg(help = format!("Presets for audio converting. \ + Generally used to minify the file size with keeping (nearly) the same quality. \ + It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ + Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] ffmpeg_preset: Vec<FFmpegPreset>, From 933d217b63d89666001b557c3777607c3fd8b315 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 7 Dec 2022 20:08:44 +0100 Subject: [PATCH 245/630] Add pre_check checking --- crunchy-cli-core/src/lib.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index c895d51..2e0cf4d 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -152,18 +152,28 @@ pub async fn cli_entrypoint() { .unwrap(); debug!("Created ctrl-c handler"); - let result = match cli.command { - Command::Archive(archive) => archive.execute(ctx).await, - Command::Download(download) => download.execute(ctx).await, + match cli.command { + Command::Archive(archive) => execute_executor(archive,ctx).await, + Command::Download(download) => execute_executor(download, ctx).await, Command::Login(login) => { if login.remove { - Ok(()) + return; } else { - login.execute(ctx).await + execute_executor(login, ctx).await } } }; - if let Err(err) = result { +} + +/// Cannot be done in the main function. I wanted to return `dyn` [`Execute`] from the match but had to +/// box it which then conflicts with [`Execute::execute`] which consumes `self` +async fn execute_executor(executor: impl Execute, ctx: Context) { + if let Err(err) = executor.pre_check() { + error!("Misconfigurations detected: {}", err); + std::process::exit(1) + } + + if let Err(err) = executor.execute(ctx).await { error!("a unexpected error occurred: {}", err); std::process::exit(1) } From ce5672588e6f04f43b789356cb7b552851a44d4b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 7 Dec 2022 19:47:03 +0100 Subject: [PATCH 246/630] Update dependencies --- Cargo.lock | 140 ++++++++++++++++++++++++++++++------ crunchy-cli-core/Cargo.lock | 140 ++++++++++++++++++++++++++++++------ crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 239 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 108a431..f3a24bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,7 +139,7 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -303,13 +303,14 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" dependencies = [ "aes", "cbc", "chrono", "crunchyroll-rs-internal", "curl-sys", + "dash-mpd", "http", "isahc", "m3u8-rs", @@ -325,7 +326,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" dependencies = [ "darling", "quote", @@ -463,10 +464,26 @@ dependencies = [ ] [[package]] -name = "data-encoding" -version = "2.3.2" +name = "dash-mpd" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "d6a181b3a4a1ef2cb4dd72f2a85e36a2e54445b8b9513635d3fad23e9b9a7c4c" +dependencies = [ + "chrono", + "log", + "quick-xml", + "regex", + "serde", + "serde_with", + "thiserror", + "xattr", +] + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "dirs" @@ -691,6 +708,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.8" @@ -810,6 +833,7 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -1077,9 +1101,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl" -version = "0.10.43" +version = "0.10.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" dependencies = [ "bitflags", "cfg-if", @@ -1118,9 +1142,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.78" +version = "0.9.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" dependencies = [ "autocfg", "cc", @@ -1227,6 +1251,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.21" @@ -1327,9 +1361,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" dependencies = [ "bitflags", "errno", @@ -1423,18 +1457,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" dependencies = [ "proc-macro2", "quote", @@ -1464,6 +1498,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.17", +] + +[[package]] +name = "serde_with_macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook" version = "0.3.14" @@ -1624,6 +1686,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1641,9 +1730,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -1654,7 +1743,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1749,9 +1838,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" @@ -2055,3 +2144,12 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index d0f951a..1e9c6d4 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -139,7 +139,7 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -271,13 +271,14 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" dependencies = [ "aes", "cbc", "chrono", "crunchyroll-rs-internal", "curl-sys", + "dash-mpd", "http", "isahc", "m3u8-rs", @@ -293,7 +294,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#7644b24864608c059d702db8b373be81ffcec643" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" dependencies = [ "darling", "quote", @@ -431,10 +432,26 @@ dependencies = [ ] [[package]] -name = "data-encoding" -version = "2.3.2" +name = "dash-mpd" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "d6a181b3a4a1ef2cb4dd72f2a85e36a2e54445b8b9513635d3fad23e9b9a7c4c" +dependencies = [ + "chrono", + "log", + "quick-xml", + "regex", + "serde", + "serde_with", + "thiserror", + "xattr", +] + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "dirs" @@ -659,6 +676,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.8" @@ -778,6 +801,7 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -1045,9 +1069,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl" -version = "0.10.43" +version = "0.10.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" dependencies = [ "bitflags", "cfg-if", @@ -1086,9 +1110,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.78" +version = "0.9.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" dependencies = [ "autocfg", "cc", @@ -1195,6 +1219,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.21" @@ -1289,9 +1323,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" dependencies = [ "bitflags", "errno", @@ -1385,18 +1419,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" dependencies = [ "proc-macro2", "quote", @@ -1426,6 +1460,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.17", +] + +[[package]] +name = "serde_with_macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook" version = "0.3.14" @@ -1580,6 +1642,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1597,9 +1686,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -1610,7 +1699,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1705,9 +1794,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" @@ -2011,3 +2100,12 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index a1d898e..da794d3 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -13,7 +13,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.0", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["stream", "parse"] } +crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["parse", "hls-stream", "dash-stream"] } ctrlc = "3.2" dirs = "4.0" isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8" } From 2e4e897dc1fa44801a02b553239eb5903f9b991a Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 7 Dec 2022 20:22:20 +0100 Subject: [PATCH 247/630] Fix only nvidia preset stack overflow --- crunchy-cli-core/src/cli/archive.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 057e7a8..e0b4513 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -11,7 +11,7 @@ use anyhow::{bail, Result}; use chrono::NaiveTime; use crunchyroll_rs::media::{Resolution, StreamSubtitle}; use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use regex::Regex; use std::collections::BTreeMap; use std::io::Write; @@ -114,7 +114,7 @@ impl FFmpegPreset { for preset in presets { if nvidia { match preset { - FFmpegPreset::Av1 => bail!("Hardware acceleration preset ('nvidia') is not available in combination with the 'av1' preset"), + FFmpegPreset::Av1 => bail!("'nvidia' hardware acceleration preset is not available in combination with the 'av1' codec preset"), FFmpegPreset::H265 => { input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); output.extend(["-c:v", "hevc_nvenc"]); @@ -142,16 +142,7 @@ impl FFmpegPreset { } if input.is_empty() && output.is_empty() { - let mut new_presets = vec![]; - if nvidia { - new_presets.push(FFmpegPreset::Nvidia) - } - - if !new_presets.is_empty() { - return FFmpegPreset::ffmpeg_presets(new_presets); - } else { - output.extend(["-c", "copy"]) - } + output.extend(["-c", "copy"]) } else { if output.is_empty() { output.extend(["-c", "copy"]) @@ -265,6 +256,11 @@ impl Execute for Archive { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; + // check if the only given preset is FFmpegPreset::Nvidia. This checking is not done in + // FFmpegPreset::ffmpeg_presets since the warning it emits would print twice + if self.ffmpeg_preset.len() == 1 && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia { + warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") + } Ok(()) } From a32d3aef87e62c98935b07abc36effc3413ae645 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 00:12:25 +0100 Subject: [PATCH 248/630] Update dependencies --- Cargo.lock | 67 ++++++++++++++++++++++++---- crunchy-cli-core/Cargo.lock | 67 ++++++++++++++++++++++++---- crunchy-cli-core/Cargo.toml | 3 ++ crunchy-cli-core/src/cli/archive.rs | 2 +- crunchy-cli-core/src/cli/download.rs | 8 ++-- 5 files changed, 124 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3a24bb..fc5d0d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -285,6 +297,7 @@ dependencies = [ "chrono", "clap", "crunchyroll-rs", + "csv", "ctrlc", "dirs", "isahc", @@ -293,6 +306,8 @@ dependencies = [ "regex", "rustls-native-certs", "sanitize-filename", + "serde", + "serde_json", "signal-hook", "sys-locale", "tempfile", @@ -303,7 +318,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" dependencies = [ "aes", "cbc", @@ -326,7 +341,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" dependencies = [ "darling", "quote", @@ -343,6 +358,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "ctrlc" version = "3.2.3" @@ -722,7 +759,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.4", ] [[package]] @@ -763,7 +800,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.4", "pin-project-lite", "socket2", "tokio", @@ -867,9 +904,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded" [[package]] name = "is-terminal" @@ -911,6 +948,12 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.4" @@ -1301,6 +1344,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.28" @@ -1481,7 +1530,7 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ - "itoa", + "itoa 1.0.4", "ryu", "serde", ] @@ -1493,7 +1542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.4", "ryu", "serde", ] @@ -1692,7 +1741,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa", + "itoa 1.0.4", "serde", "time-core", "time-macros", diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 1e9c6d4..93c7522 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -86,6 +86,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -253,6 +265,7 @@ dependencies = [ "chrono", "clap", "crunchyroll-rs", + "csv", "ctrlc", "dirs", "isahc", @@ -261,6 +274,8 @@ dependencies = [ "regex", "rustls-native-certs", "sanitize-filename", + "serde", + "serde_json", "signal-hook", "sys-locale", "tempfile", @@ -271,7 +286,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" dependencies = [ "aes", "cbc", @@ -294,7 +309,7 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#50f9ad65f65affcc5839af565d425b9cd678b73a" +source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" dependencies = [ "darling", "quote", @@ -311,6 +326,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "ctrlc" version = "3.2.3" @@ -690,7 +727,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.4", ] [[package]] @@ -731,7 +768,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.4", "pin-project-lite", "socket2", "tokio", @@ -835,9 +872,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded" [[package]] name = "is-terminal" @@ -879,6 +916,12 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.4" @@ -1269,6 +1312,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.28" @@ -1443,7 +1492,7 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ - "itoa", + "itoa 1.0.4", "ryu", "serde", ] @@ -1455,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.4", "ryu", "serde", ] @@ -1648,7 +1697,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa", + "itoa 1.0.4", "serde", "time-core", "time-macros", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index da794d3..84fdcf2 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -14,6 +14,7 @@ async-trait = "0.1" clap = { version = "4.0", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["parse", "hls-stream", "dash-stream"] } +csv = "1.1" ctrlc = "3.2" dirs = "4.0" isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8" } @@ -21,6 +22,8 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.13" regex = "1.6" sanitize-filename = "0.4" +serde = "1.0" +serde_json = "1.0" signal-hook = "0.3" tempfile = "3.3" terminal_size = "0.2" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index d4b0a5d..626a4f0 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -341,7 +341,7 @@ async fn formats_from_series( } let streams = episode.streams().await?; - let streaming_data = streams.streaming_data(None).await?; + let streaming_data = streams.hls_streaming_data(None).await?; let Some(stream) = find_resolution(streaming_data, &archive.resolution) else { bail!( "Resolution ({}x{}) is not available for episode {} ({}) of season {} ({}) of {}", diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index b0d0ffa..393e45a 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -352,9 +352,9 @@ async fn format_from_episode( ); return Ok(None); } - streams.streaming_data(Some(subtitle.clone())).await? + streams.hls_streaming_data(Some(subtitle.clone())).await? } else { - streams.streaming_data(None).await? + streams.hls_streaming_data(None).await? }; let Some(stream) = find_resolution(streaming_data, &download.resolution) else { @@ -400,9 +400,9 @@ async fn format_from_movie( error!("Movie {} has no {} subtitles", movie.title, subtitle); return Ok(None); } - streams.streaming_data(Some(subtitle.clone())).await? + streams.hls_streaming_data(Some(subtitle.clone())).await? } else { - streams.streaming_data(None).await? + streams.hls_streaming_data(None).await? }; streaming_data.sort_by(|a, b| a.resolution.width.cmp(&b.resolution.width).reverse()); From 54018f977325f6f5a04ff273e0f09a90126f5d5d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 01:37:37 +0100 Subject: [PATCH 249/630] Fmt --- crunchy-cli-core/src/cli/download.rs | 7 ++++++- crunchy-cli-core/src/lib.rs | 10 +++++++--- crunchy-cli-core/src/utils/os.rs | 8 +++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 393e45a..ddf45b3 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -68,7 +68,12 @@ impl Execute for Download { fn pre_check(&self) -> Result<()> { if has_ffmpeg() { debug!("FFmpeg detected") - } else if PathBuf::from(&self.output).extension().unwrap_or_default().to_string_lossy() != "ts" { + } else if PathBuf::from(&self.output) + .extension() + .unwrap_or_default() + .to_string_lossy() + != "ts" + { bail!("File extension is not '.ts'. If you want to use a custom file format, please install ffmpeg") } diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 2e0cf4d..92a076f 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -12,11 +12,13 @@ use std::{env, fs}; mod cli; mod utils; -pub use cli::{archive::Archive, download::Download, login::Login}; +pub use cli::{archive::Archive, download::Download, login::Login, search::Search}; #[async_trait::async_trait(?Send)] trait Execute { - fn pre_check(&self) -> Result<()> { Ok(()) } + fn pre_check(&self) -> Result<()> { + Ok(()) + } async fn execute(self, ctx: Context) -> Result<()>; } @@ -57,6 +59,7 @@ enum Command { Archive(Archive), Download(Download), Login(Login), + Search(Search), } #[derive(Debug, Parser)] @@ -153,7 +156,7 @@ pub async fn cli_entrypoint() { debug!("Created ctrl-c handler"); match cli.command { - Command::Archive(archive) => execute_executor(archive,ctx).await, + Command::Archive(archive) => execute_executor(archive, ctx).await, Command::Download(download) => execute_executor(download, ctx).await, Command::Login(login) => { if login.remove { @@ -162,6 +165,7 @@ pub async fn cli_entrypoint() { execute_executor(login, ctx).await } } + Command::Search(search) => execute_executor(search, ctx).await, }; } diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 4919c08..c6e262e 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -44,8 +44,8 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { let ext = path.extension().unwrap().to_string_lossy(); let mut filename = path.file_stem().unwrap().to_str().unwrap(); - if filename.ends_with(&format!(" ({})", i-1)) { - filename = filename.strip_suffix(&format!(" ({})", i-1)).unwrap(); + if filename.ends_with(&format!(" ({})", i - 1)) { + filename = filename.strip_suffix(&format!(" ({})", i - 1)).unwrap(); } path.set_file_name(format!("{} ({}).{}", filename, i, ext)) @@ -55,5 +55,7 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { /// Sanitizes the given path to not contain any invalid file character. pub fn sanitize_file(path: PathBuf) -> PathBuf { - path.with_file_name(sanitize_filename::sanitize(path.file_name().unwrap().to_string_lossy())) + path.with_file_name(sanitize_filename::sanitize( + path.file_name().unwrap().to_string_lossy(), + )) } From c4540ada50fe0de6660c50a82dc166f4d5b26183 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 01:40:19 +0100 Subject: [PATCH 250/630] Remove unwanted ffmpeg output when check if available --- crunchy-cli-core/src/utils/os.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index c6e262e..abdf151 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -1,12 +1,12 @@ use log::debug; use std::io::ErrorKind; use std::path::PathBuf; -use std::process::Command; +use std::process::{Command, Stdio}; use std::{env, io}; use tempfile::{Builder, NamedTempFile}; pub fn has_ffmpeg() -> bool { - if let Err(e) = Command::new("ffmpeg").spawn() { + if let Err(e) = Command::new("ffmpeg").stderr(Stdio::null()).spawn() { if ErrorKind::NotFound != e.kind() { debug!( "unknown error occurred while checking if ffmpeg exists: {}", From 985ec2ade9d8c1ee36bbb8730035183dd33b8215 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 01:57:14 +0100 Subject: [PATCH 251/630] Remove search command (wrong commit oops) --- crunchy-cli-core/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 92a076f..a13ac2f 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -12,7 +12,7 @@ use std::{env, fs}; mod cli; mod utils; -pub use cli::{archive::Archive, download::Download, login::Login, search::Search}; +pub use cli::{archive::Archive, download::Download, login::Login}; #[async_trait::async_trait(?Send)] trait Execute { @@ -59,7 +59,6 @@ enum Command { Archive(Archive), Download(Download), Login(Login), - Search(Search), } #[derive(Debug, Parser)] @@ -165,7 +164,6 @@ pub async fn cli_entrypoint() { execute_executor(login, ctx).await } } - Command::Search(search) => execute_executor(search, ctx).await, }; } From 2f7e992a6ea9ab591e4e089843a81ce00d0f1f9f Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 14:04:43 +0100 Subject: [PATCH 252/630] Move ffmpeg presets to utils --- crunchy-cli-core/src/cli/archive.rs | 129 +--------------------------- crunchy-cli-core/src/cli/utils.rs | 123 +++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 127 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index e0b4513..3fa7b3c 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -1,5 +1,5 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, find_resolution}; +use crate::cli::utils::{download_segments, FFmpegPreset, find_resolution}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; @@ -37,127 +37,6 @@ impl MergeBehavior { } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum FFmpegPreset { - Nvidia, - - Av1, - H265, - H264, -} - -impl ToString for FFmpegPreset { - fn to_string(&self) -> String { - match self { - &FFmpegPreset::Nvidia => "nvidia", - &FFmpegPreset::Av1 => "av1", - &FFmpegPreset::H265 => "h265", - &FFmpegPreset::H264 => "h264", - } - .to_string() - } -} - -impl FFmpegPreset { - fn all() -> Vec<FFmpegPreset> { - vec![ - FFmpegPreset::Nvidia, - FFmpegPreset::Av1, - FFmpegPreset::H265, - FFmpegPreset::H264, - ] - } - - fn description(self) -> String { - match self { - FFmpegPreset::Nvidia => "If you're have a nvidia card, use hardware / gpu accelerated video processing if available", - FFmpegPreset::Av1 => "Encode the video(s) with the av1 codec. Hardware acceleration is currently not possible with this", - FFmpegPreset::H265 => "Encode the video(s) with the h265 codec", - FFmpegPreset::H264 => "Encode the video(s) with the h264 codec" - }.to_string() - } - - fn parse(s: &str) -> Result<FFmpegPreset, String> { - Ok(match s.to_lowercase().as_str() { - "nvidia" => FFmpegPreset::Nvidia, - "av1" => FFmpegPreset::Av1, - "h265" | "h.265" | "hevc" => FFmpegPreset::H265, - "h264" | "h.264" => FFmpegPreset::H264, - _ => return Err(format!("'{}' is not a valid ffmpeg preset", s)), - }) - } - - fn ffmpeg_presets(mut presets: Vec<FFmpegPreset>) -> Result<(Vec<String>, Vec<String>)> { - fn preset_check_remove(presets: &mut Vec<FFmpegPreset>, preset: FFmpegPreset) -> bool { - if let Some(i) = presets.iter().position(|p| p == &preset) { - presets.remove(i); - true - } else { - false - } - } - - let nvidia = preset_check_remove(&mut presets, FFmpegPreset::Nvidia); - if presets.len() > 1 { - bail!( - "Can only use one video codec, {} found: {}", - presets.len(), - presets - .iter() - .map(|p| p.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - let (mut input, mut output) = (vec![], vec![]); - for preset in presets { - if nvidia { - match preset { - FFmpegPreset::Av1 => bail!("'nvidia' hardware acceleration preset is not available in combination with the 'av1' codec preset"), - FFmpegPreset::H265 => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "hevc_nvenc"]); - } - FFmpegPreset::H264 => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "h264_nvenc"]); - } - _ => () - } - } else { - match preset { - FFmpegPreset::Av1 => { - output.extend(["-c:v", "libaom-av1"]); - } - FFmpegPreset::H265 => { - output.extend(["-c:v", "libx265"]); - } - FFmpegPreset::H264 => { - output.extend(["-c:v", "libx264"]); - } - _ => (), - } - } - } - - if input.is_empty() && output.is_empty() { - output.extend(["-c", "copy"]) - } else { - if output.is_empty() { - output.extend(["-c", "copy"]) - } else { - output.extend(["-c:a", "copy", "-c:s", "copy"]) - } - } - - Ok(( - input.into_iter().map(|i| i.to_string()).collect(), - output.into_iter().map(|o| o.to_string()).collect(), - )) - } -} - #[derive(Debug, clap::Parser)] #[clap(about = "Archive a video")] #[command(arg_required_else_help(true))] @@ -214,9 +93,9 @@ pub struct Archive { #[arg(value_parser = MergeBehavior::parse)] merge: MergeBehavior, - #[arg(help = format!("Presets for audio converting. \ + #[arg(help = format!("Presets for video converting. \ Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] - #[arg(help = format!("Presets for audio converting. \ + #[arg(help = format!("Presets for video converting. \ Generally used to minify the file size with keeping (nearly) the same quality. \ It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] @@ -256,8 +135,6 @@ impl Execute for Archive { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; - // check if the only given preset is FFmpegPreset::Nvidia. This checking is not done in - // FFmpegPreset::ffmpeg_presets since the warning it emits would print twice if self.ffmpeg_preset.len() == 1 && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia { warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") } diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 758636d..9b817e4 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -1,5 +1,5 @@ use crate::utils::context::Context; -use anyhow::Result; +use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; use isahc::AsyncReadResponseExt; use log::{debug, LevelFilter}; @@ -183,3 +183,124 @@ pub async fn download_segments( Ok(()) } + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FFmpegPreset { + Nvidia, + + Av1, + H265, + H264, +} + +impl ToString for FFmpegPreset { + fn to_string(&self) -> String { + match self { + &FFmpegPreset::Nvidia => "nvidia", + &FFmpegPreset::Av1 => "av1", + &FFmpegPreset::H265 => "h265", + &FFmpegPreset::H264 => "h264", + } + .to_string() + } +} + +impl FFmpegPreset { + pub(crate) fn all() -> Vec<FFmpegPreset> { + vec![ + FFmpegPreset::Nvidia, + FFmpegPreset::Av1, + FFmpegPreset::H265, + FFmpegPreset::H264, + ] + } + + pub(crate) fn description(self) -> String { + match self { + FFmpegPreset::Nvidia => "If you're have a nvidia card, use hardware / gpu accelerated video processing if available", + FFmpegPreset::Av1 => "Encode the video(s) with the av1 codec. Hardware acceleration is currently not possible with this", + FFmpegPreset::H265 => "Encode the video(s) with the h265 codec", + FFmpegPreset::H264 => "Encode the video(s) with the h264 codec" + }.to_string() + } + + pub(crate) fn parse(s: &str) -> Result<FFmpegPreset, String> { + Ok(match s.to_lowercase().as_str() { + "nvidia" => FFmpegPreset::Nvidia, + "av1" => FFmpegPreset::Av1, + "h265" | "h.265" | "hevc" => FFmpegPreset::H265, + "h264" | "h.264" => FFmpegPreset::H264, + _ => return Err(format!("'{}' is not a valid ffmpeg preset", s)), + }) + } + + pub(crate) fn ffmpeg_presets(mut presets: Vec<FFmpegPreset>) -> Result<(Vec<String>, Vec<String>)> { + fn preset_check_remove(presets: &mut Vec<FFmpegPreset>, preset: FFmpegPreset) -> bool { + if let Some(i) = presets.iter().position(|p| p == &preset) { + presets.remove(i); + true + } else { + false + } + } + + let nvidia = preset_check_remove(&mut presets, FFmpegPreset::Nvidia); + if presets.len() > 1 { + bail!( + "Can only use one video codec, {} found: {}", + presets.len(), + presets + .iter() + .map(|p| p.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + + let (mut input, mut output) = (vec![], vec![]); + for preset in presets { + if nvidia { + match preset { + FFmpegPreset::Av1 => bail!("'nvidia' hardware acceleration preset is not available in combination with the 'av1' codec preset"), + FFmpegPreset::H265 => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "hevc_nvenc"]); + } + FFmpegPreset::H264 => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "h264_nvenc"]); + } + _ => () + } + } else { + match preset { + FFmpegPreset::Av1 => { + output.extend(["-c:v", "libaom-av1"]); + } + FFmpegPreset::H265 => { + output.extend(["-c:v", "libx265"]); + } + FFmpegPreset::H264 => { + output.extend(["-c:v", "libx264"]); + } + _ => (), + } + } + } + + if input.is_empty() && output.is_empty() { + output.extend(["-c", "copy"]) + } else { + if output.is_empty() { + output.extend(["-c", "copy"]) + } else { + output.extend(["-c:a", "copy", "-c:s", "copy"]) + } + } + + Ok(( + input.into_iter().map(|i| i.to_string()).collect(), + output.into_iter().map(|o| o.to_string()).collect(), + )) + } +} From f0de4509c50ab40ca3c451dd61d8b483631a51f6 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 14:09:19 +0100 Subject: [PATCH 253/630] Add ffmpeg presets to download --- crunchy-cli-core/src/cli/download.rs | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index df48d54..de536dc 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,5 +1,5 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, find_resolution}; +use crate::cli::utils::{download_segments, FFmpegPreset, find_resolution}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; @@ -12,7 +12,7 @@ use crunchyroll_rs::media::{Resolution, VariantData}; use crunchyroll_rs::{ Episode, Locale, Media, MediaCollection, Movie, MovieListing, Season, Series, }; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use std::fs::File; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -59,6 +59,16 @@ pub struct Download { #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] resolution: Resolution, + #[arg(help = format!("Presets for video converting. \ + Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + #[arg(help = format!("Presets for video converting. \ + Generally used to minify the file size with keeping (nearly) the same quality. \ + It is recommended to only use this if you download videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ + Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + #[arg(long)] + #[arg(value_parser = FFmpegPreset::parse)] + ffmpeg_preset: Vec<FFmpegPreset>, + #[arg(help = "Url(s) to Crunchyroll episodes or series")] urls: Vec<String>, } @@ -75,6 +85,13 @@ impl Execute for Download { != "ts" { bail!("File extension is not '.ts'. If you want to use a custom file format, please install ffmpeg") + } else if !self.ffmpeg_preset.is_empty() { + bail!("FFmpeg is required to use (ffmpeg) presets") + } + + let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; + if self.ffmpeg_preset.len() == 1 && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia { + warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") } Ok(()) @@ -211,8 +228,8 @@ impl Execute for Download { tab_info!("Resolution: {}", format.stream.resolution); tab_info!("FPS: {:.2}", format.stream.fps); - if path.extension().unwrap_or_default().to_string_lossy() != "ts" { - download_ffmpeg(&ctx, format.stream, path.as_path()).await?; + if path.extension().unwrap_or_default().to_string_lossy() != "ts" || !self.ffmpeg_preset.is_empty() { + download_ffmpeg(&ctx, &self, format.stream, path.as_path()).await?; } else if path.to_str().unwrap() == "-" { let mut stdout = std::io::stdout().lock(); download_segments(&ctx, &mut stdout, None, format.stream).await?; @@ -227,15 +244,18 @@ impl Execute for Download { } } -async fn download_ffmpeg(ctx: &Context, variant_data: VariantData, target: &Path) -> Result<()> { +async fn download_ffmpeg(ctx: &Context, download: &Download, variant_data: VariantData, target: &Path) -> Result<()> { + let (input_presets, output_presets) = + FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; + let ffmpeg = Command::new("ffmpeg") .stdin(Stdio::piped()) .stdout(Stdio::null()) .stderr(Stdio::piped()) .arg("-y") + .args(input_presets) .args(["-f", "mpegts", "-i", "pipe:"]) - .args(["-safe", "0"]) - .args(["-c", "copy"]) + .args(output_presets) .arg(target.to_str().unwrap()) .spawn()?; From 01e2603e845f7f495ebbd3ad44b678975df03fef Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 14:21:52 +0100 Subject: [PATCH 254/630] Update ffmpeg preset command help --- crunchy-cli-core/src/cli/archive.rs | 4 ++-- crunchy-cli-core/src/cli/download.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 3fa7b3c..8d95968 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -93,9 +93,9 @@ pub struct Archive { #[arg(value_parser = MergeBehavior::parse)] merge: MergeBehavior, - #[arg(help = format!("Presets for video converting. \ + #[arg(help = format!("Presets for video converting. Can be used multiple times. \ Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] - #[arg(help = format!("Presets for video converting. \ + #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ Generally used to minify the file size with keeping (nearly) the same quality. \ It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index de536dc..edd58cc 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -59,9 +59,9 @@ pub struct Download { #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] resolution: Resolution, - #[arg(help = format!("Presets for video converting. \ + #[arg(help = format!("Presets for video converting. Can be used multiple times. \ Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] - #[arg(help = format!("Presets for video converting. \ + #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ Generally used to minify the file size with keeping (nearly) the same quality. \ It is recommended to only use this if you download videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] From a5e60ea6b710f2fba403606b512e05a6a96d0721 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 15:10:53 +0100 Subject: [PATCH 255/630] Add generating file cli output --- crunchy-cli-core/src/cli/archive.rs | 4 +++- crunchy-cli-core/src/cli/download.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index e8a5ca2..f9bb6a5 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -307,7 +307,9 @@ impl Execute for Archive { )) } - generate_mkv(&self, path, video_paths, audio_paths, subtitle_paths)? + let _progess_handler = progress!("Generating mkv"); + generate_mkv(&self, path, video_paths, audio_paths, subtitle_paths)?; + info!("Mkv generated") } } diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index f90355b..b7093aa 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -248,7 +248,7 @@ async fn download_ffmpeg(ctx: &Context, download: &Download, variant_data: Varia let (input_presets, output_presets) = FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; - let ffmpeg = Command::new("ffmpeg") + let mut ffmpeg = Command::new("ffmpeg") .stdin(Stdio::piped()) .stdout(Stdio::null()) .stderr(Stdio::piped()) @@ -259,7 +259,11 @@ async fn download_ffmpeg(ctx: &Context, download: &Download, variant_data: Varia .arg(target.to_str().unwrap()) .spawn()?; - download_segments(ctx, &mut ffmpeg.stdin.unwrap(), None, variant_data).await?; + download_segments(ctx, &mut ffmpeg.stdin.take().unwrap(), None, variant_data).await?; + + let _progress_handler = progress!("Generating output file"); + ffmpeg.wait()?; + info!("Output file generated"); Ok(()) } From 578e5ea5b7cc927147e77b983fa54bcb5c4daa79 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 8 Dec 2022 18:32:38 +0100 Subject: [PATCH 256/630] Update version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc5d0d0..a31d2fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,7 +277,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.1" +version = "3.0.0-dev.3" dependencies = [ "chrono", "clap", @@ -290,7 +290,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.1" +version = "3.0.0-dev.3" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 1b815c6..a853f11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.1" +version = "3.0.0-dev.3" edition = "2021" [features] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 93c7522..43acaea 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -258,7 +258,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.1" +version = "3.0.0-dev.3" dependencies = [ "anyhow", "async-trait", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 84fdcf2..bc34907 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.1" +version = "3.0.0-dev.3" edition = "2021" [features] From b814529aa21b455686bb6c34d10caa4e95a34cd6 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 9 Dec 2022 16:33:34 +0100 Subject: [PATCH 257/630] Add anonymous login --- crunchy-cli-core/src/lib.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index a13ac2f..d669b64 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -81,9 +81,8 @@ struct Verbosity { #[derive(Debug, Parser)] struct LoginMethod { - #[arg(help = "Login with credentials (username or email and password)")] #[arg( - long_help = "Login with credentials (username or email and password). Must be provided as user:password" + help = "Login with credentials (username or email and password). Must be provided as user:password" )] #[arg(long)] credentials: Option<String>, @@ -93,6 +92,9 @@ struct LoginMethod { )] #[arg(long)] etp_rt: Option<String>, + #[arg(help = "Login anonymously / without an account")] + #[arg(long, default_value_t = false)] + anonymous: bool, } pub async fn cli_entrypoint() { @@ -190,8 +192,12 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { let mut builder = Crunchyroll::builder(); builder.locale(cli.lang.clone().unwrap_or_else(system_locale)); + let login_methods_count = cli.login_method.credentials.is_some() as u8 + + cli.login_method.etp_rt.is_some() as u8 + + cli.login_method.anonymous as u8; + let _progress_handler = progress!("Logging in"); - if cli.login_method.credentials.is_none() && cli.login_method.etp_rt.is_none() { + if login_methods_count == 0 { if let Some(login_file_path) = cli::login::login_file_path() { if login_file_path.exists() { let session = fs::read_to_string(login_file_path)?; @@ -207,9 +213,9 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { bail!("Could not read stored session ('{}')", session) } } - bail!("Please use a login method ('--credentials' or '--etp_rt')") - } else if cli.login_method.credentials.is_some() && cli.login_method.etp_rt.is_some() { - bail!("Please use only one login method ('--credentials' or '--etp_rt')") + bail!("Please use a login method ('--credentials', '--etp-rt' or '--anonymous')") + } else if login_methods_count > 1 { + bail!("Please use only one login method ('--credentials', '--etp-rt' or '--anonymous')") } let crunchy = if let Some(credentials) = &cli.login_method.credentials { @@ -220,6 +226,8 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { } } else if let Some(etp_rt) = &cli.login_method.etp_rt { builder.login_with_etp_rt(etp_rt).await? + } else if cli.login_method.anonymous { + builder.login_anonymously().await? } else { bail!("should never happen") }; From db3697c37269f15635e7410c3c964b73245263d0 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 11 Dec 2022 23:03:25 +0100 Subject: [PATCH 258/630] Add anonymous login examples to the README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f62c988..6785579 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,11 @@ You can pass your account via credentials (username & password) or refresh token - ```shell $ crunchy --credentials "user:password" ``` +- Anonymous + - Login without an account at all is also possible. + - ```shell + $ crunchy --anonymous + ``` ### Login @@ -89,6 +94,7 @@ $ crunchy --etp-rt "abcd1234-zyxw-9876-98zy-a1b2c3d4e5f6" login ``` Once set, you do not need to provide `--etp-rt` / `--credentials` anymore when using the cli. +This does not work if you've using this with `--anonymous`. ### Download From 52ee0c48e1fa595827614fc55f3867441e68e8a8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 13 Dec 2022 08:51:37 +0100 Subject: [PATCH 259/630] Fix resolution ...p parsing --- crunchy-cli-core/src/utils/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index fbfea35..eb1b19f 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -148,7 +148,7 @@ pub fn parse_resolution(mut resolution: String) -> Result<Resolution> { height: u64::MIN, }) } else if resolution.ends_with('p') { - let without_p = resolution.as_str()[0..resolution.len() - 2] + let without_p = resolution.as_str()[0..resolution.len() - 1] .parse() .map_err(|_| anyhow!("Could not parse resolution"))?; Ok(Resolution { From f254df3bb3cfb1bb474b6521af3c3bdfb549053a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 16 Dec 2022 08:50:11 +0100 Subject: [PATCH 260/630] Fix ci badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f62c988..c41f2a6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> </a> <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> - <img src="https://img.shields.io/github/workflow/status/crunchy-labs/crunchy-cli/ci?style=flat-square" alt="CI"> + <img src="https://img.shields.io/github/actions/workflow/status/crunchy-labs/crunchy-cli/ci.yml?branch=master&style=flat-square" alt="CI"> </a> </p> From cc9342cd0acb8809410ae6b42953690ce4bc76bb Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 16 Dec 2022 10:09:41 +0100 Subject: [PATCH 261/630] Update dependencies --- .github/workflows/ci.yml | 75 +--- Cargo.lock | 506 +++++++++------------------ Cargo.toml | 16 +- build.rs | 3 - crunchy-cli-core/Cargo.lock | 505 ++++++++++---------------- crunchy-cli-core/Cargo.toml | 17 +- crunchy-cli-core/src/cli/archive.rs | 6 +- crunchy-cli-core/src/cli/download.rs | 6 +- crunchy-cli-core/src/cli/utils.rs | 7 +- 9 files changed, 373 insertions(+), 768 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eddc7b2..caef473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,15 @@ jobs: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl platform: linux + ext: + - os: windows-latest + toolchain: x86_64-pc-windows-gnu + platform: windows + ext: .exe - os: macos-latest toolchain: x86_64-apple-darwin platform: darwin + ext: steps: - name: Checkout uses: actions/checkout@v3 @@ -58,74 +64,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: crunchy-cli_${{ matrix.platform }} - path: ./target/release/crunchy-cli - if-no-files-found: error - - - name: Upload manpages artifact - uses: actions/upload-artifact@v3 - with: - name: manpages - path: ./target/release/manpages - if-no-files-found: error - - - name: Upload completions artifact - uses: actions/upload-artifact@v3 - with: - name: completions - path: ./target/release/completions - if-no-files-found: error - - build-windows: - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - run: vcpkg integrate install - - - name: Install OpenSSL - run: vcpkg install openssl:x64-windows-static-md - - - name: Set env variables - shell: bash - run: echo "CFLAGS=-I$(echo $VCPKG_INSTALLATION_ROOT)\packages\openssl_x64-windows-static-md\include" >> $GITHUB_ENV - - - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: x86_64-pc-windows-msvc - default: true - - - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all-features - - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --all-features - - - name: Upload binary artifact - uses: actions/upload-artifact@v3 - with: - name: crunchy-cli_windows - path: ./target/release/crunchy-cli.exe + path: ./target/release/crunchy-cli${{ matrix.ext }} if-no-files-found: error - name: Upload manpages artifact diff --git a/Cargo.lock b/Cargo.lock index a31d2fa..15acee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,17 +37,6 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - [[package]] name = "async-trait" version = "0.1.59" @@ -110,15 +99,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - [[package]] name = "cbc" version = "0.1.2" @@ -130,9 +110,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -233,12 +213,30 @@ dependencies = [ ] [[package]] -name = "concurrent-queue" -version = "2.0.0" +name = "cookie" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" dependencies = [ - "crossbeam-utils", + "percent-encoding", + "time 0.3.17", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" +dependencies = [ + "cookie", + "idna 0.2.3", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.3.17", + "url", ] [[package]] @@ -266,15 +264,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] - [[package]] name = "crunchy-cli" version = "3.0.0-dev.3" @@ -284,7 +273,6 @@ dependencies = [ "clap_complete", "clap_mangen", "crunchy-cli-core", - "static_vcruntime", "tokio", ] @@ -300,11 +288,9 @@ dependencies = [ "csv", "ctrlc", "dirs", - "isahc", "log", "num_cpus", "regex", - "rustls-native-certs", "sanitize-filename", "serde", "serde_json", @@ -317,31 +303,32 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5032be36dc6c4757af4f28152659e7a772b48f6925f0e0ac81b5c384e291314" dependencies = [ "aes", "cbc", "chrono", "crunchyroll-rs-internal", - "curl-sys", - "dash-mpd", "http", - "isahc", "m3u8-rs", "regex", "reqwest", + "rustls", "serde", "serde_json", "serde_urlencoded", "smart-default", "tokio", + "webpki-roots", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e4aa1f09fd76f44329455abfac35e22c654ac782e93bc8a7d3ee1be27509f4" dependencies = [ "darling", "quote", @@ -382,43 +369,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.3" +version = "3.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ "nix", - "winapi", -] - -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.59+curl-7.86.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -500,28 +456,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dash-mpd" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a181b3a4a1ef2cb4dd72f2a85e36a2e54445b8b9513635d3fad23e9b9a7c4c" -dependencies = [ - "chrono", - "log", - "quick-xml", - "regex", - "serde", - "serde_with", - "thiserror", - "xattr", -] - -[[package]] -name = "data-encoding" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" - [[package]] name = "dirs" version = "4.0.0" @@ -572,12 +506,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "fastrand" version = "1.8.0" @@ -638,16 +566,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "futures-sink" version = "0.3.25" @@ -745,12 +663,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "http" version = "0.2.8" @@ -809,6 +721,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -852,6 +777,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.3.0" @@ -870,7 +806,6 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", - "serde", ] [[package]] @@ -904,9 +839,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "is-terminal" @@ -920,34 +855,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "isahc" -version = "1.7.0" -source = "git+https://github.com/sagebind/isahc?rev=c39f6f8#c39f6f85aaa1f36c5857064c6c3c80f4d307d863" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "data-encoding", - "encoding_rs", - "event-listener", - "futures-io", - "futures-lite", - "http", - "httpdate", - "log", - "mime", - "once_cell", - "polling", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itoa" version = "0.4.8" @@ -981,28 +888,6 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "link-cplusplus" version = "1.0.7" @@ -1014,9 +899,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "log" @@ -1037,6 +922,12 @@ dependencies = [ "nom", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -1087,14 +978,14 @@ dependencies = [ [[package]] name = "nix" -version = "0.25.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" dependencies = [ - "autocfg", "bitflags", "cfg-if", "libc", + "static_assertions", ] [[package]] @@ -1174,15 +1065,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "111.24.0+1.1.1s" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.79" @@ -1192,7 +1074,6 @@ dependencies = [ "autocfg", "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -1209,26 +1090,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1247,20 +1108,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "polling" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "windows-sys 0.42.0", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1285,6 +1132,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.47" @@ -1295,13 +1148,19 @@ dependencies = [ ] [[package]] -name = "quick-xml" -version = "0.26.0" +name = "psl-types" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" dependencies = [ - "memchr", - "serde", + "idna 0.3.0", + "psl-types", ] [[package]] @@ -1373,6 +1232,8 @@ checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64", "bytes", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -1380,6 +1241,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -1389,19 +1251,39 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "proc-macro-hack", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "roff" version = "0.2.1" @@ -1423,15 +1305,15 @@ dependencies = [ ] [[package]] -name = "rustls-native-certs" -version = "0.6.2" +name = "rustls" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", + "log", + "ring", + "sct", + "webpki", ] [[package]] @@ -1443,12 +1325,6 @@ dependencies = [ "base64", ] -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - [[package]] name = "ryu" version = "1.0.11" @@ -1481,6 +1357,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.7.0" @@ -1506,18 +1392,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", @@ -1547,34 +1433,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap", - "serde", - "serde_json", - "serde_with_macros", - "time 0.3.17", -] - -[[package]] -name = "serde_with_macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "signal-hook" version = "0.3.14" @@ -1603,17 +1461,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smart-default" version = "0.6.0" @@ -1636,10 +1483,16 @@ dependencies = [ ] [[package]] -name = "static_vcruntime" -version = "2.0.0" +name = "spin" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954e3e877803def9dc46075bf4060147c55cd70db97873077232eae0269dc89b" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" @@ -1816,6 +1669,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-util" version = "0.7.4" @@ -1843,23 +1707,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.30" @@ -1869,16 +1720,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -1918,6 +1759,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1925,7 +1772,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", + "idna 0.3.0", "percent-encoding", ] @@ -1941,12 +1788,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "want" version = "0.3.0" @@ -2046,12 +1887,22 @@ dependencies = [ ] [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "webpki" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "cc", + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", ] [[package]] @@ -2193,12 +2044,3 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] - -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] diff --git a/Cargo.toml b/Cargo.toml index a853f11..b2af93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,17 +4,8 @@ authors = ["Crunchy Labs Maintainers"] version = "3.0.0-dev.3" edition = "2021" -[features] -default = ["static-curl"] - -# Embed a static curl library into the binary instead of just linking it. -static-curl = ["crunchy-cli-core/static-curl"] -# Embed a static tls library into the binary instead of just linking it. -# has no effect on Windows, always activated there. -static-ssl = ["crunchy-cli-core/static-ssl"] - [dependencies] -tokio = { version = "1.22", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.23", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } @@ -26,10 +17,7 @@ clap_mangen = "0.2" # The static-* features must be used here since build dependency features cannot be manipulated from the features # specified in this Cargo.toml [features]. -crunchy-cli-core = { path = "./crunchy-cli-core", features = ["static-curl"] } - -[target.'cfg(all(windows, target_env = "msvc"))'.build-dependencies] -static_vcruntime = "2.0" +crunchy-cli-core = { path = "./crunchy-cli-core" } [profile.release] strip = true diff --git a/build.rs b/build.rs index 927f4dd..1e4d71f 100644 --- a/build.rs +++ b/build.rs @@ -3,9 +3,6 @@ use clap_complete::shells; use std::path::{Path, PathBuf}; fn main() -> std::io::Result<()> { - #[cfg(all(windows, target_env = "msvc"))] - static_vcruntime::metabuild(); - // note that we're using an anti-pattern here / violate the rust conventions. build script are // not supposed to write outside of 'OUT_DIR'. to have the generated files in the build "root" // (the same directory where the output binary lives) is much simpler than in 'OUT_DIR' since diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 43acaea..aba7b4a 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -37,17 +37,6 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - [[package]] name = "async-trait" version = "0.1.59" @@ -110,15 +99,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - [[package]] name = "cbc" version = "0.1.2" @@ -130,9 +110,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -214,12 +194,30 @@ dependencies = [ ] [[package]] -name = "concurrent-queue" -version = "2.0.0" +name = "cookie" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" dependencies = [ - "crossbeam-utils", + "percent-encoding", + "time 0.3.17", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" +dependencies = [ + "cookie", + "idna 0.2.3", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.3.17", + "url", ] [[package]] @@ -247,15 +245,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] - [[package]] name = "crunchy-cli-core" version = "3.0.0-dev.3" @@ -268,11 +257,9 @@ dependencies = [ "csv", "ctrlc", "dirs", - "isahc", "log", "num_cpus", "regex", - "rustls-native-certs", "sanitize-filename", "serde", "serde_json", @@ -285,31 +272,32 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5032be36dc6c4757af4f28152659e7a772b48f6925f0e0ac81b5c384e291314" dependencies = [ "aes", "cbc", "chrono", "crunchyroll-rs-internal", - "curl-sys", - "dash-mpd", "http", - "isahc", "m3u8-rs", "regex", "reqwest", + "rustls", "serde", "serde_json", "serde_urlencoded", "smart-default", "tokio", + "webpki-roots", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.1.0" -source = "git+https://github.com/crunchy-labs/crunchyroll-rs#86fb8307a531aedec708dd1c8c88b76bcf2a8c38" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e4aa1f09fd76f44329455abfac35e22c654ac782e93bc8a7d3ee1be27509f4" dependencies = [ "darling", "quote", @@ -350,43 +338,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.3" +version = "3.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ "nix", - "winapi", -] - -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.59+curl-7.86.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -468,28 +425,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dash-mpd" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a181b3a4a1ef2cb4dd72f2a85e36a2e54445b8b9513635d3fad23e9b9a7c4c" -dependencies = [ - "chrono", - "log", - "quick-xml", - "regex", - "serde", - "serde_with", - "thiserror", - "xattr", -] - -[[package]] -name = "data-encoding" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" - [[package]] name = "dirs" version = "4.0.0" @@ -540,12 +475,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "fastrand" version = "1.8.0" @@ -606,16 +535,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "futures-sink" version = "0.3.25" @@ -713,12 +632,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "http" version = "0.2.8" @@ -777,6 +690,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -820,6 +746,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.3.0" @@ -838,7 +775,6 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", - "serde", ] [[package]] @@ -872,9 +808,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "is-terminal" @@ -888,34 +824,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "isahc" -version = "1.7.0" -source = "git+https://github.com/sagebind/isahc?rev=c39f6f8#c39f6f85aaa1f36c5857064c6c3c80f4d307d863" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "data-encoding", - "encoding_rs", - "event-listener", - "futures-io", - "futures-lite", - "http", - "httpdate", - "log", - "mime", - "once_cell", - "polling", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itoa" version = "0.4.8" @@ -949,28 +857,6 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "link-cplusplus" version = "1.0.7" @@ -982,9 +868,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "log" @@ -1005,6 +891,12 @@ dependencies = [ "nom", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -1055,14 +947,14 @@ dependencies = [ [[package]] name = "nix" -version = "0.25.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" dependencies = [ - "autocfg", "bitflags", "cfg-if", "libc", + "static_assertions", ] [[package]] @@ -1142,15 +1034,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "111.24.0+1.1.1s" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.79" @@ -1160,7 +1043,6 @@ dependencies = [ "autocfg", "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -1177,26 +1059,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1215,20 +1077,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "polling" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "windows-sys 0.42.0", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1253,6 +1101,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.47" @@ -1263,13 +1117,19 @@ dependencies = [ ] [[package]] -name = "quick-xml" -version = "0.26.0" +name = "psl-types" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" dependencies = [ - "memchr", - "serde", + "idna 0.3.0", + "psl-types", ] [[package]] @@ -1341,6 +1201,8 @@ checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64", "bytes", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -1348,6 +1210,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -1357,19 +1220,39 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "proc-macro-hack", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustix" version = "0.36.5" @@ -1385,15 +1268,15 @@ dependencies = [ ] [[package]] -name = "rustls-native-certs" -version = "0.6.2" +name = "rustls" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", + "log", + "ring", + "sct", + "webpki", ] [[package]] @@ -1405,12 +1288,6 @@ dependencies = [ "base64", ] -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - [[package]] name = "ryu" version = "1.0.11" @@ -1443,6 +1320,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.7.0" @@ -1468,18 +1355,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", @@ -1509,34 +1396,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap", - "serde", - "serde_json", - "serde_with_macros", - "time 0.3.17", -] - -[[package]] -name = "serde_with_macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "signal-hook" version = "0.3.14" @@ -1565,17 +1424,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smart-default" version = "0.6.0" @@ -1597,6 +1445,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -1772,6 +1632,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-util" version = "0.7.4" @@ -1799,23 +1670,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.30" @@ -1825,16 +1683,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -1874,6 +1722,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1881,7 +1735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", + "idna 0.3.0", "percent-encoding", ] @@ -1897,12 +1751,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "want" version = "0.3.0" @@ -2002,12 +1850,22 @@ dependencies = [ ] [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "webpki" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "cc", + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", ] [[package]] @@ -2149,12 +2007,3 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] - -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index bc34907..bd04867 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -4,35 +4,26 @@ authors = ["Crunchy Labs Maintainers"] version = "3.0.0-dev.3" edition = "2021" -[features] -static-curl = ["crunchyroll-rs/static-curl"] -static-ssl = ["crunchyroll-rs/static-ssl"] - [dependencies] anyhow = "1.0" async-trait = "0.1" clap = { version = "4.0", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { git = "https://github.com/crunchy-labs/crunchyroll-rs", default-features = false, features = ["parse", "hls-stream", "dash-stream"] } +crunchyroll-rs = "0.2" csv = "1.1" ctrlc = "3.2" dirs = "4.0" -isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8" } log = { version = "0.4", features = ["std"] } -num_cpus = "1.13" -regex = "1.6" +num_cpus = "1.14" +regex = "1.7" sanitize-filename = "0.4" serde = "1.0" serde_json = "1.0" signal-hook = "0.3" tempfile = "3.3" terminal_size = "0.2" -tokio = { version = "1.21", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.23", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.2" -[target.'cfg(all(windows, target_env = "msvc"))'.dependencies] -isahc = { git = "https://github.com/sagebind/isahc", rev = "c39f6f8", features = ["data-encoding"] } -rustls-native-certs = "0.6" - [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index f9bb6a5..5aabbd8 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -332,7 +332,7 @@ async fn formats_from_series( .locale .clone() .into_iter() - .filter(|l| !season.iter().any(|s| &s.metadata.audio_locale == l)) + .filter(|l| !season.iter().any(|s| s.metadata.audio_locales.contains(l))) .collect::<Vec<Locale>>(); for not_present in not_present_audio { error!( @@ -346,7 +346,7 @@ async fn formats_from_series( // remove all seasons with the wrong audio for the current iterated season number seasons.retain(|s| { s.metadata.season_number != season.first().unwrap().metadata.season_number - || archive.locale.contains(&s.metadata.audio_locale) + || archive.locale.iter().any(|l| s.metadata.audio_locales.contains(l)) }) } @@ -355,7 +355,7 @@ async fn formats_from_series( BTreeMap::new(); for season in series.seasons().await? { if !url_filter.is_season_valid(season.metadata.season_number) - || !archive.locale.contains(&season.metadata.audio_locale) + || !archive.locale.iter().any(|l| season.metadata.audio_locales.contains(l)) { continue; } diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index b7093aa..31f8cda 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -290,7 +290,7 @@ async fn formats_from_series( // check if the current iterated season has the specified audio language if !season .iter() - .any(|s| s.metadata.audio_locale == download.audio) + .any(|s| s.metadata.audio_locales.contains(&download.audio)) { error!( "Season {} of series {} is not available with {} audio", @@ -303,7 +303,7 @@ async fn formats_from_series( // remove all seasons with the wrong audio for the current iterated season number seasons.retain(|s| { s.metadata.season_number != season.first().unwrap().metadata.season_number - || s.metadata.audio_locale == download.audio + || s.metadata.audio_locales.contains(&download.audio) }) } @@ -322,7 +322,7 @@ async fn formats_from_season( season: Media<Season>, url_filter: &UrlFilter, ) -> Result<Option<Vec<Format>>> { - if season.metadata.audio_locale != download.audio { + if !season.metadata.audio_locales.contains(&download.audio) { error!( "Season {} ({}) is not available with {} audio", season.metadata.season_number, season.title, download.audio diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 9b817e4..a64057a 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -1,7 +1,6 @@ use crate::utils::context::Context; use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; -use isahc::AsyncReadResponseExt; use log::{debug, LevelFilter}; use std::borrow::{Borrow, BorrowMut}; use std::collections::BTreeMap; @@ -26,7 +25,7 @@ pub fn find_resolution( } pub async fn download_segments( - _ctx: &Context, + ctx: &Context, writer: &mut impl Write, message: Option<String>, variant_data: VariantData, @@ -34,7 +33,7 @@ pub async fn download_segments( let segments = variant_data.segments().await?; let total_segments = segments.len(); - let client = Arc::new(variant_data.download_client()); + let client = Arc::new(ctx.crunchy.client()); let count = Arc::new(Mutex::new(0)); let amount = Arc::new(Mutex::new(0)); @@ -132,7 +131,7 @@ pub async fn download_segments( let thread_count = count.clone(); join_set.spawn(async move { for (i, segment) in thread_segments.into_iter().enumerate() { - let mut response = thread_client.get_async(&segment.url).await?; + let response = thread_client.get(&segment.url).send().await?; let mut buf = response.bytes().await?.to_vec(); *thread_amount.lock().unwrap() += buf.len(); From d49f2d8eaa9c2a3b88224c3504a5fd2b3013615e Mon Sep 17 00:00:00 2001 From: Alexandru Dracea <adracea@gmail.com> Date: Fri, 16 Dec 2022 17:09:52 +0200 Subject: [PATCH 262/630] add minimal .gitignore (#83) * add .gitignore * more ignore * newline * readd .lock Co-authored-by: Alexandru.Dracea <alexandru.dracea@finastra.com> --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bb2fd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea +/target +/.vscode From 03fe0c6f01d08ed11e9dde70b622ee655214ce5e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 16 Dec 2022 21:57:26 +0100 Subject: [PATCH 263/630] Add additional command --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caef473..3ddf65d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - build-nix: + build: runs-on: ${{ matrix.os }} strategy: matrix: @@ -16,20 +16,23 @@ jobs: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl platform: linux + command: sudo apt install -y musl ext: - os: windows-latest toolchain: x86_64-pc-windows-gnu platform: windows + command: ext: .exe - os: macos-latest toolchain: x86_64-apple-darwin platform: darwin + command: ext: steps: - name: Checkout uses: actions/checkout@v3 - - name: Cargo cache # https://github.com/actions/cache/blob/main/examples.md#rust---cargo + - name: Cargo cache uses: actions/cache@v3 with: path: | @@ -48,6 +51,9 @@ jobs: target: ${{ matrix.toolchain }} default: true + - name: Additional command + run: ${{ matrix.command }} + - name: Test uses: actions-rs/cargo@v1 with: From 2451e33639c7449fd5958e6caf4b04099cb2c43f Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 16 Dec 2022 22:07:54 +0100 Subject: [PATCH 264/630] Fix ubuntu musl package --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ddf65d..fbb064e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl platform: linux - command: sudo apt install -y musl + command: sudo apt install -y musl-tools ext: - os: windows-latest toolchain: x86_64-pc-windows-gnu @@ -43,6 +43,9 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Additional command + run: ${{ matrix.command }} + - name: Install toolchain uses: actions-rs/toolchain@v1 with: @@ -51,9 +54,6 @@ jobs: target: ${{ matrix.toolchain }} default: true - - name: Additional command - run: ${{ matrix.command }} - - name: Test uses: actions-rs/cargo@v1 with: From 306019d8b88cc5a1e7fb8a05428c1c80714f3dac Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 16 Dec 2022 23:28:30 +0100 Subject: [PATCH 265/630] Fix linux ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbb064e..0a2047f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,7 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Additional command + if: matrix.command != '' run: ${{ matrix.command }} - name: Install toolchain @@ -53,6 +54,7 @@ jobs: toolchain: stable target: ${{ matrix.toolchain }} default: true + overwrite: true - name: Test uses: actions-rs/cargo@v1 From 5de4a83e5deeab68d9d05b5bb1f400849d400e89 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 17 Dec 2022 17:13:56 +0100 Subject: [PATCH 266/630] Change rust actions used --- .github/workflows/ci.yml | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a2047f..eb4c0c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,17 +16,14 @@ jobs: - os: ubuntu-latest toolchain: x86_64-unknown-linux-musl platform: linux - command: sudo apt install -y musl-tools ext: - os: windows-latest toolchain: x86_64-pc-windows-gnu platform: windows - command: ext: .exe - os: macos-latest toolchain: x86_64-apple-darwin platform: darwin - command: ext: steps: - name: Checkout @@ -43,48 +40,39 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Additional command - if: matrix.command != '' - run: ${{ matrix.command }} + - name: Install system dependencies + if: matrix.platform == 'linux' + run: sudo apt-get install musl-tools - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: stable target: ${{ matrix.toolchain }} - default: true - overwrite: true - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all-features + run: cargo test --release --all-features --target ${{ matrix.toolchain }} - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --all-features + run: cargo build --release --all-features --target ${{ matrix.toolchain }} - name: Upload binary artifact uses: actions/upload-artifact@v3 with: name: crunchy-cli_${{ matrix.platform }} - path: ./target/release/crunchy-cli${{ matrix.ext }} + path: ./target/${{ matrix.toolchain }}/release/crunchy-cli${{ matrix.ext }} if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: name: manpages - path: ./target/release/manpages + path: ./target/${{ matrix.toolchain }}/release/manpages if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: name: completions - path: ./target/release/completions + path: ./target/${{ matrix.toolchain }}/release/completions if-no-files-found: error From 4f107d8cf24854b94615ab4917f9f99b0fbcec29 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 18 Dec 2022 12:51:36 +0100 Subject: [PATCH 267/630] Update version and dependencies --- Cargo.lock | 94 ++++++++++++++++++------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 92 ++++++++++++++++++------------------ crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 95 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15acee9..f205883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,15 +33,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "7724808837b77f4b4de9d283820f9d98bcf496d5692934b857a2399d31ff22e6" [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", "time 0.3.17", @@ -266,7 +266,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.3" +version = "3.0.0-dev.4" dependencies = [ "chrono", "clap", @@ -278,7 +278,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.3" +version = "3.0.0-dev.4" dependencies = [ "anyhow", "async-trait", @@ -379,9 +379,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "27874566aca772cb515af4c6e997b5fe2119820bca447689145e39bb734d19a0" dependencies = [ "cc", "cxxbridge-flags", @@ -391,9 +391,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "e7bb951f2523a49533003656a72121306b225ec16a49a09dc6b0ba0d6f3ec3c0" dependencies = [ "cc", "codespan-reporting", @@ -406,15 +406,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "be778b6327031c1c7b61dd2e48124eee5361e6aa76b8de93692f011b08870ab4" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "7b8a2b87662fe5a0a0b38507756ab66aff32638876a0866e5a5fc82ceb07ee49" dependencies = [ "proc-macro2", "quote", @@ -671,7 +671,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] @@ -712,7 +712,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.4", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -863,9 +863,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -890,9 +890,9 @@ checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -1140,9 +1140,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f" dependencies = [ "unicode-ident", ] @@ -1165,9 +1165,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8" dependencies = [ "proc-macro2", ] @@ -1327,9 +1327,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "sanitize-filename" @@ -1353,9 +1353,9 @@ dependencies = [ [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -1392,18 +1392,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" dependencies = [ "proc-macro2", "quote", @@ -1412,11 +1412,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] @@ -1428,7 +1428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] @@ -1502,9 +1502,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b" dependencies = [ "proc-macro2", "quote", @@ -1559,18 +1559,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1594,7 +1594,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "serde", "time-core", "time-macros", @@ -1740,9 +1740,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index b2af93c..f5f988b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.3" +version = "3.0.0-dev.4" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index aba7b4a..a85defe 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -33,15 +33,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "7724808837b77f4b4de9d283820f9d98bcf496d5692934b857a2399d31ff22e6" [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", "time 0.3.17", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.3" +version = "3.0.0-dev.4" dependencies = [ "anyhow", "async-trait", @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "27874566aca772cb515af4c6e997b5fe2119820bca447689145e39bb734d19a0" dependencies = [ "cc", "cxxbridge-flags", @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "e7bb951f2523a49533003656a72121306b225ec16a49a09dc6b0ba0d6f3ec3c0" dependencies = [ "cc", "codespan-reporting", @@ -375,15 +375,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "be778b6327031c1c7b61dd2e48124eee5361e6aa76b8de93692f011b08870ab4" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "7b8a2b87662fe5a0a0b38507756ab66aff32638876a0866e5a5fc82ceb07ee49" dependencies = [ "proc-macro2", "quote", @@ -640,7 +640,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] @@ -681,7 +681,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.4", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -832,9 +832,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -859,9 +859,9 @@ checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -1109,9 +1109,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f" dependencies = [ "unicode-ident", ] @@ -1134,9 +1134,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8" dependencies = [ "proc-macro2", ] @@ -1290,9 +1290,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "sanitize-filename" @@ -1316,9 +1316,9 @@ dependencies = [ [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -1355,18 +1355,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" dependencies = [ "proc-macro2", "quote", @@ -1375,11 +1375,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] @@ -1391,7 +1391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] @@ -1465,9 +1465,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b" dependencies = [ "proc-macro2", "quote", @@ -1522,18 +1522,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1557,7 +1557,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "serde", "time-core", "time-macros", @@ -1703,9 +1703,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index bd04867..6dccd4d 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.3" +version = "3.0.0-dev.4" edition = "2021" [dependencies] From 4bfc6f22e111ff53b6ad73811bbab692b938331c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 18 Dec 2022 13:50:20 +0100 Subject: [PATCH 268/630] Fix discord link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21f84a6..8008de2 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: <a href="https://github.com/crunchy-labs/crunchy-cli/releases"> <img src="https://img.shields.io/github/v/release/crunchy-labs/crunchy-cli?include_prereleases&style=flat-square" alt="Release"> </a> - <a href="https://discord.gg/gUWwekeNNg"> - <img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord"> + <a href="https://discord.gg/PXGPGpQxgk"> + <img src="https://img.shields.io/discord/994882878125121596?label=discord&style=flat-square" alt="Discord"> </a> <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> <img src="https://img.shields.io/github/actions/workflow/status/crunchy-labs/crunchy-cli/ci.yml?branch=master&style=flat-square" alt="CI"> From 67bbc00d87c3fd5c0f64f6ae537a331bc5b54702 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 18 Dec 2022 15:03:16 +0100 Subject: [PATCH 269/630] Add pre-release notice --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8008de2..bf57145 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: > We are in no way affiliated with, maintained, authorized, sponsored, or officially associated with Crunchyroll LLC or any of its subsidiaries or affiliates. > The official Crunchyroll website can be found at https://crunchyroll.com/. +> This README belongs to the _master_ branch which is currently under heavy development towards the next major version (3.0). +> It is mostly stable but some issues may still occur. +> If you do not want to use an under-development / pre-release version, head over to the _[golang](https://github.com/crunchy-labs/crunchy-cli/tree/golang)_ branch which contains the EOL but last stable version (and documentation for it). + ## โœจ Features - Download single videos and entire series from [Crunchyroll](https://www.crunchyroll.com). From 8bb2c9c7501cf4382d3b0117d55e4f6c7b42d93a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 19 Dec 2022 15:22:45 +0100 Subject: [PATCH 270/630] Fix file name sanitizing --- crunchy-cli-core/src/cli/archive.rs | 1 + crunchy-cli-core/src/cli/download.rs | 1 + crunchy-cli-core/src/utils/format.rs | 31 ++++++++++++++++++---------- crunchy-cli-core/src/utils/os.rs | 9 +------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 5aabbd8..a551bd1 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -237,6 +237,7 @@ impl Execute for Archive { } .to_string(), primary, + true, )), ); diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 31f8cda..e13d3f0 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -209,6 +209,7 @@ impl Execute for Download { } .to_string(), &format, + true, )), ); diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 3dd7f74..b111dce 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -63,15 +63,24 @@ impl Format { } } -pub fn format_string(s: String, format: &Format) -> String { - s.replace("{title}", &format.title) - .replace("{series_name}", &format.series_name) - .replace("{season_name}", &format.season_title) - .replace("{audio}", &format.audio.to_string()) - .replace("{resolution}", &format.stream.resolution.to_string()) - .replace("{season_number}", &format.season_number.to_string()) - .replace("{episode_number}", &format.number.to_string()) - .replace("{series_id}", &format.series_id) - .replace("{season_id}", &format.season_id) - .replace("{episode_id}", &format.id) +/// Formats the given string if it has specific pattern in it. It's possible to sanitize it which +/// removes characters which can cause failures if the output string is used as a file name. +pub fn format_string(s: String, format: &Format, sanitize: bool) -> String { + let sanitize_func = if sanitize { + |s: &str| sanitize_filename::sanitize(s) + } else { + // converting this to a string is actually unnecessary + |s: &str| s.to_string() + }; + + s.replace("{title}", &sanitize_func(&format.title)) + .replace("{series_name}", &sanitize_func(&format.series_name)) + .replace("{season_name}", &sanitize_func(&format.season_title)) + .replace("{audio}", &sanitize_func(&format.audio.to_string())) + .replace("{resolution}", &sanitize_func(&format.stream.resolution.to_string())) + .replace("{season_number}", &sanitize_func(&format.season_number.to_string())) + .replace("{episode_number}", &sanitize_func(&format.number.to_string())) + .replace("{series_id}", &sanitize_func(&format.series_id)) + .replace("{season_id}", &sanitize_func(&format.season_id)) + .replace("{episode_id}", &sanitize_func(&format.id)) } diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index abdf151..01d0f89 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -50,12 +50,5 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { path.set_file_name(format!("{} ({}).{}", filename, i, ext)) } - sanitize_file(path) -} - -/// Sanitizes the given path to not contain any invalid file character. -pub fn sanitize_file(path: PathBuf) -> PathBuf { - path.with_file_name(sanitize_filename::sanitize( - path.file_name().unwrap().to_string_lossy(), - )) + path } From af9aca4d0cfc3608369507766d63ca3e40fe449a Mon Sep 17 00:00:00 2001 From: Alexandru Dracea <adracea@gmail.com> Date: Mon, 19 Dec 2022 18:35:37 +0200 Subject: [PATCH 271/630] Add padding --- crunchy-cli-core/src/cli/archive.rs | 22 ++++++++++++---------- crunchy-cli-core/src/cli/download.rs | 22 ++++++++++++---------- crunchy-cli-core/src/utils/format.rs | 23 ++++++++++++++++++++--- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index a551bd1..6e56e81 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -58,16 +58,18 @@ pub struct Archive { #[arg(help = "Name of the output file")] #[arg(long_help = "Name of the output file.\ If you use one of the following pattern they will get replaced:\n \ - {title} โ†’ Title of the video\n \ - {series_name} โ†’ Name of the series\n \ - {season_name} โ†’ Name of the season\n \ - {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ - {season_number} โ†’ Number of the season\n \ - {episode_number} โ†’ Number of the episode\n \ - {series_id} โ†’ ID of the series\n \ - {season_id} โ†’ ID of the season\n \ - {episode_id} โ†’ ID of the episode")] + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {padded_season_number} โ†’ Number of the season padded to double digits\n \ + {season_number} โ†’ Number of the season\n \ + {padded_episode_number} โ†’ Number of the episode padded to double digits\n \ + {episode_number} โ†’ Number of the episode\n \ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] #[arg(short, long, default_value = "{title}.mkv")] output: String, diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index e13d3f0..4e4eb3c 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -36,16 +36,18 @@ pub struct Download { #[arg(help = "Name of the output file")] #[arg(long_help = "Name of the output file.\ If you use one of the following pattern they will get replaced:\n \ - {title} โ†’ Title of the video\n \ - {series_name} โ†’ Name of the series\n \ - {season_name} โ†’ Name of the season\n \ - {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ - {season_number} โ†’ Number of the season\n \ - {episode_number} โ†’ Number of the episode\n \ - {series_id} โ†’ ID of the series\n \ - {season_id} โ†’ ID of the season\n \ - {episode_id} โ†’ ID of the episode")] + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {padded_season_number} โ†’ Number of the season padded to double digits\n \ + {season_number} โ†’ Number of the season\n \ + {padded_episode_number} โ†’ Number of the episode padded to double digits\n \ + {episode_number} โ†’ Number of the episode\n \ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] #[arg(short, long, default_value = "{title}.ts")] output: String, diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index b111dce..c463ea8 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -77,9 +77,26 @@ pub fn format_string(s: String, format: &Format, sanitize: bool) -> String { .replace("{series_name}", &sanitize_func(&format.series_name)) .replace("{season_name}", &sanitize_func(&format.season_title)) .replace("{audio}", &sanitize_func(&format.audio.to_string())) - .replace("{resolution}", &sanitize_func(&format.stream.resolution.to_string())) - .replace("{season_number}", &sanitize_func(&format.season_number.to_string())) - .replace("{episode_number}", &sanitize_func(&format.number.to_string())) + .replace( + "{resolution}", + &sanitize_func(&format.stream.resolution.to_string()), + ) + .replace( + "{padded_season_number}", + &sanitize_func(&format!("{:0>2}", format.season_number.to_string())), + ) + .replace( + "{season_number}", + &sanitize_func(&format.season_number.to_string()), + ) + .replace( + "{padded_episode_number}", + &sanitize_func(&format!("{:0>2}", format.number.to_string())), + ) + .replace( + "{episode_number}", + &sanitize_func(&format.number.to_string()), + ) .replace("{series_id}", &sanitize_func(&format.series_id)) .replace("{season_id}", &sanitize_func(&format.season_id)) .replace("{episode_id}", &sanitize_func(&format.id)) From 17fa045c32e103a13535a819dad4da6b6731347a Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 22 Dec 2022 14:45:56 +0100 Subject: [PATCH 272/630] Use library for progress --- Cargo.lock | 130 ++++++++++++++++++--------- crunchy-cli-core/Cargo.lock | 130 ++++++++++++++++++--------- crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/cli/archive.rs | 33 ++++--- crunchy-cli-core/src/cli/download.rs | 28 +++--- crunchy-cli-core/src/cli/log.rs | 130 +++++++-------------------- crunchy-cli-core/src/cli/utils.rs | 127 +++++++++----------------- crunchy-cli-core/src/lib.rs | 6 +- crunchy-cli-core/src/utils/log.rs | 17 +++- 9 files changed, 307 insertions(+), 295 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f205883..a7432c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7724808837b77f4b4de9d283820f9d98bcf496d5692934b857a2399d31ff22e6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-trait" @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.29" +version = "4.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +checksum = "656ad1e55e23d287773f7d8192c300dc715c3eeded93b3da651d11c42cfd74d2" dependencies = [ "bitflags", "clap_derive", @@ -212,6 +212,20 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size 0.1.17", + "unicode-width", + "winapi", +] + [[package]] name = "cookie" version = "0.16.2" @@ -288,6 +302,7 @@ dependencies = [ "csv", "ctrlc", "dirs", + "indicatif", "log", "num_cpus", "regex", @@ -297,7 +312,7 @@ dependencies = [ "signal-hook", "sys-locale", "tempfile", - "terminal_size", + "terminal_size 0.2.3", "tokio", ] @@ -379,9 +394,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27874566aca772cb515af4c6e997b5fe2119820bca447689145e39bb734d19a0" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -391,9 +406,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb951f2523a49533003656a72121306b225ec16a49a09dc6b0ba0d6f3ec3c0" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -406,15 +421,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be778b6327031c1c7b61dd2e48124eee5361e6aa76b8de93692f011b08870ab4" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8a2b87662fe5a0a0b38507756ab66aff32638876a0866e5a5fc82ceb07ee49" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", @@ -476,6 +491,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -645,15 +666,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -808,6 +820,18 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "inout" version = "0.1.3" @@ -845,11 +869,11 @@ checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "is-terminal" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "io-lifetimes", "rustix", "windows-sys 0.42.0", @@ -1019,14 +1043,20 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "once_cell" version = "1.16.0" @@ -1035,9 +1065,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl" -version = "0.10.44" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if", @@ -1067,9 +1097,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.79" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", @@ -1108,6 +1138,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "portable-atomic" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bdd679d533107e090c2704a35982fc06302e30898e63ffa26a81155c012e92" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1134,15 +1170,15 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] @@ -1165,9 +1201,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1412,9 +1448,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa 1.0.5", "ryu", @@ -1502,9 +1538,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1547,6 +1583,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "terminal_size" version = "0.2.3" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index a85defe..8347aa7 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7724808837b77f4b4de9d283820f9d98bcf496d5692934b857a2399d31ff22e6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-trait" @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.29" +version = "4.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +checksum = "656ad1e55e23d287773f7d8192c300dc715c3eeded93b3da651d11c42cfd74d2" dependencies = [ "bitflags", "clap_derive", @@ -193,6 +193,20 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size 0.1.17", + "unicode-width", + "winapi", +] + [[package]] name = "cookie" version = "0.16.2" @@ -257,6 +271,7 @@ dependencies = [ "csv", "ctrlc", "dirs", + "indicatif", "log", "num_cpus", "regex", @@ -266,7 +281,7 @@ dependencies = [ "signal-hook", "sys-locale", "tempfile", - "terminal_size", + "terminal_size 0.2.3", "tokio", ] @@ -348,9 +363,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27874566aca772cb515af4c6e997b5fe2119820bca447689145e39bb734d19a0" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -360,9 +375,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb951f2523a49533003656a72121306b225ec16a49a09dc6b0ba0d6f3ec3c0" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -375,15 +390,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be778b6327031c1c7b61dd2e48124eee5361e6aa76b8de93692f011b08870ab4" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8a2b87662fe5a0a0b38507756ab66aff32638876a0866e5a5fc82ceb07ee49" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", @@ -445,6 +460,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -614,15 +635,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -777,6 +789,18 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "inout" version = "0.1.3" @@ -814,11 +838,11 @@ checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "is-terminal" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "io-lifetimes", "rustix", "windows-sys 0.42.0", @@ -988,14 +1012,20 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "once_cell" version = "1.16.0" @@ -1004,9 +1034,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl" -version = "0.10.44" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if", @@ -1036,9 +1066,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.79" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", @@ -1077,6 +1107,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "portable-atomic" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bdd679d533107e090c2704a35982fc06302e30898e63ffa26a81155c012e92" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1103,15 +1139,15 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] @@ -1134,9 +1170,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1375,9 +1411,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa 1.0.5", "ryu", @@ -1465,9 +1501,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1510,6 +1546,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "terminal_size" version = "0.2.3" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 6dccd4d..780716f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -13,6 +13,7 @@ crunchyroll-rs = "0.2" csv = "1.1" ctrlc = "3.2" dirs = "4.0" +indicatif = "0.17" log = { version = "0.4", features = ["std"] } num_cpus = "1.14" regex = "1.7" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 6e56e81..b5244f7 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -1,5 +1,5 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, FFmpegPreset, find_resolution}; +use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; @@ -137,7 +137,9 @@ impl Execute for Archive { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; - if self.ffmpeg_preset.len() == 1 && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia { + if self.ffmpeg_preset.len() == 1 + && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia + { warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") } @@ -148,20 +150,20 @@ impl Execute for Archive { let mut parsed_urls = vec![]; for (i, url) in self.urls.iter().enumerate() { - let _progress_handler = progress!("Parsing url {}", i + 1); + let progress_handler = progress!("Parsing url {}", i + 1); match parse_url(&ctx.crunchy, url.clone(), true).await { Ok((media_collection, url_filter)) => { parsed_urls.push((media_collection, url_filter)); - info!("Parsed url {}", i + 1) + progress_handler.stop(format!("Parsed url {}", i + 1)) } Err(e) => bail!("url {} could not be parsed: {}", url, e), } } for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { + let progress_handler = progress!("Fetching series details"); let archive_formats = match media_collection { MediaCollection::Series(series) => { - let _progress_handler = progress!("Fetching series details"); formats_from_series(&self, series, &url_filter).await? } MediaCollection::Season(_) => bail!("Archiving a season is not supported"), @@ -171,10 +173,13 @@ impl Execute for Archive { }; if archive_formats.is_empty() { - info!("Skipping url {} (no matching episodes found)", i + 1); + progress_handler.stop(format!( + "Skipping url {} (no matching episodes found)", + i + 1 + )); continue; } - info!("Loaded series information for url {}", i + 1); + progress_handler.stop(format!("Loaded series information for url {}", i + 1)); if log::max_level() == log::Level::Debug { let seasons = sort_formats_after_seasons( @@ -310,9 +315,9 @@ impl Execute for Archive { )) } - let _progess_handler = progress!("Generating mkv"); + let progess_handler = progress!("Generating mkv"); generate_mkv(&self, path, video_paths, audio_paths, subtitle_paths)?; - info!("Mkv generated") + progess_handler.stop("Mkv generated") } } @@ -349,7 +354,10 @@ async fn formats_from_series( // remove all seasons with the wrong audio for the current iterated season number seasons.retain(|s| { s.metadata.season_number != season.first().unwrap().metadata.season_number - || archive.locale.iter().any(|l| s.metadata.audio_locales.contains(l)) + || archive + .locale + .iter() + .any(|l| s.metadata.audio_locales.contains(l)) }) } @@ -358,7 +366,10 @@ async fn formats_from_series( BTreeMap::new(); for season in series.seasons().await? { if !url_filter.is_season_valid(season.metadata.season_number) - || !archive.locale.iter().any(|l| season.metadata.audio_locales.contains(l)) + || !archive + .locale + .iter() + .any(|l| season.metadata.audio_locales.contains(l)) { continue; } diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 4e4eb3c..d8a4ada 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,5 +1,5 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, FFmpegPreset, find_resolution}; +use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; @@ -92,7 +92,9 @@ impl Execute for Download { } let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; - if self.ffmpeg_preset.len() == 1 && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia { + if self.ffmpeg_preset.len() == 1 + && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia + { warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") } @@ -103,18 +105,18 @@ impl Execute for Download { let mut parsed_urls = vec![]; for (i, url) in self.urls.iter().enumerate() { - let _progress_handler = progress!("Parsing url {}", i + 1); + let progress_handler = progress!("Parsing url {}", i + 1); match parse_url(&ctx.crunchy, url.clone(), true).await { Ok((media_collection, url_filter)) => { parsed_urls.push((media_collection, url_filter)); - info!("Parsed url {}", i + 1) + progress_handler.stop(format!("Parsed url {}", i + 1)) } Err(e) => bail!("url {} could not be parsed: {}", url, e), } } for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { - let _progress_handler = progress!("Fetching series details"); + let progress_handler = progress!("Fetching series details"); let formats = match media_collection { MediaCollection::Series(series) => { debug!("Url {} is series ({})", i + 1, series.title); @@ -156,11 +158,10 @@ impl Execute for Download { }; let Some(formats) = formats else { - info!("Skipping url {} (no matching episodes found)", i + 1); + progress_handler.stop(format!("Skipping url {} (no matching episodes found)", i + 1)); continue; }; - info!("Loaded series information for url {}", i + 1); - drop(_progress_handler); + progress_handler.stop(format!("Loaded series information for url {}", i + 1)); if log::max_level() == log::Level::Debug { let seasons = sort_formats_after_seasons(formats.clone()); @@ -231,7 +232,9 @@ impl Execute for Download { tab_info!("Resolution: {}", format.stream.resolution); tab_info!("FPS: {:.2}", format.stream.fps); - if path.extension().unwrap_or_default().to_string_lossy() != "ts" || !self.ffmpeg_preset.is_empty() { + if path.extension().unwrap_or_default().to_string_lossy() != "ts" + || !self.ffmpeg_preset.is_empty() + { download_ffmpeg(&ctx, &self, format.stream, path.as_path()).await?; } else if path.to_str().unwrap() == "-" { let mut stdout = std::io::stdout().lock(); @@ -247,7 +250,12 @@ impl Execute for Download { } } -async fn download_ffmpeg(ctx: &Context, download: &Download, variant_data: VariantData, target: &Path) -> Result<()> { +async fn download_ffmpeg( + ctx: &Context, + download: &Download, + variant_data: VariantData, + target: &Path, +) -> Result<()> { let (input_presets, output_presets) = FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs index d1f0225..377685b 100644 --- a/crunchy-cli-core/src/cli/log.rs +++ b/crunchy-cli-core/src/cli/log.rs @@ -1,106 +1,17 @@ +use indicatif::{ProgressBar, ProgressStyle}; use log::{ set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, SetLoggerError, }; use std::io::{stdout, Write}; -use std::sync::{mpsc, Mutex}; +use std::sync::Mutex; use std::thread; -use std::thread::JoinHandle; use std::time::Duration; -struct CliProgress { - handler: JoinHandle<()>, - sender: mpsc::SyncSender<(String, Level)>, -} - -impl CliProgress { - fn new(record: &Record) -> Self { - let (tx, rx) = mpsc::sync_channel(1); - - let init_message = format!("{}", record.args()); - let init_level = record.level(); - let handler = thread::spawn(move || { - #[cfg(not(windows))] - let ok = 'โœ”'; - #[cfg(windows)] - // windows does not support all unicode characters by default in their consoles, so - // we're using this (square root?) symbol instead. microsoft. - let ok = 'โˆš'; - let states = ["-", "\\", "|", "/"]; - - let mut old_message = init_message.clone(); - let mut latest_info_message = init_message; - let mut old_level = init_level; - for i in 0.. { - let (msg, level) = match rx.try_recv() { - Ok(payload) => payload, - Err(e) => match e { - mpsc::TryRecvError::Empty => (old_message.clone(), old_level), - mpsc::TryRecvError::Disconnected => break, - }, - }; - - // clear last line - // prefix (2), space (1), state (1), space (1), message(n) - let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len())); - - if old_level != level || old_message != msg { - if old_level <= Level::Warn { - let _ = writeln!(stdout(), "\r:: โ€ข {}", old_message); - } else if old_level == Level::Info && level <= Level::Warn { - let _ = writeln!(stdout(), "\r:: โ†’ {}", old_message); - } else if level == Level::Info { - latest_info_message = msg.clone(); - } - } - - let _ = write!( - stdout(), - "\r:: {} {}", - states[i / 2 % states.len()], - if level == Level::Info { - &msg - } else { - &latest_info_message - } - ); - let _ = stdout().flush(); - - old_message = msg; - old_level = level; - - thread::sleep(Duration::from_millis(100)); - } - - // clear last line - // prefix (2), space (1), state (1), space (1), message(n) - let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len())); - let _ = writeln!(stdout(), "\r:: {} {}", ok, old_message); - let _ = stdout().flush(); - }); - - Self { - handler, - sender: tx, - } - } - - fn send(&self, record: &Record) { - let _ = self - .sender - .send((format!("{}", record.args()), record.level())); - } - - fn stop(self) { - drop(self.sender); - let _ = self.handler.join(); - } -} - #[allow(clippy::type_complexity)] pub struct CliLogger { all: bool, level: LevelFilter, - progress: Mutex<Option<CliProgress>>, + progress: Mutex<Option<ProgressBar>>, } impl Log for CliLogger { @@ -127,7 +38,7 @@ impl Log for CliLogger { "progress_end" => self.progress(record, true), _ => { if self.progress.lock().unwrap().is_some() { - self.progress(record, false); + self.progress(record, false) } else if record.level() > Level::Warn { self.normal(record) } else { @@ -182,13 +93,34 @@ impl CliLogger { } fn progress(&self, record: &Record, stop: bool) { - let mut progress_option = self.progress.lock().unwrap(); - if stop && progress_option.is_some() { - progress_option.take().unwrap().stop() - } else if let Some(p) = &*progress_option { - p.send(record); + let mut progress = self.progress.lock().unwrap(); + + let msg = format!("{}", record.args()); + if stop && progress.is_some() { + if msg.is_empty() { + progress.take().unwrap().finish() + } else { + progress.take().unwrap().finish_with_message(msg) + } + } else if let Some(p) = &*progress { + p.println(format!(":: โ†’ {}", msg)) } else { - *progress_option = Some(CliProgress::new(record)) + #[cfg(not(windows))] + let finish_str = "โœ”"; + #[cfg(windows)] + // windows does not support all unicode characters by default in their consoles, so + // we're using this (square root?) symbol instead. microsoft. + let finish_str = "โˆš"; + + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::with_template(":: {spinner} {msg}") + .unwrap() + .tick_strings(&["-", "\\", "|", "/", finish_str]), + ); + pb.enable_steady_tick(Duration::from_millis(200)); + pb.set_message(msg); + *progress = Some(pb) } } } diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index a64057a..6045ce4 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -1,13 +1,12 @@ use crate::utils::context::Context; use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; +use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use log::{debug, LevelFilter}; use std::borrow::{Borrow, BorrowMut}; use std::collections::BTreeMap; -use std::io; use std::io::Write; use std::sync::{mpsc, Arc, Mutex}; -use std::time::Duration; use tokio::task::JoinSet; pub fn find_resolution( @@ -35,78 +34,25 @@ pub async fn download_segments( let client = Arc::new(ctx.crunchy.client()); let count = Arc::new(Mutex::new(0)); - let amount = Arc::new(Mutex::new(0)); - // only print progress when log level is info - let output_handler = if log::max_level() == LevelFilter::Info { - let output_count = count.clone(); - let output_amount = amount.clone(); - Some(tokio::spawn(async move { - let sleep_time_ms = 100; - let iter_per_sec = 1000f64 / sleep_time_ms as f64; + let progress = if log::max_level() == LevelFilter::Info { + let estimated_file_size = (variant_data.bandwidth / 8) + * segments + .iter() + .map(|s| s.length.unwrap_or_default().as_secs()) + .sum::<u64>(); - let mut bytes_start = 0f64; - let mut speed = 0f64; - let mut percentage = 0f64; - - while *output_count.lock().unwrap() < total_segments || percentage < 100f64 { - let tmp_amount = *output_amount.lock().unwrap() as f64; - - let tmp_speed = (tmp_amount - bytes_start) / 1024f64 / 1024f64; - if *output_count.lock().unwrap() < 3 { - speed = tmp_speed; - } else { - let (old_speed_ratio, new_speed_ratio) = if iter_per_sec <= 1f64 { - (0f64, 1f64) - } else { - (1f64 - (1f64 / iter_per_sec), (1f64 / iter_per_sec)) - }; - - // calculate the average download speed "smoother" - speed = (speed * old_speed_ratio) + (tmp_speed * new_speed_ratio); - } - - percentage = - (*output_count.lock().unwrap() as f64 / total_segments as f64) * 100f64; - - let size = terminal_size::terminal_size() - .unwrap_or((terminal_size::Width(60), terminal_size::Height(0))) - .0 - .0 as usize; - - // there is a offset of 1 "length" (idk how to describe it), so removing 1 from - // `progress_available` would fill the terminal width completely. on multiple - // systems there is a bug that printing until the end of the line causes a newline - // even though technically there shouldn't be one. on my tests, this only happens on - // windows and mac machines and (at the addressed environments) only with release - // builds. so maybe an unwanted optimization? - let progress_available = size - - if let Some(msg) = &message { - 35 + msg.len() - } else { - 34 - }; - let progress_done_count = - (progress_available as f64 * (percentage / 100f64)).ceil() as usize; - let progress_to_do_count = progress_available - progress_done_count; - - let _ = write!( - io::stdout(), - "\r:: {}{:>5.1} MiB {:>5.2} MiB/s [{}{}] {:>3}%", - message.clone().map_or("".to_string(), |msg| msg + " "), - tmp_amount / 1024f64 / 1024f64, - speed * iter_per_sec, - "#".repeat(progress_done_count), - "-".repeat(progress_to_do_count), - percentage as usize - ); - - bytes_start = tmp_amount; - - tokio::time::sleep(Duration::from_millis(sleep_time_ms)).await; - } - println!() - })) + let progress = ProgressBar::new(estimated_file_size) + .with_style( + ProgressStyle::with_template( + ":: {msg}{bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", + ) + .unwrap() + .progress_chars("##-"), + ) + .with_message(message.map(|m| m + " ").unwrap_or_default()) + .with_finish(ProgressFinish::Abandon); + Some(progress) } else { None }; @@ -116,7 +62,7 @@ pub async fn download_segments( for _ in 0..cpus { segs.push(vec![]) } - for (i, segment) in segments.into_iter().enumerate() { + for (i, segment) in segments.clone().into_iter().enumerate() { segs[i - ((i / cpus) * cpus)].push(segment); } @@ -127,15 +73,12 @@ pub async fn download_segments( let thread_client = client.clone(); let thread_sender = sender.clone(); let thread_segments = segs.remove(0); - let thread_amount = amount.clone(); let thread_count = count.clone(); join_set.spawn(async move { for (i, segment) in thread_segments.into_iter().enumerate() { let response = thread_client.get(&segment.url).send().await?; let mut buf = response.bytes().await?.to_vec(); - *thread_amount.lock().unwrap() += buf.len(); - buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); debug!( "Downloaded and decrypted segment {} ({})", @@ -155,13 +98,28 @@ pub async fn download_segments( let mut buf: BTreeMap<usize, Vec<u8>> = BTreeMap::new(); loop { // is always `Some` because `sender` does not get dropped when all threads are finished - let data = receiver.recv().unwrap(); + let (pos, bytes) = receiver.recv().unwrap(); - if data_pos == data.0 { - writer.write_all(data.1.borrow())?; + if let Some(p) = &progress { + let progress_len = p.length().unwrap(); + let estimated_segment_len = (variant_data.bandwidth / 8) + * segments + .get(pos) + .unwrap() + .length + .unwrap_or_default() + .as_secs(); + let bytes_len = bytes.len() as u64; + + p.set_length(progress_len - estimated_segment_len + bytes_len); + p.inc(bytes_len) + } + + if data_pos == pos { + writer.write_all(bytes.borrow())?; data_pos += 1; } else { - buf.insert(data.0, data.1); + buf.insert(pos, bytes); } while let Some(b) = buf.remove(&data_pos) { writer.write_all(b.borrow())?; @@ -176,9 +134,6 @@ pub async fn download_segments( while let Some(joined) = join_set.join_next().await { joined?? } - if let Some(handler) = output_handler { - handler.await? - } Ok(()) } @@ -200,7 +155,7 @@ impl ToString for FFmpegPreset { &FFmpegPreset::H265 => "h265", &FFmpegPreset::H264 => "h264", } - .to_string() + .to_string() } } @@ -233,7 +188,9 @@ impl FFmpegPreset { }) } - pub(crate) fn ffmpeg_presets(mut presets: Vec<FFmpegPreset>) -> Result<(Vec<String>, Vec<String>)> { + pub(crate) fn ffmpeg_presets( + mut presets: Vec<FFmpegPreset>, + ) -> Result<(Vec<String>, Vec<String>)> { fn preset_check_remove(presets: &mut Vec<FFmpegPreset>, preset: FFmpegPreset) -> bool { if let Some(i) = presets.iter().position(|p| p == &preset) { presets.remove(i); diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index d669b64..00699e4 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -6,7 +6,7 @@ use anyhow::bail; use anyhow::Result; use clap::{Parser, Subcommand}; use crunchyroll_rs::{Crunchyroll, Locale}; -use log::{debug, error, info, LevelFilter}; +use log::{debug, error, LevelFilter}; use std::{env, fs}; mod cli; @@ -196,7 +196,7 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { + cli.login_method.etp_rt.is_some() as u8 + cli.login_method.anonymous as u8; - let _progress_handler = progress!("Logging in"); + let progress_handler = progress!("Logging in"); if login_methods_count == 0 { if let Some(login_file_path) = cli::login::login_file_path() { if login_file_path.exists() { @@ -232,7 +232,7 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { bail!("should never happen") }; - info!("Logged in"); + progress_handler.stop("Logged in"); Ok(crunchy) } diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs index b9fa939..24fed5d 100644 --- a/crunchy-cli-core/src/utils/log.rs +++ b/crunchy-cli-core/src/utils/log.rs @@ -1,10 +1,21 @@ use log::info; -pub struct ProgressHandler; +pub struct ProgressHandler { + pub(crate) stopped: bool, +} impl Drop for ProgressHandler { fn drop(&mut self) { - info!(target: "progress_end", "") + if !self.stopped { + info!(target: "progress_end", "") + } + } +} + +impl ProgressHandler { + pub(crate) fn stop<S: AsRef<str>>(mut self, msg: S) { + self.stopped = true; + info!(target: "progress_end", "{}", msg.as_ref()) } } @@ -12,7 +23,7 @@ macro_rules! progress { ($($arg:tt)+) => { { log::info!(target: "progress", $($arg)+); - $crate::utils::log::ProgressHandler{} + $crate::utils::log::ProgressHandler{stopped: false} } } } From 2c3bd78fc1a03450d792733820dddf0d8da92368 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 27 Dec 2022 20:37:45 +0100 Subject: [PATCH 273/630] Leave special files untouched from renaming --- crunchy-cli-core/src/utils/os.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 01d0f89..e3320fb 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -37,6 +37,12 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { /// Check if the given path exists and rename it until the new (renamed) file does not exist. pub fn free_file(mut path: PathBuf) -> PathBuf { + // if path is not a file and not a dir it's probably a pipe on linux which reguarly is intended + // and thus does not need to be renamed. what it is on windows ยฏ\_(ใƒ„)_/ยฏ + if !path.is_file() && !path.is_dir() { + return path; + } + let mut i = 0; while path.exists() { i += 1; From c37e2495e1e66d9ee7a5d17d3570dcb5f081e137 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 27 Dec 2022 20:49:53 +0100 Subject: [PATCH 274/630] Create output parent directory if it doesn't exists (#91) --- crunchy-cli-core/src/cli/archive.rs | 7 +++++++ crunchy-cli-core/src/cli/download.rs | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index b5244f7..70240cf 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -647,6 +647,13 @@ fn generate_mkv( debug!("ffmpeg {}", command_args.join(" ")); + // create parent directory if it does not exist + if let Some(parent) = target.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)? + } + } + let ffmpeg = Command::new("ffmpeg") .stdout(Stdio::null()) .stderr(Stdio::piped()) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index d8a4ada..99da758 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -240,6 +240,12 @@ impl Execute for Download { let mut stdout = std::io::stdout().lock(); download_segments(&ctx, &mut stdout, None, format.stream).await?; } else { + // create parent directory if it does not exist + if let Some(parent) = path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)? + } + } let mut file = File::options().create(true).write(true).open(&path)?; download_segments(&ctx, &mut file, None, format.stream).await? } @@ -259,6 +265,13 @@ async fn download_ffmpeg( let (input_presets, output_presets) = FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; + // create parent directory if it does not exist + if let Some(parent) = target.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)? + } + } + let mut ffmpeg = Command::new("ffmpeg") .stdin(Stdio::piped()) .stdout(Stdio::null()) From 14f42833cb5257e0f9ef10f4b243c0ec51cbf28e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 27 Dec 2022 22:59:35 +0100 Subject: [PATCH 275/630] Fix output to special file (pipes etc.) --- crunchy-cli-core/src/cli/archive.rs | 9 +++++++-- crunchy-cli-core/src/cli/download.rs | 27 ++++++++++++++++++++++----- crunchy-cli-core/src/utils/os.rs | 13 +++++++++---- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 70240cf..e3d5520 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -3,7 +3,7 @@ use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; -use crate::utils::os::{free_file, has_ffmpeg, tempfile}; +use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::Execute; @@ -133,6 +133,7 @@ impl Execute for Archive { .unwrap_or_default() .to_string_lossy() != "mkv" + && !is_special_file(PathBuf::from(&self.output)) { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } @@ -251,7 +252,11 @@ impl Execute for Archive { info!( "Downloading {} to '{}'", primary.title, - path.to_str().unwrap() + if is_special_file(&path) { + path.to_str().unwrap() + } else { + path.file_name().unwrap().to_str().unwrap() + } ); tab_info!( "Episode: S{:02}E{:02}", diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 99da758..0411a5e 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -3,7 +3,7 @@ use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; -use crate::utils::os::{free_file, has_ffmpeg}; +use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::Execute; @@ -219,7 +219,11 @@ impl Execute for Download { info!( "Downloading {} to '{}'", format.title, - path.file_name().unwrap().to_str().unwrap() + if is_special_file(&path) { + path.to_str().unwrap() + } else { + path.file_name().unwrap().to_str().unwrap() + } ); tab_info!("Episode: S{:02}E{:02}", format.season_number, format.number); tab_info!("Audio: {}", format.audio); @@ -232,9 +236,9 @@ impl Execute for Download { tab_info!("Resolution: {}", format.stream.resolution); tab_info!("FPS: {:.2}", format.stream.fps); - if path.extension().unwrap_or_default().to_string_lossy() != "ts" - || !self.ffmpeg_preset.is_empty() - { + let extension = path.extension().unwrap_or_default().to_string_lossy(); + + if (!extension.is_empty() && extension != "ts") || !self.ffmpeg_preset.is_empty() { download_ffmpeg(&ctx, &self, format.stream, path.as_path()).await?; } else if path.to_str().unwrap() == "-" { let mut stdout = std::io::stdout().lock(); @@ -279,6 +283,19 @@ async fn download_ffmpeg( .arg("-y") .args(input_presets) .args(["-f", "mpegts", "-i", "pipe:"]) + .args( + if target + .extension() + .unwrap_or_default() + .to_string_lossy() + .is_empty() + { + vec!["-f", "mpegts"] + } else { + vec![] + } + .as_slice(), + ) .args(output_presets) .arg(target.to_str().unwrap()) .spawn()?; diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index e3320fb..d2d4c3c 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -1,6 +1,6 @@ use log::debug; use std::io::ErrorKind; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::{env, io}; use tempfile::{Builder, NamedTempFile}; @@ -37,9 +37,8 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { /// Check if the given path exists and rename it until the new (renamed) file does not exist. pub fn free_file(mut path: PathBuf) -> PathBuf { - // if path is not a file and not a dir it's probably a pipe on linux which reguarly is intended - // and thus does not need to be renamed. what it is on windows ยฏ\_(ใƒ„)_/ยฏ - if !path.is_file() && !path.is_dir() { + // if it's a special file does not rename it + if is_special_file(&path) { return path; } @@ -58,3 +57,9 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { } path } + +/// Check if the given path is a special file. On Linux this is probably a pipe and on Windows +/// ยฏ\_(ใƒ„)_/ยฏ +pub fn is_special_file<P: AsRef<Path>>(path: P) -> bool { + path.as_ref().exists() && !path.as_ref().is_file() && !path.as_ref().is_dir() +} From c5940a240c8b2a6c39d6b3033ea6b5f6ee761335 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 28 Dec 2022 02:18:17 +0100 Subject: [PATCH 276/630] Slightly change download process to be more verbose in error situations --- crunchy-cli-core/src/cli/utils.rs | 37 ++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 6045ce4..1f6bf40 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -93,13 +93,16 @@ pub async fn download_segments( Ok(()) }); } + // drop the sender already here so it does not outlive all (download) threads which are the only + // real consumers of it + drop(sender); + // this is the main loop which writes the data. it uses a BTreeMap as a buffer as the write + // happens synchronized. the download consist of multiple segments. the map keys are representing + // the segment number and the values the corresponding bytes let mut data_pos = 0usize; let mut buf: BTreeMap<usize, Vec<u8>> = BTreeMap::new(); - loop { - // is always `Some` because `sender` does not get dropped when all threads are finished - let (pos, bytes) = receiver.recv().unwrap(); - + for (pos, bytes) in receiver.iter() { if let Some(p) = &progress { let progress_len = p.length().unwrap(); let estimated_segment_len = (variant_data.bandwidth / 8) @@ -115,26 +118,44 @@ pub async fn download_segments( p.inc(bytes_len) } + // check if the currently sent bytes are the next in the buffer. if so, write them directly + // to the target without first adding them to the buffer. + // if not, add them to the buffer if data_pos == pos { writer.write_all(bytes.borrow())?; data_pos += 1; } else { buf.insert(pos, bytes); } + // check if the buffer contains the next segment(s) while let Some(b) = buf.remove(&data_pos) { writer.write_all(b.borrow())?; data_pos += 1; } - - if *count.lock().unwrap() >= total_segments && buf.is_empty() { - break; - } } + // write the remaining buffer, if existent + while let Some(b) = buf.remove(&data_pos) { + writer.write_all(b.borrow())?; + data_pos += 1; + } + + // if any error has occured while downloading it gets returned here. maybe a little late, if one + // out of, for example 12, threads has the error while let Some(joined) = join_set.join_next().await { joined?? } + if !buf.is_empty() { + bail!( + "Download buffer is not empty. Remaining segments: {}", + buf.into_keys() + .map(|k| k.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + Ok(()) } From 240e5563a3db767fccccfe523af74a8d862f5da0 Mon Sep 17 00:00:00 2001 From: Alexandru Dracea <adracea@gmail.com> Date: Wed, 28 Dec 2022 15:44:45 +0200 Subject: [PATCH 277/630] Add error handling and retry attempts Handles cases where the segments fail to download and sometimes get stuck by introducing a timeout and retrying on failure. --- crunchy-cli-core/src/cli/utils.rs | 51 +++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 1f6bf40..3d14318 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -76,8 +76,55 @@ pub async fn download_segments( let thread_count = count.clone(); join_set.spawn(async move { for (i, segment) in thread_segments.into_iter().enumerate() { - let response = thread_client.get(&segment.url).send().await?; - let mut buf = response.bytes().await?.to_vec(); + let response_res = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60u64)) + .send() + .await; + let verfified_response = match response_res { + Ok(x) => x, + Err(y) => panic!("This is likely a netowrking error: {}", y), + }; + let possible_error_in_response = verfified_response.bytes().await; + let mut buf = if let Ok(r) = possible_error_in_response { + r.to_vec() + } else { + debug!( + "Segment Failed to download: {}, retrying.", + num + (i * cpus) + ); + let mut resp = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60u64)) + .send() + .await + .unwrap() + .bytes() + .await; + if resp.is_err() { + let mut retry_ctr = 1; + loop { + debug!( + "Segment Failed to download: {}, retry {}.", + num + (i * cpus), + retry_ctr + ); + resp = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60u64)) + .send() + .await + .unwrap() + .bytes() + .await; + if resp.is_ok() { + break; + } + retry_ctr += 1; + } + } + resp.unwrap().to_vec() + }; buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); debug!( From 8a3c0132e7e0ec4206d001670fd062bcf9673052 Mon Sep 17 00:00:00 2001 From: Alexandru Dracea <adracea@gmail.com> Date: Wed, 28 Dec 2022 15:59:55 +0200 Subject: [PATCH 278/630] Update utils.rs --- crunchy-cli-core/src/cli/utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 3d14318..afc431f 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -3,7 +3,8 @@ use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use log::{debug, LevelFilter}; -use std::borrow::{Borrow, BorrowMut}; +use std::borrow::Borrow; +use std::time::Duration; use std::collections::BTreeMap; use std::io::Write; use std::sync::{mpsc, Arc, Mutex}; From c2ae622d01c1913ff51264858a4c6b0f28353690 Mon Sep 17 00:00:00 2001 From: Alexandru Dracea <adracea@gmail.com> Date: Wed, 28 Dec 2022 16:01:55 +0200 Subject: [PATCH 279/630] Update utils.rs --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index afc431f..cd45859 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -3,7 +3,7 @@ use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use log::{debug, LevelFilter}; -use std::borrow::Borrow; +use std::borrow::{Borrow, BorrowMut}; use std::time::Duration; use std::collections::BTreeMap; use std::io::Write; From d0681c7f6cc01caa684b39f30c459a20d5f09c24 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 28 Dec 2022 15:18:12 +0100 Subject: [PATCH 280/630] Simplify retry segment download --- crunchy-cli-core/src/cli/utils.rs | 62 ++++++++++--------------------- 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index cd45859..7c32e86 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -4,10 +4,10 @@ use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use log::{debug, LevelFilter}; use std::borrow::{Borrow, BorrowMut}; -use std::time::Duration; use std::collections::BTreeMap; use std::io::Write; use std::sync::{mpsc, Arc, Mutex}; +use std::time::Duration; use tokio::task::JoinSet; pub fn find_resolution( @@ -77,54 +77,30 @@ pub async fn download_segments( let thread_count = count.clone(); join_set.spawn(async move { for (i, segment) in thread_segments.into_iter().enumerate() { - let response_res = thread_client - .get(&segment.url) - .timeout(Duration::from_secs(60u64)) - .send() - .await; - let verfified_response = match response_res { - Ok(x) => x, - Err(y) => panic!("This is likely a netowrking error: {}", y), - }; - let possible_error_in_response = verfified_response.bytes().await; - let mut buf = if let Ok(r) = possible_error_in_response { - r.to_vec() - } else { - debug!( - "Segment Failed to download: {}, retrying.", - num + (i * cpus) - ); - let mut resp = thread_client + let mut retry_count = 0; + let mut buf = loop { + let response = thread_client .get(&segment.url) - .timeout(Duration::from_secs(60u64)) + .timeout(Duration::from_secs(10)) .send() .await - .unwrap() - .bytes() - .await; - if resp.is_err() { - let mut retry_ctr = 1; - loop { - debug!( - "Segment Failed to download: {}, retry {}.", - num + (i * cpus), - retry_ctr - ); - resp = thread_client - .get(&segment.url) - .timeout(Duration::from_secs(60u64)) - .send() - .await - .unwrap() - .bytes() - .await; - if resp.is_ok() { - break; + .unwrap(); + + match response.bytes().await { + Ok(b) => break b.to_vec(), + Err(e) => { + if e.is_body() { + if retry_count == 5 { + panic!("Max retry count reached ({}), multiple errors occured while receiving segment {}: {}", retry_count, num + (i * cpus), e) + } + debug!("Failed to download segment {}. Retrying ({} out of 5 retries left)", num + (i * cpus), 5 - retry_count) + } else { + panic!("{}", e) } - retry_ctr += 1; } } - resp.unwrap().to_vec() + + retry_count += 1; }; buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); From 7115c5546d1c71c496c26b82d85b2f1aaf114aca Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 28 Dec 2022 15:25:10 +0100 Subject: [PATCH 281/630] Show error message on segment download retry --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 7c32e86..e2bbc63 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -93,7 +93,7 @@ pub async fn download_segments( if retry_count == 5 { panic!("Max retry count reached ({}), multiple errors occured while receiving segment {}: {}", retry_count, num + (i * cpus), e) } - debug!("Failed to download segment {}. Retrying ({} out of 5 retries left)", num + (i * cpus), 5 - retry_count) + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) } else { panic!("{}", e) } From b8e46099f9035c609b376c2cfa1b0d28719564c9 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 28 Dec 2022 15:35:38 +0100 Subject: [PATCH 282/630] Re-increase segment request timeout --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index e2bbc63..841dbef 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -81,7 +81,7 @@ pub async fn download_segments( let mut buf = loop { let response = thread_client .get(&segment.url) - .timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(60)) .send() .await .unwrap(); From 03db38b31ce85f7264fe90d259c09e7ad3ee8679 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Wed, 28 Dec 2022 15:45:33 +0100 Subject: [PATCH 283/630] Add debug segment percentage (#93) * Fix file extension unwrap panic * Change log output name from crunchy_cli_core to crunchy_cli * Add percentage output --- crunchy-cli-core/src/cli/log.rs | 5 +++-- crunchy-cli-core/src/cli/utils.rs | 8 ++++++-- crunchy-cli-core/src/utils/os.rs | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs index 377685b..3f63eb0 100644 --- a/crunchy-cli-core/src/cli/log.rs +++ b/crunchy-cli-core/src/cli/log.rs @@ -75,8 +75,9 @@ impl CliLogger { // replace the 'progress' prefix if this function is invoked via 'progress!' record .target() - .replacen("progress", "crunchy_cli", 1) - .replacen("progress_end", "crunchy_cli", 1), + .replacen("crunchy_cli_core", "crunchy_cli", 1) + .replacen("progress_end", "crunchy_cli", 1) + .replacen("progress", "crunchy_cli", 1), format!("{:?}", thread::current().id()) .replace("ThreadId(", "") .replace(')', ""), diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 6045ce4..4c8c10e 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -80,14 +80,18 @@ pub async fn download_segments( let mut buf = response.bytes().await?.to_vec(); buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); + + let mut c = thread_count.lock().unwrap(); debug!( - "Downloaded and decrypted segment {} ({})", + "Downloaded and decrypted segment [{}/{} {:.2}%] {}", num + (i * cpus), + total_segments, + ((*c + 1) as f64 / total_segments as f64) * 100f64, segment.url ); thread_sender.send((num + (i * cpus), buf))?; - *thread_count.lock().unwrap() += 1; + *c += 1; } Ok(()) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index d2d4c3c..a7b3fbf 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -46,8 +46,8 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { while path.exists() { i += 1; - let ext = path.extension().unwrap().to_string_lossy(); - let mut filename = path.file_stem().unwrap().to_str().unwrap(); + let ext = path.extension().unwrap_or_default().to_string_lossy(); + let mut filename = path.file_stem().unwrap_or_default().to_str().unwrap(); if filename.ends_with(&format!(" ({})", i - 1)) { filename = filename.strip_suffix(&format!(" ({})", i - 1)).unwrap(); From 0c139420165e412516313d612124dd62fe2b117f Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 2 Jan 2023 17:53:54 +0100 Subject: [PATCH 284/630] Update dependencies --- Cargo.lock | 69 ++++++++++++++++--------------------- crunchy-cli-core/Cargo.lock | 61 ++++++++++++++------------------ 2 files changed, 56 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7432c8..8221757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.30" +version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656ad1e55e23d287773f7d8192c300dc715c3eeded93b3da651d11c42cfd74d2" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ "bitflags", "clap_derive", @@ -163,9 +163,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.0.6" +version = "4.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da" +checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b" dependencies = [ "clap", ] @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "clap_mangen" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e503c3058af0a0854668ea01db55c622482a080092fede9dd2e00a00a9436504" +checksum = "904eb24d05ad587557e0f484ddce5c737c30cf81372badb16d13e41c4b8340b1" dependencies = [ "clap", "roff", @@ -214,16 +214,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +checksum = "5556015fe3aad8b968e5d4124980fbe2f6aaee7aeec6b749de1faaa2ca5d0a4c" dependencies = [ "encode_unicode", "lazy_static", "libc", - "terminal_size 0.1.17", "unicode-width", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -312,21 +311,23 @@ dependencies = [ "signal-hook", "sys-locale", "tempfile", - "terminal_size 0.2.3", + "terminal_size", "tokio", ] [[package]] name = "crunchyroll-rs" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5032be36dc6c4757af4f28152659e7a772b48f6925f0e0ac81b5c384e291314" +checksum = "5ea0bc79ecd9fafc95c6049d0b38e0e00c8d4665314ae3ee675db67b622ce3b7" dependencies = [ "aes", + "async-trait", "cbc", "chrono", "crunchyroll-rs-internal", "http", + "lazy_static", "m3u8-rs", "regex", "reqwest", @@ -341,9 +342,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e4aa1f09fd76f44329455abfac35e22c654ac782e93bc8a7d3ee1be27509f4" +checksum = "66be957a34f7498e4bc324af67c58bb7ace299c214c9353281545f92dbc45d78" dependencies = [ "darling", "quote", @@ -908,9 +909,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "link-cplusplus" @@ -1014,9 +1015,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -1059,9 +1060,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "openssl" @@ -1140,9 +1141,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "portable-atomic" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bdd679d533107e090c2704a35982fc06302e30898e63ffa26a81155c012e92" +checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" [[package]] name = "proc-macro-error" @@ -1328,9 +1329,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.36.5" +version = "0.36.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" dependencies = [ "bitflags", "errno", @@ -1428,18 +1429,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.151" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.151" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1583,16 +1584,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "terminal_size" version = "0.2.3" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 8347aa7..6a805d3 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.30" +version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656ad1e55e23d287773f7d8192c300dc715c3eeded93b3da651d11c42cfd74d2" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ "bitflags", "clap_derive", @@ -195,16 +195,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +checksum = "5556015fe3aad8b968e5d4124980fbe2f6aaee7aeec6b749de1faaa2ca5d0a4c" dependencies = [ "encode_unicode", "lazy_static", "libc", - "terminal_size 0.1.17", "unicode-width", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -281,21 +280,23 @@ dependencies = [ "signal-hook", "sys-locale", "tempfile", - "terminal_size 0.2.3", + "terminal_size", "tokio", ] [[package]] name = "crunchyroll-rs" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5032be36dc6c4757af4f28152659e7a772b48f6925f0e0ac81b5c384e291314" +checksum = "5ea0bc79ecd9fafc95c6049d0b38e0e00c8d4665314ae3ee675db67b622ce3b7" dependencies = [ "aes", + "async-trait", "cbc", "chrono", "crunchyroll-rs-internal", "http", + "lazy_static", "m3u8-rs", "regex", "reqwest", @@ -310,9 +311,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e4aa1f09fd76f44329455abfac35e22c654ac782e93bc8a7d3ee1be27509f4" +checksum = "66be957a34f7498e4bc324af67c58bb7ace299c214c9353281545f92dbc45d78" dependencies = [ "darling", "quote", @@ -877,9 +878,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "link-cplusplus" @@ -983,9 +984,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -1028,9 +1029,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "openssl" @@ -1109,9 +1110,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "portable-atomic" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bdd679d533107e090c2704a35982fc06302e30898e63ffa26a81155c012e92" +checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" [[package]] name = "proc-macro-error" @@ -1291,9 +1292,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.5" +version = "0.36.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" dependencies = [ "bitflags", "errno", @@ -1391,18 +1392,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.151" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.151" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1546,16 +1547,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "terminal_size" version = "0.2.3" From fae5d699331fcb9ced5856f0ab521982edc0fb76 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 2 Jan 2023 17:56:50 +0100 Subject: [PATCH 285/630] Apply stabilizations fixes (#89) --- crunchy-cli-core/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 00699e4..4f2ce32 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -189,8 +189,9 @@ async fn create_ctx(cli: &Cli) -> Result<Context> { } async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { - let mut builder = Crunchyroll::builder(); - builder.locale(cli.lang.clone().unwrap_or_else(system_locale)); + let mut builder = Crunchyroll::builder() + .locale(cli.lang.clone().unwrap_or_else(system_locale)) + .stabilization_locales(true); let login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 From 3c3b7b65662e4f315ff34634d2c7b92a2445a212 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 3 Jan 2023 01:24:17 +0100 Subject: [PATCH 286/630] Fix panic on specific filenames --- crunchy-cli-core/src/utils/os.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index d2d4c3c..a7b3fbf 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -46,8 +46,8 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { while path.exists() { i += 1; - let ext = path.extension().unwrap().to_string_lossy(); - let mut filename = path.file_stem().unwrap().to_str().unwrap(); + let ext = path.extension().unwrap_or_default().to_string_lossy(); + let mut filename = path.file_stem().unwrap_or_default().to_str().unwrap(); if filename.ends_with(&format!(" ({})", i - 1)) { filename = filename.strip_suffix(&format!(" ({})", i - 1)).unwrap(); From b365bda5dca57754a383bdb3de0484f600c9697c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 3 Jan 2023 01:28:42 +0100 Subject: [PATCH 287/630] Fix download threads to properly return errors --- crunchy-cli-core/src/cli/utils.rs | 93 ++++++++++++++++++------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 841dbef..99d4f67 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -74,47 +74,56 @@ pub async fn download_segments( let thread_client = client.clone(); let thread_sender = sender.clone(); let thread_segments = segs.remove(0); - let thread_count = count.clone(); join_set.spawn(async move { - for (i, segment) in thread_segments.into_iter().enumerate() { - let mut retry_count = 0; - let mut buf = loop { - let response = thread_client - .get(&segment.url) - .timeout(Duration::from_secs(60)) - .send() - .await - .unwrap(); + let after_download_sender = thread_sender.clone(); - match response.bytes().await { - Ok(b) => break b.to_vec(), - Err(e) => { - if e.is_body() { - if retry_count == 5 { - panic!("Max retry count reached ({}), multiple errors occured while receiving segment {}: {}", retry_count, num + (i * cpus), e) + // the download process is encapsulated in its own function. this is done to easily + // catch errors which get returned with `...?` and `bail!(...)` and that the thread + // itself can report that an error has occured + let download = || async move { + for (i, segment) in thread_segments.into_iter().enumerate() { + let mut retry_count = 0; + let mut buf = loop { + let response = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60)) + .send() + .await?; + + match response.bytes().await { + Ok(b) => break b.to_vec(), + Err(e) => { + if e.is_body() { + if retry_count == 5 { + bail!("Max retry count reached ({}), multiple errors occured while receiving segment {}: {}", retry_count, num + (i * cpus), e) + } + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) + } else { + bail!("{}", e) } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) - } else { - panic!("{}", e) } } - } - retry_count += 1; - }; + retry_count += 1; + }; - buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); - debug!( - "Downloaded and decrypted segment {} ({})", - num + (i * cpus), - segment.url - ); - thread_sender.send((num + (i * cpus), buf))?; + buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); + debug!( + "Downloaded and decrypted segment {} ({})", + num + (i * cpus), + segment.url + ); + thread_sender.send((num as i32 + (i * cpus) as i32, buf))?; + } + Ok(()) + }; - *thread_count.lock().unwrap() += 1; + let result = download().await; + if result.is_err() { + after_download_sender.send((-1 as i32, vec![]))?; } - Ok(()) + result }); } // drop the sender already here so it does not outlive all (download) threads which are the only @@ -124,14 +133,19 @@ pub async fn download_segments( // this is the main loop which writes the data. it uses a BTreeMap as a buffer as the write // happens synchronized. the download consist of multiple segments. the map keys are representing // the segment number and the values the corresponding bytes - let mut data_pos = 0usize; - let mut buf: BTreeMap<usize, Vec<u8>> = BTreeMap::new(); + let mut data_pos = 0; + let mut buf: BTreeMap<i32, Vec<u8>> = BTreeMap::new(); for (pos, bytes) in receiver.iter() { + // if the position is lower than 0, an error occured in the sending download thread + if pos < 0 { + break + } + if let Some(p) = &progress { let progress_len = p.length().unwrap(); let estimated_segment_len = (variant_data.bandwidth / 8) * segments - .get(pos) + .get(pos as usize) .unwrap() .length .unwrap_or_default() @@ -158,18 +172,17 @@ pub async fn download_segments( } } + // if any error has occured while downloading it gets returned here + while let Some(joined) = join_set.join_next().await { + joined?? + } + // write the remaining buffer, if existent while let Some(b) = buf.remove(&data_pos) { writer.write_all(b.borrow())?; data_pos += 1; } - // if any error has occured while downloading it gets returned here. maybe a little late, if one - // out of, for example 12, threads has the error - while let Some(joined) = join_set.join_next().await { - joined?? - } - if !buf.is_empty() { bail!( "Download buffer is not empty. Remaining segments: {}", From 29c6129e6efbb18a1ac1ae7cbbc46893e4870603 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 3 Jan 2023 14:50:12 +0100 Subject: [PATCH 288/630] Update dependencies & version --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 14 +++++++------- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/lib.rs | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8221757..a564687 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "console" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556015fe3aad8b968e5d4124980fbe2f6aaee7aeec6b749de1faaa2ca5d0a4c" +checksum = "c9b6515d269224923b26b5febea2ed42b2d5f2ce37284a4dd670fedd6cb8347a" dependencies = [ "encode_unicode", "lazy_static", @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.4" +version = "3.0.0-dev.5" dependencies = [ "chrono", "clap", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.4" +version = "3.0.0-dev.5" dependencies = [ "anyhow", "async-trait", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea0bc79ecd9fafc95c6049d0b38e0e00c8d4665314ae3ee675db67b622ce3b7" +checksum = "f8ec300b509afbd8977f71cd7feb7d0f20d38d8e38976b7fcd51f6128cbdefe6" dependencies = [ "aes", "async-trait", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66be957a34f7498e4bc324af67c58bb7ace299c214c9353281545f92dbc45d78" +checksum = "10d4b870818d5ce0993d70271bf803dbfbcc8a3a0fa7398b182f5f4b4e88509d" dependencies = [ "darling", "quote", diff --git a/Cargo.toml b/Cargo.toml index f5f988b..c67d599 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.4" +version = "3.0.0-dev.5" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 6a805d3..37ee2c3 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "console" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556015fe3aad8b968e5d4124980fbe2f6aaee7aeec6b749de1faaa2ca5d0a4c" +checksum = "c9b6515d269224923b26b5febea2ed42b2d5f2ce37284a4dd670fedd6cb8347a" dependencies = [ "encode_unicode", "lazy_static", @@ -260,7 +260,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.4" +version = "3.0.0-dev.5" dependencies = [ "anyhow", "async-trait", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea0bc79ecd9fafc95c6049d0b38e0e00c8d4665314ae3ee675db67b622ce3b7" +checksum = "f8ec300b509afbd8977f71cd7feb7d0f20d38d8e38976b7fcd51f6128cbdefe6" dependencies = [ "aes", "async-trait", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66be957a34f7498e4bc324af67c58bb7ace299c214c9353281545f92dbc45d78" +checksum = "10d4b870818d5ce0993d70271bf803dbfbcc8a3a0fa7398b182f5f4b4e88509d" dependencies = [ "darling", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 780716f..e61031b 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.4" +version = "3.0.0-dev.5" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 4f2ce32..52a4da1 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -189,7 +189,7 @@ async fn create_ctx(cli: &Cli) -> Result<Context> { } async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { - let mut builder = Crunchyroll::builder() + let builder = Crunchyroll::builder() .locale(cli.lang.clone().unwrap_or_else(system_locale)) .stabilization_locales(true); From d0a8103e3def5a45c84bf7eafe2c8b43f8c7b98d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 4 Jan 2023 00:12:13 +0100 Subject: [PATCH 289/630] Update dependencies & version --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 10 +++++----- crunchy-cli-core/Cargo.toml | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a564687..8da4e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.5" +version = "3.0.0-dev.6" dependencies = [ "chrono", "clap", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.5" +version = "3.0.0-dev.6" dependencies = [ "anyhow", "async-trait", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ec300b509afbd8977f71cd7feb7d0f20d38d8e38976b7fcd51f6128cbdefe6" +checksum = "dd742baacdfbf9caca8656262ac2397c334928ccd65fb332c95ee8e8b8d9223d" dependencies = [ "aes", "async-trait", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d4b870818d5ce0993d70271bf803dbfbcc8a3a0fa7398b182f5f4b4e88509d" +checksum = "aadb66f2b5cdcc8b82f095aec6ca92ca4abb30016a13916f2358cf7bfd8a1997" dependencies = [ "darling", "quote", diff --git a/Cargo.toml b/Cargo.toml index c67d599..76ed950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.5" +version = "3.0.0-dev.6" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 37ee2c3..af1086a 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -260,7 +260,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.5" +version = "3.0.0-dev.6" dependencies = [ "anyhow", "async-trait", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ec300b509afbd8977f71cd7feb7d0f20d38d8e38976b7fcd51f6128cbdefe6" +checksum = "dd742baacdfbf9caca8656262ac2397c334928ccd65fb332c95ee8e8b8d9223d" dependencies = [ "aes", "async-trait", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d4b870818d5ce0993d70271bf803dbfbcc8a3a0fa7398b182f5f4b4e88509d" +checksum = "aadb66f2b5cdcc8b82f095aec6ca92ca4abb30016a13916f2358cf7bfd8a1997" dependencies = [ "darling", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e61031b..566b8c4 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.5" +version = "3.0.0-dev.6" edition = "2021" [dependencies] @@ -15,7 +15,7 @@ ctrlc = "3.2" dirs = "4.0" indicatif = "0.17" log = { version = "0.4", features = ["std"] } -num_cpus = "1.14" +num_cpus = "1.15" regex = "1.7" sanitize-filename = "0.4" serde = "1.0" From 7726287859482f508dc584be405db7022ee31b22 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 4 Jan 2023 01:28:14 +0100 Subject: [PATCH 290/630] :) --- Cargo.lock | 8 ++++---- crunchy-cli-core/Cargo.lock | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8da4e30..eb7c6a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd742baacdfbf9caca8656262ac2397c334928ccd65fb332c95ee8e8b8d9223d" +checksum = "184d0c725a09aec815316cbf41a2f362008ecb0e8c8e3b6b9930d01a89b5df21" dependencies = [ "aes", "async-trait", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aadb66f2b5cdcc8b82f095aec6ca92ca4abb30016a13916f2358cf7bfd8a1997" +checksum = "5f3c82e1766339727fc2c10d66d0c4f001b1cf42e2993f9d93997b610f408776" dependencies = [ "darling", "quote", diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index af1086a..b5dd8a1 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd742baacdfbf9caca8656262ac2397c334928ccd65fb332c95ee8e8b8d9223d" +checksum = "184d0c725a09aec815316cbf41a2f362008ecb0e8c8e3b6b9930d01a89b5df21" dependencies = [ "aes", "async-trait", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aadb66f2b5cdcc8b82f095aec6ca92ca4abb30016a13916f2358cf7bfd8a1997" +checksum = "5f3c82e1766339727fc2c10d66d0c4f001b1cf42e2993f9d93997b610f408776" dependencies = [ "darling", "quote", From 404aa496e157162c35c3b5bf983cbc43c8689676 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 5 Jan 2023 22:28:23 +0100 Subject: [PATCH 291/630] Fix subtitle look and feel typo --- crunchy-cli-core/src/cli/archive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index e3d5520..3910d24 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -481,7 +481,7 @@ fn fix_subtitle_look_and_feel(raw: Vec<u8>) -> Vec<u8> { for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { if line.trim().starts_with('[') && script_info { - new.push_str("ScaledBorderAndShadows: yes\n"); + new.push_str("ScaledBorderAndShadow: yes\n"); script_info = false } else if line.trim() == "[Script Info]" { script_info = true From 892407d1f069b1dfcaa71c4eeca1c942ff40872c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 6 Jan 2023 00:03:57 +0100 Subject: [PATCH 292/630] Fix --default-subtitle causing no such file error (#98) --- crunchy-cli-core/src/cli/archive.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 3910d24..3eb92a7 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -632,10 +632,10 @@ fn generate_mkv( if let Some(default_subtitle) = &archive.default_subtitle { // if `--default_subtitle <locale>` is given set the default subtitle to the given locale if let Some(position) = subtitle_paths - .into_iter() - .position(|s| &s.1.locale == default_subtitle) + .iter() + .position(|(_, subtitle)| &subtitle.locale == default_subtitle) { - command_args.push(format!("-disposition:s:{}", position)) + command_args.extend([format!("-disposition:s:{}", position), "default".to_string()]) } else { command_args.extend(["-disposition:s:0".to_string(), "0".to_string()]) } From 06fd9a7a98989aa4a2c84c120b96b0477f9cf150 Mon Sep 17 00:00:00 2001 From: Hannes Braun <hannesbraun@mail.de> Date: Wed, 4 Jan 2023 22:09:31 +0100 Subject: [PATCH 293/630] Archive subtitles of all versions of an episode --- crunchy-cli-core/src/cli/archive.rs | 95 +++++++++++++++++++------- crunchy-cli-core/src/utils/mod.rs | 1 + crunchy-cli-core/src/utils/subtitle.rs | 11 +++ 3 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 crunchy-cli-core/src/utils/subtitle.rs diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 3eb92a7..8259ab3 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -6,6 +6,7 @@ use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; +use crate::utils::subtitle::Subtitle; use crate::Execute; use anyhow::{bail, Result}; use chrono::NaiveTime; @@ -232,7 +233,7 @@ impl Execute for Archive { } } - for (formats, subtitles) in archive_formats { + for (formats, mut subtitles) in archive_formats { let (primary, additionally) = formats.split_first().unwrap(); let mut path = PathBuf::from(&self.output); @@ -276,13 +277,14 @@ impl Execute for Archive { "Subtitle: {}", subtitles .iter() + .filter(|s| s.primary) // Don't print subtitles of non-primary streams. They might get removed depending on the merge behavior. .map(|s| { if let Some(default) = &self.default_subtitle { - if default == &s.locale { + if default == &s.stream_subtitle.locale { return format!("{} (primary)", default); } } - s.locale.to_string() + s.stream_subtitle.locale.to_string() }) .collect::<Vec<String>>() .join(", ") @@ -309,13 +311,23 @@ impl Execute for Archive { } else { video_paths.push((path, additional)) } + + // Remove subtitles of deleted video + if only_audio { + subtitles.retain(|s| s.episode_id != additional.id); + } } let (primary_video, _) = video_paths.get(0).unwrap(); let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap(); for subtitle in subtitles { subtitle_paths.push(( - download_subtitle(&self, subtitle.clone(), primary_video_length).await?, + download_subtitle( + &self, + subtitle.stream_subtitle.clone(), + primary_video_length, + ) + .await?, subtitle, )) } @@ -334,7 +346,7 @@ async fn formats_from_series( archive: &Archive, series: Media<Series>, url_filter: &UrlFilter, -) -> Result<Vec<(Vec<Format>, Vec<StreamSubtitle>)>> { +) -> Result<Vec<(Vec<Format>, Vec<Subtitle>)>> { let mut seasons = series.seasons().await?; // filter any season out which does not contain the specified audio languages @@ -367,8 +379,8 @@ async fn formats_from_series( } #[allow(clippy::type_complexity)] - let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<StreamSubtitle>)>> = - BTreeMap::new(); + let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); + let mut primary_season = true; for season in series.seasons().await? { if !url_filter.is_season_valid(season.metadata.season_number) || !archive @@ -402,20 +414,26 @@ async fn formats_from_series( ) }; - let (ref mut formats, _) = result + let (ref mut formats, subtitles) = result .entry(season.metadata.season_number) .or_insert_with(BTreeMap::new) .entry(episode.metadata.episode_number) - .or_insert_with(|| { - let subtitles: Vec<StreamSubtitle> = archive - .subtitle - .iter() - .filter_map(|l| streams.subtitles.get(l).cloned()) - .collect(); - (vec![], subtitles) - }); + .or_insert_with(|| (vec![], vec![])); + subtitles.extend(archive.subtitle.iter().filter_map(|l| { + let stream_subtitle = streams.subtitles.get(l).cloned()?; + let subtitle = Subtitle { + stream_subtitle, + audio_locale: episode.metadata.audio_locale.clone(), + episode_id: episode.id.clone(), + forced: !episode.metadata.is_subbed, + primary: primary_season, + }; + Some(subtitle) + })); formats.push(Format::new_from_episode(episode, stream)); } + + primary_season = false; } Ok(result.into_values().flat_map(|v| v.into_values()).collect()) @@ -562,11 +580,12 @@ fn generate_mkv( target: PathBuf, video_paths: Vec<(TempPath, &Format)>, audio_paths: Vec<(TempPath, &Format)>, - subtitle_paths: Vec<(TempPath, StreamSubtitle)>, + subtitle_paths: Vec<(TempPath, Subtitle)>, ) -> Result<()> { let mut input = vec![]; let mut maps = vec![]; let mut metadata = vec![]; + let mut dispositions = vec![vec![]; subtitle_paths.len()]; for (i, (video_path, format)) in video_paths.iter().enumerate() { input.extend(["-i".to_string(), video_path.to_string_lossy().to_string()]); @@ -611,12 +630,26 @@ fn generate_mkv( ]); metadata.extend([ format!("-metadata:s:s:{}", i), - format!("language={}", subtitle.locale), + format!("language={}", subtitle.stream_subtitle.locale), ]); metadata.extend([ format!("-metadata:s:s:{}", i), - format!("title={}", subtitle.locale.to_human_readable()), + format!( + "title={}", + subtitle.stream_subtitle.locale.to_human_readable() + + if !subtitle.primary { + format!(" [Video: {}]", subtitle.audio_locale.to_human_readable()) + } else { + "".to_string() + } + .as_str() + ), ]); + + // mark forced subtitles + if subtitle.forced { + dispositions[i].push("forced"); + } } let (input_presets, output_presets) = @@ -633,16 +666,28 @@ fn generate_mkv( // if `--default_subtitle <locale>` is given set the default subtitle to the given locale if let Some(position) = subtitle_paths .iter() - .position(|(_, subtitle)| &subtitle.locale == default_subtitle) + .position(|(_, subtitle)| &subtitle.stream_subtitle.locale == default_subtitle) { - command_args.extend([format!("-disposition:s:{}", position), "default".to_string()]) - } else { - command_args.extend(["-disposition:s:0".to_string(), "0".to_string()]) + dispositions[position].push("default"); } - } else { - command_args.extend(["-disposition:s:0".to_string(), "0".to_string()]) } + let disposition_args: Vec<String> = dispositions + .iter() + .enumerate() + .flat_map(|(i, d)| { + vec![ + format!("-disposition:s:{}", i), + if !d.is_empty() { + d.join("+") + } else { + "0".to_string() + }, + ] + }) + .collect(); + command_args.extend(disposition_args); + command_args.extend(output_presets); command_args.extend([ "-f".to_string(), diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index 5f7a5d2..3b15a89 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -6,3 +6,4 @@ pub mod log; pub mod os; pub mod parse; pub mod sort; +pub mod subtitle; diff --git a/crunchy-cli-core/src/utils/subtitle.rs b/crunchy-cli-core/src/utils/subtitle.rs new file mode 100644 index 0000000..86b9359 --- /dev/null +++ b/crunchy-cli-core/src/utils/subtitle.rs @@ -0,0 +1,11 @@ +use crunchyroll_rs::media::StreamSubtitle; +use crunchyroll_rs::Locale; + +#[derive(Clone)] +pub struct Subtitle { + pub stream_subtitle: StreamSubtitle, + pub audio_locale: Locale, + pub episode_id: String, + pub forced: bool, + pub primary: bool, +} From 7588621f345e5b699985bda9a84cba2226151341 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 7 Jan 2023 16:02:51 +0100 Subject: [PATCH 294/630] Add interactive input to choose season on duplicated season numbers (#55, #82) --- crunchy-cli-core/src/cli/archive.rs | 14 +++- crunchy-cli-core/src/cli/download.rs | 12 ++- crunchy-cli-core/src/cli/utils.rs | 106 ++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 8259ab3..2c952d6 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -1,5 +1,5 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset}; +use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset, find_multiple_seasons_with_same_number, interactive_season_choosing}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; @@ -120,6 +120,10 @@ pub struct Archive { #[arg(long)] no_subtitle_optimizations: bool, + #[arg(help = "Ignore interactive input")] + #[arg(short, long, default_value_t = false)] + yes: bool, + #[arg(help = "Crunchyroll series url(s)")] urls: Vec<String>, } @@ -378,10 +382,16 @@ async fn formats_from_series( }) } + if !archive.yes && !find_multiple_seasons_with_same_number(&seasons).is_empty() { + info!(target: "progress_end", "Fetched seasons"); + seasons = interactive_season_choosing(seasons); + info!(target: "progress", "Fetching series details") + } + #[allow(clippy::type_complexity)] let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); let mut primary_season = true; - for season in series.seasons().await? { + for season in seasons { if !url_filter.is_season_valid(season.metadata.season_number) || !archive .locale diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 0411a5e..8da833a 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,5 +1,5 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset}; +use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset, interactive_season_choosing, find_multiple_seasons_with_same_number}; use crate::utils::context::Context; use crate::utils::format::{format_string, Format}; use crate::utils::log::progress; @@ -71,6 +71,10 @@ pub struct Download { #[arg(value_parser = FFmpegPreset::parse)] ffmpeg_preset: Vec<FFmpegPreset>, + #[arg(help = "Ignore interactive input")] + #[arg(short, long, default_value_t = false)] + yes: bool, + #[arg(help = "Url(s) to Crunchyroll episodes or series")] urls: Vec<String>, } @@ -348,6 +352,12 @@ async fn formats_from_series( }) } + if !download.yes && !find_multiple_seasons_with_same_number(&seasons).is_empty() { + info!(target: "progress_end", "Fetched seasons"); + seasons = interactive_season_choosing(seasons); + info!(target: "progress", "Fetching series details") + } + let mut formats = vec![]; for season in seasons { if let Some(fmts) = formats_from_season(download, season, url_filter).await? { diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 26d939c..fde1d08 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -5,9 +5,11 @@ use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use log::{debug, LevelFilter}; use std::borrow::{Borrow, BorrowMut}; use std::collections::BTreeMap; -use std::io::Write; +use std::io::{BufRead, Write}; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; +use crunchyroll_rs::{Media, Season}; +use regex::Regex; use tokio::task::JoinSet; pub fn find_resolution( @@ -327,3 +329,105 @@ impl FFmpegPreset { )) } } + +pub(crate) fn find_multiple_seasons_with_same_number(seasons: &Vec<Media<Season>>) -> Vec<u32> { + let mut seasons_map: BTreeMap<u32, u32> = BTreeMap::new(); + for season in seasons { + if let Some(s) = seasons_map.get_mut(&season.metadata.season_number) { + *s += 1; + } else { + seasons_map.insert(season.metadata.season_number, 1); + } + } + + seasons_map + .into_iter() + .filter_map(|(k, v)| if v > 1 { Some(k) } else { None }) + .collect() +} + +pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Media<Season>> { + let input_regex = + Regex::new(r"((?P<single>\d+)|(?P<range_from>\d+)-(?P<range_to>\d+)?)(\s|$)").unwrap(); + + let mut seasons_map: BTreeMap<u32, Vec<Media<Season>>> = BTreeMap::new(); + for season in seasons { + if let Some(s) = seasons_map.get_mut(&season.metadata.season_number) { + s.push(season); + } else { + seasons_map.insert(season.metadata.season_number, vec![season]); + } + } + + for (num, season_vec) in seasons_map.iter_mut() { + if season_vec.len() == 1 { + continue; + } + println!(":: Found multiple seasons for season number {}", num); + println!(":: Select the number of the seasons you want to download (eg \"1 2 4\", \"1-3\", \"1-3 5\"):"); + for (i, season) in season_vec.iter().enumerate() { + println!(":: \t{}. {}", i + 1, season.title) + } + let mut stdout = std::io::stdout(); + let _ = write!(stdout, ":: => "); + let _ = stdout.flush(); + let mut user_input = String::new(); + std::io::stdin().lock() + .read_line(&mut user_input) + .expect("cannot open stdin"); + + let mut nums = vec![]; + for capture in input_regex.captures_iter(&user_input) { + if let Some(single) = capture.name("single") { + nums.push(single.as_str().parse().unwrap()); + } else { + let range_from = capture.name("range_from"); + let range_to = capture.name("range_to"); + + // input is '-' which means use all seasons + if range_from.is_none() && range_to.is_none() { + nums = vec![]; + break; + } + let from = range_from + .map(|f| f.as_str().parse::<usize>().unwrap() - 1) + .unwrap_or(usize::MIN); + let to = range_from + .map(|f| f.as_str().parse::<usize>().unwrap() - 1) + .unwrap_or(usize::MAX); + + nums.extend( + season_vec + .iter() + .enumerate() + .filter_map(|(i, _)| { + if i >= from && i <= to { + Some(i) + } else { + None + } + }) + .collect::<Vec<usize>>(), + ) + } + } + nums.dedup(); + + if !nums.is_empty() { + let mut remove_count = 0; + for i in 0..season_vec.len() - 1 { + if !nums.contains(&i) { + season_vec.remove(i - remove_count); + remove_count += 1 + } + } + } + } + + seasons_map + .into_values() + .into_iter() + .flatten() + .collect::<Vec<Media<Season>>>() +} + From b991614dc38e8d36ffb2026a91429e0c686fc855 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 7 Jan 2023 17:14:47 +0100 Subject: [PATCH 295/630] Fix output and download order on duplicated seasons --- crunchy-cli-core/src/utils/sort.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/utils/sort.rs b/crunchy-cli-core/src/utils/sort.rs index 089fe18..21df74e 100644 --- a/crunchy-cli-core/src/utils/sort.rs +++ b/crunchy-cli-core/src/utils/sort.rs @@ -30,8 +30,11 @@ pub fn sort_formats_after_seasons(formats: Vec<Format>) -> Vec<Vec<Format>> { let mut as_map = BTreeMap::new(); for format in formats { - as_map.entry(format.season_number).or_insert_with(Vec::new); - as_map.get_mut(&format.season_number).unwrap().push(format); + // the season title is used as key instead of season number to distinguish duplicated season + // numbers which are actually two different seasons; season id is not used as this somehow + // messes up ordering when duplicated seasons exist + as_map.entry(format.season_title.clone()).or_insert_with(Vec::new); + as_map.get_mut(&format.season_title).unwrap().push(format); } let mut sorted = as_map @@ -41,7 +44,7 @@ pub fn sort_formats_after_seasons(formats: Vec<Format>) -> Vec<Vec<Format>> { values }) .collect::<Vec<Vec<Format>>>(); - sorted.sort_by(|a, b| a[0].series_id.cmp(&b[0].series_id)); + sorted.sort_by(|a, b| a[0].season_number.cmp(&b[0].season_number)); sorted } From b65c0e9dfdefb49add0294e136912729ae308ee4 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 8 Jan 2023 17:44:31 +0100 Subject: [PATCH 296/630] Update dependencies & version --- Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 4 ++-- crunchy-cli-core/Cargo.lock | 30 +++++++++++++++--------------- crunchy-cli-core/Cargo.toml | 4 ++-- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb7c6a3..7177ac1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-trait" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" dependencies = [ "proc-macro2", "quote", @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.6" +version = "3.0.0-dev.7" dependencies = [ "chrono", "clap", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.6" +version = "3.0.0-dev.7" dependencies = [ "anyhow", "async-trait", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" +checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" dependencies = [ "cc", "cxxbridge-flags", @@ -407,9 +407,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" +checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" dependencies = [ "cc", "codespan-reporting", @@ -422,15 +422,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" +checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" [[package]] name = "cxxbridge-macro" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" +checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" dependencies = [ "proc-macro2", "quote", @@ -1669,9 +1669,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.23.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" dependencies = [ "autocfg", "bytes", @@ -1759,9 +1759,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" diff --git a/Cargo.toml b/Cargo.toml index 76ed950..ec8e4ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.6" +version = "3.0.0-dev.7" edition = "2021" [dependencies] -tokio = { version = "1.23", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index b5dd8a1..4b1a723 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -39,9 +39,9 @@ checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-trait" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" dependencies = [ "proc-macro2", "quote", @@ -260,7 +260,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.6" +version = "3.0.0-dev.7" dependencies = [ "anyhow", "async-trait", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" +checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" dependencies = [ "cc", "cxxbridge-flags", @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" +checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" dependencies = [ "cc", "codespan-reporting", @@ -391,15 +391,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" +checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" [[package]] name = "cxxbridge-macro" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" +checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" dependencies = [ "proc-macro2", "quote", @@ -1632,9 +1632,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.23.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" dependencies = [ "autocfg", "bytes", @@ -1722,9 +1722,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 566b8c4..a3ae301 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.6" +version = "3.0.0-dev.7" edition = "2021" [dependencies] @@ -23,7 +23,7 @@ serde_json = "1.0" signal-hook = "0.3" tempfile = "3.3" terminal_size = "0.2" -tokio = { version = "1.23", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.2" [build-dependencies] From 13f54c0da636e7093d347bba5c126f8e895ebfe1 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 8 Jan 2023 18:06:37 +0100 Subject: [PATCH 297/630] Fix interactive season choosing activation on url filter excluded seasons --- crunchy-cli-core/src/cli/archive.rs | 5 ++++- crunchy-cli-core/src/cli/download.rs | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 2c952d6..b9b7de0 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -379,7 +379,10 @@ async fn formats_from_series( .locale .iter() .any(|l| s.metadata.audio_locales.contains(l)) - }) + }); + // remove seasons which match the url filter. this is mostly done to not trigger the + // interactive season choosing when dupilcated seasons are excluded by the filter + seasons.retain(|s| url_filter.is_season_valid(s.metadata.season_number)) } if !archive.yes && !find_multiple_seasons_with_same_number(&seasons).is_empty() { diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 8da833a..25c1262 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -349,7 +349,10 @@ async fn formats_from_series( seasons.retain(|s| { s.metadata.season_number != season.first().unwrap().metadata.season_number || s.metadata.audio_locales.contains(&download.audio) - }) + }); + // remove seasons which match the url filter. this is mostly done to not trigger the + // interactive season choosing when dupilcated seasons are excluded by the filter + seasons.retain(|s| url_filter.is_season_valid(s.metadata.season_number)) } if !download.yes && !find_multiple_seasons_with_same_number(&seasons).is_empty() { @@ -373,14 +376,14 @@ async fn formats_from_season( season: Media<Season>, url_filter: &UrlFilter, ) -> Result<Option<Vec<Format>>> { - if !season.metadata.audio_locales.contains(&download.audio) { + if !url_filter.is_season_valid(season.metadata.season_number) { + return Ok(None); + } else if !season.metadata.audio_locales.contains(&download.audio) { error!( "Season {} ({}) is not available with {} audio", season.metadata.season_number, season.title, download.audio ); return Ok(None); - } else if !url_filter.is_season_valid(season.metadata.season_number) { - return Ok(None); } let mut formats = vec![]; From 4b33ef02c61d1ce8b60900785868a3445f779e90 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 10:27:28 +0100 Subject: [PATCH 298/630] Fix output formatting for full path (#101) --- crunchy-cli-core/src/cli/archive.rs | 20 ++++++-------------- crunchy-cli-core/src/cli/download.rs | 20 ++++++-------------- crunchy-cli-core/src/utils/format.rs | 9 ++++++--- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index b9b7de0..f9a6a04 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -1,7 +1,7 @@ use crate::cli::log::tab_info; use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset, find_multiple_seasons_with_same_number, interactive_season_choosing}; use crate::utils::context::Context; -use crate::utils::format::{format_string, Format}; +use crate::utils::format::{Format, format_path}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; @@ -240,19 +240,11 @@ impl Execute for Archive { for (formats, mut subtitles) in archive_formats { let (primary, additionally) = formats.split_first().unwrap(); - let mut path = PathBuf::from(&self.output); - path = free_file( - path.with_file_name(format_string( - if let Some(fname) = path.file_name() { - fname.to_str().unwrap() - } else { - "{title}.mkv" - } - .to_string(), - primary, - true, - )), - ); + let path = free_file(format_path(if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + }.into(), &primary, true)); info!( "Downloading {} to '{}'", diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 25c1262..a7ed773 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,7 +1,7 @@ use crate::cli::log::tab_info; use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset, interactive_season_choosing, find_multiple_seasons_with_same_number}; use crate::utils::context::Context; -use crate::utils::format::{format_string, Format}; +use crate::utils::format::{Format, format_path}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::{parse_url, UrlFilter}; @@ -206,19 +206,11 @@ impl Execute for Download { } for format in formats { - let mut path = PathBuf::from(&self.output); - path = free_file( - path.with_file_name(format_string( - if let Some(fname) = path.file_name() { - fname.to_str().unwrap() - } else { - "{title}.ts" - } - .to_string(), - &format, - true, - )), - ); + let path = free_file(format_path(if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + }.into(), &format, true)); info!( "Downloading {} to '{}'", diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index c463ea8..ee48e2a 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use crunchyroll_rs::media::VariantData; use crunchyroll_rs::{Episode, Locale, Media, Movie}; use std::time::Duration; @@ -65,7 +66,7 @@ impl Format { /// Formats the given string if it has specific pattern in it. It's possible to sanitize it which /// removes characters which can cause failures if the output string is used as a file name. -pub fn format_string(s: String, format: &Format, sanitize: bool) -> String { +pub fn format_path(path: PathBuf, format: &Format, sanitize: bool) -> PathBuf { let sanitize_func = if sanitize { |s: &str| sanitize_filename::sanitize(s) } else { @@ -73,7 +74,9 @@ pub fn format_string(s: String, format: &Format, sanitize: bool) -> String { |s: &str| s.to_string() }; - s.replace("{title}", &sanitize_func(&format.title)) + let as_string = path.to_string_lossy().to_string(); + + PathBuf::from(as_string.replace("{title}", &sanitize_func(&format.title)) .replace("{series_name}", &sanitize_func(&format.series_name)) .replace("{season_name}", &sanitize_func(&format.season_title)) .replace("{audio}", &sanitize_func(&format.audio.to_string())) @@ -99,5 +102,5 @@ pub fn format_string(s: String, format: &Format, sanitize: bool) -> String { ) .replace("{series_id}", &sanitize_func(&format.series_id)) .replace("{season_id}", &sanitize_func(&format.season_id)) - .replace("{episode_id}", &sanitize_func(&format.id)) + .replace("{episode_id}", &sanitize_func(&format.id))) } From 12be16417ffdd0bda87b9c06aaf7f50d5454b178 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 16:55:10 +0100 Subject: [PATCH 299/630] Fix interactive season choosing false-positive triggering --- Cargo.lock | 85 +++++++--------------------- crunchy-cli-core/Cargo.lock | 85 +++++++--------------------- crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/cli/archive.rs | 31 +++++----- crunchy-cli-core/src/cli/download.rs | 22 ++++--- crunchy-cli-core/src/cli/utils.rs | 67 ++++++++++++++++------ crunchy-cli-core/src/utils/format.rs | 59 ++++++++++--------- crunchy-cli-core/src/utils/sort.rs | 4 +- 8 files changed, 157 insertions(+), 197 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7177ac1..77e1342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,7 +222,7 @@ dependencies = [ "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -302,6 +302,7 @@ dependencies = [ "ctrlc", "dirs", "indicatif", + "lazy_static", "log", "num_cpus", "regex", @@ -390,7 +391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ "nix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -859,14 +860,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "ipnet" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" @@ -877,7 +878,7 @@ dependencies = [ "hermit-abi", "io-lifetimes", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -980,7 +981,7 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1231,9 +1232,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1338,7 +1339,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1380,12 +1381,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -1591,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1682,7 +1682,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1973,19 +1973,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -1993,12 +1980,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] @@ -2007,48 +1994,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -2061,12 +2024,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 4b1a723..5bdabf0 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -203,7 +203,7 @@ dependencies = [ "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -271,6 +271,7 @@ dependencies = [ "ctrlc", "dirs", "indicatif", + "lazy_static", "log", "num_cpus", "regex", @@ -359,7 +360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ "nix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -828,14 +829,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "ipnet" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" @@ -846,7 +847,7 @@ dependencies = [ "hermit-abi", "io-lifetimes", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -949,7 +950,7 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1200,9 +1201,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1301,7 +1302,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1343,12 +1344,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -1554,7 +1554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1645,7 +1645,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1936,19 +1936,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -1956,12 +1943,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] @@ -1970,48 +1957,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -2024,12 +1987,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index a3ae301..5bda1db 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -14,6 +14,7 @@ csv = "1.1" ctrlc = "3.2" dirs = "4.0" indicatif = "0.17" +lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.15" regex = "1.7" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index f9a6a04..7a96c05 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -1,7 +1,10 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset, find_multiple_seasons_with_same_number, interactive_season_choosing}; +use crate::cli::utils::{ + download_segments, find_multiple_seasons_with_same_number, find_resolution, + interactive_season_choosing, FFmpegPreset, +}; use crate::utils::context::Context; -use crate::utils::format::{Format, format_path}; +use crate::utils::format::{format_path, Format}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; @@ -240,11 +243,16 @@ impl Execute for Archive { for (formats, mut subtitles) in archive_formats { let (primary, additionally) = formats.split_first().unwrap(); - let path = free_file(format_path(if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - }.into(), &primary, true)); + let path = free_file(format_path( + if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + } + .into(), + &primary, + true, + )); info!( "Downloading {} to '{}'", @@ -387,15 +395,6 @@ async fn formats_from_series( let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); let mut primary_season = true; for season in seasons { - if !url_filter.is_season_valid(season.metadata.season_number) - || !archive - .locale - .iter() - .any(|l| season.metadata.audio_locales.contains(l)) - { - continue; - } - for episode in season.episodes().await? { if !url_filter.is_episode_valid( episode.metadata.episode_number, diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index a7ed773..bdca291 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,7 +1,10 @@ use crate::cli::log::tab_info; -use crate::cli::utils::{download_segments, find_resolution, FFmpegPreset, interactive_season_choosing, find_multiple_seasons_with_same_number}; +use crate::cli::utils::{ + download_segments, find_multiple_seasons_with_same_number, find_resolution, + interactive_season_choosing, FFmpegPreset, +}; use crate::utils::context::Context; -use crate::utils::format::{Format, format_path}; +use crate::utils::format::{format_path, Format}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::{parse_url, UrlFilter}; @@ -206,11 +209,16 @@ impl Execute for Download { } for format in formats { - let path = free_file(format_path(if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - }.into(), &format, true)); + let path = free_file(format_path( + if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + } + .into(), + &format, + true, + )); info!( "Downloading {} to '{}'", diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index fde1d08..bc19b07 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -1,15 +1,16 @@ use crate::utils::context::Context; use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; +use crunchyroll_rs::{Media, Season}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; +use lazy_static::lazy_static; use log::{debug, LevelFilter}; +use regex::Regex; use std::borrow::{Borrow, BorrowMut}; use std::collections::BTreeMap; use std::io::{BufRead, Write}; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; -use crunchyroll_rs::{Media, Season}; -use regex::Regex; use tokio::task::JoinSet; pub fn find_resolution( @@ -111,7 +112,7 @@ pub async fn download_segments( }; buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); - + let mut c = thread_count.lock().unwrap(); debug!( "Downloaded and decrypted segment [{}/{} {:.2}%] {}", @@ -120,14 +121,14 @@ pub async fn download_segments( ((*c + 1) as f64 / total_segments as f64) * 100f64, segment.url ); - + thread_sender.send((num as i32 + (i * cpus) as i32, buf))?; - + *c += 1; } Ok(()) }; - + let result = download().await; if result.is_err() { @@ -149,7 +150,7 @@ pub async fn download_segments( for (pos, bytes) in receiver.iter() { // if the position is lower than 0, an error occured in the sending download thread if pos < 0 { - break + break; } if let Some(p) = &progress { @@ -330,6 +331,10 @@ impl FFmpegPreset { } } +lazy_static! { + static ref DUPLICATED_SEASONS_MULTILANG_REGEX: Regex = Regex::new(r"(-castilian|-english|-english-in|-french|-german|-hindi|-italian|-portuguese|-russian|-spanish)$").unwrap(); +} + pub(crate) fn find_multiple_seasons_with_same_number(seasons: &Vec<Media<Season>>) -> Vec<u32> { let mut seasons_map: BTreeMap<u32, u32> = BTreeMap::new(); for season in seasons { @@ -342,7 +347,25 @@ pub(crate) fn find_multiple_seasons_with_same_number(seasons: &Vec<Media<Season> seasons_map .into_iter() - .filter_map(|(k, v)| if v > 1 { Some(k) } else { None }) + .filter_map(|(k, v)| { + if v > 1 { + // check if the different seasons are actual the same but with different dub languages + let mut multilang_season_vec: Vec<String> = seasons + .iter() + .map(|s| { + DUPLICATED_SEASONS_MULTILANG_REGEX + .replace(s.slug_title.trim_end_matches("-dub"), "") + .to_string() + }) + .collect(); + multilang_season_vec.dedup(); + + if multilang_season_vec.len() > 1 { + return Some(k); + } + } + None + }) .collect() } @@ -363,6 +386,22 @@ pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Me if season_vec.len() == 1 { continue; } + + // check if the different seasons are actual the same but with different dub languages + let mut multilang_season_vec: Vec<String> = season_vec + .iter() + .map(|s| { + DUPLICATED_SEASONS_MULTILANG_REGEX + .replace(s.slug_title.trim_end_matches("-dub"), "") + .to_string() + }) + .collect(); + multilang_season_vec.dedup(); + + if multilang_season_vec.len() == 1 { + continue; + } + println!(":: Found multiple seasons for season number {}", num); println!(":: Select the number of the seasons you want to download (eg \"1 2 4\", \"1-3\", \"1-3 5\"):"); for (i, season) in season_vec.iter().enumerate() { @@ -372,7 +411,8 @@ pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Me let _ = write!(stdout, ":: => "); let _ = stdout.flush(); let mut user_input = String::new(); - std::io::stdin().lock() + std::io::stdin() + .lock() .read_line(&mut user_input) .expect("cannot open stdin"); @@ -400,13 +440,7 @@ pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Me season_vec .iter() .enumerate() - .filter_map(|(i, _)| { - if i >= from && i <= to { - Some(i) - } else { - None - } - }) + .filter_map(|(i, _)| if i >= from && i <= to { Some(i) } else { None }) .collect::<Vec<usize>>(), ) } @@ -430,4 +464,3 @@ pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Me .flatten() .collect::<Vec<Media<Season>>>() } - diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index ee48e2a..035a61c 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,6 +1,6 @@ -use std::path::PathBuf; use crunchyroll_rs::media::VariantData; use crunchyroll_rs::{Episode, Locale, Media, Movie}; +use std::path::PathBuf; use std::time::Duration; #[derive(Clone)] @@ -76,31 +76,34 @@ pub fn format_path(path: PathBuf, format: &Format, sanitize: bool) -> PathBuf { let as_string = path.to_string_lossy().to_string(); - PathBuf::from(as_string.replace("{title}", &sanitize_func(&format.title)) - .replace("{series_name}", &sanitize_func(&format.series_name)) - .replace("{season_name}", &sanitize_func(&format.season_title)) - .replace("{audio}", &sanitize_func(&format.audio.to_string())) - .replace( - "{resolution}", - &sanitize_func(&format.stream.resolution.to_string()), - ) - .replace( - "{padded_season_number}", - &sanitize_func(&format!("{:0>2}", format.season_number.to_string())), - ) - .replace( - "{season_number}", - &sanitize_func(&format.season_number.to_string()), - ) - .replace( - "{padded_episode_number}", - &sanitize_func(&format!("{:0>2}", format.number.to_string())), - ) - .replace( - "{episode_number}", - &sanitize_func(&format.number.to_string()), - ) - .replace("{series_id}", &sanitize_func(&format.series_id)) - .replace("{season_id}", &sanitize_func(&format.season_id)) - .replace("{episode_id}", &sanitize_func(&format.id))) + PathBuf::from( + as_string + .replace("{title}", &sanitize_func(&format.title)) + .replace("{series_name}", &sanitize_func(&format.series_name)) + .replace("{season_name}", &sanitize_func(&format.season_title)) + .replace("{audio}", &sanitize_func(&format.audio.to_string())) + .replace( + "{resolution}", + &sanitize_func(&format.stream.resolution.to_string()), + ) + .replace( + "{padded_season_number}", + &sanitize_func(&format!("{:0>2}", format.season_number.to_string())), + ) + .replace( + "{season_number}", + &sanitize_func(&format.season_number.to_string()), + ) + .replace( + "{padded_episode_number}", + &sanitize_func(&format!("{:0>2}", format.number.to_string())), + ) + .replace( + "{episode_number}", + &sanitize_func(&format.number.to_string()), + ) + .replace("{series_id}", &sanitize_func(&format.series_id)) + .replace("{season_id}", &sanitize_func(&format.season_id)) + .replace("{episode_id}", &sanitize_func(&format.id)), + ) } diff --git a/crunchy-cli-core/src/utils/sort.rs b/crunchy-cli-core/src/utils/sort.rs index 21df74e..9f8d81c 100644 --- a/crunchy-cli-core/src/utils/sort.rs +++ b/crunchy-cli-core/src/utils/sort.rs @@ -33,7 +33,9 @@ pub fn sort_formats_after_seasons(formats: Vec<Format>) -> Vec<Vec<Format>> { // the season title is used as key instead of season number to distinguish duplicated season // numbers which are actually two different seasons; season id is not used as this somehow // messes up ordering when duplicated seasons exist - as_map.entry(format.season_title.clone()).or_insert_with(Vec::new); + as_map + .entry(format.season_title.clone()) + .or_insert_with(Vec::new); as_map.get_mut(&format.season_title).unwrap().push(format); } From 29845ba6e53ce877d85aca47db840c52835fa61b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 17:26:04 +0100 Subject: [PATCH 300/630] Re-order instructions --- crunchy-cli-core/src/cli/archive.rs | 8 +++--- crunchy-cli-core/src/cli/download.rs | 10 +++++-- crunchy-cli-core/src/utils/format.rs | 43 +++++++++++++++------------- crunchy-cli-core/src/utils/sort.rs | 2 +- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 7a96c05..1de0997 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -208,7 +208,7 @@ impl Execute for Archive { format.stream.resolution, format.stream.fps, format.season_number, - format.number, + format.episode_number, ) } } @@ -234,7 +234,7 @@ impl Execute for Archive { format.stream.resolution, format.stream.fps, format.season_number, - format.number + format.episode_number ) } } @@ -266,7 +266,7 @@ impl Execute for Archive { tab_info!( "Episode: S{:02}E{:02}", primary.season_number, - primary.number + primary.episode_number ); tab_info!( "Audio: {} (primary), {}", @@ -318,7 +318,7 @@ impl Execute for Archive { // Remove subtitles of deleted video if only_audio { - subtitles.retain(|s| s.episode_id != additional.id); + subtitles.retain(|s| s.episode_id != additional.episode_id); } } diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index bdca291..14f9503 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -182,7 +182,7 @@ impl Execute for Download { format.stream.resolution, format.stream.fps, format.season_number, - format.number, + format.episode_number, ) } } @@ -202,7 +202,7 @@ impl Execute for Download { format.stream.resolution, format.stream.fps, format.season_number, - format.number + format.episode_number ) } } @@ -229,7 +229,11 @@ impl Execute for Download { path.file_name().unwrap().to_str().unwrap() } ); - tab_info!("Episode: S{:02}E{:02}", format.season_number, format.number); + tab_info!( + "Episode: S{:02}E{:02}", + format.season_number, + format.episode_number + ); tab_info!("Audio: {}", format.audio); tab_info!( "Subtitles: {}", diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 035a61c..5570960 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -5,10 +5,9 @@ use std::time::Duration; #[derive(Clone)] pub struct Format { - pub id: String, pub title: String, pub description: String, - pub number: u32, + pub audio: Locale, pub duration: Duration, @@ -20,15 +19,17 @@ pub struct Format { pub season_id: String, pub season_title: String, pub season_number: u32, + + pub episode_id: String, + pub episode_number: f32, } impl Format { pub fn new_from_episode(episode: Media<Episode>, stream: VariantData) -> Self { Self { - id: episode.id, title: episode.title, description: episode.description, - number: episode.metadata.episode_number, + audio: episode.metadata.audio_locale, duration: episode.metadata.duration.to_std().unwrap(), @@ -40,15 +41,17 @@ impl Format { season_id: episode.metadata.season_id, season_title: episode.metadata.season_title, season_number: episode.metadata.season_number, + + episode_id: episode.id, + episode_number: episode.metadata.episode.parse().unwrap_or(episode.metadata.sequence_number), } } pub fn new_from_movie(movie: Media<Movie>, stream: VariantData) -> Self { Self { - id: movie.id, title: movie.title, description: movie.description, - number: 1, + audio: Locale::ja_JP, duration: movie.metadata.duration.to_std().unwrap(), @@ -60,6 +63,9 @@ impl Format { season_id: movie.metadata.movie_listing_id, season_title: movie.metadata.movie_listing_title, season_number: 1, + + episode_id: movie.id, + episode_number: 1.0, } } } @@ -79,31 +85,28 @@ pub fn format_path(path: PathBuf, format: &Format, sanitize: bool) -> PathBuf { PathBuf::from( as_string .replace("{title}", &sanitize_func(&format.title)) - .replace("{series_name}", &sanitize_func(&format.series_name)) - .replace("{season_name}", &sanitize_func(&format.season_title)) .replace("{audio}", &sanitize_func(&format.audio.to_string())) .replace( "{resolution}", &sanitize_func(&format.stream.resolution.to_string()), ) - .replace( - "{padded_season_number}", - &sanitize_func(&format!("{:0>2}", format.season_number.to_string())), - ) + .replace("{series_id}", &sanitize_func(&format.series_id)) + .replace("{series_name}", &sanitize_func(&format.series_name)) + .replace("{season_id}", &sanitize_func(&format.season_id)) + .replace("{season_name}", &sanitize_func(&format.season_title)) .replace( "{season_number}", &sanitize_func(&format.season_number.to_string()), ) .replace( - "{padded_episode_number}", - &sanitize_func(&format!("{:0>2}", format.number.to_string())), + "{padded_season_number}", + &sanitize_func(&format!("{:0>2}", format.season_number.to_string())), ) + .replace("{episode_id}", &sanitize_func(&format.episode_id)) + .replace("{episode_number}", &sanitize_func(&format.episode_number.to_string())) .replace( - "{episode_number}", - &sanitize_func(&format.number.to_string()), - ) - .replace("{series_id}", &sanitize_func(&format.series_id)) - .replace("{season_id}", &sanitize_func(&format.season_id)) - .replace("{episode_id}", &sanitize_func(&format.id)), + "{padded_episode_number}", + &sanitize_func(&format!("{:0>2}", format.episode_number.to_string())), + ), ) } diff --git a/crunchy-cli-core/src/utils/sort.rs b/crunchy-cli-core/src/utils/sort.rs index 9f8d81c..1af0194 100644 --- a/crunchy-cli-core/src/utils/sort.rs +++ b/crunchy-cli-core/src/utils/sort.rs @@ -42,7 +42,7 @@ pub fn sort_formats_after_seasons(formats: Vec<Format>) -> Vec<Vec<Format>> { let mut sorted = as_map .into_iter() .map(|(_, mut values)| { - values.sort_by(|a, b| a.number.cmp(&b.number)); + values.sort_by(|a, b| a.episode_number.total_cmp(&b.episode_number)); values }) .collect::<Vec<Vec<Format>>>(); From 7d3a90e8112a4ce27158b217a564ded45c08c576 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 19:12:00 +0100 Subject: [PATCH 301/630] Add relative episode number to format --- crunchy-cli-core/src/cli/archive.rs | 29 +++--- crunchy-cli-core/src/cli/download.rs | 54 +++++++--- crunchy-cli-core/src/utils/format.rs | 150 ++++++++++++++++----------- 3 files changed, 145 insertions(+), 88 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 1de0997..96d0737 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -4,7 +4,7 @@ use crate::cli::utils::{ interactive_season_choosing, FFmpegPreset, }; use crate::utils::context::Context; -use crate::utils::format::{format_path, Format}; +use crate::utils::format::Format; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; @@ -243,16 +243,17 @@ impl Execute for Archive { for (formats, mut subtitles) in archive_formats { let (primary, additionally) = formats.split_first().unwrap(); - let path = free_file(format_path( - if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - } - .into(), - &primary, - true, - )); + let path = free_file( + primary.format_path( + if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + } + .into(), + true, + ), + ); info!( "Downloading {} to '{}'", @@ -395,7 +396,9 @@ async fn formats_from_series( let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); let mut primary_season = true; for season in seasons { - for episode in season.episodes().await? { + let episodes = season.episodes().await?; + + for episode in episodes.iter() { if !url_filter.is_episode_valid( episode.metadata.episode_number, episode.metadata.season_number, @@ -434,7 +437,7 @@ async fn formats_from_series( }; Some(subtitle) })); - formats.push(Format::new_from_episode(episode, stream)); + formats.push(Format::new_from_episode(episode, &episodes, stream)); } primary_season = false; diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 14f9503..d027df7 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -4,7 +4,7 @@ use crate::cli::utils::{ interactive_season_choosing, FFmpegPreset, }; use crate::utils::context::Context; -use crate::utils::format::{format_path, Format}; +use crate::utils::format::Format; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::{parse_url, UrlFilter}; @@ -16,6 +16,7 @@ use crunchyroll_rs::{ Episode, Locale, Media, MediaCollection, Movie, MovieListing, Season, Series, }; use log::{debug, error, info, warn}; +use std::borrow::Cow; use std::fs::File; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -148,7 +149,7 @@ impl Execute for Download { episode.metadata.season_title, episode.metadata.series_title ); - format_from_episode(&self, episode, &url_filter, false) + format_from_episode(&self, &episode, &url_filter, None, false) .await? .map(|fmt| vec![fmt]) } @@ -209,16 +210,17 @@ impl Execute for Download { } for format in formats { - let path = free_file(format_path( - if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - } - .into(), - &format, - true, - )); + let path = free_file( + format.format_path( + if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + } + .into(), + true, + ), + ); info!( "Downloading {} to '{}'", @@ -392,8 +394,11 @@ async fn formats_from_season( let mut formats = vec![]; - for episode in season.episodes().await? { - if let Some(fmt) = format_from_episode(download, episode, url_filter, true).await? { + let episodes = season.episodes().await?; + for episode in episodes.iter() { + if let Some(fmt) = + format_from_episode(download, &episode, url_filter, Some(&episodes), true).await? + { formats.push(fmt) } } @@ -403,8 +408,9 @@ async fn formats_from_season( async fn format_from_episode( download: &Download, - episode: Media<Episode>, + episode: &Media<Episode>, url_filter: &UrlFilter, + season_episodes: Option<&Vec<Media<Episode>>>, filter_audio: bool, ) -> Result<Option<Format>> { if filter_audio && episode.metadata.audio_locale != download.audio { @@ -457,7 +463,21 @@ async fn format_from_episode( ) }; - Ok(Some(Format::new_from_episode(episode, stream))) + let season_eps = if Format::has_relative_episodes_fmt(&download.output) { + if let Some(eps) = season_episodes { + Cow::from(eps) + } else { + Cow::from(episode.season().await?.episodes().await?) + } + } else { + Cow::from(vec![]) + }; + + Ok(Some(Format::new_from_episode( + episode, + &season_eps.to_vec(), + stream, + ))) } async fn format_from_movie_listing( @@ -515,7 +535,7 @@ async fn format_from_movie( } }; - Ok(Some(Format::new_from_movie(movie, stream))) + Ok(Some(Format::new_from_movie(&movie, stream))) } fn some_vec_or_none<T>(v: Vec<T>) -> Option<Vec<T>> { diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 5570960..3db28c3 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,5 +1,6 @@ use crunchyroll_rs::media::VariantData; use crunchyroll_rs::{Episode, Locale, Media, Movie}; +use log::warn; use std::path::PathBuf; use std::time::Duration; @@ -22,35 +23,56 @@ pub struct Format { pub episode_id: String, pub episode_number: f32, + pub relative_episode_number: f32, } impl Format { - pub fn new_from_episode(episode: Media<Episode>, stream: VariantData) -> Self { + pub fn new_from_episode( + episode: &Media<Episode>, + season_episodes: &Vec<Media<Episode>>, + stream: VariantData, + ) -> Self { Self { - title: episode.title, - description: episode.description, + title: episode.title.clone(), + description: episode.description.clone(), - audio: episode.metadata.audio_locale, + audio: episode.metadata.audio_locale.clone(), duration: episode.metadata.duration.to_std().unwrap(), stream, - series_id: episode.metadata.series_id, - series_name: episode.metadata.series_title, + series_id: episode.metadata.series_id.clone(), + series_name: episode.metadata.series_title.clone(), - season_id: episode.metadata.season_id, - season_title: episode.metadata.season_title, - season_number: episode.metadata.season_number, + season_id: episode.metadata.season_id.clone(), + season_title: episode.metadata.season_title.clone(), + season_number: episode.metadata.season_number.clone(), - episode_id: episode.id, - episode_number: episode.metadata.episode.parse().unwrap_or(episode.metadata.sequence_number), + episode_id: episode.id.clone(), + episode_number: episode + .metadata + .episode + .parse() + .unwrap_or(episode.metadata.sequence_number), + relative_episode_number: season_episodes + .iter() + .enumerate() + .find_map(|(i, e)| if e == episode { Some((i + 1) as f32) } else { None }) + .unwrap_or_else(|| { + warn!("Cannot find relative episode number for episode {} ({}) of season {} ({}) of {}, using normal episode number", episode.metadata.episode_number, episode.title, episode.metadata.season_number, episode.metadata.season_title, episode.metadata.series_title); + episode + .metadata + .episode + .parse() + .unwrap_or(episode.metadata.sequence_number) + }), } } - pub fn new_from_movie(movie: Media<Movie>, stream: VariantData) -> Self { + pub fn new_from_movie(movie: &Media<Movie>, stream: VariantData) -> Self { Self { - title: movie.title, - description: movie.description, + title: movie.title.clone(), + description: movie.description.clone(), audio: Locale::ja_JP, @@ -60,53 +82,65 @@ impl Format { series_id: movie.metadata.movie_listing_id.clone(), series_name: movie.metadata.movie_listing_title.clone(), - season_id: movie.metadata.movie_listing_id, - season_title: movie.metadata.movie_listing_title, + season_id: movie.metadata.movie_listing_id.clone(), + season_title: movie.metadata.movie_listing_title.clone(), season_number: 1, - episode_id: movie.id, + episode_id: movie.id.clone(), episode_number: 1.0, + relative_episode_number: 1.0, } } -} - -/// Formats the given string if it has specific pattern in it. It's possible to sanitize it which -/// removes characters which can cause failures if the output string is used as a file name. -pub fn format_path(path: PathBuf, format: &Format, sanitize: bool) -> PathBuf { - let sanitize_func = if sanitize { - |s: &str| sanitize_filename::sanitize(s) - } else { - // converting this to a string is actually unnecessary - |s: &str| s.to_string() - }; - - let as_string = path.to_string_lossy().to_string(); - - PathBuf::from( - as_string - .replace("{title}", &sanitize_func(&format.title)) - .replace("{audio}", &sanitize_func(&format.audio.to_string())) - .replace( - "{resolution}", - &sanitize_func(&format.stream.resolution.to_string()), - ) - .replace("{series_id}", &sanitize_func(&format.series_id)) - .replace("{series_name}", &sanitize_func(&format.series_name)) - .replace("{season_id}", &sanitize_func(&format.season_id)) - .replace("{season_name}", &sanitize_func(&format.season_title)) - .replace( - "{season_number}", - &sanitize_func(&format.season_number.to_string()), - ) - .replace( - "{padded_season_number}", - &sanitize_func(&format!("{:0>2}", format.season_number.to_string())), - ) - .replace("{episode_id}", &sanitize_func(&format.episode_id)) - .replace("{episode_number}", &sanitize_func(&format.episode_number.to_string())) - .replace( - "{padded_episode_number}", - &sanitize_func(&format!("{:0>2}", format.episode_number.to_string())), - ), - ) + + /// Formats the given string if it has specific pattern in it. It's possible to sanitize it which + /// removes characters which can cause failures if the output string is used as a file name. + pub fn format_path(&self, path: PathBuf, sanitize: bool) -> PathBuf { + let sanitize_func = if sanitize { + |s: &str| sanitize_filename::sanitize(s) + } else { + // converting this to a string is actually unnecessary + |s: &str| s.to_string() + }; + + let as_string = path.to_string_lossy().to_string(); + + PathBuf::from( + as_string + .replace("{title}", &sanitize_func(&self.title)) + .replace("{audio}", &sanitize_func(&self.audio.to_string())) + .replace( + "{resolution}", + &sanitize_func(&self.stream.resolution.to_string()), + ) + .replace("{series_id}", &sanitize_func(&self.series_id)) + .replace("{series_name}", &sanitize_func(&self.series_name)) + .replace("{season_id}", &sanitize_func(&self.season_id)) + .replace("{season_name}", &sanitize_func(&self.season_title)) + .replace( + "{season_number}", + &sanitize_func(&self.season_number.to_string()), + ) + .replace( + "{padded_season_number}", + &sanitize_func(&format!("{:0>2}", self.season_number.to_string())), + ) + .replace("{episode_id}", &sanitize_func(&self.episode_id)) + .replace( + "{episode_number}", + &sanitize_func(&self.episode_number.to_string()), + ) + .replace( + "{padded_episode_number}", + &sanitize_func(&format!("{:0>2}", self.episode_number.to_string())), + ) + .replace( + "{relative_episode_number}", + &sanitize_func(&format!("{:0>2}", self.relative_episode_number.to_string())), + ), + ) + } + + pub fn has_relative_episodes_fmt<S: AsRef<str>>(s: S) -> bool { + return s.as_ref().contains("{relative_episode_number}"); + } } From 2ea036d4c6b7faa5bdc33da1eb9a630126ef1e05 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 19:12:31 +0100 Subject: [PATCH 302/630] Remove padded_*_number and make it default for *_number for output format --- crunchy-cli-core/src/utils/format.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 3db28c3..60596ad 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -118,19 +118,11 @@ impl Format { .replace("{season_name}", &sanitize_func(&self.season_title)) .replace( "{season_number}", - &sanitize_func(&self.season_number.to_string()), - ) - .replace( - "{padded_season_number}", &sanitize_func(&format!("{:0>2}", self.season_number.to_string())), ) .replace("{episode_id}", &sanitize_func(&self.episode_id)) .replace( "{episode_number}", - &sanitize_func(&self.episode_number.to_string()), - ) - .replace( - "{padded_episode_number}", &sanitize_func(&format!("{:0>2}", self.episode_number.to_string())), ) .replace( From a0aab3bfb964195128abb25ed5c0cfa0f4841707 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 23:25:16 +0100 Subject: [PATCH 303/630] Add arabic locale in duplicated seasons check --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index bc19b07..da56f5e 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -332,7 +332,7 @@ impl FFmpegPreset { } lazy_static! { - static ref DUPLICATED_SEASONS_MULTILANG_REGEX: Regex = Regex::new(r"(-castilian|-english|-english-in|-french|-german|-hindi|-italian|-portuguese|-russian|-spanish)$").unwrap(); + static ref DUPLICATED_SEASONS_MULTILANG_REGEX: Regex = Regex::new(r"(-arabic|-castilian|-english|-english-in|-french|-german|-hindi|-italian|-portuguese|-russian|-spanish)$").unwrap(); } pub(crate) fn find_multiple_seasons_with_same_number(seasons: &Vec<Media<Season>>) -> Vec<u32> { From 3029325776de09ef159081df847ae67cea3be93d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 9 Jan 2023 23:40:53 +0100 Subject: [PATCH 304/630] Add check if request locale is valid (#102) --- crunchy-cli-core/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 52a4da1..6cef42e 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -6,7 +6,7 @@ use anyhow::bail; use anyhow::Result; use clap::{Parser, Subcommand}; use crunchyroll_rs::{Crunchyroll, Locale}; -use log::{debug, error, LevelFilter}; +use log::{debug, error, warn, LevelFilter}; use std::{env, fs}; mod cli; @@ -189,8 +189,41 @@ async fn create_ctx(cli: &Cli) -> Result<Context> { } async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { + let supported_langs = vec![ + Locale::ar_ME, + Locale::de_DE, + Locale::en_US, + Locale::es_ES, + Locale::es_419, + Locale::fr_FR, + Locale::it_IT, + Locale::pt_BR, + Locale::pt_PT, + Locale::ru_RU, + ]; + let locale = if let Some(lang) = &cli.lang { + if !supported_langs.contains(lang) { + bail!( + "Via `--lang` specified language is not supported. Supported languages: {}", + supported_langs + .iter() + .map(|l| format!("`{}` ({})", l.to_string(), l.to_human_readable())) + .collect::<Vec<String>>() + .join(", ") + ) + } + lang.clone() + } else { + let mut lang = system_locale(); + if !supported_langs.contains(&lang) { + warn!("Recognized system locale is not supported. Using en-US as default. Use `--lang` to overwrite the used language"); + lang = Locale::en_US + } + lang + }; + let builder = Crunchyroll::builder() - .locale(cli.lang.clone().unwrap_or_else(system_locale)) + .locale(locale) .stabilization_locales(true); let login_methods_count = cli.login_method.credentials.is_some() as u8 From 5ce5b249c91fbfa752e4779bb0a9776c740ec88b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 10 Jan 2023 19:20:08 +0100 Subject: [PATCH 305/630] Add relative episode number to cli help --- crunchy-cli-core/src/cli/archive.rs | 3 +-- crunchy-cli-core/src/cli/download.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 96d0737..768c629 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -67,10 +67,9 @@ pub struct Archive { {season_name} โ†’ Name of the season\n \ {audio} โ†’ Audio language of the video\n \ {resolution} โ†’ Resolution of the video\n \ - {padded_season_number} โ†’ Number of the season padded to double digits\n \ {season_number} โ†’ Number of the season\n \ - {padded_episode_number} โ†’ Number of the episode padded to double digits\n \ {episode_number} โ†’ Number of the episode\n \ + {relative_episode_number} โ†’ Number of the episode relative to its season\ {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index d027df7..7a49a4c 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -45,10 +45,9 @@ pub struct Download { {season_name} โ†’ Name of the season\n \ {audio} โ†’ Audio language of the video\n \ {resolution} โ†’ Resolution of the video\n \ - {padded_season_number} โ†’ Number of the season padded to double digits\n \ {season_number} โ†’ Number of the season\n \ - {padded_episode_number} โ†’ Number of the episode padded to double digits\n \ {episode_number} โ†’ Number of the episode\n \ + {relative_episode_number} โ†’ Number of the episode relative to its season\ {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] From 17233f2fd2721dc4f9e1b86957cb404d09f73249 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 10 Jan 2023 22:15:36 +0100 Subject: [PATCH 306/630] Update dependencies and version --- Cargo.lock | 26 ++++++++++++++++---------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 24 +++++++++++++++--------- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77e1342..42fe778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -279,7 +285,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.7" +version = "3.0.0-dev.8" dependencies = [ "chrono", "clap", @@ -291,7 +297,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.7" +version = "3.0.0-dev.8" dependencies = [ "anyhow", "async-trait", @@ -318,9 +324,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184d0c725a09aec815316cbf41a2f362008ecb0e8c8e3b6b9930d01a89b5df21" +checksum = "f3770cda4c67e68c689c8e361af46bb9d017caf82263905358fd0751d10657a0" dependencies = [ "aes", "async-trait", @@ -343,9 +349,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3c82e1766339727fc2c10d66d0c4f001b1cf42e2993f9d93997b610f408776" +checksum = "4a260a73e733bb0ce30343caaed5e968d3c1cc2ea0ab27c601481e9ef22a2fd7" dependencies = [ "darling", "quote", @@ -1268,7 +1274,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ - "base64", + "base64 0.13.1", "bytes", "cookie", "cookie_store", @@ -1356,11 +1362,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ec8e4ef..db1f129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.7" +version = "3.0.0-dev.8" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 5bdabf0..b12b3a3 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -60,6 +60,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -260,7 +266,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.7" +version = "3.0.0-dev.8" dependencies = [ "anyhow", "async-trait", @@ -287,9 +293,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184d0c725a09aec815316cbf41a2f362008ecb0e8c8e3b6b9930d01a89b5df21" +checksum = "f3770cda4c67e68c689c8e361af46bb9d017caf82263905358fd0751d10657a0" dependencies = [ "aes", "async-trait", @@ -312,9 +318,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3c82e1766339727fc2c10d66d0c4f001b1cf42e2993f9d93997b610f408776" +checksum = "4a260a73e733bb0ce30343caaed5e968d3c1cc2ea0ab27c601481e9ef22a2fd7" dependencies = [ "darling", "quote", @@ -1237,7 +1243,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ - "base64", + "base64 0.13.1", "bytes", "cookie", "cookie_store", @@ -1319,11 +1325,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5bda1db..8eea6a8 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.7" +version = "3.0.0-dev.8" edition = "2021" [dependencies] From 6d1f8d49f67aabbc9b1cf577fce38c3209993371 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 13 Jan 2023 15:21:23 +0100 Subject: [PATCH 307/630] Add hardsubs manually to download videos (#81) --- crunchy-cli-core/src/cli/archive.rs | 151 +------------------------ crunchy-cli-core/src/cli/download.rs | 134 +++++++++++++--------- crunchy-cli-core/src/cli/utils.rs | 20 ++-- crunchy-cli-core/src/utils/format.rs | 6 +- crunchy-cli-core/src/utils/mod.rs | 1 + crunchy-cli-core/src/utils/subtitle.rs | 108 ++++++++++++++++++ crunchy-cli-core/src/utils/video.rs | 25 ++++ 7 files changed, 233 insertions(+), 212 deletions(-) create mode 100644 crunchy-cli-core/src/utils/video.rs diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 768c629..c7030b4 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -9,16 +9,14 @@ use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; -use crate::utils::subtitle::Subtitle; +use crate::utils::subtitle::{download_subtitle, Subtitle}; +use crate::utils::video::get_video_length; use crate::Execute; use anyhow::{bail, Result}; -use chrono::NaiveTime; -use crunchyroll_rs::media::{Resolution, StreamSubtitle}; +use crunchyroll_rs::media::Resolution; use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; use log::{debug, error, info, warn}; -use regex::Regex; use std::collections::BTreeMap; -use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; use tempfile::TempPath; @@ -113,14 +111,6 @@ pub struct Archive { )] #[arg(long)] default_subtitle: Option<Locale>, - #[arg(help = "Disable subtitle optimizations")] - #[arg( - long_help = "By default, Crunchyroll delivers subtitles in a format which may cause issues in some video players. \ - These issues are fixed internally by setting a flag which is not part of the official specification of the subtitle format. \ - If you do not want this fixes or they cause more trouble than they solve (for you), it can be disabled with this flag" - )] - #[arg(long)] - no_subtitle_optimizations: bool, #[arg(help = "Ignore interactive input")] #[arg(short, long, default_value_t = false)] @@ -326,12 +316,8 @@ impl Execute for Archive { let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap(); for subtitle in subtitles { subtitle_paths.push(( - download_subtitle( - &self, - subtitle.stream_subtitle.clone(), - primary_video_length, - ) - .await?, + download_subtitle(subtitle.stream_subtitle.clone(), primary_video_length) + .await?, subtitle, )) } @@ -436,7 +422,7 @@ async fn formats_from_series( }; Some(subtitle) })); - formats.push(Format::new_from_episode(episode, &episodes, stream)); + formats.push(Format::new_from_episode(episode, &episodes, stream, vec![])); } primary_season = false; @@ -476,111 +462,6 @@ async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Res Ok(path) } -async fn download_subtitle( - archive: &Archive, - subtitle: StreamSubtitle, - max_length: NaiveTime, -) -> Result<TempPath> { - let tempfile = tempfile(".ass")?; - let (mut file, path) = tempfile.into_parts(); - - let mut buf = vec![]; - subtitle.write_to(&mut buf).await?; - if !archive.no_subtitle_optimizations { - buf = fix_subtitle_look_and_feel(buf) - } - buf = fix_subtitle_length(buf, max_length); - - file.write_all(buf.as_slice())?; - - Ok(path) -} - -/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video -/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) -/// for more information. -fn fix_subtitle_look_and_feel(raw: Vec<u8>) -> Vec<u8> { - let mut script_info = false; - let mut new = String::new(); - - for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { - if line.trim().starts_with('[') && script_info { - new.push_str("ScaledBorderAndShadow: yes\n"); - script_info = false - } else if line.trim() == "[Script Info]" { - script_info = true - } - new.push_str(line); - new.push('\n') - } - - new.into_bytes() -} - -/// Fix the length of subtitles to a specified maximum amount. This is required because sometimes -/// subtitles have an unnecessary entry long after the actual video ends with artificially extends -/// the video length on some video players. To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more -/// information. -fn fix_subtitle_length(raw: Vec<u8>, max_length: NaiveTime) -> Vec<u8> { - let re = - Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) - .unwrap(); - - // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 - // digits so them have to be reduced manually to avoid the panic - fn format_naive_time(native_time: NaiveTime) -> String { - let formatted_time = native_time.format("%f").to_string(); - format!( - "{}.{}", - native_time.format("%T"), - if formatted_time.len() <= 2 { - native_time.format("%2f").to_string() - } else { - formatted_time.split_at(2).0.parse().unwrap() - } - ) - } - - let length_as_string = format_naive_time(max_length); - let mut new = String::new(); - - for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { - if let Some(capture) = re.captures(line) { - let start = capture.name("start").map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() - }); - let end = capture.name("end").map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() - }); - - if start > max_length { - continue; - } else if end > max_length { - new.push_str( - re.replace( - line, - format!( - "Dialogue: {},{},", - format_naive_time(start), - &length_as_string - ), - ) - .to_string() - .as_str(), - ) - } else { - new.push_str(line) - } - } else { - new.push_str(line) - } - new.push('\n') - } - - new.into_bytes() -} - fn generate_mkv( archive: &Archive, target: PathBuf, @@ -721,23 +602,3 @@ fn generate_mkv( Ok(()) } - -/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry -/// long after the actual video ends with artificially extends the video length on some video players. -/// To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more -/// information. -fn get_video_length(path: PathBuf) -> Result<NaiveTime> { - let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; - - let ffmpeg = Command::new("ffmpeg") - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .arg("-y") - .args(["-i", path.to_str().unwrap()]) - .output()?; - let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; - let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); - - Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) -} diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 7a49a4c..223162f 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -6,19 +6,21 @@ use crate::cli::utils::{ use crate::utils::context::Context; use crate::utils::format::Format; use crate::utils::log::progress; -use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; +use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; +use crate::utils::subtitle::download_subtitle; +use crate::utils::video::get_video_length; use crate::Execute; use anyhow::{bail, Result}; -use crunchyroll_rs::media::{Resolution, VariantData}; +use crunchyroll_rs::media::{Resolution, StreamSubtitle, VariantData}; use crunchyroll_rs::{ Episode, Locale, Media, MediaCollection, Movie, MovieListing, Season, Series, }; use log::{debug, error, info, warn}; use std::borrow::Cow; -use std::fs::File; -use std::path::{Path, PathBuf}; +use std::io::Read; +use std::path::Path; use std::process::{Command, Stdio}; #[derive(Debug, clap::Parser)] @@ -51,7 +53,7 @@ pub struct Download { {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] - #[arg(short, long, default_value = "{title}.ts")] + #[arg(short, long, default_value = "{title}.mp4")] output: String, #[arg(help = "Video resolution")] @@ -85,17 +87,14 @@ pub struct Download { #[async_trait::async_trait(?Send)] impl Execute for Download { fn pre_check(&self) -> Result<()> { - if has_ffmpeg() { - debug!("FFmpeg detected") - } else if PathBuf::from(&self.output) + if !has_ffmpeg() { + bail!("FFmpeg is needed to run this command") + } else if Path::new(&self.output) .extension() .unwrap_or_default() - .to_string_lossy() - != "ts" + .is_empty() { - bail!("File extension is not '.ts'. If you want to use a custom file format, please install ffmpeg") - } else if !self.ffmpeg_preset.is_empty() { - bail!("FFmpeg is required to use (ffmpeg) presets") + bail!("No file extension found. Please specify a file extension (via `-o`) for the output file") } let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; @@ -245,23 +244,14 @@ impl Execute for Download { tab_info!("Resolution: {}", format.stream.resolution); tab_info!("FPS: {:.2}", format.stream.fps); - let extension = path.extension().unwrap_or_default().to_string_lossy(); - - if (!extension.is_empty() && extension != "ts") || !self.ffmpeg_preset.is_empty() { - download_ffmpeg(&ctx, &self, format.stream, path.as_path()).await?; - } else if path.to_str().unwrap() == "-" { - let mut stdout = std::io::stdout().lock(); - download_segments(&ctx, &mut stdout, None, format.stream).await?; - } else { - // create parent directory if it does not exist - if let Some(parent) = path.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent)? - } - } - let mut file = File::options().create(true).write(true).open(&path)?; - download_segments(&ctx, &mut file, None, format.stream).await? - } + download_ffmpeg( + &ctx, + &self, + format.stream, + format.subtitles.get(0).cloned(), + path.as_path(), + ) + .await?; } } @@ -273,9 +263,10 @@ async fn download_ffmpeg( ctx: &Context, download: &Download, variant_data: VariantData, + subtitle: Option<StreamSubtitle>, target: &Path, ) -> Result<()> { - let (input_presets, output_presets) = + let (input_presets, mut output_presets) = FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; // create parent directory if it does not exist @@ -285,32 +276,66 @@ async fn download_ffmpeg( } } + if let Some(ext) = target.extension() { + if ext.to_string_lossy() != "mp4" && subtitle.is_some() { + warn!("Detected a non mp4 output container. Adding subtitles may take a while") + } + } + + let mut video_file = tempfile(".ts")?; + download_segments(ctx, &mut video_file, None, variant_data).await?; + let subtitle_file = if let Some(ref sub) = subtitle { + let video_len = get_video_length(video_file.path().to_path_buf())?; + Some(download_subtitle(sub.clone(), video_len).await?) + } else { + None + }; + + let subtitle_preset = if let Some(sub_file) = &subtitle_file { + if target.extension().unwrap_or_default().to_string_lossy() == "mp4" { + vec![ + "-i".to_string(), + sub_file.to_string_lossy().to_string(), + "-c:s".to_string(), + "mov_text".to_string(), + "-disposition:s:s:0".to_string(), + "forced".to_string(), + ] + } else { + // remove '-c:v copy' and '-c:a copy' from output presets as its causes issues with + // burning subs into the video + let mut last = String::new(); + let mut remove_count = 0; + for (i, s) in output_presets.clone().iter().enumerate() { + if (last == "-c:v" || last == "-c:a") && s == "copy" { + // remove last + output_presets.remove(i - remove_count - 1); + remove_count += 1; + output_presets.remove(i - remove_count); + remove_count += 1; + } + last = s.clone(); + } + + vec![ + "-vf".to_string(), + format!("subtitles={}", sub_file.to_string_lossy()), + ] + } + } else { + vec![] + }; + let mut ffmpeg = Command::new("ffmpeg") - .stdin(Stdio::piped()) - .stdout(Stdio::null()) .stderr(Stdio::piped()) .arg("-y") .args(input_presets) - .args(["-f", "mpegts", "-i", "pipe:"]) - .args( - if target - .extension() - .unwrap_or_default() - .to_string_lossy() - .is_empty() - { - vec!["-f", "mpegts"] - } else { - vec![] - } - .as_slice(), - ) + .args(["-i", video_file.path().to_string_lossy().as_ref()]) + .args(subtitle_preset) .args(output_presets) .arg(target.to_str().unwrap()) .spawn()?; - download_segments(ctx, &mut ffmpeg.stdin.take().unwrap(), None, variant_data).await?; - let _progress_handler = progress!("Generating output file"); ffmpeg.wait()?; info!("Output file generated"); @@ -431,8 +456,11 @@ async fn format_from_episode( } let streams = episode.streams().await?; - let streaming_data = if let Some(subtitle) = &download.subtitle { - if !streams.subtitles.keys().cloned().any(|x| &x == subtitle) { + let streaming_data = streams.hls_streaming_data(None).await?; + let subtitle = if let Some(subtitle) = &download.subtitle { + if let Some(sub) = streams.subtitles.get(subtitle) { + Some(sub.clone()) + } else { error!( "Episode {} ({}) of season {} ({}) of {} has no {} subtitles", episode.metadata.episode_number, @@ -444,9 +472,8 @@ async fn format_from_episode( ); return Ok(None); } - streams.hls_streaming_data(Some(subtitle.clone())).await? } else { - streams.hls_streaming_data(None).await? + None }; let Some(stream) = find_resolution(streaming_data, &download.resolution) else { @@ -476,6 +503,7 @@ async fn format_from_episode( episode, &season_eps.to_vec(), stream, + subtitle.map_or_else(|| vec![], |s| vec![s]), ))) } diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index da56f5e..8a7a79a 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -290,38 +290,32 @@ impl FFmpegPreset { FFmpegPreset::Av1 => bail!("'nvidia' hardware acceleration preset is not available in combination with the 'av1' codec preset"), FFmpegPreset::H265 => { input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "hevc_nvenc"]); + output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]); } FFmpegPreset::H264 => { input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "h264_nvenc"]); + output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]); } _ => () } } else { match preset { FFmpegPreset::Av1 => { - output.extend(["-c:v", "libaom-av1"]); + output.extend(["-c:v", "libaom-av1", "-c:a", "copy"]); } FFmpegPreset::H265 => { - output.extend(["-c:v", "libx265"]); + output.extend(["-c:v", "libx265", "-c:a", "copy"]); } FFmpegPreset::H264 => { - output.extend(["-c:v", "libx264"]); + output.extend(["-c:v", "libx264", "-c:a", "copy"]); } _ => (), } } } - if input.is_empty() && output.is_empty() { - output.extend(["-c", "copy"]) - } else { - if output.is_empty() { - output.extend(["-c", "copy"]) - } else { - output.extend(["-c:a", "copy", "-c:s", "copy"]) - } + if output.is_empty() { + output.extend(["-c:v", "copy", "-c:a", "copy"]) } Ok(( diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 60596ad..93ee773 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,4 +1,4 @@ -use crunchyroll_rs::media::VariantData; +use crunchyroll_rs::media::{StreamSubtitle, VariantData}; use crunchyroll_rs::{Episode, Locale, Media, Movie}; use log::warn; use std::path::PathBuf; @@ -10,6 +10,7 @@ pub struct Format { pub description: String, pub audio: Locale, + pub subtitles: Vec<StreamSubtitle>, pub duration: Duration, pub stream: VariantData, @@ -31,12 +32,14 @@ impl Format { episode: &Media<Episode>, season_episodes: &Vec<Media<Episode>>, stream: VariantData, + subtitles: Vec<StreamSubtitle>, ) -> Self { Self { title: episode.title.clone(), description: episode.description.clone(), audio: episode.metadata.audio_locale.clone(), + subtitles, duration: episode.metadata.duration.to_std().unwrap(), stream, @@ -78,6 +81,7 @@ impl Format { duration: movie.metadata.duration.to_std().unwrap(), stream, + subtitles: vec![], series_id: movie.metadata.movie_listing_id.clone(), series_name: movie.metadata.movie_listing_title.clone(), diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index 3b15a89..552c198 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -7,3 +7,4 @@ pub mod os; pub mod parse; pub mod sort; pub mod subtitle; +pub mod video; diff --git a/crunchy-cli-core/src/utils/subtitle.rs b/crunchy-cli-core/src/utils/subtitle.rs index 86b9359..f638b1b 100644 --- a/crunchy-cli-core/src/utils/subtitle.rs +++ b/crunchy-cli-core/src/utils/subtitle.rs @@ -1,5 +1,11 @@ +use crate::utils::os::tempfile; +use anyhow::Result; +use chrono::NaiveTime; use crunchyroll_rs::media::StreamSubtitle; use crunchyroll_rs::Locale; +use regex::Regex; +use std::io::Write; +use tempfile::TempPath; #[derive(Clone)] pub struct Subtitle { @@ -9,3 +15,105 @@ pub struct Subtitle { pub forced: bool, pub primary: bool, } + +pub async fn download_subtitle( + subtitle: StreamSubtitle, + max_length: NaiveTime, +) -> Result<TempPath> { + let tempfile = tempfile(".ass")?; + let (mut file, path) = tempfile.into_parts(); + + let mut buf = vec![]; + subtitle.write_to(&mut buf).await?; + buf = fix_subtitle_look_and_feel(buf); + buf = fix_subtitle_length(buf, max_length); + + file.write_all(buf.as_slice())?; + + Ok(path) +} + +/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video +/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) +/// for more information. +fn fix_subtitle_look_and_feel(raw: Vec<u8>) -> Vec<u8> { + let mut script_info = false; + let mut new = String::new(); + + for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { + if line.trim().starts_with('[') && script_info { + new.push_str("ScaledBorderAndShadow: yes\n"); + script_info = false + } else if line.trim() == "[Script Info]" { + script_info = true + } + new.push_str(line); + new.push('\n') + } + + new.into_bytes() +} + +/// Fix the length of subtitles to a specified maximum amount. This is required because sometimes +/// subtitles have an unnecessary entry long after the actual video ends with artificially extends +/// the video length on some video players. To prevent this, the video length must be hard set. See +/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// information. +fn fix_subtitle_length(raw: Vec<u8>, max_length: NaiveTime) -> Vec<u8> { + let re = + Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) + .unwrap(); + + // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 + // digits so them have to be reduced manually to avoid the panic + fn format_naive_time(native_time: NaiveTime) -> String { + let formatted_time = native_time.format("%f").to_string(); + format!( + "{}.{}", + native_time.format("%T"), + if formatted_time.len() <= 2 { + native_time.format("%2f").to_string() + } else { + formatted_time.split_at(2).0.parse().unwrap() + } + ) + } + + let length_as_string = format_naive_time(max_length); + let mut new = String::new(); + + for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { + if let Some(capture) = re.captures(line) { + let start = capture.name("start").map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }); + let end = capture.name("end").map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }); + + if start > max_length { + continue; + } else if end > max_length { + new.push_str( + re.replace( + line, + format!( + "Dialogue: {},{},", + format_naive_time(start), + &length_as_string + ), + ) + .to_string() + .as_str(), + ) + } else { + new.push_str(line) + } + } else { + new.push_str(line) + } + new.push('\n') + } + + new.into_bytes() +} diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs new file mode 100644 index 0000000..2d12bfc --- /dev/null +++ b/crunchy-cli-core/src/utils/video.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use chrono::NaiveTime; +use regex::Regex; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry +/// long after the actual video ends with artificially extends the video length on some video players. +/// To prevent this, the video length must be hard set. See +/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// information. +pub fn get_video_length(path: PathBuf) -> Result<NaiveTime> { + let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; + + let ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .arg("-y") + .args(["-i", path.to_str().unwrap()]) + .output()?; + let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; + let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); + + Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) +} From 08c4e30a06d13703d948ccc431a4f459e8c53fa5 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 13 Jan 2023 16:03:19 +0100 Subject: [PATCH 308/630] (Re-)add download pipe to stdout --- crunchy-cli-core/src/cli/download.rs | 43 ++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 223162f..9885b3c 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -19,8 +19,7 @@ use crunchyroll_rs::{ }; use log::{debug, error, info, warn}; use std::borrow::Cow; -use std::io::Read; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; #[derive(Debug, clap::Parser)] @@ -93,10 +92,19 @@ impl Execute for Download { .extension() .unwrap_or_default() .is_empty() + && self.output != "-" { bail!("No file extension found. Please specify a file extension (via `-o`) for the output file") } + if self.subtitle.is_some() { + if let Some(ext) = Path::new(&self.output).extension() { + if ext.to_string_lossy() != "mp4" { + warn!("Detected a non mp4 output container. Adding subtitles may take a while") + } + } + } + let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; if self.ffmpeg_preset.len() == 1 && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia @@ -249,7 +257,7 @@ impl Execute for Download { &self, format.stream, format.subtitles.get(0).cloned(), - path.as_path(), + path.to_path_buf(), ) .await?; } @@ -264,7 +272,7 @@ async fn download_ffmpeg( download: &Download, variant_data: VariantData, subtitle: Option<StreamSubtitle>, - target: &Path, + mut target: PathBuf, ) -> Result<()> { let (input_presets, mut output_presets) = FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; @@ -276,12 +284,6 @@ async fn download_ffmpeg( } } - if let Some(ext) = target.extension() { - if ext.to_string_lossy() != "mp4" && subtitle.is_some() { - warn!("Detected a non mp4 output container. Adding subtitles may take a while") - } - } - let mut video_file = tempfile(".ts")?; download_segments(ctx, &mut video_file, None, variant_data).await?; let subtitle_file = if let Some(ref sub) = subtitle { @@ -291,11 +293,22 @@ async fn download_ffmpeg( None }; - let subtitle_preset = if let Some(sub_file) = &subtitle_file { + let stdout_tempfile = if target.to_string_lossy() == "-" { + let file = tempfile(".mp4")?; + target = file.path().to_path_buf(); + + Some(file) + } else { + None + }; + + let subtitle_presets = if let Some(sub_file) = &subtitle_file { if target.extension().unwrap_or_default().to_string_lossy() == "mp4" { vec![ "-i".to_string(), sub_file.to_string_lossy().to_string(), + "-movflags".to_string(), + "faststart".to_string(), "-c:s".to_string(), "mov_text".to_string(), "-disposition:s:s:0".to_string(), @@ -331,7 +344,7 @@ async fn download_ffmpeg( .arg("-y") .args(input_presets) .args(["-i", video_file.path().to_string_lossy().as_ref()]) - .args(subtitle_preset) + .args(subtitle_presets) .args(output_presets) .arg(target.to_str().unwrap()) .spawn()?; @@ -340,6 +353,12 @@ async fn download_ffmpeg( ffmpeg.wait()?; info!("Output file generated"); + if let Some(mut stdout_file) = stdout_tempfile { + let mut stdout = std::io::stdout(); + + std::io::copy(&mut stdout_file, &mut stdout)?; + } + Ok(()) } From 3d145b021baa207016b9ca419c33cfbd4bf8aa46 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 13 Jan 2023 16:04:53 +0100 Subject: [PATCH 309/630] Add download ffmpeg error output --- crunchy-cli-core/src/cli/download.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 9885b3c..66027a2 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -340,6 +340,7 @@ async fn download_ffmpeg( }; let mut ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) .stderr(Stdio::piped()) .arg("-y") .args(input_presets) @@ -350,7 +351,9 @@ async fn download_ffmpeg( .spawn()?; let _progress_handler = progress!("Generating output file"); - ffmpeg.wait()?; + if !ffmpeg.wait()?.success() { + bail!("{}", std::io::read_to_string(ffmpeg.stderr.unwrap())?) + } info!("Output file generated"); if let Some(mut stdout_file) = stdout_tempfile { From 497f22ee49e0476203e75abef56413e891a584cb Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Fri, 13 Jan 2023 22:38:29 +0100 Subject: [PATCH 310/630] Fix double download progress output message --- crunchy-cli-core/src/cli/download.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 66027a2..05372a4 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -350,11 +350,11 @@ async fn download_ffmpeg( .arg(target.to_str().unwrap()) .spawn()?; - let _progress_handler = progress!("Generating output file"); + let progress_handler = progress!("Generating output file"); if !ffmpeg.wait()?.success() { bail!("{}", std::io::read_to_string(ffmpeg.stderr.unwrap())?) } - info!("Output file generated"); + progress_handler.stop("Output file generated"); if let Some(mut stdout_file) = stdout_tempfile { let mut stdout = std::io::stdout(); From 685ac85857c37b7d6abd53c3171faeda4b1a8fd5 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 15 Jan 2023 21:38:35 +0100 Subject: [PATCH 311/630] Add flag to skip existing files (#67, #109) --- crunchy-cli-core/src/cli/archive.rs | 31 +++++++++++++++++++--------- crunchy-cli-core/src/cli/download.rs | 31 +++++++++++++++++++--------- crunchy-cli-core/src/utils/os.rs | 6 +++--- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 768c629..a8b79b6 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -122,6 +122,10 @@ pub struct Archive { #[arg(long)] no_subtitle_optimizations: bool, + #[arg(help = "Skip files which are already existing")] + #[arg(long, default_value_t = false)] + skip_existing: bool, + #[arg(help = "Ignore interactive input")] #[arg(short, long, default_value_t = false)] yes: bool, @@ -242,17 +246,24 @@ impl Execute for Archive { for (formats, mut subtitles) in archive_formats { let (primary, additionally) = formats.split_first().unwrap(); - let path = free_file( - primary.format_path( - if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - } - .into(), - true, - ), + let formatted_path = primary.format_path( + if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + } + .into(), + true, ); + let (path, changed) = free_file(formatted_path.clone()); + + if changed && self.skip_existing { + debug!( + "Skipping already existing file '{}'", + formatted_path.to_string_lossy() + ); + continue; + } info!( "Downloading {} to '{}'", diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 7a49a4c..fddbfd3 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -74,6 +74,10 @@ pub struct Download { #[arg(value_parser = FFmpegPreset::parse)] ffmpeg_preset: Vec<FFmpegPreset>, + #[arg(help = "Skip files which are already existing")] + #[arg(long, default_value_t = false)] + skip_existing: bool, + #[arg(help = "Ignore interactive input")] #[arg(short, long, default_value_t = false)] yes: bool, @@ -209,17 +213,24 @@ impl Execute for Download { } for format in formats { - let path = free_file( - format.format_path( - if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - } - .into(), - true, - ), + let formatted_path = format.format_path( + if self.output.is_empty() { + "{title}.mkv" + } else { + &self.output + } + .into(), + true, ); + let (path, changed) = free_file(formatted_path.clone()); + + if changed && self.skip_existing { + debug!( + "Skipping already existing file '{}'", + formatted_path.to_string_lossy() + ); + continue; + } info!( "Downloading {} to '{}'", diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index a7b3fbf..94e9d02 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -36,10 +36,10 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { } /// Check if the given path exists and rename it until the new (renamed) file does not exist. -pub fn free_file(mut path: PathBuf) -> PathBuf { +pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { // if it's a special file does not rename it if is_special_file(&path) { - return path; + return (path, false); } let mut i = 0; @@ -55,7 +55,7 @@ pub fn free_file(mut path: PathBuf) -> PathBuf { path.set_file_name(format!("{} ({}).{}", filename, i, ext)) } - path + (path, i != 0) } /// Check if the given path is a special file. On Linux this is probably a pipe and on Windows From 577c0679adee027dc52801a8745968a830d35120 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 15 Jan 2023 21:43:31 +0100 Subject: [PATCH 312/630] Remove automatically set filename if output is empty --- crunchy-cli-core/src/cli/archive.rs | 10 +--------- crunchy-cli-core/src/cli/download.rs | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index a8b79b6..31d39c6 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -246,15 +246,7 @@ impl Execute for Archive { for (formats, mut subtitles) in archive_formats { let (primary, additionally) = formats.split_first().unwrap(); - let formatted_path = primary.format_path( - if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - } - .into(), - true, - ); + let formatted_path = primary.format_path((&self.output).into(), true); let (path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index fddbfd3..d8fb003 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -213,15 +213,7 @@ impl Execute for Download { } for format in formats { - let formatted_path = format.format_path( - if self.output.is_empty() { - "{title}.mkv" - } else { - &self.output - } - .into(), - true, - ); + let formatted_path = format.format_path((&self.output).into(), true); let (path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { From 3dd8385aac345c3a7f3b9f817810c0e74f82a35e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 15 Jan 2023 22:09:23 +0100 Subject: [PATCH 313/630] (Re-)enable -l all for archive (#110) --- crunchy-cli-core/src/cli/archive.rs | 9 ++++++--- crunchy-cli-core/src/cli/download.rs | 6 +++--- crunchy-cli-core/src/cli/utils.rs | 16 +++++++++++++++- crunchy-cli-core/src/lib.rs | 6 +++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 768c629..d7e6bf9 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -1,7 +1,7 @@ use crate::cli::log::tab_info; use crate::cli::utils::{ - download_segments, find_multiple_seasons_with_same_number, find_resolution, - interactive_season_choosing, FFmpegPreset, + all_locale_in_locales, download_segments, find_multiple_seasons_with_same_number, + find_resolution, interactive_season_choosing, FFmpegPreset, }; use crate::utils::context::Context; use crate::utils::format::Format; @@ -132,7 +132,7 @@ pub struct Archive { #[async_trait::async_trait(?Send)] impl Execute for Archive { - fn pre_check(&self) -> Result<()> { + fn pre_check(&mut self) -> Result<()> { if !has_ffmpeg() { bail!("FFmpeg is needed to run this command") } else if PathBuf::from(&self.output) @@ -151,6 +151,9 @@ impl Execute for Archive { warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") } + self.locale = all_locale_in_locales(self.locale.clone()); + self.subtitle = all_locale_in_locales(self.subtitle.clone()); + Ok(()) } diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index 7a49a4c..2f83ffd 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,7 +1,7 @@ use crate::cli::log::tab_info; use crate::cli::utils::{ - download_segments, find_multiple_seasons_with_same_number, find_resolution, - interactive_season_choosing, FFmpegPreset, + download_segments, find_multiple_seasons_with_same_number, + find_resolution, interactive_season_choosing, FFmpegPreset, }; use crate::utils::context::Context; use crate::utils::format::Format; @@ -84,7 +84,7 @@ pub struct Download { #[async_trait::async_trait(?Send)] impl Execute for Download { - fn pre_check(&self) -> Result<()> { + fn pre_check(&mut self) -> Result<()> { if has_ffmpeg() { debug!("FFmpeg detected") } else if PathBuf::from(&self.output) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index da56f5e..84850f8 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -1,7 +1,7 @@ use crate::utils::context::Context; use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; -use crunchyroll_rs::{Media, Season}; +use crunchyroll_rs::{Locale, Media, Season}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use lazy_static::lazy_static; use log::{debug, LevelFilter}; @@ -369,6 +369,20 @@ pub(crate) fn find_multiple_seasons_with_same_number(seasons: &Vec<Media<Season> .collect() } +/// Check if [`Locale::Custom("all")`] is in the provided locale list and return [`Locale::all`] if +/// so. If not, just return the provided locale list. +pub(crate) fn all_locale_in_locales(locales: Vec<Locale>) -> Vec<Locale> { + if locales + .iter() + .find(|l| l.to_string().to_lowercase().trim() == "all") + .is_some() + { + Locale::all() + } else { + locales + } +} + pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Media<Season>> { let input_regex = Regex::new(r"((?P<single>\d+)|(?P<range_from>\d+)-(?P<range_to>\d+)?)(\s|$)").unwrap(); diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 6cef42e..ab3f33c 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -16,10 +16,10 @@ pub use cli::{archive::Archive, download::Download, login::Login}; #[async_trait::async_trait(?Send)] trait Execute { - fn pre_check(&self) -> Result<()> { + fn pre_check(&mut self) -> Result<()> { Ok(()) } - async fn execute(self, ctx: Context) -> Result<()>; + async fn execute(mut self, ctx: Context) -> Result<()>; } #[derive(Debug, Parser)] @@ -171,7 +171,7 @@ pub async fn cli_entrypoint() { /// Cannot be done in the main function. I wanted to return `dyn` [`Execute`] from the match but had to /// box it which then conflicts with [`Execute::execute`] which consumes `self` -async fn execute_executor(executor: impl Execute, ctx: Context) { +async fn execute_executor(mut executor: impl Execute, ctx: Context) { if let Err(err) = executor.pre_check() { error!("Misconfigurations detected: {}", err); std::process::exit(1) From 6bd75c93cb554eba55a63ccd08bb3bd05dfdc49d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 15 Jan 2023 22:18:10 +0100 Subject: [PATCH 314/630] Simplify archive no audio present output --- crunchy-cli-core/src/cli/archive.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index d7e6bf9..7c26d33 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -366,12 +366,12 @@ async fn formats_from_series( .into_iter() .filter(|l| !season.iter().any(|s| s.metadata.audio_locales.contains(l))) .collect::<Vec<Locale>>(); - for not_present in not_present_audio { + if !not_present_audio.is_empty() { error!( "Season {} of series {} is not available with {} audio", season.first().unwrap().metadata.season_number, series.title, - not_present + not_present_audio.into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ") ) } From b3226cdde54ae1381e1a38d720b3f3880ea642df Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 17 Jan 2023 17:25:09 +0100 Subject: [PATCH 315/630] Change av1 encoder to libsvtav1 (#108) --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 0e9e02f..7352d95 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -301,7 +301,7 @@ impl FFmpegPreset { } else { match preset { FFmpegPreset::Av1 => { - output.extend(["-c:v", "libaom-av1", "-c:a", "copy"]); + output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); } FFmpegPreset::H265 => { output.extend(["-c:v", "libx265", "-c:a", "copy"]); From 21a5782825e41449a0d9fe62a72775907a22cbb5 Mon Sep 17 00:00:00 2001 From: Hannes Braun <hannesbraun@mail.de> Date: Tue, 17 Jan 2023 21:06:57 +0100 Subject: [PATCH 316/630] Don't remove the subtitles if the video is detected to be identical --- crunchy-cli-core/src/cli/archive.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index e0fc9b0..2c06ca0 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -298,10 +298,11 @@ impl Execute for Archive { video_paths.push((download_video(&ctx, primary, false).await?, primary)); for additional in additionally { + let identical_video = additionally + .iter() + .all(|a| a.stream.bandwidth == primary.stream.bandwidth); let only_audio = match self.merge { - MergeBehavior::Auto => additionally - .iter() - .all(|a| a.stream.bandwidth == primary.stream.bandwidth), + MergeBehavior::Auto => identical_video, MergeBehavior::Audio => true, MergeBehavior::Video => false, }; @@ -312,8 +313,8 @@ impl Execute for Archive { video_paths.push((path, additional)) } - // Remove subtitles of deleted video - if only_audio { + // Remove subtitles of forcibly deleted video + if matches!(self.merge, MergeBehavior::Audio) && !identical_video { subtitles.retain(|s| s.episode_id != additional.episode_id); } } From e115dcd87f9a31925b771b365065604f95d23772 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Wed, 25 Jan 2023 00:48:35 +0100 Subject: [PATCH 317/630] Rework ffmpeg preset, add 3 quality levels and custom flags (#108) --- Cargo.lock | 7 + crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/cli/archive.rs | 35 ++- crunchy-cli-core/src/cli/download.rs | 32 ++- crunchy-cli-core/src/cli/utils.rs | 397 +++++++++++++++++++++------ 5 files changed, 356 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42fe778..ae46f99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", + "shlex", "signal-hook", "sys-locale", "tempfile", @@ -1476,6 +1477,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook" version = "0.3.14" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 8eea6a8..e56cf71 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -21,6 +21,7 @@ regex = "1.7" sanitize-filename = "0.4" serde = "1.0" serde_json = "1.0" +shlex = "1.1" signal-hook = "0.3" tempfile = "3.3" terminal_size = "0.2" diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index e0fc9b0..69f999a 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -15,7 +15,7 @@ use crate::Execute; use anyhow::{bail, Result}; use crunchyroll_rs::media::Resolution; use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; -use log::{debug, error, info, warn}; +use log::{debug, error, info}; use std::collections::BTreeMap; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -97,14 +97,14 @@ pub struct Archive { merge: MergeBehavior, #[arg(help = format!("Presets for video converting. Can be used multiple times. \ - Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ Generally used to minify the file size with keeping (nearly) the same quality. \ It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ - Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] - ffmpeg_preset: Vec<FFmpegPreset>, + ffmpeg_preset: Option<FFmpegPreset>, #[arg( help = "Set which subtitle language should be set as default / auto shown when starting a video" @@ -138,12 +138,6 @@ impl Execute for Archive { { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } - let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; - if self.ffmpeg_preset.len() == 1 - && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia - { - warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") - } self.locale = all_locale_in_locales(self.locale.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); @@ -360,7 +354,11 @@ async fn formats_from_series( "Season {} of series {} is not available with {} audio", season.first().unwrap().metadata.season_number, series.title, - not_present_audio.into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ") + not_present_audio + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") ) } @@ -545,8 +543,19 @@ fn generate_mkv( } } - let (input_presets, output_presets) = - FFmpegPreset::ffmpeg_presets(archive.ffmpeg_preset.clone())?; + let (input_presets, output_presets) = if let Some(preset) = archive.ffmpeg_preset.clone() { + preset.to_input_output_args() + } else { + ( + vec![], + vec![ + "-c:v".to_string(), + "copy".to_string(), + "-c:a".to_string(), + "copy".to_string(), + ], + ) + }; let mut command_args = vec!["-y".to_string()]; command_args.extend(input_presets); diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs index c8e244c..b4c037c 100644 --- a/crunchy-cli-core/src/cli/download.rs +++ b/crunchy-cli-core/src/cli/download.rs @@ -1,7 +1,7 @@ use crate::cli::log::tab_info; use crate::cli::utils::{ - download_segments, find_multiple_seasons_with_same_number, - find_resolution, interactive_season_choosing, FFmpegPreset, + download_segments, find_multiple_seasons_with_same_number, find_resolution, + interactive_season_choosing, FFmpegPreset, }; use crate::utils::context::Context; use crate::utils::format::Format; @@ -66,14 +66,14 @@ pub struct Download { resolution: Resolution, #[arg(help = format!("Presets for video converting. Can be used multiple times. \ - Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ Generally used to minify the file size with keeping (nearly) the same quality. \ It is recommended to only use this if you download videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ - Available presets: \n {}", FFmpegPreset::all().into_iter().map(|p| format!("{}: {}", p.to_string(), p.description())).collect::<Vec<String>>().join("\n ")))] + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] - ffmpeg_preset: Vec<FFmpegPreset>, + ffmpeg_preset: Option<FFmpegPreset>, #[arg(help = "Skip files which are already existing")] #[arg(long, default_value_t = false)] @@ -109,13 +109,6 @@ impl Execute for Download { } } - let _ = FFmpegPreset::ffmpeg_presets(self.ffmpeg_preset.clone())?; - if self.ffmpeg_preset.len() == 1 - && self.ffmpeg_preset.get(0).unwrap() == &FFmpegPreset::Nvidia - { - warn!("Skipping 'nvidia' hardware acceleration preset since no other codec preset was specified") - } - Ok(()) } @@ -277,8 +270,19 @@ async fn download_ffmpeg( subtitle: Option<StreamSubtitle>, mut target: PathBuf, ) -> Result<()> { - let (input_presets, mut output_presets) = - FFmpegPreset::ffmpeg_presets(download.ffmpeg_preset.clone())?; + let (input_presets, mut output_presets) = if let Some(preset) = download.ffmpeg_preset.clone() { + preset.to_input_output_args() + } else { + ( + vec![], + vec![ + "-c:v".to_string(), + "copy".to_string(), + "-c:a".to_string(), + "copy".to_string(), + ], + ) + }; // create parent directory if it does not exist if let Some(parent) = target.parent() { diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 7352d95..44cd63c 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -8,7 +8,9 @@ use log::{debug, LevelFilter}; use regex::Regex; use std::borrow::{Borrow, BorrowMut}; use std::collections::BTreeMap; +use std::env; use std::io::{BufRead, Write}; +use std::str::FromStr; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; use tokio::task::JoinSet; @@ -210,118 +212,335 @@ pub async fn download_segments( #[derive(Clone, Debug, Eq, PartialEq)] pub enum FFmpegPreset { - Nvidia, - - Av1, - H265, - H264, + Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality), + Custom(Option<String>, Option<String>), } -impl ToString for FFmpegPreset { - fn to_string(&self) -> String { - match self { - &FFmpegPreset::Nvidia => "nvidia", - &FFmpegPreset::Av1 => "av1", - &FFmpegPreset::H265 => "h265", - &FFmpegPreset::H264 => "h264", +lazy_static! { + static ref PREDEFINED_PRESET: Regex = Regex::new(r"^\w+(-\w+)*?$").unwrap(); +} + +macro_rules! FFmpegEnum { + (enum $name:ident { $($field:ident),* }) => { + #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] + pub enum $name { + $( + $field + ),*, } - .to_string() + + impl $name { + fn all() -> Vec<$name> { + vec![ + $( + $name::$field + ),*, + ] + } + } + + impl ToString for $name { + fn to_string(&self) -> String { + match self { + $( + &$name::$field => stringify!($field).to_string().to_lowercase() + ),* + } + } + } + + impl FromStr for $name { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + match s { + $( + stringify!($field) => Ok($name::$field) + ),*, + _ => bail!("{} is not a valid {}", s, stringify!($name).to_lowercase()) + } + } + } + } +} + +FFmpegEnum! { + enum FFmpegCodec { + H264, + H265, + Av1 + } +} + +FFmpegEnum! { + enum FFmpegHwAccel { + Nvidia + } +} + +FFmpegEnum! { + enum FFmpegQuality { + Lossless, + Normal, + Low } } impl FFmpegPreset { - pub(crate) fn all() -> Vec<FFmpegPreset> { - vec![ - FFmpegPreset::Nvidia, - FFmpegPreset::Av1, - FFmpegPreset::H265, - FFmpegPreset::H264, - ] + pub(crate) fn available_matches( + ) -> Vec<(FFmpegCodec, Option<FFmpegHwAccel>, Option<FFmpegQuality>)> { + let codecs = vec![ + ( + FFmpegCodec::H264, + FFmpegHwAccel::all(), + FFmpegQuality::all(), + ), + ( + FFmpegCodec::H265, + FFmpegHwAccel::all(), + FFmpegQuality::all(), + ), + (FFmpegCodec::Av1, vec![], FFmpegQuality::all()), + ]; + + let mut return_values = vec![]; + + for (codec, hwaccels, qualities) in codecs { + return_values.push((codec.clone(), None, None)); + for hwaccel in hwaccels.clone() { + return_values.push((codec.clone(), Some(hwaccel), None)); + } + for quality in qualities.clone() { + return_values.push((codec.clone(), None, Some(quality))) + } + for hwaccel in hwaccels { + for quality in qualities.clone() { + return_values.push((codec.clone(), Some(hwaccel.clone()), Some(quality))) + } + } + } + + return_values } - pub(crate) fn description(self) -> String { - match self { - FFmpegPreset::Nvidia => "If you're have a nvidia card, use hardware / gpu accelerated video processing if available", - FFmpegPreset::Av1 => "Encode the video(s) with the av1 codec. Hardware acceleration is currently not possible with this", - FFmpegPreset::H265 => "Encode the video(s) with the h265 codec", - FFmpegPreset::H264 => "Encode the video(s) with the h264 codec" - }.to_string() + pub(crate) fn available_matches_human_readable() -> Vec<String> { + let mut return_values = vec![]; + + for (codec, hwaccel, quality) in FFmpegPreset::available_matches() { + let mut description_details = vec![]; + if let Some(h) = &hwaccel { + description_details.push(format!("{} hardware acceleration", h.to_string())) + } + if let Some(q) = &quality { + description_details.push(format!("{} video quality/compression", q.to_string())) + } + + let description = if description_details.len() == 0 { + format!( + "{} encoded with default video quality/compression", + codec.to_string() + ) + } else if description_details.len() == 1 { + format!( + "{} encoded with {}", + codec.to_string(), + description_details[0] + ) + } else { + let first = description_details.remove(0); + let last = description_details.remove(description_details.len() - 1); + let mid = if !description_details.is_empty() { + format!(", {} ", description_details.join(", ")) + } else { + "".to_string() + }; + + format!( + "{} encoded with {}{} and {}", + codec.to_string(), + first, + mid, + last + ) + }; + + return_values.push(format!( + "{} ({})", + vec![ + Some(codec.to_string()), + hwaccel.map(|h| h.to_string()), + quality.map(|q| q.to_string()) + ] + .into_iter() + .flatten() + .collect::<Vec<String>>() + .join("-"), + description + )) + } + return_values } pub(crate) fn parse(s: &str) -> Result<FFmpegPreset, String> { - Ok(match s.to_lowercase().as_str() { - "nvidia" => FFmpegPreset::Nvidia, - "av1" => FFmpegPreset::Av1, - "h265" | "h.265" | "hevc" => FFmpegPreset::H265, - "h264" | "h.264" => FFmpegPreset::H264, - _ => return Err(format!("'{}' is not a valid ffmpeg preset", s)), - }) + let env_ffmpeg_input_args = env::var("FFMPEG_INPUT_ARGS").ok(); + let env_ffmpeg_output_args = env::var("FFMPEG_OUTPUT_ARGS").ok(); + + if env_ffmpeg_input_args.is_some() || env_ffmpeg_output_args.is_some() { + if let Some(input) = &env_ffmpeg_input_args { + if shlex::split(input).is_none() { + return Err(format!("Failed to parse custom ffmpeg input '{}' (`FFMPEG_INPUT_ARGS` env variable)", input)); + } + } + if let Some(output) = &env_ffmpeg_output_args { + if shlex::split(output).is_none() { + return Err(format!("Failed to parse custom ffmpeg output '{}' (`FFMPEG_INPUT_ARGS` env variable)", output)); + } + } + + return Ok(FFmpegPreset::Custom( + env_ffmpeg_input_args, + env_ffmpeg_output_args, + )); + } else if !PREDEFINED_PRESET.is_match(s) { + return Ok(FFmpegPreset::Custom(None, Some(s.to_string()))); + } + + let mut codec: Option<FFmpegCodec> = None; + let mut hwaccel: Option<FFmpegHwAccel> = None; + let mut quality: Option<FFmpegQuality> = None; + for token in s.split('-') { + if let Some(c) = FFmpegCodec::all() + .into_iter() + .find(|p| p.to_string() == token.to_lowercase()) + { + if let Some(cc) = codec { + return Err(format!( + "cannot use multiple codecs (found {} and {})", + cc.to_string(), + c.to_string() + )); + } + codec = Some(c) + } else if let Some(h) = FFmpegHwAccel::all() + .into_iter() + .find(|p| p.to_string() == token.to_lowercase()) + { + if let Some(hh) = hwaccel { + return Err(format!( + "cannot use multiple hardware accelerations (found {} and {})", + hh.to_string(), + h.to_string() + )); + } + hwaccel = Some(h) + } else if let Some(q) = FFmpegQuality::all() + .into_iter() + .find(|p| p.to_string() == token.to_lowercase()) + { + if let Some(qq) = quality { + return Err(format!( + "cannot use multiple ffmpeg preset qualities (found {} and {})", + qq.to_string(), + q.to_string() + )); + } + quality = Some(q) + } else { + return Err(format!( + "'{}' is not a valid ffmpeg preset (unknown token '{}'", + s, token + )); + } + } + + if let Some(c) = codec { + if !FFmpegPreset::available_matches().contains(&( + c.clone(), + hwaccel.clone(), + quality.clone(), + )) { + return Err(format!("ffmpeg preset is not supported")); + } + Ok(FFmpegPreset::Predefined( + c, + hwaccel, + quality.unwrap_or(FFmpegQuality::Normal), + )) + } else { + Err(format!("cannot use ffmpeg preset with without a codec")) + } } - pub(crate) fn ffmpeg_presets( - mut presets: Vec<FFmpegPreset>, - ) -> Result<(Vec<String>, Vec<String>)> { - fn preset_check_remove(presets: &mut Vec<FFmpegPreset>, preset: FFmpegPreset) -> bool { - if let Some(i) = presets.iter().position(|p| p == &preset) { - presets.remove(i); - true - } else { - false - } - } + pub(crate) fn to_input_output_args(self) -> (Vec<String>, Vec<String>) { + match self { + FFmpegPreset::Custom(input, output) => ( + input.map_or(vec![], |i| shlex::split(&i).unwrap_or_default()), + output.map_or(vec![], |o| shlex::split(&o).unwrap_or_default()), + ), + FFmpegPreset::Predefined(codec, hwaccel_opt, quality) => { + let mut input = vec![]; + let mut output = vec![]; - let nvidia = preset_check_remove(&mut presets, FFmpegPreset::Nvidia); - if presets.len() > 1 { - bail!( - "Can only use one video codec, {} found: {}", - presets.len(), - presets - .iter() - .map(|p| p.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } + match codec { + FFmpegCodec::H264 => { + if let Some(hwaccel) = hwaccel_opt { + match hwaccel { + FFmpegHwAccel::Nvidia => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) + } + } + } else { + output.extend(["-c:v", "libx264", "-c:a", "copy"]) + } - let (mut input, mut output) = (vec![], vec![]); - for preset in presets { - if nvidia { - match preset { - FFmpegPreset::Av1 => bail!("'nvidia' hardware acceleration preset is not available in combination with the 'av1' codec preset"), - FFmpegPreset::H265 => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]); + match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "18"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + } } - FFmpegPreset::H264 => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]); + FFmpegCodec::H265 => { + if let Some(hwaccel) = hwaccel_opt { + match hwaccel { + FFmpegHwAccel::Nvidia => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) + } + } + } else { + output.extend(["-c:v", "libx265", "-c:a", "copy"]) + } + + match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "20"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + } } - _ => () - } - } else { - match preset { - FFmpegPreset::Av1 => { + FFmpegCodec::Av1 => { output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); + + match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "22"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + } } - FFmpegPreset::H265 => { - output.extend(["-c:v", "libx265", "-c:a", "copy"]); - } - FFmpegPreset::H264 => { - output.extend(["-c:v", "libx264", "-c:a", "copy"]); - } - _ => (), } + + ( + input + .into_iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>(), + output + .into_iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>(), + ) } } - - if output.is_empty() { - output.extend(["-c:v", "copy", "-c:a", "copy"]) - } - - Ok(( - input.into_iter().map(|i| i.to_string()).collect(), - output.into_iter().map(|o| o.to_string()).collect(), - )) } } From e83de60efa34bb637dbf01b8049fda91fc5e7ea8 Mon Sep 17 00:00:00 2001 From: Alexandru Dracea <adracea@gmail.com> Date: Tue, 31 Jan 2023 13:37:29 +0200 Subject: [PATCH 318/630] Update README.md Adds a bit of guidance for how to properly `install` . --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index bf57145..eb40d1c 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,25 @@ $ cargo build --release ``` After the binary has built successfully it is available in `target/release`. +### Final steps + +In order to make the binary globally accessible you will need to add it to `PATH` so it's recommended you move it to a general folder. + + +Examples: + +- Linux/MacOS + - ```shell + mkdir ~/crunchy-cli + mv /path/to/repo/target/release/crunchy-cli ~/crunchy-cli/crunchy # OR + mv /path/to/downloaded/file/crunchy-cli(rest of filename here) ~/crunchy-cli/crunchy + export PATH=$PATH:~/crunchy-cli + ``` + + For persistent usage you should add the above export to your `.shellrc`(.bashrc, .zshrc ,etc. file) +- Windows + - Download the `.exe` file or build it yourself. Rename it to the way you will be calling it (ex: `crunchy.exe`) and move it into a folder where it's easily accessible. Afterwards follow a [guide](https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows) for adding that folder to the `PATH` variable. A restart of `CMD` or `powershell` might be required for the changes to take effect. + ## ๐Ÿ–ฅ๏ธ Usage > All shown command are just examples From 96b259ce9a0fbc7678195dbca6742ade5986f7e0 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 5 Feb 2023 15:00:50 +0100 Subject: [PATCH 319/630] Add url filtering section to README (#133) --- README.md | 59 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index eb40d1c..5e859c4 100644 --- a/README.md +++ b/README.md @@ -61,25 +61,6 @@ $ cargo build --release ``` After the binary has built successfully it is available in `target/release`. -### Final steps - -In order to make the binary globally accessible you will need to add it to `PATH` so it's recommended you move it to a general folder. - - -Examples: - -- Linux/MacOS - - ```shell - mkdir ~/crunchy-cli - mv /path/to/repo/target/release/crunchy-cli ~/crunchy-cli/crunchy # OR - mv /path/to/downloaded/file/crunchy-cli(rest of filename here) ~/crunchy-cli/crunchy - export PATH=$PATH:~/crunchy-cli - ``` - - For persistent usage you should add the above export to your `.shellrc`(.bashrc, .zshrc ,etc. file) -- Windows - - Download the `.exe` file or build it yourself. Rename it to the way you will be calling it (ex: `crunchy.exe`) and move it into a folder where it's easily accessible. Afterwards follow a [guide](https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows) for adding that folder to the `PATH` variable. A restart of `CMD` or `powershell` might be required for the changes to take effect. - ## ๐Ÿ–ฅ๏ธ Usage > All shown command are just examples @@ -89,7 +70,7 @@ It doesn't matter if this account is premium or not, both works (but as free use You can pass your account via credentials (username & password) or refresh token. - Refresh Token - - To get the token you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. + - To get the token you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. The easiest way to get it is via a browser extension with lets you view your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox Store](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/); [Chrome Store](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). If installed, search the `etp_rt` entry and extract the value. - ```shell @@ -126,18 +107,11 @@ This does not work if you've using this with `--anonymous`. ```shell $ crunchy download https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- Episode range - - If you want only specific episodes / seasons of an anime you can easily provide the series url along with a _filter_. - The filter has to be attached to the url. See the [wiki](https://github.com/crunchy-labs/crunchy-cli/wiki/Cli#filter) for more information - ```shell - $ crunchy download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1] - ``` - Series ```shell $ crunchy download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - + **Options** - Audio language @@ -183,7 +157,7 @@ This does not work if you've using this with `--anonymous`. ```shell $ crunchy archive https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - + **Options** - Audio languages @@ -242,7 +216,7 @@ This does not work if you've using this with `--anonymous`. - No subtitle optimizations - Subtitles, as Crunchyroll delivers them, look weird in some video players (#66). + Subtitles, as Crunchyroll delivers them, look weird in some video players (#66). This can be fixed by adding a specific entry to the subtitles. But since this entry is only a de-factor standard and not represented in the official specification of the subtitle format ([`.ass`](https://en.wikipedia.org/wiki/SubStation_Alpha)) it could cause issues with some video players (but no issue got reported so far, so it's relatively safe to use). `--no_subtitle_optimizations` can disable these optimizations. @@ -250,6 +224,31 @@ This does not work if you've using this with `--anonymous`. $ crunchy archive --no_subtitle_optimizations https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` +### Url Filtering + +If you want to download only specific episode of a series, you could either pass every single episode url to the downloader (which is fine for 1 - 3 episodes) or use _filtering_. + +It works pretty simple, just put a specific pattern surrounded by square brackets at the end of the url from the anime you want to download. +A season and / or episode as well as a range from where to where episodes should be downloaded can be specified. +Use the list below to get a better overview what is possible +- `...[E5]` - Download the fifth episode. +- `...[S1]` - Download the full first season. +- `...[-S2]` - Download all seasons up to and including season 2. +- `...[S3E4-]` - Download all episodes from and including season 3, episode 4. +- `...[S1E4-S3]` - Download all episodes from and including season 1, episode 4, until and including season 3. +- `...[S3,S5]` - Download episode 3 and 5. +- `...[S1-S3,S4E2-S4E6]` - Download season 1 to 3 and episode 2 to episode 6 of season 4. + +In practice, it would look like this: `https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E5]`. + +The `S`, followed by the number indicates the _season_ number, `E`, followed by the number indicates an _episode_ number. +It doesn't matter if `S`, `E` or both are missing. +Note that `S` must always stay before `E` when used. + +There is also a regex available at [regex101.com](https://regex101.com/r/SDZyZM) where you can test if your pattern is correct. +Just put in your pattern without square brackets into the big empty field and if the full pattern is highlighted this means it is valid. +If none or only some parts are highlighted, it's not valid not. + # โ˜๏ธ Disclaimer This tool is **ONLY** meant to be used for private purposes. To use this tool you need crunchyroll premium anyway, so there is no reason why rip and share the episodes. From ba57d3c25d261249d921b5f146fd7c706e38fc1e Mon Sep 17 00:00:00 2001 From: bocchi <121779542+hitorilabs@users.noreply.github.com> Date: Mon, 6 Feb 2023 03:11:52 -0500 Subject: [PATCH 320/630] bugfix: btreemap skips duplicate ep nums --- crunchy-cli-core/src/cli/archive.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index ff13128..b189406 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -383,7 +383,7 @@ async fn formats_from_series( } #[allow(clippy::type_complexity)] - let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); + let mut result: BTreeMap<u32, BTreeMap<String, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); let mut primary_season = true; for season in seasons { let episodes = season.episodes().await?; @@ -414,7 +414,7 @@ async fn formats_from_series( let (ref mut formats, subtitles) = result .entry(season.metadata.season_number) .or_insert_with(BTreeMap::new) - .entry(episode.metadata.episode_number) + .entry(episode.metadata.episode.clone()) .or_insert_with(|| (vec![], vec![])); subtitles.extend(archive.subtitle.iter().filter_map(|l| { let stream_subtitle = streams.subtitles.get(l).cloned()?; From 264d943a2c0eeb8f8b7e7116989dc018ef539b55 Mon Sep 17 00:00:00 2001 From: bocchi <121779542+hitorilabs@users.noreply.github.com> Date: Mon, 6 Feb 2023 03:22:04 -0500 Subject: [PATCH 321/630] use episode id instead --- crunchy-cli-core/src/cli/archive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index b189406..82e70f7 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -414,7 +414,7 @@ async fn formats_from_series( let (ref mut formats, subtitles) = result .entry(season.metadata.season_number) .or_insert_with(BTreeMap::new) - .entry(episode.metadata.episode.clone()) + .entry(episode.id.clone()) .or_insert_with(|| (vec![], vec![])); subtitles.extend(archive.subtitle.iter().filter_map(|l| { let stream_subtitle = streams.subtitles.get(l).cloned()?; From cba921f1a8352704874fe30fb3cc067885150037 Mon Sep 17 00:00:00 2001 From: bocchi <121779542+hitorilabs@users.noreply.github.com> Date: Mon, 6 Feb 2023 04:04:26 -0500 Subject: [PATCH 322/630] use sequence_number instead of episode_number --- crunchy-cli-core/src/cli/archive.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 82e70f7..9b6ac4b 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -383,7 +383,7 @@ async fn formats_from_series( } #[allow(clippy::type_complexity)] - let mut result: BTreeMap<u32, BTreeMap<String, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); + let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); let mut primary_season = true; for season in seasons { let episodes = season.episodes().await?; @@ -414,7 +414,7 @@ async fn formats_from_series( let (ref mut formats, subtitles) = result .entry(season.metadata.season_number) .or_insert_with(BTreeMap::new) - .entry(episode.id.clone()) + .entry((episode.metadata.sequence_number * 100.0) as u32) .or_insert_with(|| (vec![], vec![])); subtitles.extend(archive.subtitle.iter().filter_map(|l| { let stream_subtitle = streams.subtitles.get(l).cloned()?; From 03dd1c5264c0e304ec7ab6843b8faf9944032f61 Mon Sep 17 00:00:00 2001 From: bocchi <121779542+hitorilabs@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:04:41 -0500 Subject: [PATCH 323/630] get rid of btreemap in archive --- crunchy-cli-core/src/cli/archive.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs index 9b6ac4b..e500f21 100644 --- a/crunchy-cli-core/src/cli/archive.rs +++ b/crunchy-cli-core/src/cli/archive.rs @@ -16,7 +16,6 @@ use anyhow::{bail, Result}; use crunchyroll_rs::media::Resolution; use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; use log::{debug, error, info}; -use std::collections::BTreeMap; use std::path::PathBuf; use std::process::{Command, Stdio}; use tempfile::TempPath; @@ -383,7 +382,7 @@ async fn formats_from_series( } #[allow(clippy::type_complexity)] - let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new(); + let mut result: Vec<(Vec<Format>, Vec<Subtitle>)> = Vec::new(); let mut primary_season = true; for season in seasons { let episodes = season.episodes().await?; @@ -411,11 +410,8 @@ async fn formats_from_series( ) }; - let (ref mut formats, subtitles) = result - .entry(season.metadata.season_number) - .or_insert_with(BTreeMap::new) - .entry((episode.metadata.sequence_number * 100.0) as u32) - .or_insert_with(|| (vec![], vec![])); + let mut formats: Vec<Format> = Vec::new(); + let mut subtitles: Vec<Subtitle> = Vec::new(); subtitles.extend(archive.subtitle.iter().filter_map(|l| { let stream_subtitle = streams.subtitles.get(l).cloned()?; let subtitle = Subtitle { @@ -428,12 +424,14 @@ async fn formats_from_series( Some(subtitle) })); formats.push(Format::new_from_episode(episode, &episodes, stream, vec![])); + + result.push((formats, subtitles)); } primary_season = false; } - Ok(result.into_values().flat_map(|v| v.into_values()).collect()) + Ok(result) } async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Result<TempPath> { From e4919e80ba59105a31a049bb6f4ac499880a789e Mon Sep 17 00:00:00 2001 From: Hannes Braun <hannesbraun@mail.de> Date: Mon, 20 Feb 2023 22:34:48 +0100 Subject: [PATCH 324/630] Fix offset in interactive season choosing --- crunchy-cli-core/src/cli/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 44cd63c..753cb72 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -646,7 +646,7 @@ pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Me let mut nums = vec![]; for capture in input_regex.captures_iter(&user_input) { if let Some(single) = capture.name("single") { - nums.push(single.as_str().parse().unwrap()); + nums.push(single.as_str().parse::<usize>().unwrap() - 1); } else { let range_from = capture.name("range_from"); let range_to = capture.name("range_to"); From 656ce0b523aa16fe6fe77252db672270e5a25d44 Mon Sep 17 00:00:00 2001 From: LetMeByte <124048327+LetMeByte@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:35:53 +0100 Subject: [PATCH 325/630] Update log.rs --- crunchy-cli-core/src/cli/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs index 3f63eb0..7147c47 100644 --- a/crunchy-cli-core/src/cli/log.rs +++ b/crunchy-cli-core/src/cli/log.rs @@ -117,7 +117,7 @@ impl CliLogger { pb.set_style( ProgressStyle::with_template(":: {spinner} {msg}") .unwrap() - .tick_strings(&["-", "\\", "|", "/", finish_str]), + .tick_strings(&["โ€”", "\\", "|", "/", finish_str]), ); pb.enable_steady_tick(Duration::from_millis(200)); pb.set_message(msg); From 90212c4ec07fd2d682e5aeab3d3bf467dbee5115 Mon Sep 17 00:00:00 2001 From: LetMeByte <124048327+LetMeByte@users.noreply.github.com> Date: Wed, 22 Feb 2023 22:45:00 +0100 Subject: [PATCH 326/630] Move config dir and session file (#156) * Move config dir and session file * Update login.rs --- crunchy-cli-core/src/cli/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/cli/login.rs b/crunchy-cli-core/src/cli/login.rs index c6d876a..c41b1b7 100644 --- a/crunchy-cli-core/src/cli/login.rs +++ b/crunchy-cli-core/src/cli/login.rs @@ -35,5 +35,5 @@ impl Execute for Login { } pub fn login_file_path() -> Option<PathBuf> { - dirs::config_dir().map(|config_dir| config_dir.join(".crunchy-cli-core")) + dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli").join("session")) } From 19f9d26af9dc84ce12a5498f13b4dc25c42bdda8 Mon Sep 17 00:00:00 2001 From: LetMeByte <124048327+LetMeByte@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:13:33 +0100 Subject: [PATCH 327/630] Update utils.rs --- crunchy-cli-core/src/cli/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs index 753cb72..2937415 100644 --- a/crunchy-cli-core/src/cli/utils.rs +++ b/crunchy-cli-core/src/cli/utils.rs @@ -486,7 +486,7 @@ impl FFmpegPreset { if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + input.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-c:v", "h264_cuvid"]); output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } } @@ -504,7 +504,7 @@ impl FFmpegPreset { if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + input.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-c:v", "h264_cuvid"]); output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) } } From d6f1262c1cce60c3c509e8f6a10782b346ea800e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 27 Feb 2023 11:17:32 +0100 Subject: [PATCH 328/630] Fix no such file or directory when using login (#164) --- crunchy-cli-core/src/cli/login.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crunchy-cli-core/src/cli/login.rs b/crunchy-cli-core/src/cli/login.rs index c41b1b7..f164a0b 100644 --- a/crunchy-cli-core/src/cli/login.rs +++ b/crunchy-cli-core/src/cli/login.rs @@ -18,6 +18,8 @@ pub struct Login { impl Execute for Login { async fn execute(self, ctx: Context) -> Result<()> { if let Some(login_file_path) = login_file_path() { + fs::create_dir_all(login_file_path.parent().unwrap())?; + match ctx.crunchy.session_token().await { SessionToken::RefreshToken(refresh_token) => Ok(fs::write( login_file_path, From a6bfe0be2e13deaa31f2f9180250a02c3e063c19 Mon Sep 17 00:00:00 2001 From: LetMeByte <124048327+LetMeByte@users.noreply.github.com> Date: Wed, 8 Mar 2023 22:31:03 +0100 Subject: [PATCH 329/630] =?UTF-8?q?=F0=9F=93=9A=20Readme=20Overhaul=20?= =?UTF-8?q?=F0=9F=93=9A=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ๐Ÿ“š * Update README.md * Update README.md --- README.md | 142 +++++++++++++++++++++++++----------------------------- 1 file changed, 66 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 5e859c4..846c59f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # crunchy-cli -A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https://www.crunchyroll.com). +A pure [Rust](https://www.rust-lang.org/) CLI for [Crunchyroll](https://www.crunchyroll.com). <p align="center"> <a href="https://github.com/crunchy-labs/crunchy-cli"> @@ -26,13 +26,13 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: <p align="center"> <a href="#%EF%B8%8F-usage">Usage ๐Ÿ–ฅ๏ธ</a> โ€ข - <a href="#%EF%B8%8F-disclaimer">Disclaimer โ˜๏ธ</a> + <a href="#%EF%B8%8F-disclaimer">Disclaimer ๐Ÿ“œ</a> โ€ข <a href="#-license">License โš–</a> </p> > We are in no way affiliated with, maintained, authorized, sponsored, or officially associated with Crunchyroll LLC or any of its subsidiaries or affiliates. -> The official Crunchyroll website can be found at https://crunchyroll.com/. +> The official Crunchyroll website can be found at [crunchyroll.com](https://crunchyroll.com/). > This README belongs to the _master_ branch which is currently under heavy development towards the next major version (3.0). > It is mostly stable but some issues may still occur. @@ -41,14 +41,14 @@ A [Rust](https://www.rust-lang.org/) written cli client for [Crunchyroll](https: ## โœจ Features - Download single videos and entire series from [Crunchyroll](https://www.crunchyroll.com). -- Archive episode or seasons in an `.mkv` file with multiple subtitles and audios. -- Specify a range which episodes to download from an anime. +- Archive episodes or seasons in an `.mkv` file with multiple subtitles and audios. +- Specify a range of episodes to download from an anime. ## ๐Ÿ’พ Get the executable ### ๐Ÿ“ฅ Download the latest binaries -Checkout the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the newest (pre-)release. +Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the latest (pre-)release. ### ๐Ÿ›  Build it yourself @@ -58,47 +58,45 @@ This requires [git](https://git-scm.com/) and [Cargo](https://doc.rust-lang.org/ $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli $ cargo build --release +$ cargo install --force --path . ``` -After the binary has built successfully it is available in `target/release`. ## ๐Ÿ–ฅ๏ธ Usage -> All shown command are just examples +> All shown commands are examples ๐Ÿง‘๐Ÿผโ€๐Ÿณ -Every command requires you to be logged in with an account. -It doesn't matter if this account is premium or not, both works (but as free user you do not have access to premium content). -You can pass your account via credentials (username & password) or refresh token. +crunchy-cli requires you to log in. +Though you can use a non-premium account, you will not have access to premium content without a subscription. +You can authenticate with your credentials (username:password) or by using a refresh token. -- Refresh Token - - To get the token you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. - The easiest way to get it is via a browser extension with lets you view your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox Store](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/); [Chrome Store](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). - If installed, search the `etp_rt` entry and extract the value. - - ```shell - $ crunchy --etp-rt "abcd1234-zyxw-9876-98zy-a1b2c3d4e5f6" - ``` - Credentials - - Credentials must be provided as one single expression. - Username and password must be separated by a `:`. - ```shell $ crunchy --credentials "user:password" ``` -- Anonymous - - Login without an account at all is also possible. +- Refresh Token + - To obtain a refresh token, you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. + The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). + When installed, look for the `etp_rt` entry and extract its value. + - ```shell + $ crunchy --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" + ``` +- Stay Anonymous + - Skip the login check: - ```shell $ crunchy --anonymous ``` ### Login -If you do not want to provide your credentials every time you execute a command, they can be stored permanently on disk. -This can be done with the `login` subcommand. +crunchy-cli can store your session, so you don't have to authenticate every time you execute a command. + +Note that the `login` keyword has to be used *last*. ```shell -$ crunchy --etp-rt "abcd1234-zyxw-9876-98zy-a1b2c3d4e5f6" login +$ crunchy --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" login ``` -Once set, you do not need to provide `--etp-rt` / `--credentials` anymore when using the cli. -This does not work if you've using this with `--anonymous`. +With the session stored, you do not need to use `--credentials` / `--etp-rt` anymore. This does not work with `--anonymous`. ### Download @@ -115,25 +113,25 @@ This does not work if you've using this with `--anonymous`. **Options** - Audio language - Which audio the episode(s) should be can be set via the `-a` / `--audio` flag. + Set the audio language with the `-a` / `--audio` flag. This only works if the url points to a series since episode urls are language specific. ```shell $ crunchy download -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Default is your system language. If not supported by Crunchyroll, `en-US` (American English) is the default. + Default is your system locale. If not supported by Crunchyroll, `en-US` (American English) is the default. - Subtitle language - Besides the audio, it's also possible to specify which language the subtitles should have with the `-s` / `--subtitle` flag. - The subtitle will be hardsubbed (burned into the video) and thus, can't be turned off or on. + Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. + The subtitles will be burned into the video track (cf. [hardsub](https://www.urbandictionary.com/define.php?term=hardsub)) and thus can not be turned off. ```shell $ crunchy download -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Default is no subtitle. + Default is none. -- Output filename +- Output template - You can specify the name of the output file with the `-o` / `--output` flag. + Define an output template by using the `-o` / `--output` flag. If you want to use any other file format than [`.ts`](https://en.wikipedia.org/wiki/MPEG_transport_stream) you need [ffmpeg](https://ffmpeg.org/). ```shell $ crunchy download -o "ditf.ts" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome @@ -153,7 +151,7 @@ This does not work if you've using this with `--anonymous`. **Supported urls** - Series - Only series urls are supported since single episode urls are (audio) language locked. + Only series urls are supported, because episode urls are locked to a single audio language. ```shell $ crunchy archive https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` @@ -161,25 +159,24 @@ This does not work if you've using this with `--anonymous`. **Options** - Audio languages - Which audios the episode(s) should be can be set via the `-a` / `--audio` flag. + Set the audio language with the `-a` / `--audio` flag. Can be used multiple times. ```shell $ crunchy archive -a ja-JP -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Can be used multiple times. - Default is your system language (if not supported by Crunchyroll, `en-US` (American English) is the default) + `ja-JP` (Japanese). + Default is your system locale (if not supported by Crunchyroll, `en-US` (American English) and `ja-JP` (Japanese) are used). - Subtitle languages - Besides the audio, it's also possible to specify which languages the subtitles should have with the `-s` / `--subtitle` flag. + Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. ```shell $ crunchy archive -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Default is all subtitles. + Default is `all` subtitles. -- Output filename +- Output template - You can specify the name of the output file with the `-o` / `--output` flag. - The only supported file / container format is [`.mkv`](https://en.wikipedia.org/wiki/Matroska) since it stores / can store multiple audio, video and subtitle streams. + Define an output template by using the `-o` / `--output` flag. + crunchy-cli uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of it's ability to store multiple audio, video and subtitle tracks at once. ```shell $ crunchy archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` @@ -195,12 +192,12 @@ This does not work if you've using this with `--anonymous`. - Merge behavior - Because of local restrictions (or other reasons) some episodes with different languages does not have the same length (e.g. when some scenes were cut out). - The ideal state, when multiple audios & subtitles used, would be if only one _video_ has to be stored and all other languages can be stored as audio-only. + Due to censorship, some episodes have multiple lengths for different languages. + In the best case, when multiple audio & subtitle tracks are used, there is only one *video* track and all other languages can be stored as audio-only. But, as said, this is not always the case. - With the `-m` / `--merge` flag you can set what you want to do if some video lengths differ. - Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` else like `audio`. - Subtitles will always match to the first / primary audio and video. + With the `-m` / `--merge` flag you can define the behaviour when an episodes' video tracks differ in length. + Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` - otherwise like `audio`. + Subtitles will always match the primary audio and video. ```shell $ crunchy archive -m audio https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` @@ -208,52 +205,45 @@ This does not work if you've using this with `--anonymous`. - Default subtitle - `--default_subtitle` set which subtitle language should be set as default / auto appear when starting the downloaded video(s). + `--default_subtitle` Set which subtitle language is to be flagged as **default** and **forced**. ```shell $ crunchy archive --default_subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is none. -- No subtitle optimizations +- Subtitle optimizations - Subtitles, as Crunchyroll delivers them, look weird in some video players (#66). + Crunchyroll's subtitles look weird in some players (#66). This can be fixed by adding a specific entry to the subtitles. - But since this entry is only a de-factor standard and not represented in the official specification of the subtitle format ([`.ass`](https://en.wikipedia.org/wiki/SubStation_Alpha)) it could cause issues with some video players (but no issue got reported so far, so it's relatively safe to use). - `--no_subtitle_optimizations` can disable these optimizations. + Even though this entry is a de facto standard, it is not defined in the official specification for the `.ass` format (cf. [Advanced SubStation Subtitles](https://wiki.videolan.org/SubStation_Alpha)). This could cause compatibility issues, but no issues have been reported yet. + `--no_subtitle_optimizations` disables these optimizations. ```shell $ crunchy archive --no_subtitle_optimizations https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` -### Url Filtering +### Episode filtering -If you want to download only specific episode of a series, you could either pass every single episode url to the downloader (which is fine for 1 - 3 episodes) or use _filtering_. +Filters patterns can be used to download a specific range of episodes from a single series. -It works pretty simple, just put a specific pattern surrounded by square brackets at the end of the url from the anime you want to download. -A season and / or episode as well as a range from where to where episodes should be downloaded can be specified. -Use the list below to get a better overview what is possible +A filter pattern may consist of either a season, an episode, or a combination of the two. +When used in combination, seasons `S` must be defined before episodes `E`. + +There are many possible patterns, for example: - `...[E5]` - Download the fifth episode. -- `...[S1]` - Download the full first season. -- `...[-S2]` - Download all seasons up to and including season 2. -- `...[S3E4-]` - Download all episodes from and including season 3, episode 4. -- `...[S1E4-S3]` - Download all episodes from and including season 1, episode 4, until and including season 3. -- `...[S3,S5]` - Download episode 3 and 5. -- `...[S1-S3,S4E2-S4E6]` - Download season 1 to 3 and episode 2 to episode 6 of season 4. +- `...[S1]` - Download the whole first season. +- `...[-S2]` - Download the first two seasons. +- `...[S3E4-]` - Download everything from season three, episode four, onwards. +- `...[S1E4-S3]` - Download season one, starting at episode four, then download season two and three. +- `...[S3,S5]` - Download season three and five. +- `...[S1-S3,S4E2-S4E6]` - Download season one to three, then episodes two to six from season four. -In practice, it would look like this: `https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E5]`. +In practice, it would look like this: `https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E5]` -The `S`, followed by the number indicates the _season_ number, `E`, followed by the number indicates an _episode_ number. -It doesn't matter if `S`, `E` or both are missing. -Note that `S` must always stay before `E` when used. +# ๐Ÿ“œ Disclaimer -There is also a regex available at [regex101.com](https://regex101.com/r/SDZyZM) where you can test if your pattern is correct. -Just put in your pattern without square brackets into the big empty field and if the full pattern is highlighted this means it is valid. -If none or only some parts are highlighted, it's not valid not. +This tool is **ONLY** meant for private use. You need a subscription to [`๐Ÿ’ณ Crunchyroll Premium ๐Ÿ’ณ`](https://www.crunchyroll.com/welcome#plans) to download premium content. -# โ˜๏ธ Disclaimer - -This tool is **ONLY** meant to be used for private purposes. To use this tool you need crunchyroll premium anyway, so there is no reason why rip and share the episodes. - -**The responsibility for what happens to the downloaded videos lies entirely with the user who downloaded them.** +**You are entirely responsible for what happens to files you downloaded through crunchy-cli.** # โš– License From 0a40f3c40f7eaeca06a677debcb1813f69e45b3d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 01:17:41 +0100 Subject: [PATCH 330/630] Refactor --- Cargo.lock | 775 ++++++++++------- Cargo.toml | 8 +- crunchy-cli-core/Cargo.lock | 776 +++++++++++------- crunchy-cli-core/Cargo.toml | 12 +- crunchy-cli-core/src/archive/command.rs | 184 +++++ crunchy-cli-core/src/archive/filter.rs | 482 +++++++++++ crunchy-cli-core/src/archive/mod.rs | 4 + crunchy-cli-core/src/cli/archive.rs | 618 -------------- crunchy-cli-core/src/cli/download.rs | 603 -------------- crunchy-cli-core/src/cli/log.rs | 138 ---- crunchy-cli-core/src/cli/mod.rs | 5 - crunchy-cli-core/src/cli/utils.rs | 693 ---------------- crunchy-cli-core/src/download/command.rs | 151 ++++ crunchy-cli-core/src/download/filter.rs | 349 ++++++++ crunchy-cli-core/src/download/mod.rs | 4 + crunchy-cli-core/src/lib.rs | 22 +- .../src/{cli/login.rs => login/command.rs} | 2 + crunchy-cli-core/src/login/mod.rs | 4 + crunchy-cli-core/src/utils/download.rs | 657 +++++++++++++++ crunchy-cli-core/src/utils/ffmpeg.rs | 344 ++++++++ crunchy-cli-core/src/utils/filter.rs | 95 +++ crunchy-cli-core/src/utils/format.rs | 309 +++++-- crunchy-cli-core/src/utils/locale.rs | 14 + crunchy-cli-core/src/utils/log.rs | 140 +++- crunchy-cli-core/src/utils/mod.rs | 5 +- crunchy-cli-core/src/utils/os.rs | 2 +- crunchy-cli-core/src/utils/parse.rs | 24 +- crunchy-cli-core/src/utils/sort.rs | 52 -- crunchy-cli-core/src/utils/subtitle.rs | 119 --- crunchy-cli-core/src/utils/video.rs | 42 +- 30 files changed, 3651 insertions(+), 2982 deletions(-) create mode 100644 crunchy-cli-core/src/archive/command.rs create mode 100644 crunchy-cli-core/src/archive/filter.rs create mode 100644 crunchy-cli-core/src/archive/mod.rs delete mode 100644 crunchy-cli-core/src/cli/archive.rs delete mode 100644 crunchy-cli-core/src/cli/download.rs delete mode 100644 crunchy-cli-core/src/cli/log.rs delete mode 100644 crunchy-cli-core/src/cli/mod.rs delete mode 100644 crunchy-cli-core/src/cli/utils.rs create mode 100644 crunchy-cli-core/src/download/command.rs create mode 100644 crunchy-cli-core/src/download/filter.rs create mode 100644 crunchy-cli-core/src/download/mod.rs rename crunchy-cli-core/src/{cli/login.rs => login/command.rs} (94%) create mode 100644 crunchy-cli-core/src/login/mod.rs create mode 100644 crunchy-cli-core/src/utils/download.rs create mode 100644 crunchy-cli-core/src/utils/ffmpeg.rs create mode 100644 crunchy-cli-core/src/utils/filter.rs delete mode 100644 crunchy-cli-core/src/utils/sort.rs delete mode 100644 crunchy-cli-core/src/utils/subtitle.rs diff --git a/Cargo.lock b/Cargo.lock index ae46f99..2651e95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,19 +33,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "async-trait" -version = "0.1.61" +version = "0.1.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" +checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.5", ] [[package]] @@ -72,6 +72,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + [[package]] name = "block-padding" version = "0.3.2" @@ -81,29 +87,17 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cbc" @@ -116,9 +110,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -128,9 +122,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -144,9 +138,9 @@ dependencies = [ [[package]] name = "cipher" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", @@ -154,55 +148,55 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.32" +version = "4.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" dependencies = [ - "bitflags", + "bitflags 2.0.2", "clap_derive", "clap_lex", "is-terminal", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", ] [[package]] name = "clap_complete" -version = "4.0.7" +version = "4.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b" +checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" dependencies = [ "os_str_bytes", ] [[package]] name = "clap_mangen" -version = "0.2.6" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904eb24d05ad587557e0f484ddce5c737c30cf81372badb16d13e41c4b8340b1" +checksum = "4237e29de9c6949982ba87d51709204504fb8ed2fd38232fcb1e5bf7d4ba48c8" dependencies = [ "clap", "roff", @@ -220,15 +214,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b6515d269224923b26b5febea2ed42b2d5f2ce37284a4dd670fedd6cb8347a" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -238,7 +232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.17", + "time 0.3.20", "version_check", ] @@ -254,7 +248,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.3.17", + "time 0.3.20", "url", ] @@ -304,8 +298,8 @@ dependencies = [ "chrono", "clap", "crunchyroll-rs", - "csv", "ctrlc", + "derive_setters", "dirs", "indicatif", "lazy_static", @@ -325,15 +319,17 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3770cda4c67e68c689c8e361af46bb9d017caf82263905358fd0751d10657a0" +checksum = "bddaff98c25bdedca0f1ed2b68c28b12a3e52054144b1b11380236d23051c751" dependencies = [ "aes", "async-trait", "cbc", "chrono", "crunchyroll-rs-internal", + "dash-mpd", + "futures-util", "http", "lazy_static", "m3u8-rs", @@ -350,13 +346,13 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a260a73e733bb0ce30343caaed5e968d3c1cc2ea0ab27c601481e9ef22a2fd7" +checksum = "3f26b71b36db3139ce788545c2bffa7e69d4fd7b689f143086c6283e847a4668" dependencies = [ - "darling", + "darling 0.14.4", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -369,43 +365,21 @@ dependencies = [ "typenum", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "ctrlc" -version = "3.2.4" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" dependencies = [ "nix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "cxx" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" +checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" dependencies = [ "cc", "cxxbridge-flags", @@ -415,9 +389,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" +checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" dependencies = [ "cc", "codespan-reporting", @@ -425,79 +399,145 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.5", ] [[package]] name = "cxxbridge-flags" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" +checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" [[package]] name = "cxxbridge-macro" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" +checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.5", ] [[package]] name = "darling" -version = "0.14.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", ] [[package]] name = "darling_core" -version = "0.14.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", - "syn", + "strsim 0.9.3", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dash-mpd" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df35f3b3b0fde2747a01530de0a81f7a27620d050f656e518ef0550557f564de" +dependencies = [ + "chrono", + "fs-err", + "iso8601", + "log", + "num-traits", + "quick-xml", + "regex", + "serde", + "serde_with", + "thiserror", + "xattr", +] + +[[package]] +name = "derive_setters" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1cf41b4580a37cca5ef2ada2cc43cf5d6be3983f4522e83010d67ab6925e84b" +dependencies = [ + "darling 0.10.2", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "dirs" -version = "4.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" dependencies = [ "libc", "redox_users", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -508,9 +548,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] @@ -538,9 +578,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -576,43 +616,49 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.25" +name = "fs-err" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" + +[[package]] +name = "futures-channel" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-core", "futures-io", @@ -646,9 +692,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -671,9 +717,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -685,14 +731,26 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.8" +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -720,9 +778,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" dependencies = [ "bytes", "futures-channel", @@ -733,7 +791,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -770,16 +828,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows", ] [[package]] @@ -827,13 +885,14 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] name = "indicatif" -version = "0.17.2" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19" +checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" dependencies = [ "console", "number_prefix", @@ -862,12 +921,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -878,33 +938,36 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "iso8601" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" +dependencies = [ + "nom", ] [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -917,9 +980,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "link-cplusplus" @@ -957,9 +1020,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" @@ -969,9 +1032,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -981,14 +1044,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1011,11 +1074,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -1023,9 +1086,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1056,7 +1119,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1068,17 +1131,17 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.45" +version = "0.10.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -1095,7 +1158,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1106,9 +1169,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" dependencies = [ "autocfg", "cc", @@ -1119,9 +1182,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "percent-encoding" @@ -1162,7 +1225,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -1177,17 +1240,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ "unicode-ident", ] @@ -1209,10 +1266,20 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.23" +name = "quick-xml" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1223,7 +1290,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1239,43 +1306,28 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" dependencies = [ - "base64 0.13.1", + "base64 0.21.0", "bytes", "cookie", "cookie_store", @@ -1296,7 +1348,6 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "proc-macro-hack", "rustls", "rustls-pemfile", "serde", @@ -1337,23 +1388,23 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.36.6" +version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -1372,9 +1423,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "sanitize-filename" @@ -1392,14 +1443,14 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "scratch" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sct" @@ -1413,11 +1464,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1426,9 +1477,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -1436,31 +1487,31 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.5", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ - "itoa 1.0.5", + "itoa", "ryu", "serde", ] @@ -1472,11 +1523,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.5", + "itoa", "ryu", "serde", ] +[[package]] +name = "serde_with" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85456ffac572dc8826334164f2fb6fb40a7c766aebe195a2a21ee69ee2885ecf" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.20", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbcd6104f8a4ab6af7f6be2a0da6be86b9de3c401f6e86bb856ab2af739232f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "shlex" version = "1.1.0" @@ -1485,9 +1564,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -1495,18 +1574,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -1519,14 +1598,14 @@ checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1544,6 +1623,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "strsim" version = "0.10.0" @@ -1552,9 +1637,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c2d1c76a26822187a1fbb5964e3fff108bc208f02e820ab9dac1234f6b388a" dependencies = [ "proc-macro2", "quote", @@ -1563,68 +1659,67 @@ dependencies = [ [[package]] name = "sys-locale" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3358acbb4acd4146138b9bda219e904a6bb5aaaa237f8eed06f4d6bc1580ecee" +checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee" dependencies = [ "js-sys", "libc", "wasm-bindgen", "web-sys", - "winapi", + "windows-sys 0.45.0", ] [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.5", ] [[package]] @@ -1640,11 +1735,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa 1.0.5", + "itoa", "serde", "time-core", "time-macros", @@ -1658,9 +1753,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -1676,15 +1771,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.1" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -1695,7 +1790,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1706,14 +1801,14 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -1732,9 +1827,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -1784,15 +1879,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -1862,9 +1957,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1872,24 +1967,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -1899,9 +1994,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1909,28 +2004,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -1986,6 +2081,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -2002,46 +2106,70 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "winreg" @@ -2051,3 +2179,12 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "xattr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml index db1f129..62ff7fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,18 +5,16 @@ version = "3.0.0-dev.8" edition = "2021" [dependencies] -tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.26", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } [build-dependencies] chrono = "0.4" -clap = { version = "4.0", features = ["string"] } -clap_complete = "4.0" +clap = { version = "4.1", features = ["string"] } +clap_complete = "4.1" clap_mangen = "0.2" -# The static-* features must be used here since build dependency features cannot be manipulated from the features -# specified in this Cargo.toml [features]. crunchy-cli-core = { path = "./crunchy-cli-core" } [profile.release] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index b12b3a3..c4e2235 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -33,19 +33,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "async-trait" -version = "0.1.61" +version = "0.1.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" +checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.6", ] [[package]] @@ -72,6 +72,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + [[package]] name = "block-padding" version = "0.3.2" @@ -81,29 +87,17 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cbc" @@ -116,9 +110,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -128,9 +122,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -144,9 +138,9 @@ dependencies = [ [[package]] name = "cipher" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", @@ -154,37 +148,37 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.32" +version = "4.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" dependencies = [ - "bitflags", + "bitflags 2.0.2", "clap_derive", "clap_lex", "is-terminal", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", ] [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" dependencies = [ "os_str_bytes", ] @@ -201,15 +195,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b6515d269224923b26b5febea2ed42b2d5f2ce37284a4dd670fedd6cb8347a" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -219,7 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.17", + "time 0.3.20", "version_check", ] @@ -235,7 +229,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.3.17", + "time 0.3.20", "url", ] @@ -273,8 +267,8 @@ dependencies = [ "chrono", "clap", "crunchyroll-rs", - "csv", "ctrlc", + "derive_setters", "dirs", "indicatif", "lazy_static", @@ -284,6 +278,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", + "shlex", "signal-hook", "sys-locale", "tempfile", @@ -293,15 +288,17 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3770cda4c67e68c689c8e361af46bb9d017caf82263905358fd0751d10657a0" +checksum = "bddaff98c25bdedca0f1ed2b68c28b12a3e52054144b1b11380236d23051c751" dependencies = [ "aes", "async-trait", "cbc", "chrono", "crunchyroll-rs-internal", + "dash-mpd", + "futures-util", "http", "lazy_static", "m3u8-rs", @@ -318,13 +315,13 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a260a73e733bb0ce30343caaed5e968d3c1cc2ea0ab27c601481e9ef22a2fd7" +checksum = "3f26b71b36db3139ce788545c2bffa7e69d4fd7b689f143086c6283e847a4668" dependencies = [ - "darling", + "darling 0.14.4", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -337,43 +334,21 @@ dependencies = [ "typenum", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "ctrlc" -version = "3.2.4" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" dependencies = [ "nix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "cxx" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" +checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" dependencies = [ "cc", "cxxbridge-flags", @@ -383,9 +358,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" +checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" dependencies = [ "cc", "codespan-reporting", @@ -393,79 +368,145 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.6", ] [[package]] name = "cxxbridge-flags" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" +checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" [[package]] name = "cxxbridge-macro" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" +checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.6", ] [[package]] name = "darling" -version = "0.14.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", ] [[package]] name = "darling_core" -version = "0.14.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", - "syn", + "strsim 0.9.3", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dash-mpd" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df35f3b3b0fde2747a01530de0a81f7a27620d050f656e518ef0550557f564de" +dependencies = [ + "chrono", + "fs-err", + "iso8601", + "log", + "num-traits", + "quick-xml", + "regex", + "serde", + "serde_with", + "thiserror", + "xattr", +] + +[[package]] +name = "derive_setters" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1cf41b4580a37cca5ef2ada2cc43cf5d6be3983f4522e83010d67ab6925e84b" +dependencies = [ + "darling 0.10.2", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "dirs" -version = "4.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" dependencies = [ "libc", "redox_users", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -476,9 +517,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] @@ -506,9 +547,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -544,43 +585,49 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.25" +name = "fs-err" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" + +[[package]] +name = "futures-channel" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-core", "futures-io", @@ -614,9 +661,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -639,9 +686,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -653,14 +700,26 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.8" +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -688,9 +747,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" dependencies = [ "bytes", "futures-channel", @@ -701,7 +760,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -738,16 +797,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows", ] [[package]] @@ -795,13 +854,14 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] name = "indicatif" -version = "0.17.2" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19" +checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" dependencies = [ "console", "number_prefix", @@ -830,12 +890,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -846,33 +907,36 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "iso8601" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" +dependencies = [ + "nom", ] [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -885,9 +949,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "link-cplusplus" @@ -925,9 +989,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" @@ -937,9 +1001,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -949,14 +1013,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -979,11 +1043,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -991,9 +1055,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1024,7 +1088,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1036,17 +1100,17 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.45" +version = "0.10.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -1063,7 +1127,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1074,9 +1138,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" dependencies = [ "autocfg", "cc", @@ -1087,9 +1151,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "percent-encoding" @@ -1130,7 +1194,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -1145,17 +1209,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ "unicode-ident", ] @@ -1177,10 +1235,20 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.23" +name = "quick-xml" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1191,7 +1259,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1207,43 +1275,28 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" dependencies = [ - "base64 0.13.1", + "base64 0.21.0", "bytes", "cookie", "cookie_store", @@ -1264,7 +1317,6 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "proc-macro-hack", "rustls", "rustls-pemfile", "serde", @@ -1299,23 +1351,23 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.6" +version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -1334,9 +1386,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "sanitize-filename" @@ -1354,14 +1406,14 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "scratch" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sct" @@ -1375,11 +1427,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1388,9 +1440,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -1398,31 +1450,31 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.6", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ - "itoa 1.0.5", + "itoa", "ryu", "serde", ] @@ -1434,16 +1486,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.5", + "itoa", "ryu", "serde", ] [[package]] -name = "signal-hook" -version = "0.3.14" +name = "serde_with" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "85456ffac572dc8826334164f2fb6fb40a7c766aebe195a2a21ee69ee2885ecf" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.20", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbcd6104f8a4ab6af7f6be2a0da6be86b9de3c401f6e86bb856ab2af739232f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -1451,18 +1537,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -1475,14 +1561,14 @@ checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1500,6 +1586,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "strsim" version = "0.10.0" @@ -1508,9 +1600,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece519cfaf36269ea69d16c363fa1d59ceba8296bbfbfc003c3176d01f2816ee" dependencies = [ "proc-macro2", "quote", @@ -1519,68 +1622,67 @@ dependencies = [ [[package]] name = "sys-locale" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3358acbb4acd4146138b9bda219e904a6bb5aaaa237f8eed06f4d6bc1580ecee" +checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee" dependencies = [ "js-sys", "libc", "wasm-bindgen", "web-sys", - "winapi", + "windows-sys 0.45.0", ] [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.6", ] [[package]] @@ -1596,11 +1698,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa 1.0.5", + "itoa", "serde", "time-core", "time-macros", @@ -1614,9 +1716,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -1632,15 +1734,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.1" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -1651,7 +1753,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1662,14 +1764,14 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -1688,9 +1790,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -1740,15 +1842,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -1818,9 +1920,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1828,24 +1930,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -1855,9 +1957,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1865,28 +1967,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -1942,6 +2044,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -1958,46 +2069,70 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "winreg" @@ -2007,3 +2142,12 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "xattr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +dependencies = [ + "libc", +] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e56cf71..f86a98f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -7,12 +7,12 @@ edition = "2021" [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = { version = "4.0", features = ["derive", "string"] } +clap = { version = "4.1", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = "0.2" -csv = "1.1" +crunchyroll-rs = { version = "0.3", features = ["dash-stream"] } ctrlc = "3.2" -dirs = "4.0" +dirs = "5.0" +derive_setters = "0.1" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } @@ -23,9 +23,9 @@ serde = "1.0" serde_json = "1.0" shlex = "1.1" signal-hook = "0.3" -tempfile = "3.3" +tempfile = "3.4" terminal_size = "0.2" -tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.26", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.2" [build-dependencies] diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs new file mode 100644 index 0000000..3583996 --- /dev/null +++ b/crunchy-cli-core/src/archive/command.rs @@ -0,0 +1,184 @@ +use crate::archive::filter::ArchiveFilter; +use crate::utils::context::Context; +use crate::utils::download::MergeBehavior; +use crate::utils::ffmpeg::FFmpegPreset; +use crate::utils::filter::Filter; +use crate::utils::format::formats_visual_output; +use crate::utils::locale::all_locale_in_locales; +use crate::utils::log::progress; +use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; +use crate::utils::parse::parse_url; +use crate::Execute; +use anyhow::bail; +use anyhow::Result; +use crunchyroll_rs::media::Resolution; +use crunchyroll_rs::Locale; +use log::debug; +use std::path::PathBuf; + +#[derive(Clone, Debug, clap::Parser)] +#[clap(about = "Archive a video")] +#[command(arg_required_else_help(true))] +#[command()] +pub struct Archive { + #[arg(help = format!("Audio languages. Can be used multiple times. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Audio languages. Can be used multiple times. \ + Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] + pub(crate) locale: Vec<Locale>, + #[arg(help = format!("Subtitle languages. Can be used multiple times. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(short, long, default_values_t = Locale::all())] + pub(crate) subtitle: Vec<Locale>, + + #[arg(help = "Name of the output file")] + #[arg(long_help = "Name of the output file.\ + If you use one of the following pattern they will get replaced:\n \ + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {season_number} โ†’ Number of the season\n \ + {episode_number} โ†’ Number of the episode\n \ + {relative_episode_number} โ†’ Number of the episode relative to its season\ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] + #[arg(short, long, default_value = "{title}.mkv")] + pub(crate) output: String, + + #[arg(help = "Video resolution")] + #[arg(long_help = "The video resolution.\ + Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ + Specifying the exact pixels is not recommended, use one of the other options instead. \ + Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ + The available common-use words are 'best' (choose the best resolution available) and 'worst' (worst resolution available)")] + #[arg(short, long, default_value = "best")] + #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] + pub(crate) resolution: Resolution, + + #[arg( + help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio' and 'video'" + )] + #[arg( + long_help = "Because of local restrictions (or other reasons) some episodes with different languages does not have the same length (e.g. when some scenes were cut out). \ + With this flag you can set the behavior when handling multiple language. + Valid options are 'audio' (stores one video and all other languages as audio only), 'video' (stores the video + audio for every language) and 'auto' (detects if videos differ in length: if so, behave like 'video' else like 'audio')" + )] + #[arg(short, long, default_value = "auto")] + #[arg(value_parser = MergeBehavior::parse)] + pub(crate) merge: MergeBehavior, + + #[arg(help = format!("Presets for video converting. Can be used multiple times. \ + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] + #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ + Generally used to minify the file size with keeping (nearly) the same quality. \ + It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] + #[arg(long)] + #[arg(value_parser = FFmpegPreset::parse)] + pub(crate) ffmpeg_preset: Option<FFmpegPreset>, + + #[arg( + help = "Set which subtitle language should be set as default / auto shown when starting a video" + )] + #[arg(long)] + pub(crate) default_subtitle: Option<Locale>, + + #[arg(help = "Skip files which are already existing")] + #[arg(long, default_value_t = false)] + pub(crate) skip_existing: bool, + + #[arg(help = "Crunchyroll series url(s)")] + pub(crate) urls: Vec<String>, +} + +#[async_trait::async_trait(?Send)] +impl Execute for Archive { + fn pre_check(&mut self) -> Result<()> { + if !has_ffmpeg() { + bail!("FFmpeg is needed to run this command") + } else if PathBuf::from(&self.output) + .extension() + .unwrap_or_default() + .to_string_lossy() + != "mkv" + && !is_special_file(PathBuf::from(&self.output)) + { + bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") + } + + self.locale = all_locale_in_locales(self.locale.clone()); + self.subtitle = all_locale_in_locales(self.subtitle.clone()); + + Ok(()) + } + + async fn execute(self, ctx: Context) -> Result<()> { + let mut parsed_urls = vec![]; + + for (i, url) in self.urls.clone().into_iter().enumerate() { + let progress_handler = progress!("Parsing url {}", i + 1); + match parse_url(&ctx.crunchy, url.clone(), true).await { + Ok((media_collection, url_filter)) => { + progress_handler.stop(format!("Parsed url {}", i + 1)); + parsed_urls.push((media_collection, url_filter)) + } + Err(e) => bail!("url {} could not be parsed: {}", url, e), + }; + } + + for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { + let progress_handler = progress!("Fetching series details"); + let archive_formats = ArchiveFilter::new(url_filter, self.clone()) + .visit(media_collection) + .await?; + + if archive_formats.is_empty() { + progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); + continue; + } + progress_handler.stop(format!("Loaded series information for url {}", i + 1)); + + formats_visual_output(archive_formats.iter().map(|(_, f)| f).collect()); + + for (downloader, mut format) in archive_formats { + let formatted_path = format.format_path((&self.output).into(), true); + let (path, changed) = free_file(formatted_path.clone()); + + if changed && self.skip_existing { + debug!( + "Skipping already existing file '{}'", + formatted_path.to_string_lossy() + ); + continue; + } + + format.locales.sort_by(|(a, _), (b, _)| { + self.locale + .iter() + .position(|l| l == a) + .cmp(&self.locale.iter().position(|l| l == b)) + }); + for (_, subtitles) in format.locales.iter_mut() { + subtitles.sort_by(|a, b| { + self.subtitle + .iter() + .position(|l| l == a) + .cmp(&self.subtitle.iter().position(|l| l == b)) + }) + } + + format.visual_output(&path); + + downloader.download(&ctx, &path).await? + } + } + + Ok(()) + } +} diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs new file mode 100644 index 0000000..d0db3d5 --- /dev/null +++ b/crunchy-cli-core/src/archive/filter.rs @@ -0,0 +1,482 @@ +use crate::archive::command::Archive; +use crate::utils::download::{DownloadBuilder, DownloadFormat, Downloader, MergeBehavior}; +use crate::utils::filter::{real_dedup_vec, Filter}; +use crate::utils::format::{Format, SingleFormat}; +use crate::utils::parse::UrlFilter; +use crate::utils::video::variant_data_from_stream; +use anyhow::{bail, Result}; +use chrono::Duration; +use crunchyroll_rs::media::{Subtitle, VariantData}; +use crunchyroll_rs::{Concert, Episode, Locale, Movie, MovieListing, MusicVideo, Season, Series}; +use log::warn; +use std::collections::HashMap; +use std::hash::Hash; + +pub(crate) struct FilterResult { + format: SingleFormat, + video: VariantData, + audio: VariantData, + duration: Duration, + subtitles: Vec<Subtitle>, +} + +enum Visited { + Series, + Season, + None, +} + +pub(crate) struct ArchiveFilter { + url_filter: UrlFilter, + archive: Archive, + season_episode_count: HashMap<u32, Vec<String>>, + season_subtitles_missing: Vec<u32>, + visited: Visited, +} + +impl ArchiveFilter { + pub(crate) fn new(url_filter: UrlFilter, archive: Archive) -> Self { + Self { + url_filter, + archive, + season_episode_count: HashMap::new(), + season_subtitles_missing: vec![], + visited: Visited::None, + } + } +} + +#[async_trait::async_trait] +impl Filter for ArchiveFilter { + type T = Vec<FilterResult>; + type Output = (Downloader, Format); + + async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { + // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the + // audio is matching only if the field is populated + if !series.audio_locales.is_empty() { + let missing_audio = missing_locales(&series.audio_locales, &self.archive.locale); + if !missing_audio.is_empty() { + warn!( + "Series {} is not available with {} audio", + series.title, + missing_audio + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + let missing_subtitle = + missing_locales(&series.subtitle_locales, &self.archive.subtitle); + if !missing_subtitle.is_empty() { + warn!( + "Series {} is not available with {} subtitles", + series.title, + missing_subtitle + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + self.visited = Visited::Series + } + Ok(series.seasons().await?) + } + + async fn visit_season(&mut self, mut season: Season) -> Result<Vec<Episode>> { + if !self.url_filter.is_season_valid(season.season_number) { + return Ok(vec![]); + } + + let mut seasons = season.version(self.archive.locale.clone()).await?; + if self + .archive + .locale + .iter() + .any(|l| season.audio_locales.contains(l)) + { + seasons.insert(0, season.clone()); + } + + if !matches!(self.visited, Visited::Series) { + let mut audio_locales: Vec<Locale> = seasons + .iter() + .map(|s| s.audio_locales.clone()) + .flatten() + .collect(); + real_dedup_vec(&mut audio_locales); + let missing_audio = missing_locales(&audio_locales, &self.archive.locale); + if !missing_audio.is_empty() { + warn!( + "Season {} is not available with {} audio", + season.season_number, + missing_audio + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + + let subtitle_locales: Vec<Locale> = seasons + .iter() + .map(|s| s.subtitle_locales.clone()) + .flatten() + .collect(); + let missing_subtitle = missing_locales(&subtitle_locales, &self.archive.subtitle); + if !missing_subtitle.is_empty() { + warn!( + "Season {} is not available with {} subtitles", + season.season_number, + missing_subtitle + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + self.visited = Visited::Season + } + + let mut episodes = vec![]; + for season in seasons { + episodes.extend(season.episodes().await?) + } + + if Format::has_relative_episodes_fmt(&self.archive.output) { + for episode in episodes.iter() { + self.season_episode_count + .entry(episode.season_number) + .or_insert(vec![]) + .push(episode.id.clone()) + } + } + + Ok(episodes) + } + + async fn visit_episode(&mut self, mut episode: Episode) -> Result<Option<Self::T>> { + if !self + .url_filter + .is_episode_valid(episode.episode_number, episode.season_number) + { + return Ok(None); + } + + let mut episodes = vec![]; + if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) { + episodes.extend(episode.version(self.archive.locale.clone()).await?); + let audio_locales: Vec<Locale> = + episodes.iter().map(|e| e.audio_locale.clone()).collect(); + let missing_audio = missing_locales(&audio_locales, &self.archive.locale); + if !missing_audio.is_empty() { + warn!( + "Episode {} is not available with {} audio", + episode.episode_number, + missing_audio + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + + let mut subtitle_locales: Vec<Locale> = episodes + .iter() + .map(|e| e.subtitle_locales.clone()) + .flatten() + .collect(); + real_dedup_vec(&mut subtitle_locales); + let missing_subtitles = missing_locales(&subtitle_locales, &self.archive.subtitle); + if !missing_subtitles.is_empty() + && !self + .season_subtitles_missing + .contains(&episode.season_number) + { + warn!( + "Episode {} is not available with {} subtitles", + episode.episode_number, + missing_subtitles + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ); + self.season_subtitles_missing.push(episode.season_number) + } + } else { + episodes.push(episode.clone()) + } + + let mut formats = vec![]; + for episode in episodes { + let stream = episode.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.archive.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) is not available for episode {} ({}) of {} season {}", + &self.archive.resolution, + episode.episode_number, + episode.title, + episode.series_title, + episode.season_number, + ); + }; + let subtitles: Vec<Subtitle> = self + .archive + .subtitle + .iter() + .filter_map(|s| stream.subtitles.get(s).cloned()) + .collect(); + + let relative_episode_number = if Format::has_relative_episodes_fmt(&self.archive.output) + { + if self + .season_episode_count + .get(&episode.season_number) + .is_none() + { + let season_episodes = episode.season().await?.episodes().await?; + self.season_episode_count.insert( + episode.season_number, + season_episodes.into_iter().map(|e| e.id).collect(), + ); + } + let relative_episode_number = self + .season_episode_count + .get(&episode.season_number) + .unwrap() + .iter() + .position(|id| id == &episode.id); + if relative_episode_number.is_none() { + warn!( + "Failed to get relative episode number for episode {} ({}) of {} season {}", + episode.episode_number, + episode.title, + episode.series_title, + episode.season_number, + ) + } + relative_episode_number + } else { + None + }; + + formats.push(FilterResult { + format: SingleFormat::new_from_episode( + &episode, + &video, + subtitles.iter().map(|s| s.locale.clone()).collect(), + relative_episode_number.map(|n| n as u32), + ), + video, + audio, + duration: episode.duration.clone(), + subtitles, + }) + } + + Ok(Some(formats)) + } + + async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { + Ok(movie_listing.movies().await?) + } + + async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>> { + let stream = movie.streams().await?; + let subtitles: Vec<&Subtitle> = self + .archive + .subtitle + .iter() + .filter_map(|l| stream.subtitles.get(l)) + .collect(); + + let missing_subtitles = missing_locales( + &subtitles.iter().map(|&s| s.locale.clone()).collect(), + &self.archive.subtitle, + ); + if !missing_subtitles.is_empty() { + warn!( + "Movie '{}' is not available with {} subtitles", + movie.title, + missing_subtitles + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.archive.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) of movie {} is not available", + self.archive.resolution, + movie.title + ) + }; + + Ok(Some(vec![FilterResult { + format: SingleFormat::new_from_movie(&movie, &video, vec![]), + video, + audio, + duration: movie.duration, + subtitles: vec![], + }])) + } + + async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>> { + let stream = music_video.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.archive.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) of music video {} is not available", + self.archive.resolution, + music_video.title + ) + }; + + Ok(Some(vec![FilterResult { + format: SingleFormat::new_from_music_video(&music_video, &video), + video, + audio, + duration: music_video.duration, + subtitles: vec![], + }])) + } + + async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>> { + let stream = concert.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.archive.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}x{}) of music video {} is not available", + self.archive.resolution.width, + self.archive.resolution.height, + concert.title + ) + }; + + Ok(Some(vec![FilterResult { + format: SingleFormat::new_from_concert(&concert, &video), + video, + audio, + duration: concert.duration, + subtitles: vec![], + }])) + } + + async fn finish(self, input: Vec<Self::T>) -> Result<Vec<Self::Output>> { + let flatten_input: Vec<FilterResult> = input.into_iter().flatten().collect(); + + #[derive(Hash, Eq, PartialEq)] + struct SortKey { + season: u32, + episode: String, + } + + let mut sorted: HashMap<SortKey, Vec<FilterResult>> = HashMap::new(); + for data in flatten_input { + sorted + .entry(SortKey { + season: data.format.season_number, + episode: data.format.episode_number.to_string(), + }) + .or_insert(vec![]) + .push(data) + } + + let mut values: Vec<Vec<FilterResult>> = sorted.into_values().collect(); + values.sort_by(|a, b| { + a.first() + .unwrap() + .format + .sequence_number + .total_cmp(&b.first().unwrap().format.sequence_number) + }); + + let mut result = vec![]; + for data in values { + let single_formats: Vec<SingleFormat> = + data.iter().map(|fr| fr.format.clone()).collect(); + let format = Format::from_single_formats(single_formats); + + let mut downloader = DownloadBuilder::new() + .default_subtitle(self.archive.default_subtitle.clone()) + .ffmpeg_preset(self.archive.ffmpeg_preset.clone().unwrap_or_default()) + .output_format(Some("matroska".to_string())) + .audio_sort(Some(self.archive.locale.clone())) + .subtitle_sort(Some(self.archive.subtitle.clone())) + .build(); + + match self.archive.merge.clone() { + MergeBehavior::Video => { + for d in data { + downloader.add_format(DownloadFormat { + video: (d.video, d.format.audio.clone()), + audios: vec![(d.audio, d.format.audio.clone())], + subtitles: d.subtitles, + }) + } + } + MergeBehavior::Audio => downloader.add_format(DownloadFormat { + video: ( + data.first().unwrap().video.clone(), + data.first().unwrap().format.audio.clone(), + ), + audios: data + .iter() + .map(|d| (d.audio.clone(), d.format.audio.clone())) + .collect(), + subtitles: data.iter().map(|d| d.subtitles.clone()).flatten().collect(), + }), + MergeBehavior::Auto => { + let mut download_formats: HashMap<Duration, DownloadFormat> = HashMap::new(); + + for d in data { + if let Some(download_format) = download_formats.get_mut(&d.duration) { + download_format.audios.push((d.audio, d.format.audio)); + download_format.subtitles.extend(d.subtitles) + } else { + download_formats.insert( + d.duration, + DownloadFormat { + video: (d.video, d.format.audio.clone()), + audios: vec![(d.audio, d.format.audio)], + subtitles: d.subtitles, + }, + ); + } + } + + for download_format in download_formats.into_values() { + downloader.add_format(download_format) + } + } + } + + result.push((downloader, format)) + } + + Ok(result) + } +} + +fn missing_locales<'a>(available: &Vec<Locale>, searched: &'a Vec<Locale>) -> Vec<&'a Locale> { + searched.iter().filter(|p| !available.contains(p)).collect() +} diff --git a/crunchy-cli-core/src/archive/mod.rs b/crunchy-cli-core/src/archive/mod.rs new file mode 100644 index 0000000..c3544a4 --- /dev/null +++ b/crunchy-cli-core/src/archive/mod.rs @@ -0,0 +1,4 @@ +mod command; +mod filter; + +pub use command::Archive; diff --git a/crunchy-cli-core/src/cli/archive.rs b/crunchy-cli-core/src/cli/archive.rs deleted file mode 100644 index e500f21..0000000 --- a/crunchy-cli-core/src/cli/archive.rs +++ /dev/null @@ -1,618 +0,0 @@ -use crate::cli::log::tab_info; -use crate::cli::utils::{ - all_locale_in_locales, download_segments, find_multiple_seasons_with_same_number, - find_resolution, interactive_season_choosing, FFmpegPreset, -}; -use crate::utils::context::Context; -use crate::utils::format::Format; -use crate::utils::log::progress; -use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; -use crate::utils::parse::{parse_url, UrlFilter}; -use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; -use crate::utils::subtitle::{download_subtitle, Subtitle}; -use crate::utils::video::get_video_length; -use crate::Execute; -use anyhow::{bail, Result}; -use crunchyroll_rs::media::Resolution; -use crunchyroll_rs::{Locale, Media, MediaCollection, Series}; -use log::{debug, error, info}; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use tempfile::TempPath; - -#[derive(Clone, Debug)] -pub enum MergeBehavior { - Auto, - Audio, - Video, -} - -impl MergeBehavior { - fn parse(s: &str) -> Result<MergeBehavior, String> { - Ok(match s.to_lowercase().as_str() { - "auto" => MergeBehavior::Auto, - "audio" => MergeBehavior::Audio, - "video" => MergeBehavior::Video, - _ => return Err(format!("'{}' is not a valid merge behavior", s)), - }) - } -} - -#[derive(Debug, clap::Parser)] -#[clap(about = "Archive a video")] -#[command(arg_required_else_help(true))] -#[command()] -pub struct Archive { - #[arg(help = format!("Audio languages. Can be used multiple times. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] - #[arg(long_help = format!("Audio languages. Can be used multiple times. \ - Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] - #[arg(short, long, default_values_t = vec![crate::utils::locale::system_locale(), Locale::ja_JP])] - locale: Vec<Locale>, - #[arg(help = format!("Subtitle languages. Can be used multiple times. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] - #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] - #[arg(short, long, default_values_t = Locale::all())] - subtitle: Vec<Locale>, - - #[arg(help = "Name of the output file")] - #[arg(long_help = "Name of the output file.\ - If you use one of the following pattern they will get replaced:\n \ - {title} โ†’ Title of the video\n \ - {series_name} โ†’ Name of the series\n \ - {season_name} โ†’ Name of the season\n \ - {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ - {season_number} โ†’ Number of the season\n \ - {episode_number} โ†’ Number of the episode\n \ - {relative_episode_number} โ†’ Number of the episode relative to its season\ - {series_id} โ†’ ID of the series\n \ - {season_id} โ†’ ID of the season\n \ - {episode_id} โ†’ ID of the episode")] - #[arg(short, long, default_value = "{title}.mkv")] - output: String, - - #[arg(help = "Video resolution")] - #[arg(long_help = "The video resolution.\ - Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ - Specifying the exact pixels is not recommended, use one of the other options instead. \ - Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ - The available common-use words are 'best' (choose the best resolution available) and 'worst' (worst resolution available)")] - #[arg(short, long, default_value = "best")] - #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] - resolution: Resolution, - - #[arg( - help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio' and 'video'" - )] - #[arg( - long_help = "Because of local restrictions (or other reasons) some episodes with different languages does not have the same length (e.g. when some scenes were cut out). \ - With this flag you can set the behavior when handling multiple language. - Valid options are 'audio' (stores one video and all other languages as audio only), 'video' (stores the video + audio for every language) and 'auto' (detects if videos differ in length: if so, behave like 'video' else like 'audio')" - )] - #[arg(short, long, default_value = "auto")] - #[arg(value_parser = MergeBehavior::parse)] - merge: MergeBehavior, - - #[arg(help = format!("Presets for video converting. Can be used multiple times. \ - Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] - #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ - Generally used to minify the file size with keeping (nearly) the same quality. \ - It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ - Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] - #[arg(long)] - #[arg(value_parser = FFmpegPreset::parse)] - ffmpeg_preset: Option<FFmpegPreset>, - - #[arg( - help = "Set which subtitle language should be set as default / auto shown when starting a video" - )] - #[arg(long)] - default_subtitle: Option<Locale>, - - #[arg(help = "Skip files which are already existing")] - #[arg(long, default_value_t = false)] - skip_existing: bool, - - #[arg(help = "Ignore interactive input")] - #[arg(short, long, default_value_t = false)] - yes: bool, - - #[arg(help = "Crunchyroll series url(s)")] - urls: Vec<String>, -} - -#[async_trait::async_trait(?Send)] -impl Execute for Archive { - fn pre_check(&mut self) -> Result<()> { - if !has_ffmpeg() { - bail!("FFmpeg is needed to run this command") - } else if PathBuf::from(&self.output) - .extension() - .unwrap_or_default() - .to_string_lossy() - != "mkv" - && !is_special_file(PathBuf::from(&self.output)) - { - bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") - } - - self.locale = all_locale_in_locales(self.locale.clone()); - self.subtitle = all_locale_in_locales(self.subtitle.clone()); - - Ok(()) - } - - async fn execute(self, ctx: Context) -> Result<()> { - let mut parsed_urls = vec![]; - - for (i, url) in self.urls.iter().enumerate() { - let progress_handler = progress!("Parsing url {}", i + 1); - match parse_url(&ctx.crunchy, url.clone(), true).await { - Ok((media_collection, url_filter)) => { - parsed_urls.push((media_collection, url_filter)); - progress_handler.stop(format!("Parsed url {}", i + 1)) - } - Err(e) => bail!("url {} could not be parsed: {}", url, e), - } - } - - for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { - let progress_handler = progress!("Fetching series details"); - let archive_formats = match media_collection { - MediaCollection::Series(series) => { - formats_from_series(&self, series, &url_filter).await? - } - MediaCollection::Season(_) => bail!("Archiving a season is not supported"), - MediaCollection::Episode(episode) => bail!("Archiving a episode is not supported. Use url filtering instead to specify the episode (https://www.crunchyroll.com/series/{}/{}[S{}E{}])", episode.metadata.series_id, episode.metadata.series_slug_title, episode.metadata.season_number, episode.metadata.episode_number), - MediaCollection::MovieListing(_) => bail!("Archiving a movie listing is not supported"), - MediaCollection::Movie(_) => bail!("Archiving a movie is not supported") - }; - - if archive_formats.is_empty() { - progress_handler.stop(format!( - "Skipping url {} (no matching episodes found)", - i + 1 - )); - continue; - } - progress_handler.stop(format!("Loaded series information for url {}", i + 1)); - - if log::max_level() == log::Level::Debug { - let seasons = sort_formats_after_seasons( - archive_formats - .clone() - .into_iter() - .map(|(a, _)| a.get(0).unwrap().clone()) - .collect(), - ); - debug!("Series has {} seasons", seasons.len()); - for (i, season) in seasons.into_iter().enumerate() { - info!("Season {} ({})", i + 1, season.get(0).unwrap().season_title); - for format in season { - info!( - "{}: {}px, {:.02} FPS (S{:02}E{:02})", - format.title, - format.stream.resolution, - format.stream.fps, - format.season_number, - format.episode_number, - ) - } - } - } else { - for season in sort_formats_after_seasons( - archive_formats - .clone() - .into_iter() - .map(|(a, _)| a.get(0).unwrap().clone()) - .collect(), - ) { - let first = season.get(0).unwrap(); - info!( - "{} Season {} ({})", - first.series_name, first.season_number, first.season_title - ); - - for (i, format) in season.into_iter().enumerate() { - tab_info!( - "{}. {} ยป {}px, {:.2} FPS (S{:02}E{:02})", - i + 1, - format.title, - format.stream.resolution, - format.stream.fps, - format.season_number, - format.episode_number - ) - } - } - } - - for (formats, mut subtitles) in archive_formats { - let (primary, additionally) = formats.split_first().unwrap(); - - let formatted_path = primary.format_path((&self.output).into(), true); - let (path, changed) = free_file(formatted_path.clone()); - - if changed && self.skip_existing { - debug!( - "Skipping already existing file '{}'", - formatted_path.to_string_lossy() - ); - continue; - } - - info!( - "Downloading {} to '{}'", - primary.title, - if is_special_file(&path) { - path.to_str().unwrap() - } else { - path.file_name().unwrap().to_str().unwrap() - } - ); - tab_info!( - "Episode: S{:02}E{:02}", - primary.season_number, - primary.episode_number - ); - tab_info!( - "Audio: {} (primary), {}", - primary.audio, - additionally - .iter() - .map(|a| a.audio.to_string()) - .collect::<Vec<String>>() - .join(", ") - ); - tab_info!( - "Subtitle: {}", - subtitles - .iter() - .filter(|s| s.primary) // Don't print subtitles of non-primary streams. They might get removed depending on the merge behavior. - .map(|s| { - if let Some(default) = &self.default_subtitle { - if default == &s.stream_subtitle.locale { - return format!("{} (primary)", default); - } - } - s.stream_subtitle.locale.to_string() - }) - .collect::<Vec<String>>() - .join(", ") - ); - tab_info!("Resolution: {}", primary.stream.resolution); - tab_info!("FPS: {:.2}", primary.stream.fps); - - let mut video_paths = vec![]; - let mut audio_paths = vec![]; - let mut subtitle_paths = vec![]; - - video_paths.push((download_video(&ctx, primary, false).await?, primary)); - for additional in additionally { - let identical_video = additionally - .iter() - .all(|a| a.stream.bandwidth == primary.stream.bandwidth); - let only_audio = match self.merge { - MergeBehavior::Auto => identical_video, - MergeBehavior::Audio => true, - MergeBehavior::Video => false, - }; - let path = download_video(&ctx, additional, only_audio).await?; - if only_audio { - audio_paths.push((path, additional)) - } else { - video_paths.push((path, additional)) - } - - // Remove subtitles of forcibly deleted video - if matches!(self.merge, MergeBehavior::Audio) && !identical_video { - subtitles.retain(|s| s.episode_id != additional.episode_id); - } - } - - let (primary_video, _) = video_paths.get(0).unwrap(); - let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap(); - for subtitle in subtitles { - subtitle_paths.push(( - download_subtitle(subtitle.stream_subtitle.clone(), primary_video_length) - .await?, - subtitle, - )) - } - - let progess_handler = progress!("Generating mkv"); - generate_mkv(&self, path, video_paths, audio_paths, subtitle_paths)?; - progess_handler.stop("Mkv generated") - } - } - - Ok(()) - } -} - -async fn formats_from_series( - archive: &Archive, - series: Media<Series>, - url_filter: &UrlFilter, -) -> Result<Vec<(Vec<Format>, Vec<Subtitle>)>> { - let mut seasons = series.seasons().await?; - - // filter any season out which does not contain the specified audio languages - for season in sort_seasons_after_number(seasons.clone()) { - // get all locales which are specified but not present in the current iterated season and - // print an error saying this - let not_present_audio = archive - .locale - .clone() - .into_iter() - .filter(|l| !season.iter().any(|s| s.metadata.audio_locales.contains(l))) - .collect::<Vec<Locale>>(); - if !not_present_audio.is_empty() { - error!( - "Season {} of series {} is not available with {} audio", - season.first().unwrap().metadata.season_number, - series.title, - not_present_audio - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - // remove all seasons with the wrong audio for the current iterated season number - seasons.retain(|s| { - s.metadata.season_number != season.first().unwrap().metadata.season_number - || archive - .locale - .iter() - .any(|l| s.metadata.audio_locales.contains(l)) - }); - // remove seasons which match the url filter. this is mostly done to not trigger the - // interactive season choosing when dupilcated seasons are excluded by the filter - seasons.retain(|s| url_filter.is_season_valid(s.metadata.season_number)) - } - - if !archive.yes && !find_multiple_seasons_with_same_number(&seasons).is_empty() { - info!(target: "progress_end", "Fetched seasons"); - seasons = interactive_season_choosing(seasons); - info!(target: "progress", "Fetching series details") - } - - #[allow(clippy::type_complexity)] - let mut result: Vec<(Vec<Format>, Vec<Subtitle>)> = Vec::new(); - let mut primary_season = true; - for season in seasons { - let episodes = season.episodes().await?; - - for episode in episodes.iter() { - if !url_filter.is_episode_valid( - episode.metadata.episode_number, - episode.metadata.season_number, - ) { - continue; - } - - let streams = episode.streams().await?; - let streaming_data = streams.hls_streaming_data(None).await?; - let Some(stream) = find_resolution(streaming_data, &archive.resolution) else { - bail!( - "Resolution ({}x{}) is not available for episode {} ({}) of season {} ({}) of {}", - archive.resolution.width, - archive.resolution.height, - episode.metadata.episode_number, - episode.title, - episode.metadata.season_number, - episode.metadata.season_title, - episode.metadata.series_title - ) - }; - - let mut formats: Vec<Format> = Vec::new(); - let mut subtitles: Vec<Subtitle> = Vec::new(); - subtitles.extend(archive.subtitle.iter().filter_map(|l| { - let stream_subtitle = streams.subtitles.get(l).cloned()?; - let subtitle = Subtitle { - stream_subtitle, - audio_locale: episode.metadata.audio_locale.clone(), - episode_id: episode.id.clone(), - forced: !episode.metadata.is_subbed, - primary: primary_season, - }; - Some(subtitle) - })); - formats.push(Format::new_from_episode(episode, &episodes, stream, vec![])); - - result.push((formats, subtitles)); - } - - primary_season = false; - } - - Ok(result) -} - -async fn download_video(ctx: &Context, format: &Format, only_audio: bool) -> Result<TempPath> { - let tempfile = if only_audio { - tempfile(".aac")? - } else { - tempfile(".ts")? - }; - let (_, path) = tempfile.into_parts(); - - let ffmpeg = Command::new("ffmpeg") - .stdin(Stdio::piped()) - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .arg("-y") - .args(["-f", "mpegts"]) - .args(["-i", "pipe:"]) - .args(["-c", "copy"]) - .args(if only_audio { vec!["-vn"] } else { vec![] }) - .arg(path.to_str().unwrap()) - .spawn()?; - - download_segments( - ctx, - &mut ffmpeg.stdin.unwrap(), - Some(format!("Download {}", format.audio)), - format.stream.clone(), - ) - .await?; - - Ok(path) -} - -fn generate_mkv( - archive: &Archive, - target: PathBuf, - video_paths: Vec<(TempPath, &Format)>, - audio_paths: Vec<(TempPath, &Format)>, - subtitle_paths: Vec<(TempPath, Subtitle)>, -) -> Result<()> { - let mut input = vec![]; - let mut maps = vec![]; - let mut metadata = vec![]; - let mut dispositions = vec![vec![]; subtitle_paths.len()]; - - for (i, (video_path, format)) in video_paths.iter().enumerate() { - input.extend(["-i".to_string(), video_path.to_string_lossy().to_string()]); - maps.extend(["-map".to_string(), i.to_string()]); - metadata.extend([ - format!("-metadata:s:v:{}", i), - format!("language={}", format.audio), - ]); - metadata.extend([ - format!("-metadata:s:v:{}", i), - format!("title={}", format.audio.to_human_readable()), - ]); - metadata.extend([ - format!("-metadata:s:a:{}", i), - format!("language={}", format.audio), - ]); - metadata.extend([ - format!("-metadata:s:a:{}", i), - format!("title={}", format.audio.to_human_readable()), - ]); - } - for (i, (audio_path, format)) in audio_paths.iter().enumerate() { - input.extend(["-i".to_string(), audio_path.to_string_lossy().to_string()]); - maps.extend(["-map".to_string(), (i + video_paths.len()).to_string()]); - metadata.extend([ - format!("-metadata:s:a:{}", i + video_paths.len()), - format!("language={}", format.audio), - ]); - metadata.extend([ - format!("-metadata:s:a:{}", i + video_paths.len()), - format!("title={}", format.audio.to_human_readable()), - ]); - } - for (i, (subtitle_path, subtitle)) in subtitle_paths.iter().enumerate() { - input.extend([ - "-i".to_string(), - subtitle_path.to_string_lossy().to_string(), - ]); - maps.extend([ - "-map".to_string(), - (i + video_paths.len() + audio_paths.len()).to_string(), - ]); - metadata.extend([ - format!("-metadata:s:s:{}", i), - format!("language={}", subtitle.stream_subtitle.locale), - ]); - metadata.extend([ - format!("-metadata:s:s:{}", i), - format!( - "title={}", - subtitle.stream_subtitle.locale.to_human_readable() - + if !subtitle.primary { - format!(" [Video: {}]", subtitle.audio_locale.to_human_readable()) - } else { - "".to_string() - } - .as_str() - ), - ]); - - // mark forced subtitles - if subtitle.forced { - dispositions[i].push("forced"); - } - } - - let (input_presets, output_presets) = if let Some(preset) = archive.ffmpeg_preset.clone() { - preset.to_input_output_args() - } else { - ( - vec![], - vec![ - "-c:v".to_string(), - "copy".to_string(), - "-c:a".to_string(), - "copy".to_string(), - ], - ) - }; - - let mut command_args = vec!["-y".to_string()]; - command_args.extend(input_presets); - command_args.extend(input); - command_args.extend(maps); - command_args.extend(metadata); - - // set default subtitle - if let Some(default_subtitle) = &archive.default_subtitle { - // if `--default_subtitle <locale>` is given set the default subtitle to the given locale - if let Some(position) = subtitle_paths - .iter() - .position(|(_, subtitle)| &subtitle.stream_subtitle.locale == default_subtitle) - { - dispositions[position].push("default"); - } - } - - let disposition_args: Vec<String> = dispositions - .iter() - .enumerate() - .flat_map(|(i, d)| { - vec![ - format!("-disposition:s:{}", i), - if !d.is_empty() { - d.join("+") - } else { - "0".to_string() - }, - ] - }) - .collect(); - command_args.extend(disposition_args); - - command_args.extend(output_presets); - command_args.extend([ - "-f".to_string(), - "matroska".to_string(), - target.to_string_lossy().to_string(), - ]); - - debug!("ffmpeg {}", command_args.join(" ")); - - // create parent directory if it does not exist - if let Some(parent) = target.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent)? - } - } - - let ffmpeg = Command::new("ffmpeg") - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .args(command_args) - .output()?; - if !ffmpeg.status.success() { - bail!("{}", String::from_utf8_lossy(ffmpeg.stderr.as_slice())) - } - - Ok(()) -} diff --git a/crunchy-cli-core/src/cli/download.rs b/crunchy-cli-core/src/cli/download.rs deleted file mode 100644 index b4c037c..0000000 --- a/crunchy-cli-core/src/cli/download.rs +++ /dev/null @@ -1,603 +0,0 @@ -use crate::cli::log::tab_info; -use crate::cli::utils::{ - download_segments, find_multiple_seasons_with_same_number, find_resolution, - interactive_season_choosing, FFmpegPreset, -}; -use crate::utils::context::Context; -use crate::utils::format::Format; -use crate::utils::log::progress; -use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; -use crate::utils::parse::{parse_url, UrlFilter}; -use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; -use crate::utils::subtitle::download_subtitle; -use crate::utils::video::get_video_length; -use crate::Execute; -use anyhow::{bail, Result}; -use crunchyroll_rs::media::{Resolution, StreamSubtitle, VariantData}; -use crunchyroll_rs::{ - Episode, Locale, Media, MediaCollection, Movie, MovieListing, Season, Series, -}; -use log::{debug, error, info, warn}; -use std::borrow::Cow; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; - -#[derive(Debug, clap::Parser)] -#[clap(about = "Download a video")] -#[command(arg_required_else_help(true))] -pub struct Download { - #[arg(help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] - #[arg(long_help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ - Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] - #[arg(short, long, default_value_t = crate::utils::locale::system_locale())] - audio: Locale, - #[arg(help = format!("Subtitle language. Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] - #[arg(long_help = format!("Subtitle language. If set, the subtitle will be burned into the video and cannot be disabled. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] - #[arg(short, long)] - subtitle: Option<Locale>, - - #[arg(help = "Name of the output file")] - #[arg(long_help = "Name of the output file.\ - If you use one of the following pattern they will get replaced:\n \ - {title} โ†’ Title of the video\n \ - {series_name} โ†’ Name of the series\n \ - {season_name} โ†’ Name of the season\n \ - {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ - {season_number} โ†’ Number of the season\n \ - {episode_number} โ†’ Number of the episode\n \ - {relative_episode_number} โ†’ Number of the episode relative to its season\ - {series_id} โ†’ ID of the series\n \ - {season_id} โ†’ ID of the season\n \ - {episode_id} โ†’ ID of the episode")] - #[arg(short, long, default_value = "{title}.mp4")] - output: String, - - #[arg(help = "Video resolution")] - #[arg(long_help = "The video resolution.\ - Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ - Specifying the exact pixels is not recommended, use one of the other options instead. \ - Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ - The available common-use words are 'best' (choose the best resolution available) and 'worst' (worst resolution available)")] - #[arg(short, long, default_value = "best")] - #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] - resolution: Resolution, - - #[arg(help = format!("Presets for video converting. Can be used multiple times. \ - Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] - #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ - Generally used to minify the file size with keeping (nearly) the same quality. \ - It is recommended to only use this if you download videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ - Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] - #[arg(long)] - #[arg(value_parser = FFmpegPreset::parse)] - ffmpeg_preset: Option<FFmpegPreset>, - - #[arg(help = "Skip files which are already existing")] - #[arg(long, default_value_t = false)] - skip_existing: bool, - - #[arg(help = "Ignore interactive input")] - #[arg(short, long, default_value_t = false)] - yes: bool, - - #[arg(help = "Url(s) to Crunchyroll episodes or series")] - urls: Vec<String>, -} - -#[async_trait::async_trait(?Send)] -impl Execute for Download { - fn pre_check(&mut self) -> Result<()> { - if !has_ffmpeg() { - bail!("FFmpeg is needed to run this command") - } else if Path::new(&self.output) - .extension() - .unwrap_or_default() - .is_empty() - && self.output != "-" - { - bail!("No file extension found. Please specify a file extension (via `-o`) for the output file") - } - - if self.subtitle.is_some() { - if let Some(ext) = Path::new(&self.output).extension() { - if ext.to_string_lossy() != "mp4" { - warn!("Detected a non mp4 output container. Adding subtitles may take a while") - } - } - } - - Ok(()) - } - - async fn execute(self, ctx: Context) -> Result<()> { - let mut parsed_urls = vec![]; - - for (i, url) in self.urls.iter().enumerate() { - let progress_handler = progress!("Parsing url {}", i + 1); - match parse_url(&ctx.crunchy, url.clone(), true).await { - Ok((media_collection, url_filter)) => { - parsed_urls.push((media_collection, url_filter)); - progress_handler.stop(format!("Parsed url {}", i + 1)) - } - Err(e) => bail!("url {} could not be parsed: {}", url, e), - } - } - - for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { - let progress_handler = progress!("Fetching series details"); - let formats = match media_collection { - MediaCollection::Series(series) => { - debug!("Url {} is series ({})", i + 1, series.title); - formats_from_series(&self, series, &url_filter).await? - } - MediaCollection::Season(season) => { - debug!( - "Url {} is season {} ({})", - i + 1, - season.metadata.season_number, - season.title - ); - formats_from_season(&self, season, &url_filter).await? - } - MediaCollection::Episode(episode) => { - debug!( - "Url {} is episode {} ({}) of season {} ({}) of {}", - i + 1, - episode.metadata.episode_number, - episode.title, - episode.metadata.season_number, - episode.metadata.season_title, - episode.metadata.series_title - ); - format_from_episode(&self, &episode, &url_filter, None, false) - .await? - .map(|fmt| vec![fmt]) - } - MediaCollection::MovieListing(movie_listing) => { - debug!("Url {} is movie listing ({})", i + 1, movie_listing.title); - format_from_movie_listing(&self, movie_listing, &url_filter).await? - } - MediaCollection::Movie(movie) => { - debug!("Url {} is movie ({})", i + 1, movie.title); - format_from_movie(&self, movie, &url_filter) - .await? - .map(|fmt| vec![fmt]) - } - }; - - let Some(formats) = formats else { - progress_handler.stop(format!("Skipping url {} (no matching episodes found)", i + 1)); - continue; - }; - progress_handler.stop(format!("Loaded series information for url {}", i + 1)); - - if log::max_level() == log::Level::Debug { - let seasons = sort_formats_after_seasons(formats.clone()); - debug!("Series has {} seasons", seasons.len()); - for (i, season) in seasons.into_iter().enumerate() { - info!("Season {} ({})", i + 1, season.get(0).unwrap().season_title); - for format in season { - info!( - "{}: {}px, {:.02} FPS (S{:02}E{:02})", - format.title, - format.stream.resolution, - format.stream.fps, - format.season_number, - format.episode_number, - ) - } - } - } else { - for season in sort_formats_after_seasons(formats.clone()) { - let first = season.get(0).unwrap(); - info!( - "{} Season {} ({})", - first.series_name, first.season_number, first.season_title - ); - - for (i, format) in season.into_iter().enumerate() { - tab_info!( - "{}. {} ยป {}px, {:.2} FPS (S{:02}E{:02})", - i + 1, - format.title, - format.stream.resolution, - format.stream.fps, - format.season_number, - format.episode_number - ) - } - } - } - - for format in formats { - let formatted_path = format.format_path((&self.output).into(), true); - let (path, changed) = free_file(formatted_path.clone()); - - if changed && self.skip_existing { - debug!( - "Skipping already existing file '{}'", - formatted_path.to_string_lossy() - ); - continue; - } - - info!( - "Downloading {} to '{}'", - format.title, - if is_special_file(&path) { - path.to_str().unwrap() - } else { - path.file_name().unwrap().to_str().unwrap() - } - ); - tab_info!( - "Episode: S{:02}E{:02}", - format.season_number, - format.episode_number - ); - tab_info!("Audio: {}", format.audio); - tab_info!( - "Subtitles: {}", - self.subtitle - .clone() - .map_or("None".to_string(), |l| l.to_string()) - ); - tab_info!("Resolution: {}", format.stream.resolution); - tab_info!("FPS: {:.2}", format.stream.fps); - - download_ffmpeg( - &ctx, - &self, - format.stream, - format.subtitles.get(0).cloned(), - path.to_path_buf(), - ) - .await?; - } - } - - Ok(()) - } -} - -async fn download_ffmpeg( - ctx: &Context, - download: &Download, - variant_data: VariantData, - subtitle: Option<StreamSubtitle>, - mut target: PathBuf, -) -> Result<()> { - let (input_presets, mut output_presets) = if let Some(preset) = download.ffmpeg_preset.clone() { - preset.to_input_output_args() - } else { - ( - vec![], - vec![ - "-c:v".to_string(), - "copy".to_string(), - "-c:a".to_string(), - "copy".to_string(), - ], - ) - }; - - // create parent directory if it does not exist - if let Some(parent) = target.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent)? - } - } - - let mut video_file = tempfile(".ts")?; - download_segments(ctx, &mut video_file, None, variant_data).await?; - let subtitle_file = if let Some(ref sub) = subtitle { - let video_len = get_video_length(video_file.path().to_path_buf())?; - Some(download_subtitle(sub.clone(), video_len).await?) - } else { - None - }; - - let stdout_tempfile = if target.to_string_lossy() == "-" { - let file = tempfile(".mp4")?; - target = file.path().to_path_buf(); - - Some(file) - } else { - None - }; - - let subtitle_presets = if let Some(sub_file) = &subtitle_file { - if target.extension().unwrap_or_default().to_string_lossy() == "mp4" { - vec![ - "-i".to_string(), - sub_file.to_string_lossy().to_string(), - "-movflags".to_string(), - "faststart".to_string(), - "-c:s".to_string(), - "mov_text".to_string(), - "-disposition:s:s:0".to_string(), - "forced".to_string(), - ] - } else { - // remove '-c:v copy' and '-c:a copy' from output presets as its causes issues with - // burning subs into the video - let mut last = String::new(); - let mut remove_count = 0; - for (i, s) in output_presets.clone().iter().enumerate() { - if (last == "-c:v" || last == "-c:a") && s == "copy" { - // remove last - output_presets.remove(i - remove_count - 1); - remove_count += 1; - output_presets.remove(i - remove_count); - remove_count += 1; - } - last = s.clone(); - } - - vec![ - "-vf".to_string(), - format!("subtitles={}", sub_file.to_string_lossy()), - ] - } - } else { - vec![] - }; - - let mut ffmpeg = Command::new("ffmpeg") - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .arg("-y") - .args(input_presets) - .args(["-i", video_file.path().to_string_lossy().as_ref()]) - .args(subtitle_presets) - .args(output_presets) - .arg(target.to_str().unwrap()) - .spawn()?; - - let progress_handler = progress!("Generating output file"); - if !ffmpeg.wait()?.success() { - bail!("{}", std::io::read_to_string(ffmpeg.stderr.unwrap())?) - } - progress_handler.stop("Output file generated"); - - if let Some(mut stdout_file) = stdout_tempfile { - let mut stdout = std::io::stdout(); - - std::io::copy(&mut stdout_file, &mut stdout)?; - } - - Ok(()) -} - -async fn formats_from_series( - download: &Download, - series: Media<Series>, - url_filter: &UrlFilter, -) -> Result<Option<Vec<Format>>> { - if !series.metadata.audio_locales.is_empty() - && !series.metadata.audio_locales.contains(&download.audio) - { - error!( - "Series {} is not available with {} audio", - series.title, download.audio - ); - return Ok(None); - } - - let mut seasons = series.seasons().await?; - - // filter any season out which does not contain the specified audio language - for season in sort_seasons_after_number(seasons.clone()) { - // check if the current iterated season has the specified audio language - if !season - .iter() - .any(|s| s.metadata.audio_locales.contains(&download.audio)) - { - error!( - "Season {} of series {} is not available with {} audio", - season.first().unwrap().metadata.season_number, - series.title, - download.audio - ); - } - - // remove all seasons with the wrong audio for the current iterated season number - seasons.retain(|s| { - s.metadata.season_number != season.first().unwrap().metadata.season_number - || s.metadata.audio_locales.contains(&download.audio) - }); - // remove seasons which match the url filter. this is mostly done to not trigger the - // interactive season choosing when dupilcated seasons are excluded by the filter - seasons.retain(|s| url_filter.is_season_valid(s.metadata.season_number)) - } - - if !download.yes && !find_multiple_seasons_with_same_number(&seasons).is_empty() { - info!(target: "progress_end", "Fetched seasons"); - seasons = interactive_season_choosing(seasons); - info!(target: "progress", "Fetching series details") - } - - let mut formats = vec![]; - for season in seasons { - if let Some(fmts) = formats_from_season(download, season, url_filter).await? { - formats.extend(fmts) - } - } - - Ok(some_vec_or_none(formats)) -} - -async fn formats_from_season( - download: &Download, - season: Media<Season>, - url_filter: &UrlFilter, -) -> Result<Option<Vec<Format>>> { - if !url_filter.is_season_valid(season.metadata.season_number) { - return Ok(None); - } else if !season.metadata.audio_locales.contains(&download.audio) { - error!( - "Season {} ({}) is not available with {} audio", - season.metadata.season_number, season.title, download.audio - ); - return Ok(None); - } - - let mut formats = vec![]; - - let episodes = season.episodes().await?; - for episode in episodes.iter() { - if let Some(fmt) = - format_from_episode(download, &episode, url_filter, Some(&episodes), true).await? - { - formats.push(fmt) - } - } - - Ok(some_vec_or_none(formats)) -} - -async fn format_from_episode( - download: &Download, - episode: &Media<Episode>, - url_filter: &UrlFilter, - season_episodes: Option<&Vec<Media<Episode>>>, - filter_audio: bool, -) -> Result<Option<Format>> { - if filter_audio && episode.metadata.audio_locale != download.audio { - error!( - "Episode {} ({}) of season {} ({}) of {} has no {} audio", - episode.metadata.episode_number, - episode.title, - episode.metadata.season_number, - episode.metadata.season_title, - episode.metadata.series_title, - download.audio - ); - return Ok(None); - } else if !url_filter.is_episode_valid( - episode.metadata.episode_number, - episode.metadata.season_number, - ) { - return Ok(None); - } - - let streams = episode.streams().await?; - let streaming_data = streams.hls_streaming_data(None).await?; - let subtitle = if let Some(subtitle) = &download.subtitle { - if let Some(sub) = streams.subtitles.get(subtitle) { - Some(sub.clone()) - } else { - error!( - "Episode {} ({}) of season {} ({}) of {} has no {} subtitles", - episode.metadata.episode_number, - episode.title, - episode.metadata.season_number, - episode.metadata.season_title, - episode.metadata.series_title, - subtitle - ); - return Ok(None); - } - } else { - None - }; - - let Some(stream) = find_resolution(streaming_data, &download.resolution) else { - bail!( - "Resolution ({}x{}) is not available for episode {} ({}) of season {} ({}) of {}", - download.resolution.width, - download.resolution.height, - episode.metadata.episode_number, - episode.title, - episode.metadata.season_number, - episode.metadata.season_title, - episode.metadata.series_title - ) - }; - - let season_eps = if Format::has_relative_episodes_fmt(&download.output) { - if let Some(eps) = season_episodes { - Cow::from(eps) - } else { - Cow::from(episode.season().await?.episodes().await?) - } - } else { - Cow::from(vec![]) - }; - - Ok(Some(Format::new_from_episode( - episode, - &season_eps.to_vec(), - stream, - subtitle.map_or_else(|| vec![], |s| vec![s]), - ))) -} - -async fn format_from_movie_listing( - download: &Download, - movie_listing: Media<MovieListing>, - url_filter: &UrlFilter, -) -> Result<Option<Vec<Format>>> { - let mut formats = vec![]; - - for movie in movie_listing.movies().await? { - if let Some(fmt) = format_from_movie(download, movie, url_filter).await? { - formats.push(fmt) - } - } - - Ok(some_vec_or_none(formats)) -} - -async fn format_from_movie( - download: &Download, - movie: Media<Movie>, - _: &UrlFilter, -) -> Result<Option<Format>> { - let streams = movie.streams().await?; - let mut streaming_data = if let Some(subtitle) = &download.subtitle { - if !streams.subtitles.keys().cloned().any(|x| &x == subtitle) { - error!("Movie {} has no {} subtitles", movie.title, subtitle); - return Ok(None); - } - streams.hls_streaming_data(Some(subtitle.clone())).await? - } else { - streams.hls_streaming_data(None).await? - }; - - streaming_data.sort_by(|a, b| a.resolution.width.cmp(&b.resolution.width).reverse()); - let stream = { - match download.resolution.height { - u64::MAX => streaming_data.into_iter().next().unwrap(), - u64::MIN => streaming_data.into_iter().last().unwrap(), - _ => { - if let Some(streaming_data) = streaming_data.into_iter().find(|v| { - download.resolution.height == u64::MAX - || v.resolution.height == download.resolution.height - }) { - streaming_data - } else { - bail!( - "Resolution ({}x{}) is not available for movie {}", - download.resolution.width, - download.resolution.height, - movie.title - ) - } - } - } - }; - - Ok(Some(Format::new_from_movie(&movie, stream))) -} - -fn some_vec_or_none<T>(v: Vec<T>) -> Option<Vec<T>> { - if v.is_empty() { - None - } else { - Some(v) - } -} diff --git a/crunchy-cli-core/src/cli/log.rs b/crunchy-cli-core/src/cli/log.rs deleted file mode 100644 index 7147c47..0000000 --- a/crunchy-cli-core/src/cli/log.rs +++ /dev/null @@ -1,138 +0,0 @@ -use indicatif::{ProgressBar, ProgressStyle}; -use log::{ - set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, SetLoggerError, -}; -use std::io::{stdout, Write}; -use std::sync::Mutex; -use std::thread; -use std::time::Duration; - -#[allow(clippy::type_complexity)] -pub struct CliLogger { - all: bool, - level: LevelFilter, - progress: Mutex<Option<ProgressBar>>, -} - -impl Log for CliLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() <= self.level - } - - fn log(&self, record: &Record) { - if !self.enabled(record.metadata()) - || (record.target() != "progress" - && record.target() != "progress_end" - && (!self.all && !record.target().starts_with("crunchy_cli"))) - { - return; - } - - if self.level >= LevelFilter::Debug { - self.extended(record); - return; - } - - match record.target() { - "progress" => self.progress(record, false), - "progress_end" => self.progress(record, true), - _ => { - if self.progress.lock().unwrap().is_some() { - self.progress(record, false) - } else if record.level() > Level::Warn { - self.normal(record) - } else { - self.error(record) - } - } - } - } - - fn flush(&self) { - let _ = stdout().flush(); - } -} - -impl CliLogger { - pub fn new(all: bool, level: LevelFilter) -> Self { - Self { - all, - level, - progress: Mutex::new(None), - } - } - - pub fn init(all: bool, level: LevelFilter) -> Result<(), SetLoggerError> { - set_max_level(level); - set_boxed_logger(Box::new(CliLogger::new(all, level))) - } - - fn extended(&self, record: &Record) { - println!( - "[{}] {} {} ({}) {}", - chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"), - record.level(), - // replace the 'progress' prefix if this function is invoked via 'progress!' - record - .target() - .replacen("crunchy_cli_core", "crunchy_cli", 1) - .replacen("progress_end", "crunchy_cli", 1) - .replacen("progress", "crunchy_cli", 1), - format!("{:?}", thread::current().id()) - .replace("ThreadId(", "") - .replace(')', ""), - record.args() - ) - } - - fn normal(&self, record: &Record) { - println!(":: {}", record.args()) - } - - fn error(&self, record: &Record) { - eprintln!(":: {}", record.args()) - } - - fn progress(&self, record: &Record, stop: bool) { - let mut progress = self.progress.lock().unwrap(); - - let msg = format!("{}", record.args()); - if stop && progress.is_some() { - if msg.is_empty() { - progress.take().unwrap().finish() - } else { - progress.take().unwrap().finish_with_message(msg) - } - } else if let Some(p) = &*progress { - p.println(format!(":: โ†’ {}", msg)) - } else { - #[cfg(not(windows))] - let finish_str = "โœ”"; - #[cfg(windows)] - // windows does not support all unicode characters by default in their consoles, so - // we're using this (square root?) symbol instead. microsoft. - let finish_str = "โˆš"; - - let pb = ProgressBar::new_spinner(); - pb.set_style( - ProgressStyle::with_template(":: {spinner} {msg}") - .unwrap() - .tick_strings(&["โ€”", "\\", "|", "/", finish_str]), - ); - pb.enable_steady_tick(Duration::from_millis(200)); - pb.set_message(msg); - *progress = Some(pb) - } - } -} - -macro_rules! tab_info { - ($($arg:tt)+) => { - if log::max_level() == log::LevelFilter::Debug { - info!($($arg)+) - } else { - info!("\t{}", format!($($arg)+)) - } - } -} -pub(crate) use tab_info; diff --git a/crunchy-cli-core/src/cli/mod.rs b/crunchy-cli-core/src/cli/mod.rs deleted file mode 100644 index c28f8e0..0000000 --- a/crunchy-cli-core/src/cli/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod archive; -pub mod download; -pub mod log; -pub mod login; -mod utils; diff --git a/crunchy-cli-core/src/cli/utils.rs b/crunchy-cli-core/src/cli/utils.rs deleted file mode 100644 index 753cb72..0000000 --- a/crunchy-cli-core/src/cli/utils.rs +++ /dev/null @@ -1,693 +0,0 @@ -use crate::utils::context::Context; -use anyhow::{bail, Result}; -use crunchyroll_rs::media::{Resolution, VariantData, VariantSegment}; -use crunchyroll_rs::{Locale, Media, Season}; -use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; -use lazy_static::lazy_static; -use log::{debug, LevelFilter}; -use regex::Regex; -use std::borrow::{Borrow, BorrowMut}; -use std::collections::BTreeMap; -use std::env; -use std::io::{BufRead, Write}; -use std::str::FromStr; -use std::sync::{mpsc, Arc, Mutex}; -use std::time::Duration; -use tokio::task::JoinSet; - -pub fn find_resolution( - mut streaming_data: Vec<VariantData>, - resolution: &Resolution, -) -> Option<VariantData> { - streaming_data.sort_by(|a, b| a.resolution.width.cmp(&b.resolution.width).reverse()); - match resolution.height { - u64::MAX => Some(streaming_data.into_iter().next().unwrap()), - u64::MIN => Some(streaming_data.into_iter().last().unwrap()), - _ => streaming_data - .into_iter() - .find(|v| resolution.height == u64::MAX || v.resolution.height == resolution.height), - } -} - -pub async fn download_segments( - ctx: &Context, - writer: &mut impl Write, - message: Option<String>, - variant_data: VariantData, -) -> Result<()> { - let segments = variant_data.segments().await?; - let total_segments = segments.len(); - - let client = Arc::new(ctx.crunchy.client()); - let count = Arc::new(Mutex::new(0)); - - let progress = if log::max_level() == LevelFilter::Info { - let estimated_file_size = (variant_data.bandwidth / 8) - * segments - .iter() - .map(|s| s.length.unwrap_or_default().as_secs()) - .sum::<u64>(); - - let progress = ProgressBar::new(estimated_file_size) - .with_style( - ProgressStyle::with_template( - ":: {msg}{bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", - ) - .unwrap() - .progress_chars("##-"), - ) - .with_message(message.map(|m| m + " ").unwrap_or_default()) - .with_finish(ProgressFinish::Abandon); - Some(progress) - } else { - None - }; - - let cpus = num_cpus::get(); - let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); - for _ in 0..cpus { - segs.push(vec![]) - } - for (i, segment) in segments.clone().into_iter().enumerate() { - segs[i - ((i / cpus) * cpus)].push(segment); - } - - let (sender, receiver) = mpsc::channel(); - - let mut join_set: JoinSet<Result<()>> = JoinSet::new(); - for num in 0..cpus { - let thread_client = client.clone(); - let thread_sender = sender.clone(); - let thread_segments = segs.remove(0); - let thread_count = count.clone(); - join_set.spawn(async move { - let after_download_sender = thread_sender.clone(); - - // the download process is encapsulated in its own function. this is done to easily - // catch errors which get returned with `...?` and `bail!(...)` and that the thread - // itself can report that an error has occured - let download = || async move { - for (i, segment) in thread_segments.into_iter().enumerate() { - let mut retry_count = 0; - let mut buf = loop { - let response = thread_client - .get(&segment.url) - .timeout(Duration::from_secs(60)) - .send() - .await?; - - match response.bytes().await { - Ok(b) => break b.to_vec(), - Err(e) => { - if e.is_body() { - if retry_count == 5 { - bail!("Max retry count reached ({}), multiple errors occured while receiving segment {}: {}", retry_count, num + (i * cpus), e) - } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) - } else { - bail!("{}", e) - } - } - } - - retry_count += 1; - }; - - buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); - - let mut c = thread_count.lock().unwrap(); - debug!( - "Downloaded and decrypted segment [{}/{} {:.2}%] {}", - num + (i * cpus), - total_segments, - ((*c + 1) as f64 / total_segments as f64) * 100f64, - segment.url - ); - - thread_sender.send((num as i32 + (i * cpus) as i32, buf))?; - - *c += 1; - } - Ok(()) - }; - - - let result = download().await; - if result.is_err() { - after_download_sender.send((-1 as i32, vec![]))?; - } - - result - }); - } - // drop the sender already here so it does not outlive all (download) threads which are the only - // real consumers of it - drop(sender); - - // this is the main loop which writes the data. it uses a BTreeMap as a buffer as the write - // happens synchronized. the download consist of multiple segments. the map keys are representing - // the segment number and the values the corresponding bytes - let mut data_pos = 0; - let mut buf: BTreeMap<i32, Vec<u8>> = BTreeMap::new(); - for (pos, bytes) in receiver.iter() { - // if the position is lower than 0, an error occured in the sending download thread - if pos < 0 { - break; - } - - if let Some(p) = &progress { - let progress_len = p.length().unwrap(); - let estimated_segment_len = (variant_data.bandwidth / 8) - * segments - .get(pos as usize) - .unwrap() - .length - .unwrap_or_default() - .as_secs(); - let bytes_len = bytes.len() as u64; - - p.set_length(progress_len - estimated_segment_len + bytes_len); - p.inc(bytes_len) - } - - // check if the currently sent bytes are the next in the buffer. if so, write them directly - // to the target without first adding them to the buffer. - // if not, add them to the buffer - if data_pos == pos { - writer.write_all(bytes.borrow())?; - data_pos += 1; - } else { - buf.insert(pos, bytes); - } - // check if the buffer contains the next segment(s) - while let Some(b) = buf.remove(&data_pos) { - writer.write_all(b.borrow())?; - data_pos += 1; - } - } - - // if any error has occured while downloading it gets returned here - while let Some(joined) = join_set.join_next().await { - joined?? - } - - // write the remaining buffer, if existent - while let Some(b) = buf.remove(&data_pos) { - writer.write_all(b.borrow())?; - data_pos += 1; - } - - if !buf.is_empty() { - bail!( - "Download buffer is not empty. Remaining segments: {}", - buf.into_keys() - .map(|k| k.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - Ok(()) -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum FFmpegPreset { - Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality), - Custom(Option<String>, Option<String>), -} - -lazy_static! { - static ref PREDEFINED_PRESET: Regex = Regex::new(r"^\w+(-\w+)*?$").unwrap(); -} - -macro_rules! FFmpegEnum { - (enum $name:ident { $($field:ident),* }) => { - #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum $name { - $( - $field - ),*, - } - - impl $name { - fn all() -> Vec<$name> { - vec![ - $( - $name::$field - ),*, - ] - } - } - - impl ToString for $name { - fn to_string(&self) -> String { - match self { - $( - &$name::$field => stringify!($field).to_string().to_lowercase() - ),* - } - } - } - - impl FromStr for $name { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { - match s { - $( - stringify!($field) => Ok($name::$field) - ),*, - _ => bail!("{} is not a valid {}", s, stringify!($name).to_lowercase()) - } - } - } - } -} - -FFmpegEnum! { - enum FFmpegCodec { - H264, - H265, - Av1 - } -} - -FFmpegEnum! { - enum FFmpegHwAccel { - Nvidia - } -} - -FFmpegEnum! { - enum FFmpegQuality { - Lossless, - Normal, - Low - } -} - -impl FFmpegPreset { - pub(crate) fn available_matches( - ) -> Vec<(FFmpegCodec, Option<FFmpegHwAccel>, Option<FFmpegQuality>)> { - let codecs = vec![ - ( - FFmpegCodec::H264, - FFmpegHwAccel::all(), - FFmpegQuality::all(), - ), - ( - FFmpegCodec::H265, - FFmpegHwAccel::all(), - FFmpegQuality::all(), - ), - (FFmpegCodec::Av1, vec![], FFmpegQuality::all()), - ]; - - let mut return_values = vec![]; - - for (codec, hwaccels, qualities) in codecs { - return_values.push((codec.clone(), None, None)); - for hwaccel in hwaccels.clone() { - return_values.push((codec.clone(), Some(hwaccel), None)); - } - for quality in qualities.clone() { - return_values.push((codec.clone(), None, Some(quality))) - } - for hwaccel in hwaccels { - for quality in qualities.clone() { - return_values.push((codec.clone(), Some(hwaccel.clone()), Some(quality))) - } - } - } - - return_values - } - - pub(crate) fn available_matches_human_readable() -> Vec<String> { - let mut return_values = vec![]; - - for (codec, hwaccel, quality) in FFmpegPreset::available_matches() { - let mut description_details = vec![]; - if let Some(h) = &hwaccel { - description_details.push(format!("{} hardware acceleration", h.to_string())) - } - if let Some(q) = &quality { - description_details.push(format!("{} video quality/compression", q.to_string())) - } - - let description = if description_details.len() == 0 { - format!( - "{} encoded with default video quality/compression", - codec.to_string() - ) - } else if description_details.len() == 1 { - format!( - "{} encoded with {}", - codec.to_string(), - description_details[0] - ) - } else { - let first = description_details.remove(0); - let last = description_details.remove(description_details.len() - 1); - let mid = if !description_details.is_empty() { - format!(", {} ", description_details.join(", ")) - } else { - "".to_string() - }; - - format!( - "{} encoded with {}{} and {}", - codec.to_string(), - first, - mid, - last - ) - }; - - return_values.push(format!( - "{} ({})", - vec![ - Some(codec.to_string()), - hwaccel.map(|h| h.to_string()), - quality.map(|q| q.to_string()) - ] - .into_iter() - .flatten() - .collect::<Vec<String>>() - .join("-"), - description - )) - } - return_values - } - - pub(crate) fn parse(s: &str) -> Result<FFmpegPreset, String> { - let env_ffmpeg_input_args = env::var("FFMPEG_INPUT_ARGS").ok(); - let env_ffmpeg_output_args = env::var("FFMPEG_OUTPUT_ARGS").ok(); - - if env_ffmpeg_input_args.is_some() || env_ffmpeg_output_args.is_some() { - if let Some(input) = &env_ffmpeg_input_args { - if shlex::split(input).is_none() { - return Err(format!("Failed to parse custom ffmpeg input '{}' (`FFMPEG_INPUT_ARGS` env variable)", input)); - } - } - if let Some(output) = &env_ffmpeg_output_args { - if shlex::split(output).is_none() { - return Err(format!("Failed to parse custom ffmpeg output '{}' (`FFMPEG_INPUT_ARGS` env variable)", output)); - } - } - - return Ok(FFmpegPreset::Custom( - env_ffmpeg_input_args, - env_ffmpeg_output_args, - )); - } else if !PREDEFINED_PRESET.is_match(s) { - return Ok(FFmpegPreset::Custom(None, Some(s.to_string()))); - } - - let mut codec: Option<FFmpegCodec> = None; - let mut hwaccel: Option<FFmpegHwAccel> = None; - let mut quality: Option<FFmpegQuality> = None; - for token in s.split('-') { - if let Some(c) = FFmpegCodec::all() - .into_iter() - .find(|p| p.to_string() == token.to_lowercase()) - { - if let Some(cc) = codec { - return Err(format!( - "cannot use multiple codecs (found {} and {})", - cc.to_string(), - c.to_string() - )); - } - codec = Some(c) - } else if let Some(h) = FFmpegHwAccel::all() - .into_iter() - .find(|p| p.to_string() == token.to_lowercase()) - { - if let Some(hh) = hwaccel { - return Err(format!( - "cannot use multiple hardware accelerations (found {} and {})", - hh.to_string(), - h.to_string() - )); - } - hwaccel = Some(h) - } else if let Some(q) = FFmpegQuality::all() - .into_iter() - .find(|p| p.to_string() == token.to_lowercase()) - { - if let Some(qq) = quality { - return Err(format!( - "cannot use multiple ffmpeg preset qualities (found {} and {})", - qq.to_string(), - q.to_string() - )); - } - quality = Some(q) - } else { - return Err(format!( - "'{}' is not a valid ffmpeg preset (unknown token '{}'", - s, token - )); - } - } - - if let Some(c) = codec { - if !FFmpegPreset::available_matches().contains(&( - c.clone(), - hwaccel.clone(), - quality.clone(), - )) { - return Err(format!("ffmpeg preset is not supported")); - } - Ok(FFmpegPreset::Predefined( - c, - hwaccel, - quality.unwrap_or(FFmpegQuality::Normal), - )) - } else { - Err(format!("cannot use ffmpeg preset with without a codec")) - } - } - - pub(crate) fn to_input_output_args(self) -> (Vec<String>, Vec<String>) { - match self { - FFmpegPreset::Custom(input, output) => ( - input.map_or(vec![], |i| shlex::split(&i).unwrap_or_default()), - output.map_or(vec![], |o| shlex::split(&o).unwrap_or_default()), - ), - FFmpegPreset::Predefined(codec, hwaccel_opt, quality) => { - let mut input = vec![]; - let mut output = vec![]; - - match codec { - FFmpegCodec::H264 => { - if let Some(hwaccel) = hwaccel_opt { - match hwaccel { - FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) - } - } - } else { - output.extend(["-c:v", "libx264", "-c:a", "copy"]) - } - - match quality { - FFmpegQuality::Lossless => output.extend(["-crf", "18"]), - FFmpegQuality::Normal => (), - FFmpegQuality::Low => output.extend(["-crf", "35"]), - } - } - FFmpegCodec::H265 => { - if let Some(hwaccel) = hwaccel_opt { - match hwaccel { - FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); - output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) - } - } - } else { - output.extend(["-c:v", "libx265", "-c:a", "copy"]) - } - - match quality { - FFmpegQuality::Lossless => output.extend(["-crf", "20"]), - FFmpegQuality::Normal => (), - FFmpegQuality::Low => output.extend(["-crf", "35"]), - } - } - FFmpegCodec::Av1 => { - output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); - - match quality { - FFmpegQuality::Lossless => output.extend(["-crf", "22"]), - FFmpegQuality::Normal => (), - FFmpegQuality::Low => output.extend(["-crf", "35"]), - } - } - } - - ( - input - .into_iter() - .map(|s| s.to_string()) - .collect::<Vec<String>>(), - output - .into_iter() - .map(|s| s.to_string()) - .collect::<Vec<String>>(), - ) - } - } - } -} - -lazy_static! { - static ref DUPLICATED_SEASONS_MULTILANG_REGEX: Regex = Regex::new(r"(-arabic|-castilian|-english|-english-in|-french|-german|-hindi|-italian|-portuguese|-russian|-spanish)$").unwrap(); -} - -pub(crate) fn find_multiple_seasons_with_same_number(seasons: &Vec<Media<Season>>) -> Vec<u32> { - let mut seasons_map: BTreeMap<u32, u32> = BTreeMap::new(); - for season in seasons { - if let Some(s) = seasons_map.get_mut(&season.metadata.season_number) { - *s += 1; - } else { - seasons_map.insert(season.metadata.season_number, 1); - } - } - - seasons_map - .into_iter() - .filter_map(|(k, v)| { - if v > 1 { - // check if the different seasons are actual the same but with different dub languages - let mut multilang_season_vec: Vec<String> = seasons - .iter() - .map(|s| { - DUPLICATED_SEASONS_MULTILANG_REGEX - .replace(s.slug_title.trim_end_matches("-dub"), "") - .to_string() - }) - .collect(); - multilang_season_vec.dedup(); - - if multilang_season_vec.len() > 1 { - return Some(k); - } - } - None - }) - .collect() -} - -/// Check if [`Locale::Custom("all")`] is in the provided locale list and return [`Locale::all`] if -/// so. If not, just return the provided locale list. -pub(crate) fn all_locale_in_locales(locales: Vec<Locale>) -> Vec<Locale> { - if locales - .iter() - .find(|l| l.to_string().to_lowercase().trim() == "all") - .is_some() - { - Locale::all() - } else { - locales - } -} - -pub(crate) fn interactive_season_choosing(seasons: Vec<Media<Season>>) -> Vec<Media<Season>> { - let input_regex = - Regex::new(r"((?P<single>\d+)|(?P<range_from>\d+)-(?P<range_to>\d+)?)(\s|$)").unwrap(); - - let mut seasons_map: BTreeMap<u32, Vec<Media<Season>>> = BTreeMap::new(); - for season in seasons { - if let Some(s) = seasons_map.get_mut(&season.metadata.season_number) { - s.push(season); - } else { - seasons_map.insert(season.metadata.season_number, vec![season]); - } - } - - for (num, season_vec) in seasons_map.iter_mut() { - if season_vec.len() == 1 { - continue; - } - - // check if the different seasons are actual the same but with different dub languages - let mut multilang_season_vec: Vec<String> = season_vec - .iter() - .map(|s| { - DUPLICATED_SEASONS_MULTILANG_REGEX - .replace(s.slug_title.trim_end_matches("-dub"), "") - .to_string() - }) - .collect(); - multilang_season_vec.dedup(); - - if multilang_season_vec.len() == 1 { - continue; - } - - println!(":: Found multiple seasons for season number {}", num); - println!(":: Select the number of the seasons you want to download (eg \"1 2 4\", \"1-3\", \"1-3 5\"):"); - for (i, season) in season_vec.iter().enumerate() { - println!(":: \t{}. {}", i + 1, season.title) - } - let mut stdout = std::io::stdout(); - let _ = write!(stdout, ":: => "); - let _ = stdout.flush(); - let mut user_input = String::new(); - std::io::stdin() - .lock() - .read_line(&mut user_input) - .expect("cannot open stdin"); - - let mut nums = vec![]; - for capture in input_regex.captures_iter(&user_input) { - if let Some(single) = capture.name("single") { - nums.push(single.as_str().parse::<usize>().unwrap() - 1); - } else { - let range_from = capture.name("range_from"); - let range_to = capture.name("range_to"); - - // input is '-' which means use all seasons - if range_from.is_none() && range_to.is_none() { - nums = vec![]; - break; - } - let from = range_from - .map(|f| f.as_str().parse::<usize>().unwrap() - 1) - .unwrap_or(usize::MIN); - let to = range_from - .map(|f| f.as_str().parse::<usize>().unwrap() - 1) - .unwrap_or(usize::MAX); - - nums.extend( - season_vec - .iter() - .enumerate() - .filter_map(|(i, _)| if i >= from && i <= to { Some(i) } else { None }) - .collect::<Vec<usize>>(), - ) - } - } - nums.dedup(); - - if !nums.is_empty() { - let mut remove_count = 0; - for i in 0..season_vec.len() - 1 { - if !nums.contains(&i) { - season_vec.remove(i - remove_count); - remove_count += 1 - } - } - } - } - - seasons_map - .into_values() - .into_iter() - .flatten() - .collect::<Vec<Media<Season>>>() -} diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs new file mode 100644 index 0000000..25db55b --- /dev/null +++ b/crunchy-cli-core/src/download/command.rs @@ -0,0 +1,151 @@ +use crate::download::filter::DownloadFilter; +use crate::utils::context::Context; +use crate::utils::ffmpeg::FFmpegPreset; +use crate::utils::filter::Filter; +use crate::utils::format::formats_visual_output; +use crate::utils::log::progress; +use crate::utils::os::{free_file, has_ffmpeg}; +use crate::utils::parse::parse_url; +use crate::Execute; +use anyhow::bail; +use anyhow::Result; +use crunchyroll_rs::media::Resolution; +use crunchyroll_rs::Locale; +use log::{debug, warn}; +use std::path::Path; + +#[derive(Clone, Debug, clap::Parser)] +#[clap(about = "Download a video")] +#[command(arg_required_else_help(true))] +pub struct Download { + #[arg(help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ + Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + #[arg(short, long, default_value_t = crate::utils::locale::system_locale())] + pub(crate) audio: Locale, + #[arg(help = format!("Subtitle language. Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Subtitle language. If set, the subtitle will be burned into the video and cannot be disabled. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(short, long)] + pub(crate) subtitle: Option<Locale>, + + #[arg(help = "Name of the output file")] + #[arg(long_help = "Name of the output file.\ + If you use one of the following pattern they will get replaced:\n \ + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {season_number} โ†’ Number of the season\n \ + {episode_number} โ†’ Number of the episode\n \ + {relative_episode_number} โ†’ Number of the episode relative to its season\ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] + #[arg(short, long, default_value = "{title}.mp4")] + pub(crate) output: String, + + #[arg(help = "Video resolution")] + #[arg(long_help = "The video resolution.\ + Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ + Specifying the exact pixels is not recommended, use one of the other options instead. \ + Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ + The available common-use words are 'best' (choose the best resolution available) and 'worst' (worst resolution available)")] + #[arg(short, long, default_value = "best")] + #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] + pub(crate) resolution: Resolution, + + #[arg(help = format!("Presets for video converting. Can be used multiple times. \ + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] + #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ + Generally used to minify the file size with keeping (nearly) the same quality. \ + It is recommended to only use this if you download videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ + Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] + #[arg(long)] + #[arg(value_parser = FFmpegPreset::parse)] + pub(crate) ffmpeg_preset: Option<FFmpegPreset>, + + #[arg(help = "Skip files which are already existing")] + #[arg(long, default_value_t = false)] + pub(crate) skip_existing: bool, + + #[arg(help = "Url(s) to Crunchyroll episodes or series")] + pub(crate) urls: Vec<String>, +} + +#[async_trait::async_trait(?Send)] +impl Execute for Download { + fn pre_check(&mut self) -> Result<()> { + if !has_ffmpeg() { + bail!("FFmpeg is needed to run this command") + } else if Path::new(&self.output) + .extension() + .unwrap_or_default() + .is_empty() + && self.output != "-" + { + bail!("No file extension found. Please specify a file extension (via `-o`) for the output file") + } + + if self.subtitle.is_some() { + if let Some(ext) = Path::new(&self.output).extension() { + if ext.to_string_lossy() != "mp4" { + warn!("Detected a non mp4 output container. Adding subtitles may take a while") + } + } + } + + Ok(()) + } + + async fn execute(self, ctx: Context) -> Result<()> { + let mut parsed_urls = vec![]; + + for (i, url) in self.urls.clone().into_iter().enumerate() { + let progress_handler = progress!("Parsing url {}", i + 1); + match parse_url(&ctx.crunchy, url.clone(), true).await { + Ok((media_collection, url_filter)) => { + progress_handler.stop(format!("Parsed url {}", i + 1)); + parsed_urls.push((media_collection, url_filter)) + } + Err(e) => bail!("url {} could not be parsed: {}", url, e), + }; + } + + for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { + let progress_handler = progress!("Fetching series details"); + let download_formats = DownloadFilter::new(url_filter, self.clone()) + .visit(media_collection) + .await?; + + if download_formats.is_empty() { + progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); + continue; + } + progress_handler.stop(format!("Loaded series information for url {}", i + 1)); + + formats_visual_output(download_formats.iter().map(|(_, f)| f).collect()); + + for (downloader, format) in download_formats { + let formatted_path = format.format_path((&self.output).into(), true); + let (path, changed) = free_file(formatted_path.clone()); + + if changed && self.skip_existing { + debug!( + "Skipping already existing file '{}'", + formatted_path.to_string_lossy() + ); + continue; + } + + format.visual_output(&path); + + downloader.download(&ctx, &path).await? + } + } + + Ok(()) + } +} diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs new file mode 100644 index 0000000..81b8fa9 --- /dev/null +++ b/crunchy-cli-core/src/download/filter.rs @@ -0,0 +1,349 @@ +use crate::download::Download; +use crate::utils::download::{DownloadBuilder, DownloadFormat, Downloader}; +use crate::utils::filter::Filter; +use crate::utils::format::{Format, SingleFormat}; +use crate::utils::parse::UrlFilter; +use crate::utils::video::variant_data_from_stream; +use anyhow::{bail, Result}; +use crunchyroll_rs::media::{Subtitle, VariantData}; +use crunchyroll_rs::{Concert, Episode, Movie, MovieListing, MusicVideo, Season, Series}; +use log::{error, warn}; +use std::collections::HashMap; + +pub(crate) struct FilterResult { + format: SingleFormat, + video: VariantData, + audio: VariantData, + subtitle: Option<Subtitle>, +} + +pub(crate) struct DownloadFilter { + url_filter: UrlFilter, + download: Download, + season_episode_count: HashMap<u32, Vec<String>>, + season_subtitles_missing: Vec<u32>, +} + +impl DownloadFilter { + pub(crate) fn new(url_filter: UrlFilter, download: Download) -> Self { + Self { + url_filter, + download, + season_episode_count: HashMap::new(), + season_subtitles_missing: vec![], + } + } +} + +#[async_trait::async_trait] +impl Filter for DownloadFilter { + type T = FilterResult; + type Output = (Downloader, Format); + + async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { + // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the + // audio is matching only if the field is populated + if !series.audio_locales.is_empty() { + if !series.audio_locales.contains(&self.download.audio) { + error!( + "Series {} is not available with {} audio", + series.title, self.download.audio + ); + return Ok(vec![]); + } + } + + let seasons = series.seasons().await?; + + Ok(seasons) + } + + async fn visit_season(&mut self, mut season: Season) -> Result<Vec<Episode>> { + if !self.url_filter.is_season_valid(season.season_number) { + return Ok(vec![]); + } + + if !season + .audio_locales + .iter() + .any(|l| l == &self.download.audio) + { + if season + .available_versions() + .await? + .iter() + .any(|l| l == &self.download.audio) + { + season = season + .version(vec![self.download.audio.clone()]) + .await? + .remove(0) + } else { + error!( + "Season {} - '{}' is not available with {} audio", + season.season_number, + season.title, + self.download.audio.clone(), + ); + return Ok(vec![]); + } + } + + let mut episodes = season.episodes().await?; + + if Format::has_relative_episodes_fmt(&self.download.output) { + for episode in episodes.iter() { + self.season_episode_count + .entry(episode.season_number) + .or_insert(vec![]) + .push(episode.id.clone()) + } + } + + episodes.retain(|e| { + self.url_filter + .is_episode_valid(e.episode_number, season.season_number) + }); + + Ok(episodes) + } + + async fn visit_episode(&mut self, mut episode: Episode) -> Result<Option<Self::T>> { + if !self + .url_filter + .is_episode_valid(episode.episode_number, episode.season_number) + { + return Ok(None); + } + + // check if the audio locale is correct. + // should only be incorrect if the console input was a episode url. otherwise + // `DownloadFilter::visit_season` returns the correct episodes with matching audio + if episode.audio_locale != self.download.audio { + // check if any other version (same episode, other language) of this episode is available + // with the requested audio. if not, return an error + if !episode + .available_versions() + .await? + .contains(&self.download.audio) + { + bail!( + "Episode {} ({}) of {} season {} is not available with {} audio", + episode.episode_number, + episode.title, + episode.series_title, + episode.season_number, + self.download.audio + ) + } + // overwrite the current episode with the other version episode + episode = episode + .version(vec![self.download.audio.clone()]) + .await? + .remove(0) + } + + // check if the subtitles are supported + if let Some(subtitle_locale) = &self.download.subtitle { + if !episode.subtitle_locales.contains(subtitle_locale) { + // if the episode doesn't have the requested subtitles, print a error. to print this + // error only once per season, it's checked if an error got printed before by looking + // up if the season id is present in `self.season_subtitles_missing`. if not, print + // the error and add the season id to `self.season_subtitles_missing`. if it is + // present, skip the error printing + if !self + .season_subtitles_missing + .contains(&episode.season_number) + { + self.season_subtitles_missing.push(episode.season_number); + error!( + "{} season {} is not available with {} subtitles", + episode.series_title, episode.season_number, subtitle_locale + ); + } + return Ok(None); + } + } + + // get the correct video stream + let stream = episode.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.download.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) is not available for episode {} ({}) of {} season {}", + self.download.resolution, + episode.episode_number, + episode.title, + episode.series_title, + episode.season_number, + ) + }; + + // it is assumed that the subtitle, if requested, exists b/c the subtitle check above must + // be passed to reach this condition. + // the check isn't done in this if block to reduce unnecessary fetching of the stream + let subtitle = if let Some(subtitle_locale) = &self.download.subtitle { + stream.subtitles.get(subtitle_locale).map(|s| s.clone()) + } else { + None + }; + + // get the relative episode number. only done if the output string has the pattern to include + // the relative episode number as this requires some extra fetching + let relative_episode_number = if Format::has_relative_episodes_fmt(&self.download.output) { + if self + .season_episode_count + .get(&episode.season_number) + .is_none() + { + let season_episodes = episode.season().await?.episodes().await?; + self.season_episode_count.insert( + episode.season_number, + season_episodes.into_iter().map(|e| e.id).collect(), + ); + } + let relative_episode_number = self + .season_episode_count + .get(&episode.season_number) + .unwrap() + .iter() + .position(|id| id == &episode.id); + if relative_episode_number.is_none() { + warn!( + "Failed to get relative episode number for episode {} ({}) of {} season {}", + episode.episode_number, + episode.title, + episode.series_title, + episode.season_number, + ) + } + relative_episode_number + } else { + None + }; + + Ok(Some(FilterResult { + format: SingleFormat::new_from_episode( + &episode, + &video, + subtitle.clone().map_or(vec![], |s| vec![s.locale]), + relative_episode_number.map(|n| n as u32), + ), + video, + audio, + subtitle, + })) + } + + async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { + Ok(movie_listing.movies().await?) + } + + async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>> { + let stream = movie.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.download.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) of movie '{}' is not available", + self.download.resolution, + movie.title + ) + }; + let subtitle = if let Some(subtitle_locale) = &self.download.subtitle { + let Some(subtitle) = stream.subtitles.get(subtitle_locale) else { + error!( + "Movie '{}' has no {} subtitles", + movie.title, + subtitle_locale + ); + return Ok(None) + }; + Some(subtitle.clone()) + } else { + None + }; + + Ok(Some(FilterResult { + format: SingleFormat::new_from_movie( + &movie, + &video, + subtitle.clone().map_or(vec![], |s| vec![s.locale]), + ), + video, + audio, + subtitle, + })) + } + + async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>> { + let stream = music_video.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.download.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) of music video {} is not available", + self.download.resolution, + music_video.title + ) + }; + + Ok(Some(FilterResult { + format: SingleFormat::new_from_music_video(&music_video, &video), + video, + audio, + subtitle: None, + })) + } + + async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>> { + let stream = concert.streams().await?; + let (video, audio) = if let Some((video, audio)) = + variant_data_from_stream(&stream, &self.download.resolution).await? + { + (video, audio) + } else { + bail!( + "Resolution ({}) of music video {} is not available", + self.download.resolution, + concert.title + ) + }; + + Ok(Some(FilterResult { + format: SingleFormat::new_from_concert(&concert, &video), + video, + audio, + subtitle: None, + })) + } + + async fn finish(self, mut input: Vec<Self::T>) -> Result<Vec<Self::Output>> { + let mut result = vec![]; + input.sort_by(|a, b| { + a.format + .sequence_number + .total_cmp(&b.format.sequence_number) + }); + for data in input { + let mut downloader = DownloadBuilder::new() + .default_subtitle(self.download.subtitle.clone()) + .build(); + downloader.add_format(DownloadFormat { + video: (data.video, data.format.audio.clone()), + audios: vec![(data.audio, data.format.audio.clone())], + subtitles: data.subtitle.map_or(vec![], |s| vec![s]), + }); + result.push((downloader, Format::from_single_formats(vec![data.format]))) + } + + Ok(result) + } +} diff --git a/crunchy-cli-core/src/download/mod.rs b/crunchy-cli-core/src/download/mod.rs new file mode 100644 index 0000000..696872e --- /dev/null +++ b/crunchy-cli-core/src/download/mod.rs @@ -0,0 +1,4 @@ +mod command; +mod filter; + +pub use command::Download; diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index ab3f33c..5e0e113 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -1,7 +1,6 @@ -use crate::cli::log::CliLogger; use crate::utils::context::Context; use crate::utils::locale::system_locale; -use crate::utils::log::progress; +use crate::utils::log::{progress, CliLogger}; use anyhow::bail; use anyhow::Result; use clap::{Parser, Subcommand}; @@ -9,10 +8,14 @@ use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; use std::{env, fs}; -mod cli; +mod archive; +mod download; +mod login; mod utils; -pub use cli::{archive::Archive, download::Download, login::Login}; +pub use archive::Archive; +pub use download::Download; +pub use login::Login; #[async_trait::async_trait(?Send)] trait Execute { @@ -222,9 +225,14 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { lang }; - let builder = Crunchyroll::builder() + let mut builder = Crunchyroll::builder() .locale(locale) - .stabilization_locales(true); + .stabilization_locales(true) + .stabilization_season_number(true); + + if let Command::Download(download) = &cli.command { + builder = builder.preferred_audio_locale(download.audio.clone()) + } let login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 @@ -232,7 +240,7 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { let progress_handler = progress!("Logging in"); if login_methods_count == 0 { - if let Some(login_file_path) = cli::login::login_file_path() { + if let Some(login_file_path) = login::login_file_path() { if login_file_path.exists() { let session = fs::read_to_string(login_file_path)?; if let Some((token_type, token)) = session.split_once(':') { diff --git a/crunchy-cli-core/src/cli/login.rs b/crunchy-cli-core/src/login/command.rs similarity index 94% rename from crunchy-cli-core/src/cli/login.rs rename to crunchy-cli-core/src/login/command.rs index c41b1b7..f164a0b 100644 --- a/crunchy-cli-core/src/cli/login.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -18,6 +18,8 @@ pub struct Login { impl Execute for Login { async fn execute(self, ctx: Context) -> Result<()> { if let Some(login_file_path) = login_file_path() { + fs::create_dir_all(login_file_path.parent().unwrap())?; + match ctx.crunchy.session_token().await { SessionToken::RefreshToken(refresh_token) => Ok(fs::write( login_file_path, diff --git a/crunchy-cli-core/src/login/mod.rs b/crunchy-cli-core/src/login/mod.rs new file mode 100644 index 0000000..9025e68 --- /dev/null +++ b/crunchy-cli-core/src/login/mod.rs @@ -0,0 +1,4 @@ +mod command; + +pub use command::login_file_path; +pub use command::Login; diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs new file mode 100644 index 0000000..ce0c443 --- /dev/null +++ b/crunchy-cli-core/src/utils/download.rs @@ -0,0 +1,657 @@ +use crate::utils::context::Context; +use crate::utils::ffmpeg::FFmpegPreset; +use crate::utils::log::progress; +use crate::utils::os::tempfile; +use anyhow::{bail, Result}; +use chrono::NaiveTime; +use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; +use crunchyroll_rs::Locale; +use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; +use log::{debug, LevelFilter}; +use regex::Regex; +use std::borrow::Borrow; +use std::borrow::BorrowMut; +use std::collections::BTreeMap; +use std::io::Write; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::sync::{mpsc, Arc, Mutex}; +use std::time::Duration; +use tempfile::TempPath; +use tokio::task::JoinSet; + +#[derive(Clone, Debug)] +pub enum MergeBehavior { + Video, + Audio, + Auto, +} + +impl MergeBehavior { + pub fn parse(s: &str) -> Result<MergeBehavior, String> { + Ok(match s.to_lowercase().as_str() { + "video" => MergeBehavior::Video, + "audio" => MergeBehavior::Audio, + "auto" => MergeBehavior::Auto, + _ => return Err(format!("'{}' is not a valid merge behavior", s)), + }) + } +} + +#[derive(derive_setters::Setters)] +pub struct DownloadBuilder { + ffmpeg_preset: FFmpegPreset, + default_subtitle: Option<Locale>, + output_format: Option<String>, + audio_sort: Option<Vec<Locale>>, + subtitle_sort: Option<Vec<Locale>>, +} + +impl DownloadBuilder { + pub fn new() -> DownloadBuilder { + Self { + ffmpeg_preset: FFmpegPreset::default(), + default_subtitle: None, + output_format: None, + audio_sort: None, + subtitle_sort: None, + } + } + + pub fn build(self) -> Downloader { + Downloader { + ffmpeg_preset: self.ffmpeg_preset, + default_subtitle: self.default_subtitle, + output_format: self.output_format, + audio_sort: self.audio_sort, + subtitle_sort: self.subtitle_sort, + + formats: vec![], + } + } +} + +struct FFmpegMeta { + path: TempPath, + language: Locale, + title: String, +} + +pub struct DownloadFormat { + pub video: (VariantData, Locale), + pub audios: Vec<(VariantData, Locale)>, + pub subtitles: Vec<Subtitle>, +} + +pub struct Downloader { + ffmpeg_preset: FFmpegPreset, + default_subtitle: Option<Locale>, + output_format: Option<String>, + audio_sort: Option<Vec<Locale>>, + subtitle_sort: Option<Vec<Locale>>, + + formats: Vec<DownloadFormat>, +} + +impl Downloader { + pub fn add_format(&mut self, format: DownloadFormat) { + self.formats.push(format); + } + + pub async fn download(mut self, ctx: &Context, dst: &Path) -> Result<()> { + if let Some(audio_sort_locales) = &self.audio_sort { + self.formats.sort_by(|a, b| { + audio_sort_locales + .iter() + .position(|l| l == &a.video.1) + .cmp(&audio_sort_locales.iter().position(|l| l == &b.video.1)) + }); + } + for format in self.formats.iter_mut() { + if let Some(audio_sort_locales) = &self.audio_sort { + format.audios.sort_by(|(_, a), (_, b)| { + audio_sort_locales + .iter() + .position(|l| l == a) + .cmp(&audio_sort_locales.iter().position(|l| l == b)) + }) + } + if let Some(subtitle_sort) = &self.subtitle_sort { + format.subtitles.sort_by(|a, b| { + subtitle_sort + .iter() + .position(|l| l == &a.locale) + .cmp(&subtitle_sort.iter().position(|l| l == &b.locale)) + }) + } + } + + let mut videos = vec![]; + let mut audios = vec![]; + let mut subtitles = vec![]; + + for (i, format) in self.formats.iter().enumerate() { + let fmt_space = format + .audios + .iter() + .map(|(_, locale)| format!("Downloading {} audio", locale).len()) + .max() + .unwrap(); + + let video_path = self + .download_video( + ctx, + &format.video.0, + format!("{:<1$}", format!("Downloading video #{}", i + 1), fmt_space), + ) + .await?; + for (variant_data, locale) in format.audios.iter() { + let audio_path = self + .download_audio( + ctx, + variant_data, + format!("{:<1$}", format!("Downloading {} audio", locale), fmt_space), + ) + .await?; + audios.push(FFmpegMeta { + path: audio_path, + language: locale.clone(), + title: if i == 0 { + locale.to_human_readable() + } else { + format!("{} [Video: #{}]", locale.to_human_readable(), i + 1) + }, + }) + } + let len = get_video_length(&video_path)?; + for subtitle in format.subtitles.iter() { + let subtitle_path = self.download_subtitle(subtitle.clone(), len).await?; + subtitles.push(FFmpegMeta { + path: subtitle_path, + language: subtitle.locale.clone(), + title: if i == 0 { + subtitle.locale.to_human_readable() + } else { + format!( + "{} [Video: #{}]", + subtitle.locale.to_human_readable(), + i + 1 + ) + }, + }) + } + videos.push(FFmpegMeta { + path: video_path, + language: format.video.1.clone(), + title: if self.formats.len() == 1 { + "Default".to_string() + } else { + format!("#{}", i + 1) + }, + }); + } + + let mut input = vec![]; + let mut maps = vec![]; + let mut metadata = vec![]; + + for (i, meta) in videos.iter().enumerate() { + input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); + maps.extend(["-map".to_string(), i.to_string()]); + metadata.extend([ + format!("-metadata:s:v:{}", i), + format!("title={}", meta.title), + ]); + // the empty language metadata is created to avoid that metadata from the original track + // is copied + metadata.extend([format!("-metadata:s:v:{}", i), format!("language=")]) + } + for (i, meta) in audios.iter().enumerate() { + input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); + maps.extend(["-map".to_string(), (i + videos.len()).to_string()]); + metadata.extend([ + format!("-metadata:s:a:{}", i), + format!("language={}", meta.language), + ]); + metadata.extend([ + format!("-metadata:s:a:{}", i), + format!("title={}", meta.title), + ]); + } + for (i, meta) in subtitles.iter().enumerate() { + input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); + maps.extend([ + "-map".to_string(), + (i + videos.len() + audios.len()).to_string(), + ]); + metadata.extend([ + format!("-metadata:s:s:{}", i), + format!("language={}", meta.language), + ]); + metadata.extend([ + format!("-metadata:s:s:{}", i), + format!("title={}", meta.title), + ]); + } + + let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args(); + + let mut command_args = vec!["-y".to_string(), "-hide_banner".to_string()]; + command_args.extend(input_presets); + command_args.extend(input); + command_args.extend(maps); + command_args.extend(metadata); + + // set default subtitle + if let Some(default_subtitle) = self.default_subtitle { + if let Some(position) = subtitles + .iter() + .position(|m| m.language == default_subtitle) + { + match dst.extension().unwrap_or_default().to_str().unwrap() { + "mp4" => output_presets.extend([ + "-movflags".to_string(), + "faststart".to_string(), + "-c:s".to_string(), + "mov_text".to_string(), + format!("-disposition:s:s:{}", position), + "forced".to_string(), + ]), + "mkv" => output_presets.extend([ + format!("-disposition:s:s:{}", position), + "forced".to_string(), + ]), + _ => { + // remove '-c:v copy' and '-c:a copy' from output presets as its causes issues with + // burning subs into the video + let mut last = String::new(); + let mut remove_count = 0; + for (i, s) in output_presets.clone().iter().enumerate() { + if (last == "-c:v" || last == "-c:a") && s == "copy" { + // remove last + output_presets.remove(i - remove_count - 1); + remove_count += 1; + output_presets.remove(i - remove_count); + remove_count += 1; + } + last = s.clone(); + } + + output_presets.extend([ + "-vf".to_string(), + format!( + "subtitles={}", + subtitles.get(position).unwrap().path.to_str().unwrap() + ), + ]) + } + } + } + + if let Some(position) = subtitles + .iter() + .position(|meta| meta.language == default_subtitle) + { + command_args.extend([ + format!("-disposition:s:s:{}", position), + "forced".to_string(), + ]) + } + } + + command_args.extend(output_presets); + if let Some(output_format) = self.output_format { + command_args.extend(["-f".to_string(), output_format]); + } + command_args.push(dst.to_str().unwrap().to_string()); + + debug!("ffmpeg {}", command_args.join(" ")); + + // create parent directory if it does not exist + if let Some(parent) = dst.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)? + } + } + + let progress_handler = progress!("Generating output file"); + + let ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .args(command_args) + .output()?; + if !ffmpeg.status.success() { + bail!("{}", String::from_utf8_lossy(ffmpeg.stderr.as_slice())) + } + + progress_handler.stop("Output file generated"); + + Ok(()) + } + + async fn download_video( + &self, + ctx: &Context, + variant_data: &VariantData, + message: String, + ) -> Result<TempPath> { + let tempfile = tempfile(".mp4")?; + let (mut file, path) = tempfile.into_parts(); + + download_segments(ctx, &mut file, Some(message), variant_data).await?; + + Ok(path) + } + + async fn download_audio( + &self, + ctx: &Context, + variant_data: &VariantData, + message: String, + ) -> Result<TempPath> { + let tempfile = tempfile(".m4a")?; + let (mut file, path) = tempfile.into_parts(); + + download_segments(ctx, &mut file, Some(message), variant_data).await?; + + Ok(path) + } + + async fn download_subtitle( + &self, + subtitle: Subtitle, + max_length: NaiveTime, + ) -> Result<TempPath> { + let tempfile = tempfile(".ass")?; + let (mut file, path) = tempfile.into_parts(); + + let mut buf = vec![]; + subtitle.write_to(&mut buf).await?; + fix_subtitle_look_and_feel(&mut buf); + fix_subtitle_length(&mut buf, max_length); + + file.write_all(buf.as_slice())?; + + Ok(path) + } +} + +pub async fn download_segments( + ctx: &Context, + writer: &mut impl Write, + message: Option<String>, + variant_data: &VariantData, +) -> Result<()> { + let segments = variant_data.segments().await?; + let total_segments = segments.len(); + + let client = Arc::new(ctx.crunchy.client()); + let count = Arc::new(Mutex::new(0)); + + let progress = if log::max_level() == LevelFilter::Info { + let estimated_file_size = + (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>(); + + let progress = ProgressBar::new(estimated_file_size) + .with_style( + ProgressStyle::with_template( + ":: {msg}{bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", + ) + .unwrap() + .progress_chars("##-"), + ) + .with_message(message.map(|m| m + " ").unwrap_or_default()) + .with_finish(ProgressFinish::Abandon); + Some(progress) + } else { + None + }; + + let cpus = num_cpus::get(); + let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); + for _ in 0..cpus { + segs.push(vec![]) + } + for (i, segment) in segments.clone().into_iter().enumerate() { + segs[i - ((i / cpus) * cpus)].push(segment); + } + + let (sender, receiver) = mpsc::channel(); + + let mut join_set: JoinSet<Result<()>> = JoinSet::new(); + for num in 0..cpus { + let thread_client = client.clone(); + let thread_sender = sender.clone(); + let thread_segments = segs.remove(0); + let thread_count = count.clone(); + join_set.spawn(async move { + let after_download_sender = thread_sender.clone(); + + // the download process is encapsulated in its own function. this is done to easily + // catch errors which get returned with `...?` and `bail!(...)` and that the thread + // itself can report that an error has occurred + let download = || async move { + for (i, segment) in thread_segments.into_iter().enumerate() { + let mut retry_count = 0; + let mut buf = loop { + let response = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60)) + .send() + .await?; + + match response.bytes().await { + Ok(b) => break b.to_vec(), + Err(e) => { + if e.is_body() { + if retry_count == 5 { + bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) + } + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) + } else { + bail!("{}", e) + } + } + } + + retry_count += 1; + }; + + buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); + + let mut c = thread_count.lock().unwrap(); + debug!( + "Downloaded and decrypted segment [{}/{} {:.2}%] {}", + num + (i * cpus) + 1, + total_segments, + ((*c + 1) as f64 / total_segments as f64) * 100f64, + segment.url + ); + + thread_sender.send((num as i32 + (i * cpus) as i32, buf))?; + + *c += 1; + } + Ok(()) + }; + + + let result = download().await; + if result.is_err() { + after_download_sender.send((-1 as i32, vec![]))?; + } + + result + }); + } + // drop the sender already here so it does not outlive all download threads which are the only + // real consumers of it + drop(sender); + + // this is the main loop which writes the data. it uses a BTreeMap as a buffer as the write + // happens synchronized. the download consist of multiple segments. the map keys are representing + // the segment number and the values the corresponding bytes + let mut data_pos = 0; + let mut buf: BTreeMap<i32, Vec<u8>> = BTreeMap::new(); + for (pos, bytes) in receiver.iter() { + // if the position is lower than 0, an error occurred in the sending download thread + if pos < 0 { + break; + } + + if let Some(p) = &progress { + let progress_len = p.length().unwrap(); + let estimated_segment_len = + (variant_data.bandwidth / 8) * segments.get(pos as usize).unwrap().length.as_secs(); + let bytes_len = bytes.len() as u64; + + p.set_length(progress_len - estimated_segment_len + bytes_len); + p.inc(bytes_len) + } + + // check if the currently sent bytes are the next in the buffer. if so, write them directly + // to the target without first adding them to the buffer. + // if not, add them to the buffer + if data_pos == pos { + writer.write_all(bytes.borrow())?; + data_pos += 1; + } else { + buf.insert(pos, bytes); + } + // check if the buffer contains the next segment(s) + while let Some(b) = buf.remove(&data_pos) { + writer.write_all(b.borrow())?; + data_pos += 1; + } + } + + // if any error has occurred while downloading it gets returned here + while let Some(joined) = join_set.join_next().await { + joined?? + } + + // write the remaining buffer, if existent + while let Some(b) = buf.remove(&data_pos) { + writer.write_all(b.borrow())?; + data_pos += 1; + } + + if !buf.is_empty() { + bail!( + "Download buffer is not empty. Remaining segments: {}", + buf.into_keys() + .map(|k| k.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + + Ok(()) +} + +/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video +/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) +/// for more information. +fn fix_subtitle_look_and_feel(raw: &mut Vec<u8>) { + let mut script_info = false; + let mut new = String::new(); + + for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { + if line.trim().starts_with('[') && script_info { + new.push_str("ScaledBorderAndShadow: yes\n"); + script_info = false + } else if line.trim() == "[Script Info]" { + script_info = true + } + new.push_str(line); + new.push('\n') + } + + *raw = new.into_bytes() +} + +/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry +/// long after the actual video ends with artificially extends the video length on some video players. +/// To prevent this, the video length must be hard set. See +/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// information. +pub fn get_video_length(path: &Path) -> Result<NaiveTime> { + let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; + + let ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .arg("-y") + .arg("-hide_banner") + .args(["-i", path.to_str().unwrap()]) + .output()?; + let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; + let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); + + Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) +} + +/// Fix the length of subtitles to a specified maximum amount. This is required because sometimes +/// subtitles have an unnecessary entry long after the actual video ends with artificially extends +/// the video length on some video players. To prevent this, the video length must be hard set. See +/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// information. +fn fix_subtitle_length(raw: &mut Vec<u8>, max_length: NaiveTime) { + let re = + Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) + .unwrap(); + + // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 + // digits so them have to be reduced manually to avoid the panic + fn format_naive_time(native_time: NaiveTime) -> String { + let formatted_time = native_time.format("%f").to_string(); + format!( + "{}.{}", + native_time.format("%T"), + if formatted_time.len() <= 2 { + native_time.format("%2f").to_string() + } else { + formatted_time.split_at(2).0.parse().unwrap() + } + ) + } + + let length_as_string = format_naive_time(max_length); + let mut new = String::new(); + + for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { + if let Some(capture) = re.captures(line) { + let start = capture.name("start").map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }); + let end = capture.name("end").map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }); + + if start > max_length { + continue; + } else if end > max_length { + new.push_str( + re.replace( + line, + format!( + "Dialogue: {},{},", + format_naive_time(start), + &length_as_string + ), + ) + .to_string() + .as_str(), + ) + } else { + new.push_str(line) + } + } else { + new.push_str(line) + } + new.push('\n') + } + + *raw = new.into_bytes() +} diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs new file mode 100644 index 0000000..5f8b821 --- /dev/null +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -0,0 +1,344 @@ +use lazy_static::lazy_static; +use regex::Regex; +use std::env; +use std::str::FromStr; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FFmpegPreset { + Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality), + Custom(Option<String>, Option<String>), +} + +lazy_static! { + static ref PREDEFINED_PRESET: Regex = Regex::new(r"^\w+(-\w+)*?$").unwrap(); +} + +macro_rules! ffmpeg_enum { + (enum $name:ident { $($field:ident),* }) => { + #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] + pub enum $name { + $( + $field + ),*, + } + + impl $name { + fn all() -> Vec<$name> { + vec![ + $( + $name::$field + ),*, + ] + } + } + + impl ToString for $name { + fn to_string(&self) -> String { + match self { + $( + &$name::$field => stringify!($field).to_string().to_lowercase() + ),* + } + } + } + + impl FromStr for $name { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + match s { + $( + stringify!($field) => Ok($name::$field) + ),*, + _ => anyhow::bail!("{} is not a valid {}", s, stringify!($name).to_lowercase()) + } + } + } + } +} + +ffmpeg_enum! { + enum FFmpegCodec { + H264, + H265, + Av1 + } +} + +ffmpeg_enum! { + enum FFmpegHwAccel { + Nvidia + } +} + +ffmpeg_enum! { + enum FFmpegQuality { + Lossless, + Normal, + Low + } +} + +impl Default for FFmpegPreset { + fn default() -> Self { + Self::Custom(None, Some("-c:v copy -c:a copy".to_string())) + } +} + +impl FFmpegPreset { + pub(crate) fn available_matches( + ) -> Vec<(FFmpegCodec, Option<FFmpegHwAccel>, Option<FFmpegQuality>)> { + let codecs = vec![ + ( + FFmpegCodec::H264, + FFmpegHwAccel::all(), + FFmpegQuality::all(), + ), + ( + FFmpegCodec::H265, + FFmpegHwAccel::all(), + FFmpegQuality::all(), + ), + (FFmpegCodec::Av1, vec![], FFmpegQuality::all()), + ]; + + let mut return_values = vec![]; + + for (codec, hwaccels, qualities) in codecs { + return_values.push((codec.clone(), None, None)); + for hwaccel in hwaccels.clone() { + return_values.push((codec.clone(), Some(hwaccel), None)); + } + for quality in qualities.clone() { + return_values.push((codec.clone(), None, Some(quality))) + } + for hwaccel in hwaccels { + for quality in qualities.clone() { + return_values.push((codec.clone(), Some(hwaccel.clone()), Some(quality))) + } + } + } + + return_values + } + + pub(crate) fn available_matches_human_readable() -> Vec<String> { + let mut return_values = vec![]; + + for (codec, hwaccel, quality) in FFmpegPreset::available_matches() { + let mut description_details = vec![]; + if let Some(h) = &hwaccel { + description_details.push(format!("{} hardware acceleration", h.to_string())) + } + if let Some(q) = &quality { + description_details.push(format!("{} video quality/compression", q.to_string())) + } + + let description = if description_details.len() == 0 { + format!( + "{} encoded with default video quality/compression", + codec.to_string() + ) + } else if description_details.len() == 1 { + format!( + "{} encoded with {}", + codec.to_string(), + description_details[0] + ) + } else { + let first = description_details.remove(0); + let last = description_details.remove(description_details.len() - 1); + let mid = if !description_details.is_empty() { + format!(", {} ", description_details.join(", ")) + } else { + "".to_string() + }; + + format!( + "{} encoded with {}{} and {}", + codec.to_string(), + first, + mid, + last + ) + }; + + return_values.push(format!( + "{} ({})", + vec![ + Some(codec.to_string()), + hwaccel.map(|h| h.to_string()), + quality.map(|q| q.to_string()) + ] + .into_iter() + .flatten() + .collect::<Vec<String>>() + .join("-"), + description + )) + } + return_values + } + + pub(crate) fn parse(s: &str) -> Result<FFmpegPreset, String> { + let env_ffmpeg_input_args = env::var("FFMPEG_INPUT_ARGS").ok(); + let env_ffmpeg_output_args = env::var("FFMPEG_OUTPUT_ARGS").ok(); + + if env_ffmpeg_input_args.is_some() || env_ffmpeg_output_args.is_some() { + if let Some(input) = &env_ffmpeg_input_args { + if shlex::split(input).is_none() { + return Err(format!("Failed to find custom ffmpeg input '{}' (`FFMPEG_INPUT_ARGS` env variable)", input)); + } + } + if let Some(output) = &env_ffmpeg_output_args { + if shlex::split(output).is_none() { + return Err(format!("Failed to find custom ffmpeg output '{}' (`FFMPEG_INPUT_ARGS` env variable)", output)); + } + } + + return Ok(FFmpegPreset::Custom( + env_ffmpeg_input_args, + env_ffmpeg_output_args, + )); + } else if !PREDEFINED_PRESET.is_match(s) { + return Ok(FFmpegPreset::Custom(None, Some(s.to_string()))); + } + + let mut codec: Option<FFmpegCodec> = None; + let mut hwaccel: Option<FFmpegHwAccel> = None; + let mut quality: Option<FFmpegQuality> = None; + for token in s.split('-') { + if let Some(c) = FFmpegCodec::all() + .into_iter() + .find(|p| p.to_string() == token.to_lowercase()) + { + if let Some(cc) = codec { + return Err(format!( + "cannot use multiple codecs (found {} and {})", + cc.to_string(), + c.to_string() + )); + } + codec = Some(c) + } else if let Some(h) = FFmpegHwAccel::all() + .into_iter() + .find(|p| p.to_string() == token.to_lowercase()) + { + if let Some(hh) = hwaccel { + return Err(format!( + "cannot use multiple hardware accelerations (found {} and {})", + hh.to_string(), + h.to_string() + )); + } + hwaccel = Some(h) + } else if let Some(q) = FFmpegQuality::all() + .into_iter() + .find(|p| p.to_string() == token.to_lowercase()) + { + if let Some(qq) = quality { + return Err(format!( + "cannot use multiple ffmpeg preset qualities (found {} and {})", + qq.to_string(), + q.to_string() + )); + } + quality = Some(q) + } else { + return Err(format!( + "'{}' is not a valid ffmpeg preset (unknown token '{}'", + s, token + )); + } + } + + if let Some(c) = codec { + if !FFmpegPreset::available_matches().contains(&( + c.clone(), + hwaccel.clone(), + quality.clone(), + )) { + return Err(format!("ffmpeg preset is not supported")); + } + Ok(FFmpegPreset::Predefined( + c, + hwaccel, + quality.unwrap_or(FFmpegQuality::Normal), + )) + } else { + Err(format!("cannot use ffmpeg preset with without a codec")) + } + } + + pub(crate) fn into_input_output_args(self) -> (Vec<String>, Vec<String>) { + match self { + FFmpegPreset::Custom(input, output) => ( + input.map_or(vec![], |i| shlex::split(&i).unwrap_or_default()), + output.map_or(vec![], |o| shlex::split(&o).unwrap_or_default()), + ), + FFmpegPreset::Predefined(codec, hwaccel_opt, quality) => { + let mut input = vec![]; + let mut output = vec![]; + + match codec { + FFmpegCodec::H264 => { + if let Some(hwaccel) = hwaccel_opt { + match hwaccel { + FFmpegHwAccel::Nvidia => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) + } + } + } else { + output.extend(["-c:v", "libx264", "-c:a", "copy"]) + } + + match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "18"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + } + } + FFmpegCodec::H265 => { + if let Some(hwaccel) = hwaccel_opt { + match hwaccel { + FFmpegHwAccel::Nvidia => { + input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) + } + } + } else { + output.extend(["-c:v", "libx265", "-c:a", "copy"]) + } + + match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "20"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + } + } + FFmpegCodec::Av1 => { + output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); + + match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "22"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + } + } + } + + ( + input + .into_iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>(), + output + .into_iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>(), + ) + } + } + } +} diff --git a/crunchy-cli-core/src/utils/filter.rs b/crunchy-cli-core/src/utils/filter.rs new file mode 100644 index 0000000..b68e30d --- /dev/null +++ b/crunchy-cli-core/src/utils/filter.rs @@ -0,0 +1,95 @@ +use anyhow::Result; +use crunchyroll_rs::{ + Concert, Episode, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series, +}; + +// Check when https://github.com/dtolnay/async-trait/issues/224 is resolved and update async-trait +// to the new fixed version (as this causes some issues) +#[async_trait::async_trait] +pub trait Filter { + type T: Send + Sized; + type Output: Send + Sized; + + async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>>; + async fn visit_season(&mut self, season: Season) -> Result<Vec<Episode>>; + async fn visit_episode(&mut self, episode: Episode) -> Result<Option<Self::T>>; + async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>>; + async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>>; + async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>>; + async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>>; + + async fn visit(mut self, media_collection: MediaCollection) -> Result<Vec<Self::Output>> + where + Self: Send + Sized, + { + let mut items = vec![media_collection]; + let mut result = vec![]; + + while !items.is_empty() { + let mut new_items: Vec<MediaCollection> = vec![]; + + for i in items { + match i { + MediaCollection::Series(series) => new_items.extend( + self.visit_series(series) + .await? + .into_iter() + .map(|s| s.into()) + .collect::<Vec<MediaCollection>>(), + ), + MediaCollection::Season(season) => new_items.extend( + self.visit_season(season) + .await? + .into_iter() + .map(|s| s.into()) + .collect::<Vec<MediaCollection>>(), + ), + MediaCollection::Episode(episode) => { + if let Some(t) = self.visit_episode(episode).await? { + result.push(t) + } + } + MediaCollection::MovieListing(movie_listing) => new_items.extend( + self.visit_movie_listing(movie_listing) + .await? + .into_iter() + .map(|m| m.into()) + .collect::<Vec<MediaCollection>>(), + ), + MediaCollection::Movie(movie) => { + if let Some(t) = self.visit_movie(movie).await? { + result.push(t) + } + } + MediaCollection::MusicVideo(music_video) => { + if let Some(t) = self.visit_music_video(music_video).await? { + result.push(t) + } + } + MediaCollection::Concert(concert) => { + if let Some(t) = self.visit_concert(concert).await? { + result.push(t) + } + } + } + } + + items = new_items + } + + self.finish(result).await + } + + async fn finish(self, input: Vec<Self::T>) -> Result<Vec<Self::Output>>; +} + +/// Remove all duplicates from a [`Vec`]. +pub fn real_dedup_vec<T: Clone + Eq>(input: &mut Vec<T>) { + let mut dedup = vec![]; + for item in input.clone() { + if !dedup.contains(&item) { + dedup.push(item); + } + } + *input = dedup +} diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 93ee773..15acc85 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,19 +1,22 @@ -use crunchyroll_rs::media::{StreamSubtitle, VariantData}; -use crunchyroll_rs::{Episode, Locale, Media, Movie}; -use log::warn; -use std::path::PathBuf; -use std::time::Duration; +use crate::utils::filter::real_dedup_vec; +use crate::utils::log::tab_info; +use crate::utils::os::is_special_file; +use crunchyroll_rs::media::{Resolution, VariantData}; +use crunchyroll_rs::{Concert, Episode, Locale, Movie, MusicVideo}; +use log::{debug, info}; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; #[derive(Clone)] -pub struct Format { +pub struct SingleFormat { pub title: String, pub description: String, pub audio: Locale, - pub subtitles: Vec<StreamSubtitle>, + pub subtitles: Vec<Locale>, - pub duration: Duration, - pub stream: VariantData, + pub resolution: Resolution, + pub fps: f64, pub series_id: String, pub series_name: String, @@ -23,76 +26,148 @@ pub struct Format { pub season_number: u32, pub episode_id: String, - pub episode_number: f32, - pub relative_episode_number: f32, + pub episode_number: String, + pub sequence_number: f32, + pub relative_episode_number: Option<u32>, } -impl Format { +impl SingleFormat { pub fn new_from_episode( - episode: &Media<Episode>, - season_episodes: &Vec<Media<Episode>>, - stream: VariantData, - subtitles: Vec<StreamSubtitle>, + episode: &Episode, + video: &VariantData, + subtitles: Vec<Locale>, + relative_episode_number: Option<u32>, ) -> Self { Self { title: episode.title.clone(), description: episode.description.clone(), - - audio: episode.metadata.audio_locale.clone(), + audio: episode.audio_locale.clone(), subtitles, - - duration: episode.metadata.duration.to_std().unwrap(), - stream, - - series_id: episode.metadata.series_id.clone(), - series_name: episode.metadata.series_title.clone(), - - season_id: episode.metadata.season_id.clone(), - season_title: episode.metadata.season_title.clone(), - season_number: episode.metadata.season_number.clone(), - + resolution: video.resolution.clone(), + fps: video.fps, + series_id: episode.series_id.clone(), + series_name: episode.series_title.clone(), + season_id: episode.season_id.clone(), + season_title: episode.season_title.to_string(), + season_number: episode.season_number, episode_id: episode.id.clone(), - episode_number: episode - .metadata - .episode - .parse() - .unwrap_or(episode.metadata.sequence_number), - relative_episode_number: season_episodes - .iter() - .enumerate() - .find_map(|(i, e)| if e == episode { Some((i + 1) as f32) } else { None }) - .unwrap_or_else(|| { - warn!("Cannot find relative episode number for episode {} ({}) of season {} ({}) of {}, using normal episode number", episode.metadata.episode_number, episode.title, episode.metadata.season_number, episode.metadata.season_title, episode.metadata.series_title); - episode - .metadata - .episode - .parse() - .unwrap_or(episode.metadata.sequence_number) - }), + episode_number: if episode.episode.is_empty() { + episode.sequence_number.to_string() + } else { + episode.episode.clone() + }, + sequence_number: episode.sequence_number, + relative_episode_number, } } - pub fn new_from_movie(movie: &Media<Movie>, stream: VariantData) -> Self { + pub fn new_from_movie(movie: &Movie, video: &VariantData, subtitles: Vec<Locale>) -> Self { Self { title: movie.title.clone(), description: movie.description.clone(), - audio: Locale::ja_JP, - - duration: movie.metadata.duration.to_std().unwrap(), - stream, - subtitles: vec![], - - series_id: movie.metadata.movie_listing_id.clone(), - series_name: movie.metadata.movie_listing_title.clone(), - - season_id: movie.metadata.movie_listing_id.clone(), - season_title: movie.metadata.movie_listing_title.clone(), + subtitles, + resolution: video.resolution.clone(), + fps: video.fps, + series_id: movie.movie_listing_id.clone(), + series_name: movie.movie_listing_title.clone(), + season_id: movie.movie_listing_id.clone(), + season_title: movie.movie_listing_title.to_string(), season_number: 1, - episode_id: movie.id.clone(), - episode_number: 1.0, - relative_episode_number: 1.0, + episode_number: "1".to_string(), + sequence_number: 1.0, + relative_episode_number: Some(1), + } + } + + pub fn new_from_music_video(music_video: &MusicVideo, video: &VariantData) -> Self { + Self { + title: music_video.title.clone(), + description: music_video.description.clone(), + audio: Locale::ja_JP, + subtitles: vec![], + resolution: video.resolution.clone(), + fps: video.fps, + series_id: music_video.id.clone(), + series_name: music_video.title.clone(), + season_id: music_video.id.clone(), + season_title: music_video.title.clone(), + season_number: 1, + episode_id: music_video.id.clone(), + episode_number: "1".to_string(), + sequence_number: 1.0, + relative_episode_number: Some(1), + } + } + + pub fn new_from_concert(concert: &Concert, video: &VariantData) -> Self { + Self { + title: concert.title.clone(), + description: concert.description.clone(), + audio: Locale::ja_JP, + subtitles: vec![], + resolution: video.resolution.clone(), + fps: video.fps, + series_id: concert.id.clone(), + series_name: concert.title.clone(), + season_id: concert.id.clone(), + season_title: concert.title.clone(), + season_number: 1, + episode_id: concert.id.clone(), + episode_number: "1".to_string(), + sequence_number: 1.0, + relative_episode_number: Some(1), + } + } +} + +#[derive(Clone)] +pub struct Format { + pub title: String, + pub description: String, + + pub locales: Vec<(Locale, Vec<Locale>)>, + + pub resolution: Resolution, + pub fps: f64, + + pub series_id: String, + pub series_name: String, + + pub season_id: String, + pub season_title: String, + pub season_number: u32, + + pub episode_id: String, + pub episode_number: String, + pub sequence_number: f32, + pub relative_episode_number: Option<u32>, +} + +impl Format { + pub fn from_single_formats(mut single_formats: Vec<SingleFormat>) -> Self { + let locales: Vec<(Locale, Vec<Locale>)> = single_formats + .iter() + .map(|sf| (sf.audio.clone(), sf.subtitles.clone())) + .collect(); + let first = single_formats.remove(0); + + Self { + title: first.title, + description: first.description, + locales, + resolution: first.resolution, + fps: first.fps, + series_id: first.series_id, + series_name: first.series_name, + season_id: first.season_id, + season_title: first.season_title, + season_number: first.season_number, + episode_id: first.episode_id, + episode_number: first.episode_number, + sequence_number: first.sequence_number, + relative_episode_number: first.relative_episode_number, } } @@ -111,11 +186,18 @@ impl Format { PathBuf::from( as_string .replace("{title}", &sanitize_func(&self.title)) - .replace("{audio}", &sanitize_func(&self.audio.to_string())) .replace( - "{resolution}", - &sanitize_func(&self.stream.resolution.to_string()), + "{audio}", + &sanitize_func( + &self + .locales + .iter() + .map(|(a, _)| a.to_string()) + .collect::<Vec<String>>() + .join("|"), + ), ) + .replace("{resolution}", &sanitize_func(&self.resolution.to_string())) .replace("{series_id}", &sanitize_func(&self.series_id)) .replace("{series_name}", &sanitize_func(&self.series_name)) .replace("{season_id}", &sanitize_func(&self.season_id)) @@ -131,12 +213,109 @@ impl Format { ) .replace( "{relative_episode_number}", - &sanitize_func(&format!("{:0>2}", self.relative_episode_number.to_string())), + &sanitize_func(&format!( + "{:0>2}", + self.relative_episode_number.unwrap_or_default().to_string() + )), ), ) } + pub fn visual_output(&self, dst: &Path) { + info!( + "Downloading {} to '{}'", + self.title, + if is_special_file(&dst) { + dst.to_str().unwrap() + } else { + dst.file_name().unwrap().to_str().unwrap() + } + ); + tab_info!( + "Episode: S{:02}E{:0>2}", + self.season_number, + self.episode_number + ); + tab_info!( + "Audio: {}", + self.locales + .iter() + .map(|(a, _)| a.to_string()) + .collect::<Vec<String>>() + .join(", ") + ); + let mut subtitles: Vec<Locale> = self.locales.iter().flat_map(|(_, s)| s.clone()).collect(); + real_dedup_vec(&mut subtitles); + tab_info!( + "Subtitles: {}", + subtitles + .into_iter() + .map(|l| l.to_string()) + .collect::<Vec<String>>() + .join(", ") + ); + tab_info!("Resolution: {}", self.resolution); + tab_info!("FPS: {:.2}", self.fps) + } + pub fn has_relative_episodes_fmt<S: AsRef<str>>(s: S) -> bool { return s.as_ref().contains("{relative_episode_number}"); } } + +pub fn formats_visual_output(formats: Vec<&Format>) { + if log::max_level() == log::Level::Debug { + let seasons = sort_formats_after_seasons(formats); + debug!("Series has {} seasons", seasons.len()); + for (i, season) in seasons.into_iter().enumerate() { + info!("Season {}", i + 1); + for format in season { + info!( + "{}: {}px, {:.02} FPS (S{:02}E{:0>2})", + format.title, + format.resolution, + format.fps, + format.season_number, + format.episode_number, + ) + } + } + } else { + for season in sort_formats_after_seasons(formats) { + let first = season.get(0).unwrap(); + info!("{} Season {}", first.series_name, first.season_number); + + for (i, format) in season.into_iter().enumerate() { + tab_info!( + "{}. {} ยป {}px, {:.2} FPS (S{:02}E{:0>2})", + i + 1, + format.title, + format.resolution, + format.fps, + format.season_number, + format.episode_number + ) + } + } + } +} + +fn sort_formats_after_seasons(formats: Vec<&Format>) -> Vec<Vec<&Format>> { + let mut season_map = BTreeMap::new(); + + for format in formats { + season_map + .entry(format.season_number) + .or_insert(vec![]) + .push(format) + } + + season_map + .into_values() + .into_iter() + .map(|mut fmts| { + fmts.sort_by(|a, b| a.sequence_number.total_cmp(&b.sequence_number)); + fmts + }) + .collect() +} diff --git a/crunchy-cli-core/src/utils/locale.rs b/crunchy-cli-core/src/utils/locale.rs index 300204d..d749fcb 100644 --- a/crunchy-cli-core/src/utils/locale.rs +++ b/crunchy-cli-core/src/utils/locale.rs @@ -13,3 +13,17 @@ pub fn system_locale() -> Locale { Locale::en_US } } + +/// Check if [`Locale::Custom("all")`] is in the provided locale list and return [`Locale::all`] if +/// so. If not, just return the provided locale list. +pub fn all_locale_in_locales(locales: Vec<Locale>) -> Vec<Locale> { + if locales + .iter() + .find(|l| l.to_string().to_lowercase().trim() == "all") + .is_some() + { + Locale::all() + } else { + locales + } +} diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs index 24fed5d..8472250 100644 --- a/crunchy-cli-core/src/utils/log.rs +++ b/crunchy-cli-core/src/utils/log.rs @@ -1,4 +1,12 @@ -use log::info; +use indicatif::{ProgressBar, ProgressStyle}; +use log::{ + info, set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, + SetLoggerError, +}; +use std::io::{stdout, Write}; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; pub struct ProgressHandler { pub(crate) stopped: bool, @@ -28,3 +36,133 @@ macro_rules! progress { } } pub(crate) use progress; + +macro_rules! tab_info { + ($($arg:tt)+) => { + if log::max_level() == log::LevelFilter::Debug { + info!($($arg)+) + } else { + info!("\t{}", format!($($arg)+)) + } + } +} +pub(crate) use tab_info; + +#[allow(clippy::type_complexity)] +pub struct CliLogger { + all: bool, + level: LevelFilter, + progress: Mutex<Option<ProgressBar>>, +} + +impl Log for CliLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) + || (record.target() != "progress" + && record.target() != "progress_end" + && (!self.all && !record.target().starts_with("crunchy_cli"))) + { + return; + } + + if self.level >= LevelFilter::Debug { + self.extended(record); + return; + } + + match record.target() { + "progress" => self.progress(record, false), + "progress_end" => self.progress(record, true), + _ => { + if self.progress.lock().unwrap().is_some() { + self.progress(record, false) + } else if record.level() > Level::Warn { + self.normal(record) + } else { + self.error(record) + } + } + } + } + + fn flush(&self) { + let _ = stdout().flush(); + } +} + +impl CliLogger { + pub fn new(all: bool, level: LevelFilter) -> Self { + Self { + all, + level, + progress: Mutex::new(None), + } + } + + pub fn init(all: bool, level: LevelFilter) -> Result<(), SetLoggerError> { + set_max_level(level); + set_boxed_logger(Box::new(CliLogger::new(all, level))) + } + + fn extended(&self, record: &Record) { + println!( + "[{}] {} {} ({}) {}", + chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + // replace the 'progress' prefix if this function is invoked via 'progress!' + record + .target() + .replacen("crunchy_cli_core", "crunchy_cli", 1) + .replacen("progress_end", "crunchy_cli", 1) + .replacen("progress", "crunchy_cli", 1), + format!("{:?}", thread::current().id()) + .replace("ThreadId(", "") + .replace(')', ""), + record.args() + ) + } + + fn normal(&self, record: &Record) { + println!(":: {}", record.args()) + } + + fn error(&self, record: &Record) { + eprintln!(":: {}", record.args()) + } + + fn progress(&self, record: &Record, stop: bool) { + let mut progress = self.progress.lock().unwrap(); + + let msg = format!("{}", record.args()); + if stop && progress.is_some() { + if msg.is_empty() { + progress.take().unwrap().finish() + } else { + progress.take().unwrap().finish_with_message(msg) + } + } else if let Some(p) = &*progress { + p.println(format!(":: โ†’ {}", msg)) + } else { + #[cfg(not(windows))] + let finish_str = "โœ”"; + #[cfg(windows)] + // windows does not support all unicode characters by default in their consoles, so + // we're using this (square root?) symbol instead. microsoft. + let finish_str = "โˆš"; + + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::with_template(":: {spinner} {msg}") + .unwrap() + .tick_strings(&["โ€”", "\\", "|", "/", finish_str]), + ); + pb.enable_steady_tick(Duration::from_millis(200)); + pb.set_message(msg); + *progress = Some(pb) + } + } +} diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index 552c198..c991cd8 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -1,10 +1,11 @@ pub mod clap; pub mod context; +pub mod download; +pub mod ffmpeg; +pub mod filter; pub mod format; pub mod locale; pub mod log; pub mod os; pub mod parse; -pub mod sort; -pub mod subtitle; pub mod video; diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 94e9d02..43a37d4 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -37,7 +37,7 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { /// Check if the given path exists and rename it until the new (renamed) file does not exist. pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { - // if it's a special file does not rename it + // do not rename it if it exists but is a special file if is_special_file(&path) { return (path, false); } diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index eb1b19f..fbcba20 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -4,8 +4,8 @@ use crunchyroll_rs::{Crunchyroll, MediaCollection, UrlType}; use log::debug; use regex::Regex; -/// Define a filter, based on season and episode number to filter episodes / movies. -/// If a struct instance equals the [`Default::default()`] it's considered that no filter is applied. +/// Define a find, based on season and episode number to find episodes / movies. +/// If a struct instance equals the [`Default::default()`] it's considered that no find is applied. /// If `from_*` is [`None`] they're set to [`u32::MIN`]. /// If `to_*` is [`None`] they're set to [`u32::MAX`]. #[derive(Debug)] @@ -62,7 +62,7 @@ impl UrlFilter { /// - `...[S3,S5]` - Download episode 3 and 5. /// - `...[S1-S3,S4E2-S4E6]` - Download season 1 to 3 and episode 2 to episode 6 of season 4. -/// In practice, it would look like this: `https://beta.crunchyroll.com/series/12345678/example[S1E5-S3E2]`. +/// In practice, it would look like this: `https://crunchyroll.com/series/12345678/example[S1E5-S3E2]`. pub async fn parse_url( crunchy: &Crunchyroll, mut url: String, @@ -115,7 +115,7 @@ pub async fn parse_url( let url_filter = UrlFilter { inner: filters }; - debug!("Url filter: {:?}", url_filter); + debug!("Url find: {:?}", url_filter); url_filter } else { @@ -125,9 +125,11 @@ pub async fn parse_url( let parsed_url = crunchyroll_rs::parse_url(url).map_or(Err(anyhow!("Invalid url")), Ok)?; debug!("Url type: {:?}", parsed_url); let media_collection = match parsed_url { - UrlType::Series(id) | UrlType::MovieListing(id) | UrlType::EpisodeOrMovie(id) => { - crunchy.media_collection_from_id(id).await? - } + UrlType::Series(id) + | UrlType::MovieListing(id) + | UrlType::EpisodeOrMovie(id) + | UrlType::MusicVideo(id) + | UrlType::Concert(id) => crunchy.media_collection_from_id(id).await?, }; Ok((media_collection, url_filter)) @@ -150,7 +152,7 @@ pub fn parse_resolution(mut resolution: String) -> Result<Resolution> { } else if resolution.ends_with('p') { let without_p = resolution.as_str()[0..resolution.len() - 1] .parse() - .map_err(|_| anyhow!("Could not parse resolution"))?; + .map_err(|_| anyhow!("Could not find resolution"))?; Ok(Resolution { width: without_p * 16 / 9, height: without_p, @@ -159,12 +161,12 @@ pub fn parse_resolution(mut resolution: String) -> Result<Resolution> { Ok(Resolution { width: w .parse() - .map_err(|_| anyhow!("Could not parse resolution"))?, + .map_err(|_| anyhow!("Could not find resolution"))?, height: h .parse() - .map_err(|_| anyhow!("Could not parse resolution"))?, + .map_err(|_| anyhow!("Could not find resolution"))?, }) } else { - bail!("Could not parse resolution") + bail!("Could not find resolution") } } diff --git a/crunchy-cli-core/src/utils/sort.rs b/crunchy-cli-core/src/utils/sort.rs deleted file mode 100644 index 1af0194..0000000 --- a/crunchy-cli-core/src/utils/sort.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::utils::format::Format; -use crunchyroll_rs::{Media, Season}; -use std::collections::BTreeMap; - -/// Sort seasons after their season number. Crunchyroll may have multiple seasons for one season -/// number. They generally store different language in individual seasons with the same season number. -/// E.g. series X has one official season but crunchy has translations for it in 3 different languages -/// so there exist 3 different "seasons" on Crunchyroll which are actual the same season but with -/// different audio. -pub fn sort_seasons_after_number(seasons: Vec<Media<Season>>) -> Vec<Vec<Media<Season>>> { - let mut as_map = BTreeMap::new(); - - for season in seasons { - as_map - .entry(season.metadata.season_number) - .or_insert_with(Vec::new); - as_map - .get_mut(&season.metadata.season_number) - .unwrap() - .push(season) - } - - as_map.into_values().collect() -} - -/// Sort formats after their seasons and episodes (inside it) ascending. Make sure to have only -/// episodes from one series and in one language as argument since the function does not handle those -/// differences which could then lead to a semi messed up result. -pub fn sort_formats_after_seasons(formats: Vec<Format>) -> Vec<Vec<Format>> { - let mut as_map = BTreeMap::new(); - - for format in formats { - // the season title is used as key instead of season number to distinguish duplicated season - // numbers which are actually two different seasons; season id is not used as this somehow - // messes up ordering when duplicated seasons exist - as_map - .entry(format.season_title.clone()) - .or_insert_with(Vec::new); - as_map.get_mut(&format.season_title).unwrap().push(format); - } - - let mut sorted = as_map - .into_iter() - .map(|(_, mut values)| { - values.sort_by(|a, b| a.episode_number.total_cmp(&b.episode_number)); - values - }) - .collect::<Vec<Vec<Format>>>(); - sorted.sort_by(|a, b| a[0].season_number.cmp(&b[0].season_number)); - - sorted -} diff --git a/crunchy-cli-core/src/utils/subtitle.rs b/crunchy-cli-core/src/utils/subtitle.rs deleted file mode 100644 index f638b1b..0000000 --- a/crunchy-cli-core/src/utils/subtitle.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::utils::os::tempfile; -use anyhow::Result; -use chrono::NaiveTime; -use crunchyroll_rs::media::StreamSubtitle; -use crunchyroll_rs::Locale; -use regex::Regex; -use std::io::Write; -use tempfile::TempPath; - -#[derive(Clone)] -pub struct Subtitle { - pub stream_subtitle: StreamSubtitle, - pub audio_locale: Locale, - pub episode_id: String, - pub forced: bool, - pub primary: bool, -} - -pub async fn download_subtitle( - subtitle: StreamSubtitle, - max_length: NaiveTime, -) -> Result<TempPath> { - let tempfile = tempfile(".ass")?; - let (mut file, path) = tempfile.into_parts(); - - let mut buf = vec![]; - subtitle.write_to(&mut buf).await?; - buf = fix_subtitle_look_and_feel(buf); - buf = fix_subtitle_length(buf, max_length); - - file.write_all(buf.as_slice())?; - - Ok(path) -} - -/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video -/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) -/// for more information. -fn fix_subtitle_look_and_feel(raw: Vec<u8>) -> Vec<u8> { - let mut script_info = false; - let mut new = String::new(); - - for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { - if line.trim().starts_with('[') && script_info { - new.push_str("ScaledBorderAndShadow: yes\n"); - script_info = false - } else if line.trim() == "[Script Info]" { - script_info = true - } - new.push_str(line); - new.push('\n') - } - - new.into_bytes() -} - -/// Fix the length of subtitles to a specified maximum amount. This is required because sometimes -/// subtitles have an unnecessary entry long after the actual video ends with artificially extends -/// the video length on some video players. To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more -/// information. -fn fix_subtitle_length(raw: Vec<u8>, max_length: NaiveTime) -> Vec<u8> { - let re = - Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) - .unwrap(); - - // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 - // digits so them have to be reduced manually to avoid the panic - fn format_naive_time(native_time: NaiveTime) -> String { - let formatted_time = native_time.format("%f").to_string(); - format!( - "{}.{}", - native_time.format("%T"), - if formatted_time.len() <= 2 { - native_time.format("%2f").to_string() - } else { - formatted_time.split_at(2).0.parse().unwrap() - } - ) - } - - let length_as_string = format_naive_time(max_length); - let mut new = String::new(); - - for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { - if let Some(capture) = re.captures(line) { - let start = capture.name("start").map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() - }); - let end = capture.name("end").map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() - }); - - if start > max_length { - continue; - } else if end > max_length { - new.push_str( - re.replace( - line, - format!( - "Dialogue: {},{},", - format_naive_time(start), - &length_as_string - ), - ) - .to_string() - .as_str(), - ) - } else { - new.push_str(line) - } - } else { - new.push_str(line) - } - new.push('\n') - } - - new.into_bytes() -} diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index 2d12bfc..f2fabd4 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -1,25 +1,25 @@ use anyhow::Result; -use chrono::NaiveTime; -use regex::Regex; -use std::path::PathBuf; -use std::process::{Command, Stdio}; +use crunchyroll_rs::media::{Resolution, Stream, VariantData}; -/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry -/// long after the actual video ends with artificially extends the video length on some video players. -/// To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more -/// information. -pub fn get_video_length(path: PathBuf) -> Result<NaiveTime> { - let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; +pub async fn variant_data_from_stream( + stream: &Stream, + resolution: &Resolution, +) -> Result<Option<(VariantData, VariantData)>> { + let mut streaming_data = stream.dash_streaming_data(None).await?; + streaming_data + .0 + .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); + streaming_data + .1 + .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); - let ffmpeg = Command::new("ffmpeg") - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .arg("-y") - .args(["-i", path.to_str().unwrap()]) - .output()?; - let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; - let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); - - Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) + let video_variant = match resolution.height { + u64::MAX => Some(streaming_data.0.into_iter().next().unwrap()), + u64::MIN => Some(streaming_data.0.into_iter().last().unwrap()), + _ => streaming_data + .0 + .into_iter() + .find(|v| resolution.height == v.resolution.height), + }; + Ok(video_variant.map(|v| (v, streaming_data.1.first().unwrap().clone()))) } From 57236f2b319bee69b3f436572223d729b017aa5d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 01:51:51 +0100 Subject: [PATCH 331/630] Add a flag to enable experimental fixes (disabled by default now) --- crunchy-cli-core/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 5e0e113..8bb6768 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -38,6 +38,15 @@ pub struct Cli { #[arg(long)] lang: Option<Locale>, + #[arg(help = "Enable experimental fixes which may resolve some unexpected errors")] + #[arg( + long_help = "Enable experimental fixes which may resolve some unexpected errors. \ + If everything works as intended this option isn't needed, but sometimes Crunchyroll mislabels \ + the audio of a series/season or episode or returns a wrong season number. This is when using this option might help to solve the issue" + )] + #[arg(long, default_value_t = false)] + experimental_fixes: bool, + #[clap(flatten)] login_method: LoginMethod, @@ -227,8 +236,8 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { let mut builder = Crunchyroll::builder() .locale(locale) - .stabilization_locales(true) - .stabilization_season_number(true); + .stabilization_locales(cli.experimental_fixes) + .stabilization_season_number(cli.experimental_fixes); if let Command::Download(download) = &cli.command { builder = builder.preferred_audio_locale(download.audio.clone()) From a7adb7191e2d3d99e1837873fe50c0a6e349d57c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 12:52:44 +0100 Subject: [PATCH 332/630] Fix archive not recognizing locale when using direct episode url --- crunchy-cli-core/src/archive/filter.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index d0db3d5..abf2759 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -167,6 +167,9 @@ impl Filter for ArchiveFilter { let mut episodes = vec![]; if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) { + if self.archive.locale.contains(&episode.audio_locale) { + episodes.push(episode.clone()) + } episodes.extend(episode.version(self.archive.locale.clone()).await?); let audio_locales: Vec<Locale> = episodes.iter().map(|e| e.audio_locale.clone()).collect(); From ba1c0aaaa408da6ea5b39230b85cf6bc95720f4b Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 13:39:05 +0100 Subject: [PATCH 333/630] Enable stdout output --- crunchy-cli-core/src/archive/command.rs | 1 + crunchy-cli-core/src/download/filter.rs | 12 +++++++++--- crunchy-cli-core/src/utils/download.rs | 7 ++++++- crunchy-cli-core/src/utils/format.rs | 8 ++++---- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 3583996..46aafda 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -108,6 +108,7 @@ impl Execute for Archive { .to_string_lossy() != "mkv" && !is_special_file(PathBuf::from(&self.output)) + && self.output != "-" { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 81b8fa9..382eec5 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -333,9 +333,15 @@ impl Filter for DownloadFilter { .total_cmp(&b.format.sequence_number) }); for data in input { - let mut downloader = DownloadBuilder::new() - .default_subtitle(self.download.subtitle.clone()) - .build(); + let mut download_builder = + DownloadBuilder::new().default_subtitle(self.download.subtitle.clone()); + // set the output format to mpegts / mpeg transport stream if the output file is stdout. + // mp4 isn't used here as the output file must be readable which isn't possible when + // writing to stdout + if self.download.output == "-" { + download_builder = download_builder.output_format(Some("mpegts".to_string())) + } + let mut downloader = download_builder.build(); downloader.add_format(DownloadFormat { video: (data.video, data.format.audio.clone()), audios: vec![(data.audio, data.format.audio.clone())], diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index ce0c443..397d60f 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -317,7 +317,12 @@ impl Downloader { let progress_handler = progress!("Generating output file"); let ffmpeg = Command::new("ffmpeg") - .stdout(Stdio::null()) + // pass ffmpeg stdout to real stdout only if output file is stdout + .stdout(if dst.to_str().unwrap() == "-" { + Stdio::inherit() + } else { + Stdio::null() + }) .stderr(Stdio::piped()) .args(command_args) .output()?; diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 15acc85..8c7283a 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -223,12 +223,12 @@ impl Format { pub fn visual_output(&self, dst: &Path) { info!( - "Downloading {} to '{}'", + "Downloading {} to {}", self.title, - if is_special_file(&dst) { - dst.to_str().unwrap() + if is_special_file(&dst) || dst.to_str().unwrap() == "-" { + dst.to_string_lossy().to_string() } else { - dst.file_name().unwrap().to_str().unwrap() + format!("'{}'", dst.to_str().unwrap()) } ); tab_info!( From 55483878b32346f118c34805b23ec2aabc1cb84d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 13:53:37 +0100 Subject: [PATCH 334/630] Re-add hwaccel cuda --- crunchy-cli-core/src/utils/ffmpeg.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 5f8b821..54ededd 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -285,7 +285,7 @@ impl FFmpegPreset { if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + input.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-c:v", "h264_cuvid"]); output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } } @@ -303,7 +303,7 @@ impl FFmpegPreset { if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuvid", "-c:v", "h264_cuvid"]); + input.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-c:v", "h264_cuvid"]); output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) } } From bd20c5a7b678245f0b298cda9aef1a6788a4dd39 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 15:00:35 +0100 Subject: [PATCH 335/630] Update dependencies --- Cargo.lock | 22 +++++++++++----------- crunchy-cli-core/Cargo.lock | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2651e95..3be7641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.8", ] [[package]] @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddaff98c25bdedca0f1ed2b68c28b12a3e52054144b1b11380236d23051c751" +checksum = "808095ff5e340185e2783bdc2fc450003e918d79f110c1b27c7fe54fa4ba23eb" dependencies = [ "aes", "async-trait", @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f26b71b36db3139ce788545c2bffa7e69d4fd7b689f143086c6283e847a4668" +checksum = "1e5e2c70231ecd0bdc5a243bdb94cd3923176e4dfaa957006adee7899e484c8f" dependencies = [ "darling 0.14.4", "quote", @@ -399,7 +399,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.5", + "syn 2.0.8", ] [[package]] @@ -416,7 +416,7 @@ checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.8", ] [[package]] @@ -1502,7 +1502,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.8", ] [[package]] @@ -1648,9 +1648,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.5" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c2d1c76a26822187a1fbb5964e3fff108bc208f02e820ab9dac1234f6b388a" +checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" dependencies = [ "proc-macro2", "quote", @@ -1719,7 +1719,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.8", ] [[package]] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index c4e2235..35cea2e 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -45,7 +45,7 @@ checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.6", + "syn 2.0.8", ] [[package]] @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddaff98c25bdedca0f1ed2b68c28b12a3e52054144b1b11380236d23051c751" +checksum = "808095ff5e340185e2783bdc2fc450003e918d79f110c1b27c7fe54fa4ba23eb" dependencies = [ "aes", "async-trait", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f26b71b36db3139ce788545c2bffa7e69d4fd7b689f143086c6283e847a4668" +checksum = "1e5e2c70231ecd0bdc5a243bdb94cd3923176e4dfaa957006adee7899e484c8f" dependencies = [ "darling 0.14.4", "quote", @@ -368,7 +368,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.6", + "syn 2.0.8", ] [[package]] @@ -385,7 +385,7 @@ checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.6", + "syn 2.0.8", ] [[package]] @@ -1465,7 +1465,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.6", + "syn 2.0.8", ] [[package]] @@ -1611,9 +1611,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.6" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece519cfaf36269ea69d16c363fa1d59ceba8296bbfbfc003c3176d01f2816ee" +checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" dependencies = [ "proc-macro2", "quote", @@ -1682,7 +1682,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.6", + "syn 2.0.8", ] [[package]] From c97adb3ce7c5175b54eab9183a4c8743f6b1b168 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 23 Mar 2023 17:17:55 +0100 Subject: [PATCH 336/630] Remove duplicated subtitles on archive audio merge --- crunchy-cli-core/src/archive/filter.rs | 10 +++++++++- crunchy-cli-core/src/utils/ffmpeg.rs | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index abf2759..c1d1c4a 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -446,7 +446,15 @@ impl Filter for ArchiveFilter { .iter() .map(|d| (d.audio.clone(), d.format.audio.clone())) .collect(), - subtitles: data.iter().map(|d| d.subtitles.clone()).flatten().collect(), + // mix all subtitles together and then reduce them via a map so that only one + // subtitle per language exists + subtitles: data + .iter() + .flat_map(|d| d.subtitles.clone()) + .map(|s| (s.locale.clone(), s)) + .collect::<HashMap<Locale, Subtitle>>() + .into_values() + .collect(), }), MergeBehavior::Auto => { let mut download_formats: HashMap<Duration, DownloadFormat> = HashMap::new(); diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 54ededd..3336fca 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -285,7 +285,14 @@ impl FFmpegPreset { if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-c:v", "h264_cuvid"]); + input.extend([ + "-hwaccel", + "cuda", + "-hwaccel_output_format", + "cuda", + "-c:v", + "h264_cuvid", + ]); output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } } @@ -303,7 +310,14 @@ impl FFmpegPreset { if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { - input.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-c:v", "h264_cuvid"]); + input.extend([ + "-hwaccel", + "cuda", + "-hwaccel_output_format", + "cuda", + "-c:v", + "h264_cuvid", + ]); output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) } } From 8a523078453e92d5470e64cac73cab64f603749c Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Thu, 23 Mar 2023 19:34:26 +0000 Subject: [PATCH 337/630] Update issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 0c62d66..9590be1 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: bug assignees: '' --- @@ -24,7 +24,7 @@ If applicable, add screenshots to help explain your problem. **Client (please complete the following information):** - OS: [e.g. Windows] - - Version [e.g. v2.2.2] + - Version [e.g. 3.0.0-dev.8 (17233f2 2023-01-10)] <!-- Version 1 or 2 aren't actively supported anymore. Make sure that the bug occurs on the master branch or a version 3 pre-release --> **Additional context** Add any other context about the problem here. From 1213880df7fac410688516a4a892556ba754dd26 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Thu, 23 Mar 2023 19:39:52 +0000 Subject: [PATCH 338/630] Add feature request template --- .github/ISSUE_TEMPLATE/feature_request.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..59094e2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. From 8e972ab578d5b1247e75d4c4326a304034c538a0 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 8 Apr 2023 14:44:16 +0200 Subject: [PATCH 339/630] Move stream download logic to fix cms error/rate limiting --- crunchy-cli-core/src/archive/command.rs | 132 ++++++++- crunchy-cli-core/src/archive/filter.rs | 336 +++++------------------ crunchy-cli-core/src/download/command.rs | 73 ++++- crunchy-cli-core/src/download/filter.rs | 169 ++---------- crunchy-cli-core/src/utils/download.rs | 2 +- crunchy-cli-core/src/utils/filter.rs | 4 +- crunchy-cli-core/src/utils/format.rs | 273 ++++++++++++------ 7 files changed, 467 insertions(+), 522 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 46aafda..fe84c5b 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -1,19 +1,22 @@ use crate::archive::filter::ArchiveFilter; use crate::utils::context::Context; -use crate::utils::download::MergeBehavior; +use crate::utils::download::{DownloadBuilder, DownloadFormat, MergeBehavior}; use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::Filter; -use crate::utils::format::formats_visual_output; +use crate::utils::format::{Format, SingleFormat}; use crate::utils::locale::all_locale_in_locales; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::parse_url; +use crate::utils::video::variant_data_from_stream; use crate::Execute; use anyhow::bail; use anyhow::Result; -use crunchyroll_rs::media::Resolution; +use chrono::Duration; +use crunchyroll_rs::media::{Resolution, Subtitle}; use crunchyroll_rs::Locale; use log::debug; +use std::collections::HashMap; use std::path::PathBuf; #[derive(Clone, Debug, clap::Parser)] @@ -135,19 +138,33 @@ impl Execute for Archive { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let archive_formats = ArchiveFilter::new(url_filter, self.clone()) + let single_format_collection = ArchiveFilter::new(url_filter, self.clone()) .visit(media_collection) .await?; - if archive_formats.is_empty() { + if single_format_collection.is_empty() { progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); continue; } progress_handler.stop(format!("Loaded series information for url {}", i + 1)); - formats_visual_output(archive_formats.iter().map(|(_, f)| f).collect()); + single_format_collection.full_visual_output(); + + let download_builder = DownloadBuilder::new() + .default_subtitle(self.default_subtitle.clone()) + .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) + .output_format(Some("matroska".to_string())) + .audio_sort(Some(self.locale.clone())) + .subtitle_sort(Some(self.subtitle.clone())); + + for single_formats in single_format_collection.into_iter() { + let (download_formats, mut format) = get_format(&self, &single_formats).await?; + + let mut downloader = download_builder.clone().build(); + for download_format in download_formats { + downloader.add_format(download_format) + } - for (downloader, mut format) in archive_formats { let formatted_path = format.format_path((&self.output).into(), true); let (path, changed) = free_file(formatted_path.clone()); @@ -183,3 +200,104 @@ impl Execute for Archive { Ok(()) } } + +async fn get_format( + archive: &Archive, + single_formats: &Vec<SingleFormat>, +) -> Result<(Vec<DownloadFormat>, Format)> { + let mut format_pairs = vec![]; + let mut single_format_to_format_pairs = vec![]; + + for single_format in single_formats { + let stream = single_format.stream().await?; + let Some((video, audio)) = variant_data_from_stream(&stream, &archive.resolution).await? else { + if single_format.is_episode() { + bail!( + "Resolution ({}) is not available for episode {} ({}) of {} season {}", + archive.resolution, + single_format.episode_number, + single_format.title, + single_format.series_name, + single_format.season_number, + ) + } else { + bail!( + "Resolution ({}) is not available for {} ({})", + archive.resolution, + single_format.source_type(), + single_format.title + ) + } + }; + + let subtitles: Vec<Subtitle> = archive + .subtitle + .iter() + .filter_map(|s| stream.subtitles.get(s).cloned()) + .collect(); + + format_pairs.push((single_format, video.clone(), audio, subtitles.clone())); + single_format_to_format_pairs.push((single_format.clone(), video, subtitles)) + } + + let mut download_formats = vec![]; + + match archive.merge { + MergeBehavior::Video => { + for (single_format, video, audio, subtitles) in format_pairs { + download_formats.push(DownloadFormat { + video: (video, single_format.audio.clone()), + audios: vec![(audio, single_format.audio.clone())], + subtitles, + }) + } + } + MergeBehavior::Audio => download_formats.push(DownloadFormat { + video: ( + (*format_pairs.first().unwrap()).1.clone(), + (*format_pairs.first().unwrap()).0.audio.clone(), + ), + audios: format_pairs + .iter() + .map(|(single_format, _, audio, _)| (audio.clone(), single_format.audio.clone())) + .collect(), + // mix all subtitles together and then reduce them via a map so that only one subtitle + // per language exists + subtitles: format_pairs + .iter() + .flat_map(|(_, _, _, subtitles)| subtitles.clone()) + .map(|s| (s.locale.clone(), s)) + .collect::<HashMap<Locale, Subtitle>>() + .into_values() + .collect(), + }), + MergeBehavior::Auto => { + let mut d_formats: HashMap<Duration, DownloadFormat> = HashMap::new(); + + for (single_format, video, audio, subtitles) in format_pairs { + if let Some(d_format) = d_formats.get_mut(&single_format.duration) { + d_format.audios.push((audio, single_format.audio.clone())); + d_format.subtitles.extend(subtitles) + } else { + d_formats.insert( + single_format.duration, + DownloadFormat { + video: (video, single_format.audio.clone()), + audios: vec![(audio, single_format.audio.clone())], + subtitles, + }, + ); + } + } + + for d_format in d_formats.into_values() { + download_formats.push(d_format) + } + } + } + + Ok(( + download_formats, + Format::from_single_formats(single_format_to_format_pairs), + )) +} diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index c1d1c4a..7b51100 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -1,24 +1,11 @@ use crate::archive::command::Archive; -use crate::utils::download::{DownloadBuilder, DownloadFormat, Downloader, MergeBehavior}; use crate::utils::filter::{real_dedup_vec, Filter}; -use crate::utils::format::{Format, SingleFormat}; +use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; use crate::utils::parse::UrlFilter; -use crate::utils::video::variant_data_from_stream; -use anyhow::{bail, Result}; -use chrono::Duration; -use crunchyroll_rs::media::{Subtitle, VariantData}; +use anyhow::Result; use crunchyroll_rs::{Concert, Episode, Locale, Movie, MovieListing, MusicVideo, Season, Series}; use log::warn; -use std::collections::HashMap; -use std::hash::Hash; - -pub(crate) struct FilterResult { - format: SingleFormat, - video: VariantData, - audio: VariantData, - duration: Duration, - subtitles: Vec<Subtitle>, -} +use std::collections::{BTreeMap, HashMap}; enum Visited { Series, @@ -48,8 +35,8 @@ impl ArchiveFilter { #[async_trait::async_trait] impl Filter for ArchiveFilter { - type T = Vec<FilterResult>; - type Output = (Downloader, Format); + type T = Vec<SingleFormat>; + type Output = SingleFormatCollection; async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the @@ -168,11 +155,19 @@ impl Filter for ArchiveFilter { let mut episodes = vec![]; if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) { if self.archive.locale.contains(&episode.audio_locale) { - episodes.push(episode.clone()) + episodes.push((episode.clone(), episode.subtitle_locales.clone())) } - episodes.extend(episode.version(self.archive.locale.clone()).await?); - let audio_locales: Vec<Locale> = - episodes.iter().map(|e| e.audio_locale.clone()).collect(); + episodes.extend( + episode + .version(self.archive.locale.clone()) + .await? + .into_iter() + .map(|e| (e.clone(), e.subtitle_locales.clone())), + ); + let audio_locales: Vec<Locale> = episodes + .iter() + .map(|(e, _)| e.audio_locale.clone()) + .collect(); let missing_audio = missing_locales(&audio_locales, &self.archive.locale); if !missing_audio.is_empty() { warn!( @@ -186,11 +181,8 @@ impl Filter for ArchiveFilter { ) } - let mut subtitle_locales: Vec<Locale> = episodes - .iter() - .map(|e| e.subtitle_locales.clone()) - .flatten() - .collect(); + let mut subtitle_locales: Vec<Locale> = + episodes.iter().map(|(_, s)| s.clone()).flatten().collect(); real_dedup_vec(&mut subtitle_locales); let missing_subtitles = missing_locales(&subtitle_locales, &self.archive.subtitle); if !missing_subtitles.is_empty() @@ -210,81 +202,49 @@ impl Filter for ArchiveFilter { self.season_subtitles_missing.push(episode.season_number) } } else { - episodes.push(episode.clone()) + episodes.push((episode.clone(), episode.subtitle_locales.clone())) } - let mut formats = vec![]; - for episode in episodes { - let stream = episode.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.archive.resolution).await? + let relative_episode_number = if Format::has_relative_episodes_fmt(&self.archive.output) { + if self + .season_episode_count + .get(&episode.season_number) + .is_none() { - (video, audio) - } else { - bail!( - "Resolution ({}) is not available for episode {} ({}) of {} season {}", - &self.archive.resolution, + let season_episodes = episode.season().await?.episodes().await?; + self.season_episode_count.insert( + episode.season_number, + season_episodes.into_iter().map(|e| e.id).collect(), + ); + } + let relative_episode_number = self + .season_episode_count + .get(&episode.season_number) + .unwrap() + .iter() + .position(|id| id == &episode.id); + if relative_episode_number.is_none() { + warn!( + "Failed to get relative episode number for episode {} ({}) of {} season {}", episode.episode_number, episode.title, episode.series_title, episode.season_number, - ); - }; - let subtitles: Vec<Subtitle> = self - .archive - .subtitle - .iter() - .filter_map(|s| stream.subtitles.get(s).cloned()) - .collect(); + ) + } + relative_episode_number + } else { + None + }; - let relative_episode_number = if Format::has_relative_episodes_fmt(&self.archive.output) - { - if self - .season_episode_count - .get(&episode.season_number) - .is_none() - { - let season_episodes = episode.season().await?.episodes().await?; - self.season_episode_count.insert( - episode.season_number, - season_episodes.into_iter().map(|e| e.id).collect(), - ); - } - let relative_episode_number = self - .season_episode_count - .get(&episode.season_number) - .unwrap() - .iter() - .position(|id| id == &episode.id); - if relative_episode_number.is_none() { - warn!( - "Failed to get relative episode number for episode {} ({}) of {} season {}", - episode.episode_number, - episode.title, - episode.series_title, - episode.season_number, - ) - } - relative_episode_number - } else { - None - }; - - formats.push(FilterResult { - format: SingleFormat::new_from_episode( - &episode, - &video, - subtitles.iter().map(|s| s.locale.clone()).collect(), - relative_episode_number.map(|n| n as u32), - ), - video, - audio, - duration: episode.duration.clone(), - subtitles, - }) - } - - Ok(Some(formats)) + Ok(Some( + episodes + .into_iter() + .map(|(e, s)| { + SingleFormat::new_from_episode(e, s, relative_episode_number.map(|n| n as u32)) + }) + .collect(), + )) } async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { @@ -292,199 +252,37 @@ impl Filter for ArchiveFilter { } async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>> { - let stream = movie.streams().await?; - let subtitles: Vec<&Subtitle> = self - .archive - .subtitle - .iter() - .filter_map(|l| stream.subtitles.get(l)) - .collect(); - - let missing_subtitles = missing_locales( - &subtitles.iter().map(|&s| s.locale.clone()).collect(), - &self.archive.subtitle, - ); - if !missing_subtitles.is_empty() { - warn!( - "Movie '{}' is not available with {} subtitles", - movie.title, - missing_subtitles - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.archive.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}) of movie {} is not available", - self.archive.resolution, - movie.title - ) - }; - - Ok(Some(vec![FilterResult { - format: SingleFormat::new_from_movie(&movie, &video, vec![]), - video, - audio, - duration: movie.duration, - subtitles: vec![], - }])) + Ok(Some(vec![SingleFormat::new_from_movie(movie, vec![])])) } async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>> { - let stream = music_video.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.archive.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}) of music video {} is not available", - self.archive.resolution, - music_video.title - ) - }; - - Ok(Some(vec![FilterResult { - format: SingleFormat::new_from_music_video(&music_video, &video), - video, - audio, - duration: music_video.duration, - subtitles: vec![], - }])) + Ok(Some(vec![SingleFormat::new_from_music_video(music_video)])) } async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>> { - let stream = concert.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.archive.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}x{}) of music video {} is not available", - self.archive.resolution.width, - self.archive.resolution.height, - concert.title - ) - }; - - Ok(Some(vec![FilterResult { - format: SingleFormat::new_from_concert(&concert, &video), - video, - audio, - duration: concert.duration, - subtitles: vec![], - }])) + Ok(Some(vec![SingleFormat::new_from_concert(concert)])) } - async fn finish(self, input: Vec<Self::T>) -> Result<Vec<Self::Output>> { - let flatten_input: Vec<FilterResult> = input.into_iter().flatten().collect(); + async fn finish(self, input: Vec<Self::T>) -> Result<Self::Output> { + let flatten_input: Self::T = input.into_iter().flatten().collect(); - #[derive(Hash, Eq, PartialEq)] - struct SortKey { - season: u32, - episode: String, - } + let mut single_format_collection = SingleFormatCollection::new(); - let mut sorted: HashMap<SortKey, Vec<FilterResult>> = HashMap::new(); + struct SortKey(u32, String); + + let mut sorted: BTreeMap<(u32, String), Self::T> = BTreeMap::new(); for data in flatten_input { sorted - .entry(SortKey { - season: data.format.season_number, - episode: data.format.episode_number.to_string(), - }) + .entry((data.season_number, data.sequence_number.to_string())) .or_insert(vec![]) .push(data) } - let mut values: Vec<Vec<FilterResult>> = sorted.into_values().collect(); - values.sort_by(|a, b| { - a.first() - .unwrap() - .format - .sequence_number - .total_cmp(&b.first().unwrap().format.sequence_number) - }); - - let mut result = vec![]; - for data in values { - let single_formats: Vec<SingleFormat> = - data.iter().map(|fr| fr.format.clone()).collect(); - let format = Format::from_single_formats(single_formats); - - let mut downloader = DownloadBuilder::new() - .default_subtitle(self.archive.default_subtitle.clone()) - .ffmpeg_preset(self.archive.ffmpeg_preset.clone().unwrap_or_default()) - .output_format(Some("matroska".to_string())) - .audio_sort(Some(self.archive.locale.clone())) - .subtitle_sort(Some(self.archive.subtitle.clone())) - .build(); - - match self.archive.merge.clone() { - MergeBehavior::Video => { - for d in data { - downloader.add_format(DownloadFormat { - video: (d.video, d.format.audio.clone()), - audios: vec![(d.audio, d.format.audio.clone())], - subtitles: d.subtitles, - }) - } - } - MergeBehavior::Audio => downloader.add_format(DownloadFormat { - video: ( - data.first().unwrap().video.clone(), - data.first().unwrap().format.audio.clone(), - ), - audios: data - .iter() - .map(|d| (d.audio.clone(), d.format.audio.clone())) - .collect(), - // mix all subtitles together and then reduce them via a map so that only one - // subtitle per language exists - subtitles: data - .iter() - .flat_map(|d| d.subtitles.clone()) - .map(|s| (s.locale.clone(), s)) - .collect::<HashMap<Locale, Subtitle>>() - .into_values() - .collect(), - }), - MergeBehavior::Auto => { - let mut download_formats: HashMap<Duration, DownloadFormat> = HashMap::new(); - - for d in data { - if let Some(download_format) = download_formats.get_mut(&d.duration) { - download_format.audios.push((d.audio, d.format.audio)); - download_format.subtitles.extend(d.subtitles) - } else { - download_formats.insert( - d.duration, - DownloadFormat { - video: (d.video, d.format.audio.clone()), - audios: vec![(d.audio, d.format.audio)], - subtitles: d.subtitles, - }, - ); - } - } - - for download_format in download_formats.into_values() { - downloader.add_format(download_format) - } - } - } - - result.push((downloader, format)) + for data in sorted.into_values() { + single_format_collection.add_single_formats(data) } - Ok(result) + Ok(single_format_collection) } } diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 25db55b..fb40735 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -1,11 +1,13 @@ use crate::download::filter::DownloadFilter; use crate::utils::context::Context; +use crate::utils::download::{DownloadBuilder, DownloadFormat}; use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::Filter; -use crate::utils::format::formats_visual_output; +use crate::utils::format::{Format, SingleFormat}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg}; use crate::utils::parse::parse_url; +use crate::utils::video::variant_data_from_stream; use crate::Execute; use anyhow::bail; use anyhow::Result; @@ -116,19 +118,35 @@ impl Execute for Download { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let download_formats = DownloadFilter::new(url_filter, self.clone()) + let single_format_collection = DownloadFilter::new(url_filter, self.clone()) .visit(media_collection) .await?; - if download_formats.is_empty() { + if single_format_collection.is_empty() { progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); continue; } progress_handler.stop(format!("Loaded series information for url {}", i + 1)); - formats_visual_output(download_formats.iter().map(|(_, f)| f).collect()); + single_format_collection.full_visual_output(); + + let download_builder = DownloadBuilder::new() + .default_subtitle(self.subtitle.clone()) + .output_format(if self.output == "-" { + Some("mpegts".to_string()) + } else { + None + }); + + for mut single_formats in single_format_collection.into_iter() { + // the vec contains always only one item + let single_format = single_formats.remove(0); + + let (download_format, format) = get_format(&self, &single_format).await?; + + let mut downloader = download_builder.clone().build(); + downloader.add_format(download_format); - for (downloader, format) in download_formats { let formatted_path = format.format_path((&self.output).into(), true); let (path, changed) = free_file(formatted_path.clone()); @@ -149,3 +167,48 @@ impl Execute for Download { Ok(()) } } + +async fn get_format( + download: &Download, + single_format: &SingleFormat, +) -> Result<(DownloadFormat, Format)> { + let stream = single_format.stream().await?; + let Some((video, audio)) = variant_data_from_stream(&stream, &download.resolution).await? else { + if single_format.is_episode() { + bail!( + "Resolution ({}) is not available for episode {} ({}) of {} season {}", + download.resolution, + single_format.episode_number, + single_format.title, + single_format.series_name, + single_format.season_number, + ) + } else { + bail!( + "Resolution ({}) is not available for {} ({})", + download.resolution, + single_format.source_type(), + single_format.title + ) + } + }; + + let subtitle = if let Some(subtitle_locale) = &download.subtitle { + stream.subtitles.get(subtitle_locale).map(|s| s.clone()) + } else { + None + }; + + let download_format = DownloadFormat { + video: (video.clone(), single_format.audio.clone()), + audios: vec![(audio, single_format.audio.clone())], + subtitles: subtitle.clone().map_or(vec![], |s| vec![s]), + }; + let format = Format::from_single_formats(vec![( + single_format.clone(), + video, + subtitle.map_or(vec![], |s| vec![s]), + )]); + + Ok((download_format, format)) +} diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 382eec5..35eaaed 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -1,22 +1,12 @@ use crate::download::Download; -use crate::utils::download::{DownloadBuilder, DownloadFormat, Downloader}; use crate::utils::filter::Filter; -use crate::utils::format::{Format, SingleFormat}; +use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; use crate::utils::parse::UrlFilter; -use crate::utils::video::variant_data_from_stream; use anyhow::{bail, Result}; -use crunchyroll_rs::media::{Subtitle, VariantData}; use crunchyroll_rs::{Concert, Episode, Movie, MovieListing, MusicVideo, Season, Series}; use log::{error, warn}; use std::collections::HashMap; -pub(crate) struct FilterResult { - format: SingleFormat, - video: VariantData, - audio: VariantData, - subtitle: Option<Subtitle>, -} - pub(crate) struct DownloadFilter { url_filter: UrlFilter, download: Download, @@ -37,8 +27,8 @@ impl DownloadFilter { #[async_trait::async_trait] impl Filter for DownloadFilter { - type T = FilterResult; - type Output = (Downloader, Format); + type T = SingleFormat; + type Output = SingleFormatCollection; async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the @@ -165,32 +155,6 @@ impl Filter for DownloadFilter { } } - // get the correct video stream - let stream = episode.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.download.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}) is not available for episode {} ({}) of {} season {}", - self.download.resolution, - episode.episode_number, - episode.title, - episode.series_title, - episode.season_number, - ) - }; - - // it is assumed that the subtitle, if requested, exists b/c the subtitle check above must - // be passed to reach this condition. - // the check isn't done in this if block to reduce unnecessary fetching of the stream - let subtitle = if let Some(subtitle_locale) = &self.download.subtitle { - stream.subtitles.get(subtitle_locale).map(|s| s.clone()) - } else { - None - }; - // get the relative episode number. only done if the output string has the pattern to include // the relative episode number as this requires some extra fetching let relative_episode_number = if Format::has_relative_episodes_fmt(&self.download.output) { @@ -225,17 +189,17 @@ impl Filter for DownloadFilter { None }; - Ok(Some(FilterResult { - format: SingleFormat::new_from_episode( - &episode, - &video, - subtitle.clone().map_or(vec![], |s| vec![s.locale]), - relative_episode_number.map(|n| n as u32), - ), - video, - audio, - subtitle, - })) + Ok(Some(SingleFormat::new_from_episode( + episode.clone(), + self.download.subtitle.clone().map_or(vec![], |s| { + if episode.subtitle_locales.contains(&s) { + vec![s] + } else { + vec![] + } + }), + relative_episode_number.map(|n| n as u32), + ))) } async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { @@ -243,113 +207,24 @@ impl Filter for DownloadFilter { } async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>> { - let stream = movie.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.download.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}) of movie '{}' is not available", - self.download.resolution, - movie.title - ) - }; - let subtitle = if let Some(subtitle_locale) = &self.download.subtitle { - let Some(subtitle) = stream.subtitles.get(subtitle_locale) else { - error!( - "Movie '{}' has no {} subtitles", - movie.title, - subtitle_locale - ); - return Ok(None) - }; - Some(subtitle.clone()) - } else { - None - }; - - Ok(Some(FilterResult { - format: SingleFormat::new_from_movie( - &movie, - &video, - subtitle.clone().map_or(vec![], |s| vec![s.locale]), - ), - video, - audio, - subtitle, - })) + Ok(Some(SingleFormat::new_from_movie(movie, vec![]))) } async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>> { - let stream = music_video.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.download.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}) of music video {} is not available", - self.download.resolution, - music_video.title - ) - }; - - Ok(Some(FilterResult { - format: SingleFormat::new_from_music_video(&music_video, &video), - video, - audio, - subtitle: None, - })) + Ok(Some(SingleFormat::new_from_music_video(music_video))) } async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>> { - let stream = concert.streams().await?; - let (video, audio) = if let Some((video, audio)) = - variant_data_from_stream(&stream, &self.download.resolution).await? - { - (video, audio) - } else { - bail!( - "Resolution ({}) of music video {} is not available", - self.download.resolution, - concert.title - ) - }; - - Ok(Some(FilterResult { - format: SingleFormat::new_from_concert(&concert, &video), - video, - audio, - subtitle: None, - })) + Ok(Some(SingleFormat::new_from_concert(concert))) } - async fn finish(self, mut input: Vec<Self::T>) -> Result<Vec<Self::Output>> { - let mut result = vec![]; - input.sort_by(|a, b| { - a.format - .sequence_number - .total_cmp(&b.format.sequence_number) - }); + async fn finish(self, input: Vec<Self::T>) -> Result<Self::Output> { + let mut single_format_collection = SingleFormatCollection::new(); + for data in input { - let mut download_builder = - DownloadBuilder::new().default_subtitle(self.download.subtitle.clone()); - // set the output format to mpegts / mpeg transport stream if the output file is stdout. - // mp4 isn't used here as the output file must be readable which isn't possible when - // writing to stdout - if self.download.output == "-" { - download_builder = download_builder.output_format(Some("mpegts".to_string())) - } - let mut downloader = download_builder.build(); - downloader.add_format(DownloadFormat { - video: (data.video, data.format.audio.clone()), - audios: vec![(data.audio, data.format.audio.clone())], - subtitles: data.subtitle.map_or(vec![], |s| vec![s]), - }); - result.push((downloader, Format::from_single_formats(vec![data.format]))) + single_format_collection.add_single_formats(vec![data]) } - Ok(result) + Ok(single_format_collection) } } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 397d60f..63d9a4d 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -38,7 +38,7 @@ impl MergeBehavior { } } -#[derive(derive_setters::Setters)] +#[derive(Clone, derive_setters::Setters)] pub struct DownloadBuilder { ffmpeg_preset: FFmpegPreset, default_subtitle: Option<Locale>, diff --git a/crunchy-cli-core/src/utils/filter.rs b/crunchy-cli-core/src/utils/filter.rs index b68e30d..bb9957d 100644 --- a/crunchy-cli-core/src/utils/filter.rs +++ b/crunchy-cli-core/src/utils/filter.rs @@ -18,7 +18,7 @@ pub trait Filter { async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>>; async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>>; - async fn visit(mut self, media_collection: MediaCollection) -> Result<Vec<Self::Output>> + async fn visit(mut self, media_collection: MediaCollection) -> Result<Self::Output> where Self: Send + Sized, { @@ -80,7 +80,7 @@ pub trait Filter { self.finish(result).await } - async fn finish(self, input: Vec<Self::T>) -> Result<Vec<Self::Output>>; + async fn finish(self, input: Vec<Self::T>) -> Result<Self::Output>; } /// Remove all duplicates from a [`Vec`]. diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 8c7283a..b2ef5f2 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,9 +1,12 @@ use crate::utils::filter::real_dedup_vec; use crate::utils::log::tab_info; use crate::utils::os::is_special_file; -use crunchyroll_rs::media::{Resolution, VariantData}; -use crunchyroll_rs::{Concert, Episode, Locale, Movie, MusicVideo}; +use anyhow::Result; +use chrono::Duration; +use crunchyroll_rs::media::{Resolution, Stream, Subtitle, VariantData}; +use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; use log::{debug, info}; +use std::cmp::Ordering; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; @@ -15,9 +18,6 @@ pub struct SingleFormat { pub audio: Locale, pub subtitles: Vec<Locale>, - pub resolution: Resolution, - pub fps: f64, - pub series_id: String, pub series_name: String, @@ -29,12 +29,15 @@ pub struct SingleFormat { pub episode_number: String, pub sequence_number: f32, pub relative_episode_number: Option<u32>, + + pub duration: Duration, + + source: MediaCollection, } impl SingleFormat { pub fn new_from_episode( - episode: &Episode, - video: &VariantData, + episode: Episode, subtitles: Vec<Locale>, relative_episode_number: Option<u32>, ) -> Self { @@ -43,8 +46,6 @@ impl SingleFormat { description: episode.description.clone(), audio: episode.audio_locale.clone(), subtitles, - resolution: video.resolution.clone(), - fps: video.fps, series_id: episode.series_id.clone(), series_name: episode.series_title.clone(), season_id: episode.season_id.clone(), @@ -58,17 +59,17 @@ impl SingleFormat { }, sequence_number: episode.sequence_number, relative_episode_number, + duration: episode.duration, + source: episode.into(), } } - pub fn new_from_movie(movie: &Movie, video: &VariantData, subtitles: Vec<Locale>) -> Self { + pub fn new_from_movie(movie: Movie, subtitles: Vec<Locale>) -> Self { Self { title: movie.title.clone(), description: movie.description.clone(), audio: Locale::ja_JP, subtitles, - resolution: video.resolution.clone(), - fps: video.fps, series_id: movie.movie_listing_id.clone(), series_name: movie.movie_listing_title.clone(), season_id: movie.movie_listing_id.clone(), @@ -78,17 +79,17 @@ impl SingleFormat { episode_number: "1".to_string(), sequence_number: 1.0, relative_episode_number: Some(1), + duration: movie.duration, + source: movie.into(), } } - pub fn new_from_music_video(music_video: &MusicVideo, video: &VariantData) -> Self { + pub fn new_from_music_video(music_video: MusicVideo) -> Self { Self { title: music_video.title.clone(), description: music_video.description.clone(), audio: Locale::ja_JP, subtitles: vec![], - resolution: video.resolution.clone(), - fps: video.fps, series_id: music_video.id.clone(), series_name: music_video.title.clone(), season_id: music_video.id.clone(), @@ -98,17 +99,17 @@ impl SingleFormat { episode_number: "1".to_string(), sequence_number: 1.0, relative_episode_number: Some(1), + duration: music_video.duration, + source: music_video.into(), } } - pub fn new_from_concert(concert: &Concert, video: &VariantData) -> Self { + pub fn new_from_concert(concert: Concert) -> Self { Self { title: concert.title.clone(), description: concert.description.clone(), audio: Locale::ja_JP, subtitles: vec![], - resolution: video.resolution.clone(), - fps: video.fps, series_id: concert.id.clone(), series_name: concert.title.clone(), season_id: concert.id.clone(), @@ -118,8 +119,145 @@ impl SingleFormat { episode_number: "1".to_string(), sequence_number: 1.0, relative_episode_number: Some(1), + duration: concert.duration, + source: concert.into(), } } + + pub async fn stream(&self) -> Result<Stream> { + let stream = match &self.source { + MediaCollection::Episode(e) => e.streams().await?, + MediaCollection::Movie(m) => m.streams().await?, + MediaCollection::MusicVideo(mv) => mv.streams().await?, + MediaCollection::Concert(c) => c.streams().await?, + _ => unreachable!(), + }; + Ok(stream) + } + + pub fn source_type(&self) -> String { + match &self.source { + MediaCollection::Episode(_) => "episode", + MediaCollection::Movie(_) => "movie", + MediaCollection::MusicVideo(_) => "music video", + MediaCollection::Concert(_) => "concert", + _ => unreachable!(), + } + .to_string() + } + + pub fn is_episode(&self) -> bool { + match self.source { + MediaCollection::Episode(_) => true, + _ => false, + } + } +} + +struct SingleFormatCollectionEpisodeKey(f32); + +impl PartialOrd for SingleFormatCollectionEpisodeKey { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.0.partial_cmp(&other.0) + } +} +impl Ord for SingleFormatCollectionEpisodeKey { + fn cmp(&self, other: &Self) -> Ordering { + self.0.total_cmp(&other.0) + } +} +impl PartialEq for SingleFormatCollectionEpisodeKey { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} +impl Eq for SingleFormatCollectionEpisodeKey {} + +pub struct SingleFormatCollection( + BTreeMap<u32, BTreeMap<SingleFormatCollectionEpisodeKey, Vec<SingleFormat>>>, +); + +impl SingleFormatCollection { + pub fn new() -> Self { + Self(BTreeMap::new()) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn add_single_formats(&mut self, single_formats: Vec<SingleFormat>) { + let format = single_formats.first().unwrap(); + self.0 + .entry(format.season_number) + .or_insert(BTreeMap::new()) + .insert( + SingleFormatCollectionEpisodeKey(format.sequence_number), + single_formats, + ); + } + + pub fn full_visual_output(&self) { + debug!("Series has {} seasons", self.0.len()); + for (season_number, episodes) in &self.0 { + info!( + "{} Season {}", + episodes + .first_key_value() + .unwrap() + .1 + .first() + .unwrap() + .series_name + .clone(), + season_number + ); + for (i, (_, formats)) in episodes.iter().enumerate() { + let format = formats.first().unwrap(); + if log::max_level() == log::Level::Debug { + info!( + "{} S{:02}E{:0>2}", + format.title, format.season_number, format.episode_number + ) + } else { + tab_info!( + "{}. {} ยป S{:02}E{:0>2}", + i + 1, + format.title, + format.season_number, + format.episode_number + ) + } + } + } + } +} + +impl IntoIterator for SingleFormatCollection { + type Item = Vec<SingleFormat>; + type IntoIter = SingleFormatCollectionIterator; + + fn into_iter(self) -> Self::IntoIter { + SingleFormatCollectionIterator(self) + } +} + +pub struct SingleFormatCollectionIterator(SingleFormatCollection); + +impl Iterator for SingleFormatCollectionIterator { + type Item = Vec<SingleFormat>; + + fn next(&mut self) -> Option<Self::Item> { + let Some((_, episodes)) = self.0.0.iter_mut().next() else { + return None + }; + + let value = episodes.pop_first().unwrap().1; + if episodes.is_empty() { + self.0 .0.pop_first(); + } + Some(value) + } } #[derive(Clone)] @@ -146,28 +284,38 @@ pub struct Format { } impl Format { - pub fn from_single_formats(mut single_formats: Vec<SingleFormat>) -> Self { + pub fn from_single_formats( + mut single_formats: Vec<(SingleFormat, VariantData, Vec<Subtitle>)>, + ) -> Self { let locales: Vec<(Locale, Vec<Locale>)> = single_formats .iter() - .map(|sf| (sf.audio.clone(), sf.subtitles.clone())) + .map(|(single_format, _, subtitles)| { + ( + single_format.audio.clone(), + subtitles + .into_iter() + .map(|s| s.locale.clone()) + .collect::<Vec<Locale>>(), + ) + }) .collect(); - let first = single_formats.remove(0); + let (first_format, first_stream, _) = single_formats.remove(0); Self { - title: first.title, - description: first.description, + title: first_format.title, + description: first_format.description, locales, - resolution: first.resolution, - fps: first.fps, - series_id: first.series_id, - series_name: first.series_name, - season_id: first.season_id, - season_title: first.season_title, - season_number: first.season_number, - episode_id: first.episode_id, - episode_number: first.episode_number, - sequence_number: first.sequence_number, - relative_episode_number: first.relative_episode_number, + resolution: first_stream.resolution, + fps: first_stream.fps, + series_id: first_format.series_id, + series_name: first_format.series_name, + season_id: first_format.season_id, + season_title: first_format.season_title, + season_number: first_format.season_number, + episode_id: first_format.episode_id, + episode_number: first_format.episode_number, + sequence_number: first_format.sequence_number, + relative_episode_number: first_format.relative_episode_number, } } @@ -262,60 +410,3 @@ impl Format { return s.as_ref().contains("{relative_episode_number}"); } } - -pub fn formats_visual_output(formats: Vec<&Format>) { - if log::max_level() == log::Level::Debug { - let seasons = sort_formats_after_seasons(formats); - debug!("Series has {} seasons", seasons.len()); - for (i, season) in seasons.into_iter().enumerate() { - info!("Season {}", i + 1); - for format in season { - info!( - "{}: {}px, {:.02} FPS (S{:02}E{:0>2})", - format.title, - format.resolution, - format.fps, - format.season_number, - format.episode_number, - ) - } - } - } else { - for season in sort_formats_after_seasons(formats) { - let first = season.get(0).unwrap(); - info!("{} Season {}", first.series_name, first.season_number); - - for (i, format) in season.into_iter().enumerate() { - tab_info!( - "{}. {} ยป {}px, {:.2} FPS (S{:02}E{:0>2})", - i + 1, - format.title, - format.resolution, - format.fps, - format.season_number, - format.episode_number - ) - } - } - } -} - -fn sort_formats_after_seasons(formats: Vec<&Format>) -> Vec<Vec<&Format>> { - let mut season_map = BTreeMap::new(); - - for format in formats { - season_map - .entry(format.season_number) - .or_insert(vec![]) - .push(format) - } - - season_map - .into_values() - .into_iter() - .map(|mut fmts| { - fmts.sort_by(|a, b| a.sequence_number.total_cmp(&b.sequence_number)); - fmts - }) - .collect() -} From bd61c18859c407e344a8bbc89880bd9a202a2a20 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 8 Apr 2023 15:10:37 +0200 Subject: [PATCH 340/630] Remove dependabot --- .github/dependabot.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 40d73b8..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - ignore: - - dependency-name: "*" - update-types: [ "version-update:semver-patch" ] - - - package-ecosystem: "cargo" - directory: "/crunchy-cli-core" - schedule: - interval: "weekly" - ignore: - - dependency-name: "*" - update-types: [ "version-update:semver-patch" ] From 8d1be6b573a9154fd532e950f39cd7070de092dc Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 8 Apr 2023 15:13:19 +0200 Subject: [PATCH 341/630] Update dependencies --- Cargo.lock | 460 ++++++++++++++++++++++-------------- Cargo.toml | 6 +- crunchy-cli-core/Cargo.lock | 456 +++++++++++++++++++++-------------- crunchy-cli-core/Cargo.toml | 8 +- 4 files changed, 565 insertions(+), 365 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3be7641..235ccd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,46 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + [[package]] name = "anyhow" version = "1.0.70" @@ -39,13 +79,13 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "async-trait" -version = "0.1.67" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] @@ -72,17 +112,11 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" - [[package]] name = "block-padding" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array", ] @@ -148,49 +182,54 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.11" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" dependencies = [ - "bitflags 2.0.2", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim 0.10.0", - "termcolor", ] [[package]] name = "clap_complete" -version = "4.1.5" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1" +checksum = "01c22dcfb410883764b29953103d9ef7bb8fe21b3fa1158bc99986c2067294bd" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.1.9" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] name = "clap_lex" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" -dependencies = [ - "os_str_bytes", -] +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "clap_mangen" @@ -212,6 +251,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + [[package]] name = "console" version = "0.15.5" @@ -264,15 +318,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] @@ -377,9 +431,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -389,9 +443,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -399,24 +453,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] name = "cxxbridge-flags" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] @@ -557,13 +611,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -623,42 +677,42 @@ checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-io", @@ -671,9 +725,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -681,9 +735,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -828,9 +882,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.54" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -879,9 +933,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -921,31 +975,31 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -980,9 +1034,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "link-cplusplus" @@ -995,9 +1049,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" [[package]] name = "log" @@ -1078,7 +1132,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "libc", "static_assertions", @@ -1137,11 +1191,11 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.47" +version = "0.10.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" +checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1152,13 +1206,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] @@ -1169,23 +1223,16 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.82" +version = "0.9.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" +checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - [[package]] name = "percent-encoding" version = "2.2.0" @@ -1216,35 +1263,11 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1290,7 +1313,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", ] [[package]] @@ -1300,15 +1332,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -1323,9 +1355,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.15" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" dependencies = [ "base64 0.21.0", "bytes", @@ -1388,16 +1420,16 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.36.11" +version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1468,7 +1500,7 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1487,29 +1519,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.158" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "itoa", "ryu", @@ -1530,9 +1562,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85456ffac572dc8826334164f2fb6fb40a7c766aebe195a2a21ee69ee2885ecf" +checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" dependencies = [ "base64 0.13.1", "chrono", @@ -1546,9 +1578,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbcd6104f8a4ab6af7f6be2a0da6be86b9de3c401f6e86bb856ab2af739232f" +checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" dependencies = [ "darling 0.14.4", "proc-macro2", @@ -1648,9 +1680,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.8" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" dependencies = [ "proc-macro2", "quote", @@ -1659,28 +1691,25 @@ dependencies = [ [[package]] name = "sys-locale" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee" +checksum = "ea0b9eefabb91675082b41eb94c3ecd91af7656caee3fb4961a07c0ec8c7ca6f" dependencies = [ - "js-sys", "libc", - "wasm-bindgen", - "web-sys", "windows-sys 0.45.0", ] [[package]] name = "tempfile" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1694,12 +1723,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1719,7 +1748,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] @@ -1777,14 +1806,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", @@ -1795,13 +1823,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] @@ -1921,6 +1949,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2083,11 +2117,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -2096,13 +2130,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2111,7 +2145,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -2120,13 +2163,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -2135,42 +2193,84 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 62ff7fb..c70f658 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,14 +5,14 @@ version = "3.0.0-dev.8" edition = "2021" [dependencies] -tokio = { version = "1.26", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.27", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } [build-dependencies] chrono = "0.4" -clap = { version = "4.1", features = ["string"] } -clap_complete = "4.1" +clap = { version = "4.2", features = ["string"] } +clap_complete = "4.2" clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 35cea2e..f4cdcea 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -31,6 +31,46 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + [[package]] name = "anyhow" version = "1.0.70" @@ -39,13 +79,13 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "async-trait" -version = "0.1.67" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] @@ -72,17 +112,11 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" - [[package]] name = "block-padding" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array", ] @@ -148,40 +182,45 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.11" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" dependencies = [ - "bitflags 2.0.2", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim 0.10.0", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.9" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] name = "clap_lex" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" -dependencies = [ - "os_str_bytes", -] +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "codespan-reporting" @@ -193,6 +232,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + [[package]] name = "console" version = "0.15.5" @@ -245,15 +299,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] @@ -346,9 +400,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -358,9 +412,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -368,24 +422,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] name = "cxxbridge-flags" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] @@ -526,13 +580,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -592,42 +646,42 @@ checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-io", @@ -640,9 +694,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -650,9 +704,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -797,9 +851,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.54" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -848,9 +902,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -890,31 +944,31 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -949,9 +1003,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "link-cplusplus" @@ -964,9 +1018,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" [[package]] name = "log" @@ -1047,7 +1101,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "libc", "static_assertions", @@ -1106,11 +1160,11 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.47" +version = "0.10.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" +checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1121,13 +1175,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] @@ -1138,23 +1192,16 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.82" +version = "0.9.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" +checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - [[package]] name = "percent-encoding" version = "2.2.0" @@ -1185,35 +1232,11 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1259,7 +1282,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", ] [[package]] @@ -1269,15 +1301,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -1292,9 +1324,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.15" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" dependencies = [ "base64 0.21.0", "bytes", @@ -1351,16 +1383,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.11" +version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1431,7 +1463,7 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1450,29 +1482,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.158" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "itoa", "ryu", @@ -1493,9 +1525,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85456ffac572dc8826334164f2fb6fb40a7c766aebe195a2a21ee69ee2885ecf" +checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" dependencies = [ "base64 0.13.1", "chrono", @@ -1509,9 +1541,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbcd6104f8a4ab6af7f6be2a0da6be86b9de3c401f6e86bb856ab2af739232f" +checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" dependencies = [ "darling 0.14.4", "proc-macro2", @@ -1611,9 +1643,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.8" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" dependencies = [ "proc-macro2", "quote", @@ -1622,28 +1654,25 @@ dependencies = [ [[package]] name = "sys-locale" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee" +checksum = "ea0b9eefabb91675082b41eb94c3ecd91af7656caee3fb4961a07c0ec8c7ca6f" dependencies = [ - "js-sys", "libc", - "wasm-bindgen", - "web-sys", "windows-sys 0.45.0", ] [[package]] name = "tempfile" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1657,12 +1686,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1682,7 +1711,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.13", ] [[package]] @@ -1740,14 +1769,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", @@ -1758,13 +1786,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] @@ -1884,6 +1912,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2046,11 +2080,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -2059,13 +2093,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2074,7 +2108,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -2083,13 +2126,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -2098,42 +2156,84 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.10.1" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f86a98f..e08f1f1 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = { version = "4.1", features = ["derive", "string"] } +clap = { version = "4.2", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { version = "0.3", features = ["dash-stream"] } ctrlc = "3.2" @@ -23,10 +23,10 @@ serde = "1.0" serde_json = "1.0" shlex = "1.1" signal-hook = "0.3" -tempfile = "3.4" +tempfile = "3.5" terminal_size = "0.2" -tokio = { version = "1.26", features = ["macros", "rt-multi-thread", "time"] } -sys-locale = "0.2" +tokio = { version = "1.27", features = ["macros", "rt-multi-thread", "time"] } +sys-locale = "0.3" [build-dependencies] chrono = "0.4" From 7e34076a7b24cb0bc8d7250142e75fdc7b3d136c Mon Sep 17 00:00:00 2001 From: Hannes Braun <hannesbraun@mail.de> Date: Sat, 8 Apr 2023 15:54:50 +0200 Subject: [PATCH 342/630] Fix offset in relative episode number --- crunchy-cli-core/src/archive/filter.rs | 3 ++- crunchy-cli-core/src/download/filter.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 7b51100..0633320 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -222,7 +222,8 @@ impl Filter for ArchiveFilter { .get(&episode.season_number) .unwrap() .iter() - .position(|id| id == &episode.id); + .position(|id| id == &episode.id) + .map(|index| index + 1); if relative_episode_number.is_none() { warn!( "Failed to get relative episode number for episode {} ({}) of {} season {}", diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 35eaaed..f6a4a2e 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -174,7 +174,8 @@ impl Filter for DownloadFilter { .get(&episode.season_number) .unwrap() .iter() - .position(|id| id == &episode.id); + .position(|id| id == &episode.id) + .map(|index| index + 1); if relative_episode_number.is_none() { warn!( "Failed to get relative episode number for episode {} ({}) of {} season {}", From d79f00871e8fec29e0b88bf77428f6238b0fe638 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 11:24:12 +0200 Subject: [PATCH 343/630] Enable special files to be declared as output file --- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/download/command.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index fe84c5b..bc923dc 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -110,7 +110,7 @@ impl Execute for Archive { .unwrap_or_default() .to_string_lossy() != "mkv" - && !is_special_file(PathBuf::from(&self.output)) + && !is_special_file(&self.output) && self.output != "-" { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index fb40735..67cb633 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -5,7 +5,7 @@ use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat}; use crate::utils::log::progress; -use crate::utils::os::{free_file, has_ffmpeg}; +use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::parse_url; use crate::utils::video::variant_data_from_stream; use crate::Execute; @@ -86,6 +86,7 @@ impl Execute for Download { .extension() .unwrap_or_default() .is_empty() + && !is_special_file(&self.output) && self.output != "-" { bail!("No file extension found. Please specify a file extension (via `-o`) for the output file") @@ -132,7 +133,7 @@ impl Execute for Download { let download_builder = DownloadBuilder::new() .default_subtitle(self.subtitle.clone()) - .output_format(if self.output == "-" { + .output_format(if is_special_file(&self.output) || self.output == "-" { Some("mpegts".to_string()) } else { None From cb7612e86b9c53ec862ea1052584107aa4088148 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 11:26:15 +0200 Subject: [PATCH 344/630] Fix help message indentation --- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/download/command.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index bc923dc..5dfa3cf 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -47,7 +47,7 @@ pub struct Archive { {resolution} โ†’ Resolution of the video\n \ {season_number} โ†’ Number of the season\n \ {episode_number} โ†’ Number of the episode\n \ - {relative_episode_number} โ†’ Number of the episode relative to its season\ + {relative_episode_number} โ†’ Number of the episode relative to its season\n \ {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 67cb633..d18320a 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -42,7 +42,7 @@ pub struct Download { {resolution} โ†’ Resolution of the video\n \ {season_number} โ†’ Number of the season\n \ {episode_number} โ†’ Number of the episode\n \ - {relative_episode_number} โ†’ Number of the episode relative to its season\ + {relative_episode_number} โ†’ Number of the episode relative to its season\n \ {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] From 481f35d232ce6cd3ffcb09944e106a4466319764 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 11:34:50 +0200 Subject: [PATCH 345/630] Update version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 235ccd2..da0bcf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.8" +version = "3.0.0-dev.9" dependencies = [ "chrono", "clap", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.8" +version = "3.0.0-dev.9" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index c70f658..65580d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.8" +version = "3.0.0-dev.9" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e08f1f1..f9869cd 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.8" +version = "3.0.0-dev.9" edition = "2021" [dependencies] From fcbcd175e16f5fd97677909a866441b58fd8aa1e Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 14:22:43 +0200 Subject: [PATCH 346/630] Add env variable to make temp directory configurable (#153) --- crunchy-cli-core/src/utils/os.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 43a37d4..f33fee2 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -24,10 +24,12 @@ pub fn has_ffmpeg() -> bool { /// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like /// setting the wrong prefix if done manually. pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { + let tmp_dir = env::var("CRUNCHY_CLI_TEMP_DIR").map_or(env::temp_dir(), |d| PathBuf::from(d)); + let tempfile = Builder::default() .prefix(".crunchy-cli_") .suffix(suffix.as_ref()) - .tempfile_in(&env::temp_dir())?; + .tempfile_in(tmp_dir)?; debug!( "Created temporary file: {}", tempfile.path().to_string_lossy() From baa6ca5018a9fadfac7fb83daaf9ba5d8b7cd688 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 15:52:21 +0200 Subject: [PATCH 347/630] Add function to get temp directory --- crunchy-cli-core/src/utils/os.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index f33fee2..bd30c2a 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -19,17 +19,21 @@ pub fn has_ffmpeg() -> bool { } } +/// Get the temp directory either by the specified `CRUNCHY_CLI_TEMP_DIR` env variable or the dir +/// provided by the os. +pub fn temp_directory() -> PathBuf { + env::var("CRUNCHY_CLI_TEMP_DIR").map_or(env::temp_dir(), |d| PathBuf::from(d)) +} + /// Any tempfile should be created with this function. The prefix and directory of every file /// created with this method stays the same which is helpful to query all existing tempfiles and /// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like /// setting the wrong prefix if done manually. pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { - let tmp_dir = env::var("CRUNCHY_CLI_TEMP_DIR").map_or(env::temp_dir(), |d| PathBuf::from(d)); - let tempfile = Builder::default() .prefix(".crunchy-cli_") .suffix(suffix.as_ref()) - .tempfile_in(tmp_dir)?; + .tempfile_in(temp_directory())?; debug!( "Created temporary file: {}", tempfile.path().to_string_lossy() From ee1344fc6b018699b51538138acc9ed960f14a7c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 20:37:10 +0200 Subject: [PATCH 348/630] Update version --- Cargo.lock | 35 +++++++++++++++++++++++++++-------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 33 ++++++++++++++++++++++++++------- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da0bcf8..fd653dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.9" +version = "3.0.0-dev.10" dependencies = [ "chrono", "clap", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.9" +version = "3.0.0-dev.10" dependencies = [ "anyhow", "async-trait", @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808095ff5e340185e2783bdc2fc450003e918d79f110c1b27c7fe54fa4ba23eb" +checksum = "942a21f27140a954654d3b6b4aab8e8e3888b33cec736a51f0feab5e7fedc15f" dependencies = [ "aes", "async-trait", @@ -395,14 +395,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots", + "webpki-roots 0.23.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5e2c70231ecd0bdc5a243bdb94cd3923176e4dfaa957006adee7899e484c8f" +checksum = "9d90bc631b1f94891b3f5d5b9ca0b8c7f7a33e4e8d9ae98dbc6bcb5aee56b817" dependencies = [ "darling 0.14.4", "quote", @@ -1393,7 +1393,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.22.6", "winreg", ] @@ -1453,6 +1453,16 @@ dependencies = [ "base64 0.21.0", ] +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.13" @@ -2084,6 +2094,15 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" +dependencies = [ + "rustls-webpki", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 65580d7..f6b17f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.9" +version = "3.0.0-dev.10" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index f4cdcea..95bb978 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.8" +version = "3.0.0-dev.10" dependencies = [ "anyhow", "async-trait", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808095ff5e340185e2783bdc2fc450003e918d79f110c1b27c7fe54fa4ba23eb" +checksum = "942a21f27140a954654d3b6b4aab8e8e3888b33cec736a51f0feab5e7fedc15f" dependencies = [ "aes", "async-trait", @@ -364,14 +364,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots", + "webpki-roots 0.23.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5e2c70231ecd0bdc5a243bdb94cd3923176e4dfaa957006adee7899e484c8f" +checksum = "9d90bc631b1f94891b3f5d5b9ca0b8c7f7a33e4e8d9ae98dbc6bcb5aee56b817" dependencies = [ "darling 0.14.4", "quote", @@ -1362,7 +1362,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.22.6", "winreg", ] @@ -1416,6 +1416,16 @@ dependencies = [ "base64 0.21.0", ] +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.13" @@ -2047,6 +2057,15 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" +dependencies = [ + "rustls-webpki", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f9869cd..dd20081 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.9" +version = "3.0.0-dev.10" edition = "2021" [dependencies] From 5f98cfb1860d82c3c5633683641cc86989944061 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 9 Apr 2023 19:26:42 +0200 Subject: [PATCH 349/630] Check for remaining disk space (#150) --- Cargo.lock | 11 +++ crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/utils/download.rs | 103 +++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd653dd..cd468a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,6 +355,7 @@ dependencies = [ "ctrlc", "derive_setters", "dirs", + "fs2", "indicatif", "lazy_static", "log", @@ -675,6 +676,16 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures-channel" version = "0.3.28" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index dd20081..5def6c3 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -13,6 +13,7 @@ crunchyroll-rs = { version = "0.3", features = ["dash-stream"] } ctrlc = "3.2" dirs = "5.0" derive_setters = "0.1" +fs2 = "0.4" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 63d9a4d..57ddc5f 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,19 +1,20 @@ use crate::utils::context::Context; use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::log::progress; -use crate::utils::os::tempfile; +use crate::utils::os::{is_special_file, temp_directory, tempfile}; use anyhow::{bail, Result}; use chrono::NaiveTime; use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; use crunchyroll_rs::Locale; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; -use log::{debug, LevelFilter}; +use log::{debug, warn, LevelFilter}; use regex::Regex; use std::borrow::Borrow; use std::borrow::BorrowMut; use std::collections::BTreeMap; +use std::env; use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; @@ -99,6 +100,32 @@ impl Downloader { } pub async fn download(mut self, ctx: &Context, dst: &Path) -> Result<()> { + // `.unwrap_or_default()` here unless https://doc.rust-lang.org/stable/std/path/fn.absolute.html + // gets stabilized as the function might throw error on weird file paths + let required = self.check_free_space(dst).await.unwrap_or_default(); + if let Some((path, tmp_required)) = &required.0 { + let kb = (*tmp_required as f64) / 1024.0; + let mb = kb / 1024.0; + let gb = mb / 1024.0; + warn!( + "You may have not enough disk space to store temporary files. The temp directory ({}) should have at least {}{} free space", + path.to_string_lossy(), + if gb < 1.0 { mb.ceil().to_string() } else { format!("{:.2}", gb) }, + if gb < 1.0 { "MB" } else { "GB" } + ) + } + if let Some((path, dst_required)) = &required.1 { + let kb = (*dst_required as f64) / 1024.0; + let mb = kb / 1024.0; + let gb = mb / 1024.0; + warn!( + "You may have not enough disk space to store the output file. The directory {} should have at least {}{} free space", + path.to_string_lossy(), + if gb < 1.0 { mb.ceil().to_string() } else { format!("{:.2}", gb) }, + if gb < 1.0 { "MB" } else { "GB" } + ) + } + if let Some(audio_sort_locales) = &self.audio_sort { self.formats.sort_by(|a, b| { audio_sort_locales @@ -335,6 +362,69 @@ impl Downloader { Ok(()) } + async fn check_free_space( + &self, + dst: &Path, + ) -> Result<(Option<(PathBuf, u64)>, Option<(PathBuf, u64)>)> { + let mut all_variant_data = vec![]; + for format in &self.formats { + all_variant_data.push(&format.video.0); + all_variant_data.extend(format.audios.iter().map(|(a, _)| a)) + } + let mut estimated_required_space: u64 = 0; + for variant_data in all_variant_data { + // nearly no overhead should be generated with this call(s) as we're using dash as + // stream provider and generating the dash segments does not need any fetching of + // additional (http) resources as hls segments would + let segments = variant_data.segments().await?; + + // sum the length of all streams up + estimated_required_space += estimate_variant_file_size(variant_data, &segments); + } + + let tmp_stat = fs2::statvfs(temp_directory()).unwrap(); + let mut dst_file = if dst.is_absolute() { + dst.to_path_buf() + } else { + env::current_dir()?.join(dst) + }; + for ancestor in dst_file.ancestors() { + if ancestor.exists() { + dst_file = ancestor.to_path_buf(); + break; + } + } + let dst_stat = fs2::statvfs(&dst_file).unwrap(); + + let mut tmp_space = tmp_stat.available_space(); + let mut dst_space = dst_stat.available_space(); + + // this checks if the partition the two directories are located on are the same to prevent + // that the space fits both file sizes each but not together. this is done by checking the + // total space if each partition and the free space of each partition (the free space can + // differ by 10MB as some tiny I/O operations could be performed between the two calls which + // are checking the disk space) + if tmp_stat.total_space() == dst_stat.total_space() + && (tmp_stat.available_space() as i64 - dst_stat.available_space() as i64).abs() < 10240 + { + tmp_space *= 2; + dst_space *= 2; + } + + let mut tmp_required = None; + let mut dst_required = None; + + if tmp_space < estimated_required_space { + tmp_required = Some((temp_directory(), estimated_required_space)) + } + if (!is_special_file(dst) && dst.to_string_lossy() != "-") + && dst_space < estimated_required_space + { + dst_required = Some((dst_file, estimated_required_space)) + } + Ok((tmp_required, dst_required)) + } + async fn download_video( &self, ctx: &Context, @@ -395,8 +485,7 @@ pub async fn download_segments( let count = Arc::new(Mutex::new(0)); let progress = if log::max_level() == LevelFilter::Info { - let estimated_file_size = - (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>(); + let estimated_file_size = estimate_variant_file_size(variant_data, &segments); let progress = ProgressBar::new(estimated_file_size) .with_style( @@ -555,6 +644,10 @@ pub async fn download_segments( Ok(()) } +fn estimate_variant_file_size(variant_data: &VariantData, segments: &Vec<VariantSegment>) -> u64 { + (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() +} + /// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video /// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) /// for more information. From d33e2fa36be5e41c428085eb37eafdc849b35956 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 11 Apr 2023 21:14:06 +0200 Subject: [PATCH 350/630] Add proxy flag (#142) --- Cargo.lock | 28 ++++++++++++++++++++++++---- crunchy-cli-core/Cargo.toml | 3 ++- crunchy-cli-core/src/lib.rs | 16 ++++++++++++++++ crunchy-cli-core/src/utils/clap.rs | 5 +++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd468a4..6531bcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,7 @@ dependencies = [ "log", "num_cpus", "regex", + "reqwest", "sanitize-filename", "serde", "serde_json", @@ -374,9 +375,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942a21f27140a954654d3b6b4aab8e8e3888b33cec736a51f0feab5e7fedc15f" +checksum = "be975d4a27439853f6e80311b497e910fc49fd24525afdc12ca27ace18b84eeb" dependencies = [ "aes", "async-trait", @@ -401,9 +402,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d90bc631b1f94891b3f5d5b9ca0b8c7f7a33e4e8d9ae98dbc6bcb5aee56b817" +checksum = "b8d71a343838c462ace0531b2f5556fd1ea6b677d7a3e61ac28251e87d158c47" dependencies = [ "darling 0.14.4", "quote", @@ -595,6 +596,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1399,6 +1406,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "tokio-socks", "tower-service", "url", "wasm-bindgen", @@ -1874,6 +1882,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.7" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5def6c3..407383b 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.2", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.3", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.3.3", features = ["dash-stream"] } ctrlc = "3.2" dirs = "5.0" derive_setters = "0.1" @@ -19,6 +19,7 @@ lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.15" regex = "1.7" +reqwest = { version = "0.11", features = ["socks"] } sanitize-filename = "0.4" serde = "1.0" serde_json = "1.0" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 8bb6768..e2a584c 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -4,8 +4,10 @@ use crate::utils::log::{progress, CliLogger}; use anyhow::bail; use anyhow::Result; use clap::{Parser, Subcommand}; +use crunchyroll_rs::crunchyroll::CrunchyrollBuilder; use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; +use reqwest::Proxy; use std::{env, fs}; mod archive; @@ -50,6 +52,14 @@ pub struct Cli { #[clap(flatten)] login_method: LoginMethod, + #[arg(help = "Use a proxy to route all traffic through")] + #[arg(long_help = "Use a proxy to route all traffic through. \ + Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself" + )] + #[clap(long)] + #[arg(value_parser = crate::utils::clap::clap_parse_proxy)] + proxy: Option<Proxy>, + #[clap(subcommand)] command: Command, } @@ -234,7 +244,13 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { lang }; + let mut client_builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(proxy) = &cli.proxy { + client_builder = client_builder.proxy(proxy.clone()) + } + let mut builder = Crunchyroll::builder() + .client(client_builder.build()?) .locale(locale) .stabilization_locales(cli.experimental_fixes) .stabilization_season_number(cli.experimental_fixes); diff --git a/crunchy-cli-core/src/utils/clap.rs b/crunchy-cli-core/src/utils/clap.rs index 4e27a5e..c3088d8 100644 --- a/crunchy-cli-core/src/utils/clap.rs +++ b/crunchy-cli-core/src/utils/clap.rs @@ -1,6 +1,11 @@ use crate::utils::parse::parse_resolution; use crunchyroll_rs::media::Resolution; +use reqwest::Proxy; pub fn clap_parse_resolution(s: &str) -> Result<Resolution, String> { parse_resolution(s.to_string()).map_err(|e| e.to_string()) } + +pub fn clap_parse_proxy(s: &str) -> Result<Proxy, String> { + Proxy::all(s).map_err(|e| e.to_string()) +} From c4c15f9b11995c051a77b792795147c36d5bdf77 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Tue, 11 Apr 2023 22:54:24 +0200 Subject: [PATCH 351/630] Disable default features for reqwest --- crunchy-cli-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 407383b..708303f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -19,7 +19,7 @@ lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.15" regex = "1.7" -reqwest = { version = "0.11", features = ["socks"] } +reqwest = { version = "0.11", default-features = false, features = ["socks"] } sanitize-filename = "0.4" serde = "1.0" serde_json = "1.0" From bfc50653b1b52d978bc92bf40ecd52c2569440c5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 19:17:55 +0200 Subject: [PATCH 352/630] Fix cms rate limiting error for episodes and movies (#180) --- Cargo.lock | 8 ++++---- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/lib.rs | 3 +-- crunchy-cli-core/src/utils/format.rs | 22 +++++++++++++++++++--- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6531bcd..3f49945 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,9 +375,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be975d4a27439853f6e80311b497e910fc49fd24525afdc12ca27ace18b84eeb" +checksum = "b75b403a64b4cc8ed9a96cb16588475a93cb5c8643cf960907a54ae6f6ac3f0d" dependencies = [ "aes", "async-trait", @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d71a343838c462ace0531b2f5556fd1ea6b677d7a3e61ac28251e87d158c47" +checksum = "d91a9978a505750f606a1fcf1efd5970d20521310e886839f2ad95c31354e54e" dependencies = [ "darling 0.14.4", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 708303f..a8a517b 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.2", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.3.3", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.3.4", features = ["dash-stream"] } ctrlc = "3.2" dirs = "5.0" derive_setters = "0.1" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index e2a584c..77df2c8 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -54,8 +54,7 @@ pub struct Cli { #[arg(help = "Use a proxy to route all traffic through")] #[arg(long_help = "Use a proxy to route all traffic through. \ - Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself" - )] + Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself")] #[clap(long)] #[arg(value_parser = crate::utils::clap::clap_parse_proxy)] proxy: Option<Proxy>, diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index b2ef5f2..c6f3168 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -5,7 +5,7 @@ use anyhow::Result; use chrono::Duration; use crunchyroll_rs::media::{Resolution, Stream, Subtitle, VariantData}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; -use log::{debug, info}; +use log::{debug, info, warn}; use std::cmp::Ordering; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; @@ -126,8 +126,24 @@ impl SingleFormat { pub async fn stream(&self) -> Result<Stream> { let stream = match &self.source { - MediaCollection::Episode(e) => e.streams().await?, - MediaCollection::Movie(m) => m.streams().await?, + MediaCollection::Episode(e) => { + if let Ok(stream) = e.legacy_streams().await { + stream + } else { + let stream = e.streams().await?; + warn!("Failed to get stream via legacy endpoint"); + stream + } + } + MediaCollection::Movie(m) => { + if let Ok(stream) = m.legacy_streams().await { + stream + } else { + let stream = m.streams().await?; + warn!("Failed to get stream via legacy endpoint"); + stream + } + } MediaCollection::MusicVideo(mv) => mv.streams().await?, MediaCollection::Concert(c) => c.streams().await?, _ => unreachable!(), From 273db9fe6a38d560519e4bae2e47e667346fb292 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 20:58:29 +0200 Subject: [PATCH 353/630] Add rate limit notice if detected --- crunchy-cli-core/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 77df2c8..30b9157 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -16,6 +16,7 @@ mod login; mod utils; pub use archive::Archive; +use crunchyroll_rs::error::CrunchyrollError; pub use download::Download; pub use login::Login; @@ -200,6 +201,20 @@ async fn execute_executor(mut executor: impl Execute, ctx: Context) { if let Err(err) = executor.execute(ctx).await { error!("a unexpected error occurred: {}", err); + + if let Some(crunchy_error) = err.downcast_ref::<CrunchyrollError>() { + let message = match crunchy_error { + CrunchyrollError::Internal(i) => &i.message, + CrunchyrollError::Request(r) => &r.message, + CrunchyrollError::Decode(d) => &d.message, + CrunchyrollError::Authentication(a) => &a.message, + CrunchyrollError::Input(i) => &i.message, + }; + if message.contains("content.get_video_streams_v2.cms_service_error") { + error!("You've probably hit a rate limit. Try again later, generally after 10-20 minutes the rate limit is over and you can continue to use the cli") + } + } + std::process::exit(1) } } From f584c8028fb9d9b2f05359ccb03cc33f7373c9de Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 21:22:33 +0200 Subject: [PATCH 354/630] Deprecate archive -l flag --- crunchy-cli-core/src/archive/command.rs | 24 ++++++++++++++++++------ crunchy-cli-core/src/archive/filter.rs | 14 +++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 5dfa3cf..cf24c84 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -15,7 +15,7 @@ use anyhow::Result; use chrono::Duration; use crunchyroll_rs::media::{Resolution, Subtitle}; use crunchyroll_rs::Locale; -use log::debug; +use log::{debug, warn}; use std::collections::HashMap; use std::path::PathBuf; @@ -29,7 +29,10 @@ pub struct Archive { #[arg(long_help = format!("Audio languages. Can be used multiple times. \ Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] - pub(crate) locale: Vec<Locale>, + pub(crate) audio: Vec<Locale>, + #[arg(help = "Deprecated. Use '-a' / '--audio' instead")] + #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] + locale: Vec<Locale>, #[arg(help = format!("Subtitle languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ @@ -116,7 +119,16 @@ impl Execute for Archive { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } - self.locale = all_locale_in_locales(self.locale.clone()); + if !self.locale.is_empty() { + warn!("The '-l' / '--locale' flag is deprecated, use '-a' / '--audio' instead"); + for locale in &self.locale { + if !self.audio.contains(locale) { + self.audio.push(locale.clone()) + } + } + } + + self.audio = all_locale_in_locales(self.audio.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); Ok(()) @@ -154,7 +166,7 @@ impl Execute for Archive { .default_subtitle(self.default_subtitle.clone()) .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) .output_format(Some("matroska".to_string())) - .audio_sort(Some(self.locale.clone())) + .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())); for single_formats in single_format_collection.into_iter() { @@ -177,10 +189,10 @@ impl Execute for Archive { } format.locales.sort_by(|(a, _), (b, _)| { - self.locale + self.audio .iter() .position(|l| l == a) - .cmp(&self.locale.iter().position(|l| l == b)) + .cmp(&self.audio.iter().position(|l| l == b)) }); for (_, subtitles) in format.locales.iter_mut() { subtitles.sort_by(|a, b| { diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 0633320..d135800 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -42,7 +42,7 @@ impl Filter for ArchiveFilter { // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the // audio is matching only if the field is populated if !series.audio_locales.is_empty() { - let missing_audio = missing_locales(&series.audio_locales, &self.archive.locale); + let missing_audio = missing_locales(&series.audio_locales, &self.archive.audio); if !missing_audio.is_empty() { warn!( "Series {} is not available with {} audio", @@ -77,10 +77,10 @@ impl Filter for ArchiveFilter { return Ok(vec![]); } - let mut seasons = season.version(self.archive.locale.clone()).await?; + let mut seasons = season.version(self.archive.audio.clone()).await?; if self .archive - .locale + .audio .iter() .any(|l| season.audio_locales.contains(l)) { @@ -94,7 +94,7 @@ impl Filter for ArchiveFilter { .flatten() .collect(); real_dedup_vec(&mut audio_locales); - let missing_audio = missing_locales(&audio_locales, &self.archive.locale); + let missing_audio = missing_locales(&audio_locales, &self.archive.audio); if !missing_audio.is_empty() { warn!( "Season {} is not available with {} audio", @@ -154,12 +154,12 @@ impl Filter for ArchiveFilter { let mut episodes = vec![]; if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) { - if self.archive.locale.contains(&episode.audio_locale) { + if self.archive.audio.contains(&episode.audio_locale) { episodes.push((episode.clone(), episode.subtitle_locales.clone())) } episodes.extend( episode - .version(self.archive.locale.clone()) + .version(self.archive.audio.clone()) .await? .into_iter() .map(|e| (e.clone(), e.subtitle_locales.clone())), @@ -168,7 +168,7 @@ impl Filter for ArchiveFilter { .iter() .map(|(e, _)| e.audio_locale.clone()) .collect(); - let missing_audio = missing_locales(&audio_locales, &self.archive.locale); + let missing_audio = missing_locales(&audio_locales, &self.archive.audio); if !missing_audio.is_empty() { warn!( "Episode {} is not available with {} audio", From d8d1f8a443fef631354e276b929e6fff9fc95a39 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 21:30:54 +0200 Subject: [PATCH 355/630] Execute cli pre-check before logging in --- crunchy-cli-core/src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 30b9157..91612f1 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -120,7 +120,7 @@ struct LoginMethod { } pub async fn cli_entrypoint() { - let cli: Cli = Cli::parse(); + let mut cli: Cli = Cli::parse(); if let Some(verbosity) = &cli.verbosity { if verbosity.v as u8 + verbosity.q as u8 + verbosity.vv as u8 > 1 { @@ -139,6 +139,12 @@ pub async fn cli_entrypoint() { debug!("cli input: {:?}", cli); + match &mut cli.command { + Command::Archive(archive) => pre_check_executor(archive).await, + Command::Download(download) => pre_check_executor(download).await, + Command::Login(login) => pre_check_executor(login).await, + }; + let ctx = match create_ctx(&cli).await { Ok(ctx) => ctx, Err(e) => { @@ -191,14 +197,14 @@ pub async fn cli_entrypoint() { }; } -/// Cannot be done in the main function. I wanted to return `dyn` [`Execute`] from the match but had to -/// box it which then conflicts with [`Execute::execute`] which consumes `self` -async fn execute_executor(mut executor: impl Execute, ctx: Context) { +async fn pre_check_executor(executor: &mut impl Execute) { if let Err(err) = executor.pre_check() { error!("Misconfigurations detected: {}", err); std::process::exit(1) } +} +async fn execute_executor(executor: impl Execute, ctx: Context) { if let Err(err) = executor.execute(ctx).await { error!("a unexpected error occurred: {}", err); From 3c648f419273f191d43265d7b25466d2b58b6f8d Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 21:35:28 +0200 Subject: [PATCH 356/630] Actually remove session file if login remove flag is set --- crunchy-cli-core/src/lib.rs | 22 +++++++++++++--------- crunchy-cli-core/src/login/command.rs | 4 ++-- crunchy-cli-core/src/login/mod.rs | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 91612f1..65c3ca1 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -15,6 +15,7 @@ mod download; mod login; mod utils; +use crate::login::session_file_path; pub use archive::Archive; use crunchyroll_rs::error::CrunchyrollError; pub use download::Download; @@ -142,7 +143,16 @@ pub async fn cli_entrypoint() { match &mut cli.command { Command::Archive(archive) => pre_check_executor(archive).await, Command::Download(download) => pre_check_executor(download).await, - Command::Login(login) => pre_check_executor(login).await, + Command::Login(login) => { + if login.remove { + if let Some(session_file) = session_file_path() { + let _ = fs::remove_file(session_file); + } + return; + } else { + pre_check_executor(login).await + } + } }; let ctx = match create_ctx(&cli).await { @@ -187,13 +197,7 @@ pub async fn cli_entrypoint() { match cli.command { Command::Archive(archive) => execute_executor(archive, ctx).await, Command::Download(download) => execute_executor(download, ctx).await, - Command::Login(login) => { - if login.remove { - return; - } else { - execute_executor(login, ctx).await - } - } + Command::Login(login) => execute_executor(login, ctx).await, }; } @@ -285,7 +289,7 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { let progress_handler = progress!("Logging in"); if login_methods_count == 0 { - if let Some(login_file_path) = login::login_file_path() { + if let Some(login_file_path) = login::session_file_path() { if login_file_path.exists() { let session = fs::read_to_string(login_file_path)?; if let Some((token_type, token)) = session.split_once(':') { diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index f164a0b..ad101da 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -17,7 +17,7 @@ pub struct Login { #[async_trait::async_trait(?Send)] impl Execute for Login { async fn execute(self, ctx: Context) -> Result<()> { - if let Some(login_file_path) = login_file_path() { + if let Some(login_file_path) = session_file_path() { fs::create_dir_all(login_file_path.parent().unwrap())?; match ctx.crunchy.session_token().await { @@ -36,6 +36,6 @@ impl Execute for Login { } } -pub fn login_file_path() -> Option<PathBuf> { +pub fn session_file_path() -> Option<PathBuf> { dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli").join("session")) } diff --git a/crunchy-cli-core/src/login/mod.rs b/crunchy-cli-core/src/login/mod.rs index 9025e68..e2ab88c 100644 --- a/crunchy-cli-core/src/login/mod.rs +++ b/crunchy-cli-core/src/login/mod.rs @@ -1,4 +1,4 @@ mod command; -pub use command::login_file_path; +pub use command::session_file_path; pub use command::Login; From 95f8cc542ce0a9297b7a1e47dc5dfa758be698fe Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 21:53:46 +0200 Subject: [PATCH 357/630] Add retry if connection got reset by peer (#144) --- crunchy-cli-core/src/utils/download.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 57ddc5f..f0f51c1 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -529,11 +529,21 @@ pub async fn download_segments( for (i, segment) in thread_segments.into_iter().enumerate() { let mut retry_count = 0; let mut buf = loop { - let response = thread_client + let request = thread_client .get(&segment.url) .timeout(Duration::from_secs(60)) - .send() - .await?; + .send(); + + let response = match request.await { + Ok(r) => r, + Err(e) => { + if retry_count == 5 { + bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) + } + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count); + continue + } + }; match response.bytes().await { Ok(b) => break b.to_vec(), From d754f9339bddbb9a0c2cd279960b4947df17863c Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 21:59:27 +0200 Subject: [PATCH 358/630] Update version --- Cargo.lock | 106 +++++++++++++------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 143 ++++++++++++++++++++++-------------- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 142 insertions(+), 111 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f49945..7a8cb6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,42 +33,51 @@ dependencies = [ [[package]] name = "anstream" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" dependencies = [ "anstyle", "anstyle-parse", + "anstyle-query", "anstyle-wincon", - "concolor-override", - "concolor-query", + "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "0.3.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ "utf8parse", ] [[package]] -name = "anstyle-wincon" -version = "0.2.0" +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" dependencies = [ "anstyle", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -85,7 +94,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -182,9 +191,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" dependencies = [ "clap_builder", "clap_derive", @@ -193,9 +202,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" dependencies = [ "anstream", "anstyle", @@ -222,7 +231,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -252,19 +261,10 @@ dependencies = [ ] [[package]] -name = "concolor-override" +name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.10" +version = "3.0.0-dev.11" dependencies = [ "chrono", "clap", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.10" +version = "3.0.0-dev.11" dependencies = [ "anyhow", "async-trait", @@ -455,7 +455,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -472,7 +472,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -764,9 +764,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" dependencies = [ "bytes", "fnv", @@ -1209,9 +1209,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.49" +version = "0.10.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" dependencies = [ "bitflags", "cfg-if", @@ -1230,7 +1230,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1241,9 +1241,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.84" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" dependencies = [ "cc", "libc", @@ -1308,9 +1308,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" dependencies = [ "memchr", "serde", @@ -1548,29 +1548,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1709,9 +1709,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -1777,7 +1777,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1858,7 +1858,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f6b17f4..f53a9b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.10" +version = "3.0.0-dev.11" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 95bb978..595596f 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -33,42 +33,51 @@ dependencies = [ [[package]] name = "anstream" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" dependencies = [ "anstyle", "anstyle-parse", + "anstyle-query", "anstyle-wincon", - "concolor-override", - "concolor-query", + "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "0.3.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ "utf8parse", ] [[package]] -name = "anstyle-wincon" -version = "0.2.0" +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" dependencies = [ "anstyle", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -85,7 +94,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -182,9 +191,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" dependencies = [ "clap_builder", "clap_derive", @@ -193,9 +202,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" dependencies = [ "anstream", "anstyle", @@ -213,7 +222,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -233,19 +242,10 @@ dependencies = [ ] [[package]] -name = "concolor-override" +name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.10" +version = "3.0.0-dev.11" dependencies = [ "anyhow", "async-trait", @@ -324,11 +324,13 @@ dependencies = [ "ctrlc", "derive_setters", "dirs", + "fs2", "indicatif", "lazy_static", "log", "num_cpus", "regex", + "reqwest", "sanitize-filename", "serde", "serde_json", @@ -342,9 +344,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942a21f27140a954654d3b6b4aab8e8e3888b33cec736a51f0feab5e7fedc15f" +checksum = "b75b403a64b4cc8ed9a96cb16588475a93cb5c8643cf960907a54ae6f6ac3f0d" dependencies = [ "aes", "async-trait", @@ -369,9 +371,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d90bc631b1f94891b3f5d5b9ca0b8c7f7a33e4e8d9ae98dbc6bcb5aee56b817" +checksum = "d91a9978a505750f606a1fcf1efd5970d20521310e886839f2ad95c31354e54e" dependencies = [ "darling 0.14.4", "quote", @@ -422,7 +424,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -439,7 +441,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -563,6 +565,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -644,6 +652,16 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -715,9 +733,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" dependencies = [ "bytes", "fnv", @@ -1160,9 +1178,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.49" +version = "0.10.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" dependencies = [ "bitflags", "cfg-if", @@ -1181,7 +1199,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1192,9 +1210,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.84" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" dependencies = [ "cc", "libc", @@ -1259,9 +1277,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" dependencies = [ "memchr", "serde", @@ -1357,6 +1375,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "tokio-socks", "tower-service", "url", "wasm-bindgen", @@ -1492,29 +1511,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1653,9 +1672,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -1721,7 +1740,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1802,7 +1821,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1826,6 +1845,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.7" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index a8a517b..6000843 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.10" +version = "3.0.0-dev.11" edition = "2021" [dependencies] From 13d8cc26c95f75032d297be6c8e7d37042882dd9 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Thu, 13 Apr 2023 22:47:14 +0200 Subject: [PATCH 359/630] Remove signal-hook dependency --- Cargo.lock | 20 -------------------- crunchy-cli-core/Cargo.lock | 20 -------------------- crunchy-cli-core/Cargo.toml | 1 - 3 files changed, 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a8cb6a..c8328f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,6 @@ dependencies = [ "serde", "serde_json", "shlex", - "signal-hook", "sys-locale", "tempfile", "terminal_size", @@ -1623,25 +1622,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.8" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 595596f..a60668a 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -335,7 +335,6 @@ dependencies = [ "serde", "serde_json", "shlex", - "signal-hook", "sys-locale", "tempfile", "terminal_size", @@ -1586,25 +1585,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.8" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 6000843..bbae22e 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -24,7 +24,6 @@ sanitize-filename = "0.4" serde = "1.0" serde_json = "1.0" shlex = "1.1" -signal-hook = "0.3" tempfile = "3.5" terminal_size = "0.2" tokio = { version = "1.27", features = ["macros", "rt-multi-thread", "time"] } From c4a4651164523d87f66dcf454d40f23d355b9757 Mon Sep 17 00:00:00 2001 From: bocchi <121779542+hitorilabs@users.noreply.github.com> Date: Fri, 14 Apr 2023 23:11:58 -0400 Subject: [PATCH 360/630] set default value for locale to empty vec --- crunchy-cli-core/src/archive/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index cf24c84..7fadf55 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -31,7 +31,7 @@ pub struct Archive { #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] pub(crate) audio: Vec<Locale>, #[arg(help = "Deprecated. Use '-a' / '--audio' instead")] - #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] + #[arg(short, long, default_values_t = Vec::<Locale>::new())] locale: Vec<Locale>, #[arg(help = format!("Subtitle languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] From c0e2df480484394c912d9693ae2c0fb1141273d0 Mon Sep 17 00:00:00 2001 From: bocchi <121779542+hitorilabs@users.noreply.github.com> Date: Fri, 14 Apr 2023 23:25:56 -0400 Subject: [PATCH 361/630] redundant to specify default value --- crunchy-cli-core/src/archive/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7fadf55..580276b 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -31,7 +31,7 @@ pub struct Archive { #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] pub(crate) audio: Vec<Locale>, #[arg(help = "Deprecated. Use '-a' / '--audio' instead")] - #[arg(short, long, default_values_t = Vec::<Locale>::new())] + #[arg(short, long)] locale: Vec<Locale>, #[arg(help = format!("Subtitle languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] From 847c6a1abcb0ca00ec08896f270f2183b3e09b93 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 17 Apr 2023 18:14:54 +0200 Subject: [PATCH 362/630] Mark CC subtitle track as CC & grab normal subtitles and CC when using -m audio (#141) --- crunchy-cli-core/src/archive/command.rs | 13 +++++--- crunchy-cli-core/src/download/command.rs | 6 ++-- crunchy-cli-core/src/utils/download.rs | 41 ++++++++++++++---------- crunchy-cli-core/src/utils/format.rs | 4 +-- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 580276b..a048685 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -242,10 +242,16 @@ async fn get_format( } }; - let subtitles: Vec<Subtitle> = archive + let subtitles: Vec<(Subtitle, bool)> = archive .subtitle .iter() - .filter_map(|s| stream.subtitles.get(s).cloned()) + .filter_map(|s| { + stream + .subtitles + .get(s) + .cloned() + .map(|l| (l, single_format.audio == Locale::ja_JP)) + }) .collect(); format_pairs.push((single_format, video.clone(), audio, subtitles.clone())); @@ -278,9 +284,6 @@ async fn get_format( subtitles: format_pairs .iter() .flat_map(|(_, _, _, subtitles)| subtitles.clone()) - .map(|s| (s.locale.clone(), s)) - .collect::<HashMap<Locale, Subtitle>>() - .into_values() .collect(), }), MergeBehavior::Auto => { diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index d18320a..142bf9c 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -203,12 +203,14 @@ async fn get_format( let download_format = DownloadFormat { video: (video.clone(), single_format.audio.clone()), audios: vec![(audio, single_format.audio.clone())], - subtitles: subtitle.clone().map_or(vec![], |s| vec![s]), + subtitles: subtitle + .clone() + .map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP)]), }; let format = Format::from_single_formats(vec![( single_format.clone(), video, - subtitle.map_or(vec![], |s| vec![s]), + subtitle.map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP)]), )]); Ok((download_format, format)) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index f0f51c1..57baf83 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -11,6 +11,7 @@ use log::{debug, warn, LevelFilter}; use regex::Regex; use std::borrow::Borrow; use std::borrow::BorrowMut; +use std::cmp::Ordering; use std::collections::BTreeMap; use std::env; use std::io::Write; @@ -81,7 +82,7 @@ struct FFmpegMeta { pub struct DownloadFormat { pub video: (VariantData, Locale), pub audios: Vec<(VariantData, Locale)>, - pub subtitles: Vec<Subtitle>, + pub subtitles: Vec<(Subtitle, bool)>, } pub struct Downloader { @@ -144,12 +145,19 @@ impl Downloader { }) } if let Some(subtitle_sort) = &self.subtitle_sort { - format.subtitles.sort_by(|a, b| { - subtitle_sort - .iter() - .position(|l| l == &a.locale) - .cmp(&subtitle_sort.iter().position(|l| l == &b.locale)) - }) + format + .subtitles + .sort_by(|(a_subtitle, a_not_cc), (b_subtitle, b_not_cc)| { + let ordering = subtitle_sort + .iter() + .position(|l| l == &a_subtitle.locale) + .cmp(&subtitle_sort.iter().position(|l| l == &b_subtitle.locale)); + if matches!(ordering, Ordering::Equal) { + a_not_cc.cmp(b_not_cc).reverse() + } else { + ordering + } + }) } } @@ -191,20 +199,19 @@ impl Downloader { }) } let len = get_video_length(&video_path)?; - for subtitle in format.subtitles.iter() { + for (subtitle, not_cc) in format.subtitles.iter() { let subtitle_path = self.download_subtitle(subtitle.clone(), len).await?; + let mut subtitle_title = subtitle.locale.to_human_readable(); + if !not_cc { + subtitle_title += " (CC)" + } + if i != 0 { + subtitle_title += &format!(" [Video: #{}]", i + 1) + } subtitles.push(FFmpegMeta { path: subtitle_path, language: subtitle.locale.clone(), - title: if i == 0 { - subtitle.locale.to_human_readable() - } else { - format!( - "{} [Video: #{}]", - subtitle.locale.to_human_readable(), - i + 1 - ) - }, + title: subtitle_title, }) } videos.push(FFmpegMeta { diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index c6f3168..2f546df 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -301,7 +301,7 @@ pub struct Format { impl Format { pub fn from_single_formats( - mut single_formats: Vec<(SingleFormat, VariantData, Vec<Subtitle>)>, + mut single_formats: Vec<(SingleFormat, VariantData, Vec<(Subtitle, bool)>)>, ) -> Self { let locales: Vec<(Locale, Vec<Locale>)> = single_formats .iter() @@ -310,7 +310,7 @@ impl Format { single_format.audio.clone(), subtitles .into_iter() - .map(|s| s.locale.clone()) + .map(|(s, _)| s.locale.clone()) .collect::<Vec<Locale>>(), ) }) From e277b4200f37536ee3b70eedc0c84b7b3f39df1a Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Mon, 17 Apr 2023 18:24:20 +0200 Subject: [PATCH 363/630] Update version --- Cargo.lock | 13 +++++++------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 11 ++++++----- crunchy-cli-core/Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8328f9..d154304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.11" +version = "3.0.0-dev.12" dependencies = [ "chrono", "clap", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.11" +version = "3.0.0-dev.12" dependencies = [ "anyhow", "async-trait", @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df35f3b3b0fde2747a01530de0a81f7a27620d050f656e518ef0550557f564de" +checksum = "4937210c839d6f764b196afa05349927a8a2cc204ad613086e81defcddd4ade1" dependencies = [ "chrono", "fs-err", @@ -560,6 +560,7 @@ dependencies = [ "serde", "serde_with", "thiserror", + "tokio", "xattr", ] @@ -849,9 +850,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", diff --git a/Cargo.toml b/Cargo.toml index f53a9b1..2b7a255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.11" +version = "3.0.0-dev.12" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index a60668a..15f2bea 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.11" +version = "3.0.0-dev.12" dependencies = [ "anyhow", "async-trait", @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df35f3b3b0fde2747a01530de0a81f7a27620d050f656e518ef0550557f564de" +checksum = "4937210c839d6f764b196afa05349927a8a2cc204ad613086e81defcddd4ade1" dependencies = [ "chrono", "fs-err", @@ -529,6 +529,7 @@ dependencies = [ "serde", "serde_with", "thiserror", + "tokio", "xattr", ] @@ -818,9 +819,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index bbae22e..6cdb2af 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.11" +version = "3.0.0-dev.12" edition = "2021" [dependencies] From ff258c072220ee494eaf4e5302a0b9ec81107227 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 23 Apr 2023 13:21:50 +0200 Subject: [PATCH 364/630] Remove --vv flag --- crunchy-cli-core/src/lib.rs | 19 ++++++------------- crunchy-cli-core/src/utils/log.rs | 10 ++++------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 65c3ca1..fee019f 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::bail; use anyhow::Result; use clap::{Parser, Subcommand}; use crunchyroll_rs::crunchyroll::CrunchyrollBuilder; +use crunchyroll_rs::error::CrunchyrollError; use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; use reqwest::Proxy; @@ -15,9 +16,7 @@ mod download; mod login; mod utils; -use crate::login::session_file_path; pub use archive::Archive; -use crunchyroll_rs::error::CrunchyrollError; pub use download::Download; pub use login::Login; @@ -90,10 +89,6 @@ struct Verbosity { #[arg(short)] v: bool, - #[arg(help = "Very verbose output. Generally not recommended, use '-v' instead")] - #[arg(long)] - vv: bool, - #[arg(help = "Quiet output. Does not print anything unless it's a error")] #[arg( long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout" @@ -124,18 +119,16 @@ pub async fn cli_entrypoint() { let mut cli: Cli = Cli::parse(); if let Some(verbosity) = &cli.verbosity { - if verbosity.v as u8 + verbosity.q as u8 + verbosity.vv as u8 > 1 { + if verbosity.v as u8 + verbosity.q as u8 > 1 { eprintln!("Output cannot be verbose ('-v') and quiet ('-q') at the same time"); std::process::exit(1) } else if verbosity.v { - CliLogger::init(false, LevelFilter::Debug).unwrap() + CliLogger::init(LevelFilter::Debug).unwrap() } else if verbosity.q { - CliLogger::init(false, LevelFilter::Error).unwrap() - } else if verbosity.vv { - CliLogger::init(true, LevelFilter::Debug).unwrap() + CliLogger::init(LevelFilter::Error).unwrap() } } else { - CliLogger::init(false, LevelFilter::Info).unwrap() + CliLogger::init(LevelFilter::Info).unwrap() } debug!("cli input: {:?}", cli); @@ -145,7 +138,7 @@ pub async fn cli_entrypoint() { Command::Download(download) => pre_check_executor(download).await, Command::Login(login) => { if login.remove { - if let Some(session_file) = session_file_path() { + if let Some(session_file) = login::session_file_path() { let _ = fs::remove_file(session_file); } return; diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs index 8472250..37bc5a9 100644 --- a/crunchy-cli-core/src/utils/log.rs +++ b/crunchy-cli-core/src/utils/log.rs @@ -50,7 +50,6 @@ pub(crate) use tab_info; #[allow(clippy::type_complexity)] pub struct CliLogger { - all: bool, level: LevelFilter, progress: Mutex<Option<ProgressBar>>, } @@ -64,7 +63,7 @@ impl Log for CliLogger { if !self.enabled(record.metadata()) || (record.target() != "progress" && record.target() != "progress_end" - && (!self.all && !record.target().starts_with("crunchy_cli"))) + && !record.target().starts_with("crunchy_cli")) { return; } @@ -95,17 +94,16 @@ impl Log for CliLogger { } impl CliLogger { - pub fn new(all: bool, level: LevelFilter) -> Self { + pub fn new(level: LevelFilter) -> Self { Self { - all, level, progress: Mutex::new(None), } } - pub fn init(all: bool, level: LevelFilter) -> Result<(), SetLoggerError> { + pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> { set_max_level(level); - set_boxed_logger(Box::new(CliLogger::new(all, level))) + set_boxed_logger(Box::new(CliLogger::new(level))) } fn extended(&self, record: &Record) { From ce358041bef4c19b47803d0a91f317f11e04f1d4 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 23 Apr 2023 13:45:08 +0200 Subject: [PATCH 365/630] Removed unused struct --- crunchy-cli-core/src/archive/filter.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index d135800..e44ebab 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -269,8 +269,6 @@ impl Filter for ArchiveFilter { let mut single_format_collection = SingleFormatCollection::new(); - struct SortKey(u32, String); - let mut sorted: BTreeMap<(u32, String), Self::T> = BTreeMap::new(); for data in flatten_input { sorted From 7b1ed30b20212c076567f571cfb0c11674f698fd Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 23 Apr 2023 13:45:53 +0200 Subject: [PATCH 366/630] Fix subtitle burn-in error (#198) --- crunchy-cli-core/src/utils/download.rs | 63 ++++++++++++++------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 57baf83..8bd8c28 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -252,20 +252,28 @@ impl Downloader { format!("title={}", meta.title), ]); } - for (i, meta) in subtitles.iter().enumerate() { - input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); - maps.extend([ - "-map".to_string(), - (i + videos.len() + audios.len()).to_string(), - ]); - metadata.extend([ - format!("-metadata:s:s:{}", i), - format!("language={}", meta.language), - ]); - metadata.extend([ - format!("-metadata:s:s:{}", i), - format!("title={}", meta.title), - ]); + + // this formats are supporting embedding subtitles into the video container instead of + // burning it into the video stream directly + let container_supports_softsubs = + ["mkv", "mp4"].contains(&dst.extension().unwrap_or_default().to_str().unwrap()); + + if container_supports_softsubs { + for (i, meta) in subtitles.iter().enumerate() { + input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); + maps.extend([ + "-map".to_string(), + (i + videos.len() + audios.len()).to_string(), + ]); + metadata.extend([ + format!("-metadata:s:s:{}", i), + format!("language={}", meta.language), + ]); + metadata.extend([ + format!("-metadata:s:s:{}", i), + format!("title={}", meta.title), + ]); + } } let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args(); @@ -283,17 +291,12 @@ impl Downloader { .position(|m| m.language == default_subtitle) { match dst.extension().unwrap_or_default().to_str().unwrap() { + "mkv" => (), "mp4" => output_presets.extend([ "-movflags".to_string(), "faststart".to_string(), "-c:s".to_string(), "mov_text".to_string(), - format!("-disposition:s:s:{}", position), - "forced".to_string(), - ]), - "mkv" => output_presets.extend([ - format!("-disposition:s:s:{}", position), - "forced".to_string(), ]), _ => { // remove '-c:v copy' and '-c:a copy' from output presets as its causes issues with @@ -314,7 +317,7 @@ impl Downloader { output_presets.extend([ "-vf".to_string(), format!( - "subtitles={}", + "ass={}", subtitles.get(position).unwrap().path.to_str().unwrap() ), ]) @@ -322,14 +325,16 @@ impl Downloader { } } - if let Some(position) = subtitles - .iter() - .position(|meta| meta.language == default_subtitle) - { - command_args.extend([ - format!("-disposition:s:s:{}", position), - "forced".to_string(), - ]) + if container_supports_softsubs { + if let Some(position) = subtitles + .iter() + .position(|meta| meta.language == default_subtitle) + { + command_args.extend([ + format!("-disposition:s:s:{}", position), + "forced".to_string(), + ]) + } } } From 94fcf1590ac1ecdcfd1c9807da18effb668674d5 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 23 Apr 2023 14:02:52 +0200 Subject: [PATCH 367/630] Add .mov to known soft-sub containers --- crunchy-cli-core/src/utils/download.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 8bd8c28..0fbd792 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -256,7 +256,7 @@ impl Downloader { // this formats are supporting embedding subtitles into the video container instead of // burning it into the video stream directly let container_supports_softsubs = - ["mkv", "mp4"].contains(&dst.extension().unwrap_or_default().to_str().unwrap()); + ["mkv", "mov", "mp4"].contains(&dst.extension().unwrap_or_default().to_str().unwrap()); if container_supports_softsubs { for (i, meta) in subtitles.iter().enumerate() { @@ -292,7 +292,7 @@ impl Downloader { { match dst.extension().unwrap_or_default().to_str().unwrap() { "mkv" => (), - "mp4" => output_presets.extend([ + "mov" | "mp4" => output_presets.extend([ "-movflags".to_string(), "faststart".to_string(), "-c:s".to_string(), From 0f73d8dbeceb2c02a54313d9e3ade0e1bdf34595 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 23 Apr 2023 15:57:49 +0200 Subject: [PATCH 368/630] Update conditions for subtitle to be marked as CC --- crunchy-cli-core/src/archive/command.rs | 9 ++++++++- crunchy-cli-core/src/download/command.rs | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index a048685..a09c1a3 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -250,7 +250,14 @@ async fn get_format( .subtitles .get(s) .cloned() - .map(|l| (l, single_format.audio == Locale::ja_JP)) + // the subtitle is probably not cc if the audio is japanese or more than one + // subtitle exists for this stream + .map(|l| { + ( + l, + single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, + ) + }) }) .collect(); diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 142bf9c..c1388f1 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -205,12 +205,12 @@ async fn get_format( audios: vec![(audio, single_format.audio.clone())], subtitles: subtitle .clone() - .map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP)]), + .map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1)]), }; let format = Format::from_single_formats(vec![( single_format.clone(), video, - subtitle.map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP)]), + subtitle.map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1)]), )]); Ok((download_format, format)) From dc431a963755e428b2f7b9771984182c6dfd094f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 25 Apr 2023 10:47:08 +0200 Subject: [PATCH 369/630] Update version --- Cargo.lock | 68 ++++++++++++++++++------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 62 ++++++++++++++++----------------- crunchy-cli-core/Cargo.toml | 4 +-- 4 files changed, 68 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d154304..4635644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" dependencies = [ "anstyle", "anstyle-parse", @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" [[package]] name = "bytes" @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" dependencies = [ "clap_builder", "clap_derive", @@ -202,9 +202,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" dependencies = [ "anstream", "anstyle", @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.2.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c22dcfb410883764b29953103d9ef7bb8fe21b3fa1158bc99986c2067294bd" +checksum = "1a19591b2ab0e3c04b588a0e04ddde7b9eaa423646d1b4a8092879216bf47473" dependencies = [ "clap", ] @@ -324,16 +324,16 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] [[package]] name = "crunchy-cli" -version = "3.0.0-dev.12" +version = "3.0.0-dev.13" dependencies = [ "chrono", "clap", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.12" +version = "3.0.0-dev.13" dependencies = [ "anyhow", "async-trait", @@ -764,9 +764,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -1052,9 +1052,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "link-cplusplus" @@ -1067,9 +1067,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" [[package]] name = "log" @@ -1209,9 +1209,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.50" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ "bitflags", "cfg-if", @@ -1241,9 +1241,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.85" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ "cc", "libc", @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", @@ -1367,9 +1367,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "reqwest" @@ -1439,9 +1439,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.37.11" +version = "0.37.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" dependencies = [ "bitflags", "errno", diff --git a/Cargo.toml b/Cargo.toml index 2b7a255..bad4772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.12" +version = "3.0.0-dev.13" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 15f2bea..3904323 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" dependencies = [ "anstyle", "anstyle-parse", @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" [[package]] name = "bytes" @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" dependencies = [ "clap_builder", "clap_derive", @@ -202,9 +202,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" dependencies = [ "anstream", "anstyle", @@ -305,16 +305,16 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.12" +version = "3.0.0-dev.13" dependencies = [ "anyhow", "async-trait", @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -1021,9 +1021,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "link-cplusplus" @@ -1036,9 +1036,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" [[package]] name = "log" @@ -1178,9 +1178,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.50" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ "bitflags", "cfg-if", @@ -1210,9 +1210,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.85" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ "cc", "libc", @@ -1325,9 +1325,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", @@ -1336,9 +1336,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "reqwest" @@ -1402,9 +1402,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.11" +version = "0.37.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" dependencies = [ "bitflags", "errno", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 6cdb2af..62f83ec 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.12" +version = "3.0.0-dev.13" edition = "2021" [dependencies] @@ -18,7 +18,7 @@ indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.15" -regex = "1.7" +regex = "1.8" reqwest = { version = "0.11", default-features = false, features = ["socks"] } sanitize-filename = "0.4" serde = "1.0" From c2e953043e02d6049ee8c92f1bbc53b6e071f5e1 Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sat, 6 May 2023 22:06:52 +0200 Subject: [PATCH 370/630] Fix output filename if file stem is empty but file exists --- crunchy-cli-core/src/utils/os.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index bd30c2a..f6d9ad0 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -52,9 +52,18 @@ pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { while path.exists() { i += 1; - let ext = path.extension().unwrap_or_default().to_string_lossy(); + let mut ext = path.extension().unwrap_or_default().to_str().unwrap(); let mut filename = path.file_stem().unwrap_or_default().to_str().unwrap(); + // if the extension is empty, the filename without extension is probably empty + // (e.g. `.mp4`). in this case Rust assumes that `.mp4` is the file stem rather than the + // extension. if this is the case, set the extension to the file stem and make the file stem + // empty + if ext.is_empty() { + ext = filename; + filename = ""; + } + if filename.ends_with(&format!(" ({})", i - 1)) { filename = filename.strip_suffix(&format!(" ({})", i - 1)).unwrap(); } From 61766c74fab93397d46620f786b1b116a7272cee Mon Sep 17 00:00:00 2001 From: ByteDream <bytedream@protonmail.com> Date: Sun, 7 May 2023 01:34:41 +0200 Subject: [PATCH 371/630] Enable usage of auth flags behind login command --- crunchy-cli-core/src/download/command.rs | 16 ++++++-- crunchy-cli-core/src/lib.rs | 50 ++++++++++++------------ crunchy-cli-core/src/login/command.rs | 23 ++++++++++- crunchy-cli-core/src/login/mod.rs | 3 +- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index c1388f1..7d42d97 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -203,14 +203,22 @@ async fn get_format( let download_format = DownloadFormat { video: (video.clone(), single_format.audio.clone()), audios: vec![(audio, single_format.audio.clone())], - subtitles: subtitle - .clone() - .map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1)]), + subtitles: subtitle.clone().map_or(vec![], |s| { + vec![( + s, + single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, + )] + }), }; let format = Format::from_single_formats(vec![( single_format.clone(), video, - subtitle.map_or(vec![], |s| vec![(s, single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1)]), + subtitle.map_or(vec![], |s| { + vec![( + s, + single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, + )] + }), )]); Ok((download_format, format)) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index fee019f..fdc801c 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -51,7 +51,7 @@ pub struct Cli { experimental_fixes: bool, #[clap(flatten)] - login_method: LoginMethod, + login_method: login::LoginMethod, #[arg(help = "Use a proxy to route all traffic through")] #[arg(long_help = "Use a proxy to route all traffic through. \ @@ -97,24 +97,6 @@ struct Verbosity { q: bool, } -#[derive(Debug, Parser)] -struct LoginMethod { - #[arg( - help = "Login with credentials (username or email and password). Must be provided as user:password" - )] - #[arg(long)] - credentials: Option<String>, - #[arg(help = "Login with the etp-rt cookie")] - #[arg( - long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there" - )] - #[arg(long)] - etp_rt: Option<String>, - #[arg(help = "Login anonymously / without an account")] - #[arg(long, default_value_t = false)] - anonymous: bool, -} - pub async fn cli_entrypoint() { let mut cli: Cli = Cli::parse(); @@ -148,7 +130,7 @@ pub async fn cli_entrypoint() { } }; - let ctx = match create_ctx(&cli).await { + let ctx = match create_ctx(&mut cli).await { Ok(ctx) => ctx, Err(e) => { error!("{}", e); @@ -222,12 +204,12 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { } } -async fn create_ctx(cli: &Cli) -> Result<Context> { +async fn create_ctx(cli: &mut Cli) -> Result<Context> { let crunchy = crunchyroll_session(cli).await?; Ok(Context { crunchy }) } -async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { +async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { let supported_langs = vec![ Locale::ar_ME, Locale::de_DE, @@ -276,12 +258,18 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { builder = builder.preferred_audio_locale(download.audio.clone()) } - let login_methods_count = cli.login_method.credentials.is_some() as u8 + let root_login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 + cli.login_method.anonymous as u8; + let mut login_login_methods_count = 0; + if let Command::Login(login) = &cli.command { + login_login_methods_count += login.login_method.credentials.is_some() as u8 + + cli.login_method.etp_rt.is_some() as u8 + + cli.login_method.anonymous as u8 + } let progress_handler = progress!("Logging in"); - if login_methods_count == 0 { + if root_login_methods_count + login_login_methods_count == 0 { if let Some(login_file_path) = login::session_file_path() { if login_file_path.exists() { let session = fs::read_to_string(login_file_path)?; @@ -298,11 +286,21 @@ async fn crunchyroll_session(cli: &Cli) -> Result<Crunchyroll> { } } bail!("Please use a login method ('--credentials', '--etp-rt' or '--anonymous')") - } else if login_methods_count > 1 { + } else if root_login_methods_count + login_login_methods_count > 1 { bail!("Please use only one login method ('--credentials', '--etp-rt' or '--anonymous')") } - let crunchy = if let Some(credentials) = &cli.login_method.credentials { + let login_method = if login_login_methods_count > 0 { + if let Command::Login(login) = &cli.command { + login.login_method.clone() + } else { + unreachable!() + } + } else { + cli.login_method.clone() + }; + + let crunchy = if let Some(credentials) = &login_method.credentials { if let Some((user, password)) = credentials.split_once(':') { builder.login_with_credentials(user, password).await? } else { diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index ad101da..ab16e06 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -2,6 +2,7 @@ use crate::utils::context::Context; use crate::Execute; use anyhow::bail; use anyhow::Result; +use clap::Parser; use crunchyroll_rs::crunchyroll::SessionToken; use std::fs; use std::path::PathBuf; @@ -9,7 +10,9 @@ use std::path::PathBuf; #[derive(Debug, clap::Parser)] #[clap(about = "Save your login credentials persistent on disk")] pub struct Login { - #[arg(help = "Remove your stored credentials (instead of save them)")] + #[clap(flatten)] + pub login_method: LoginMethod, + #[arg(help = "Remove your stored credentials (instead of saving them)")] #[arg(long)] pub remove: bool, } @@ -36,6 +39,24 @@ impl Execute for Login { } } +#[derive(Clone, Debug, Parser)] +pub struct LoginMethod { + #[arg( + help = "Login with credentials (username or email and password). Must be provided as user:password" + )] + #[arg(long)] + pub credentials: Option<String>, + #[arg(help = "Login with the etp-rt cookie")] + #[arg( + long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there" + )] + #[arg(long)] + pub etp_rt: Option<String>, + #[arg(help = "Login anonymously / without an account")] + #[arg(long, default_value_t = false)] + pub anonymous: bool, +} + pub fn session_file_path() -> Option<PathBuf> { dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli").join("session")) } diff --git a/crunchy-cli-core/src/login/mod.rs b/crunchy-cli-core/src/login/mod.rs index e2ab88c..8c1220a 100644 --- a/crunchy-cli-core/src/login/mod.rs +++ b/crunchy-cli-core/src/login/mod.rs @@ -1,4 +1,3 @@ mod command; -pub use command::session_file_path; -pub use command::Login; +pub use command::{session_file_path, Login, LoginMethod}; From b24827dc6bd5faa7819d51e35b2d993087f5aa11 Mon Sep 17 00:00:00 2001 From: bocchi <131238467+hitorilabs@users.noreply.github.com> Date: Sat, 13 May 2023 12:19:44 -0400 Subject: [PATCH 372/630] fix login (#202) --- crunchy-cli-core/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index fdc801c..9ae8e83 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -264,8 +264,8 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { let mut login_login_methods_count = 0; if let Command::Login(login) = &cli.command { login_login_methods_count += login.login_method.credentials.is_some() as u8 - + cli.login_method.etp_rt.is_some() as u8 - + cli.login_method.anonymous as u8 + + login.login_method.etp_rt.is_some() as u8 + + login.login_method.anonymous as u8 } let progress_handler = progress!("Logging in"); From 4bd172df060157d62e19b85005de714fca75a67c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 24 May 2023 12:55:47 +0200 Subject: [PATCH 373/630] Fix japanese episode download if episode isn't available in specified language with archive (#207) --- crunchy-cli-core/src/archive/filter.rs | 29 +++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index e44ebab..514b540 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -129,7 +129,34 @@ impl Filter for ArchiveFilter { let mut episodes = vec![]; for season in seasons { - episodes.extend(season.episodes().await?) + let season_locale = season + .audio_locales + .get(0) + .cloned() + .unwrap_or(Locale::ja_JP); + let mut eps = season.episodes().await?; + let before_len = eps.len(); + eps.retain(|e| e.audio_locale == season_locale); + if eps.len() != before_len { + if eps.len() == 0 { + if matches!(self.visited, Visited::Series) { + warn!( + "Season {} is not available with {} audio", + season.season_number, season_locale + ) + } + } else { + let last_episode = eps.last().unwrap(); + warn!( + "Season {} is only available with {} audio until episode {} ({})", + season.season_number, + season_locale, + last_episode.episode_number, + last_episode.title + ) + } + } + episodes.extend(eps) } if Format::has_relative_episodes_fmt(&self.archive.output) { From a2b7c78752f916923f619864144a550f1610bedf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 24 May 2023 13:15:28 +0200 Subject: [PATCH 374/630] Fix long help language formatting --- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/download/command.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index a09c1a3..9af14fb 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -27,7 +27,7 @@ pub struct Archive { #[arg(help = format!("Audio languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Audio languages. Can be used multiple times. \ - Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + Available languages are:\n {}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] pub(crate) audio: Vec<Locale>, #[arg(help = "Deprecated. Use '-a' / '--audio' instead")] diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 7d42d97..644e4e5 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -23,7 +23,7 @@ pub struct Download { #[arg(help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ - Available languages are:\n{}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + Available languages are:\n {}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_value_t = crate::utils::locale::system_locale())] pub(crate) audio: Locale, #[arg(help = format!("Subtitle language. Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] From f3f900064a0800a4175f5ea4b27e2e1efe032e09 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 25 May 2023 18:54:41 +0200 Subject: [PATCH 375/630] Fix flag README typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 846c59f..b1cd9ea 100644 --- a/README.md +++ b/README.md @@ -205,9 +205,9 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any - Default subtitle - `--default_subtitle` Set which subtitle language is to be flagged as **default** and **forced**. + `--default-subtitle` Set which subtitle language is to be flagged as **default** and **forced**. ```shell - $ crunchy archive --default_subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy archive --default-subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is none. From 19f79a434916663c1e33c2911ebebdd875aa65ba Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 25 May 2023 18:58:03 +0200 Subject: [PATCH 376/630] Remove no-subtitle-optimization flag in README --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index b1cd9ea..059c7f2 100644 --- a/README.md +++ b/README.md @@ -211,16 +211,6 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any ``` Default is none. -- Subtitle optimizations - - Crunchyroll's subtitles look weird in some players (#66). - This can be fixed by adding a specific entry to the subtitles. - Even though this entry is a de facto standard, it is not defined in the official specification for the `.ass` format (cf. [Advanced SubStation Subtitles](https://wiki.videolan.org/SubStation_Alpha)). This could cause compatibility issues, but no issues have been reported yet. - `--no_subtitle_optimizations` disables these optimizations. - ```shell - $ crunchy archive --no_subtitle_optimizations https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx - ``` - ### Episode filtering Filters patterns can be used to download a specific range of episodes from a single series. From 49d64805cae6b60f26f3710ccd3d9a680674a4d5 Mon Sep 17 00:00:00 2001 From: StepBroBD <Hi@StepBroBD.com> Date: Sun, 4 Jun 2023 09:57:28 -0600 Subject: [PATCH 377/630] add nix flake (#210) - add following functionality: - nix develop with direnv support - nix run and nix shell - nix fmt for flake.nix - and package overlay for https://github.com/NixOS/nixpkgs/pull/225502 - useful docs - https://stackoverflow.com/questions/53272197/how-do-i-override-the-libc-in-a-nix-package-to-be-musl - https://github.com/NixOS/nixpkgs/blob/dd3aca2d0b9dcb4888779bfb08805d79596607c9/pkgs/top-level/stage.nix#L136 - inspired by https://github.com/typst/typst/blob/main/flake.nix --- .envrc | 1 + .gitignore | 9 ++++++- README.md | 7 +++++ flake.lock | 59 +++++++++++++++++++++++++++++++++++++++++ flake.nix | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 5bb2fd7..76cbb0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ -/.idea +# Rust /target + +# Editor +/.idea /.vscode + +# Nix +/result +/.direnv diff --git a/README.md b/README.md index 059c7f2..0858123 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,13 @@ A pure [Rust](https://www.rust-lang.org/) CLI for [Crunchyroll](https://www.crun Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the latest (pre-)release. +### โ„๏ธ The nix way + +This requires [nix](https://nixos.org) and you'll probably need `--extra-experimental-features "nix-command flakes"` depending on your configurations. +```shell +$ nix <run|shell|develop> github:crunchy-labs/crunchy-cli +``` + ### ๐Ÿ›  Build it yourself Since we do not support every platform and architecture you may have to build the project yourself. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..91bbf3d --- /dev/null +++ b/flake.lock @@ -0,0 +1,59 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1685866647, + "narHash": "sha256-4jKguNHY/edLYImB+uL8jKPL/vpfOvMmSlLAGfxSrnY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a53a3bec10deef6e1cc1caba5bc60f53b959b1e8", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixpkgs-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..4a48dec --- /dev/null +++ b/flake.nix @@ -0,0 +1,78 @@ +{ + inputs = { + nixpkgs.url = "flake:nixpkgs/nixpkgs-unstable"; + utils.url = "flake:flake-utils"; + }; + + outputs = { self, nixpkgs, utils, ... }: utils.lib.eachSystem [ + "aarch64-darwin" + "x86_64-darwin" + "aarch64-linux" + "x86_64-linux" + ] + (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + # enable musl on Linux makes the build time 100x slower + # since it will trigger a toolchain rebuild + # if nixpkgs.legacyPackages.${system}.stdenv.hostPlatform.isLinux + # then nixpkgs.legacyPackages.${system}.pkgsMusl + # else nixpkgs.legacyPackages.${system}; + + crunchy-cli = pkgs.rustPlatform.buildRustPackage.override { stdenv = pkgs.clangStdenv; } rec { + pname = "crunchy-cli"; + inherit ((pkgs.lib.importTOML ./Cargo.toml).package) version; + + src = pkgs.lib.cleanSource ./.; + + cargoLock = { + lockFile = ./Cargo.lock; + allowBuiltinFetchGit = true; + }; + + nativeBuildInputs = [ + pkgs.pkg-config + ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ + pkgs.xcbuild + ]; + + buildInputs = [ + pkgs.openssl + ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ + pkgs.darwin.Security + ]; + }; + in + { + packages.default = crunchy-cli; + + overlays.default = _: prev: { + crunchy-cli = prev.crunchy-cli.override { }; + }; + + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + cargo + clippy + rust-analyzer + rustc + rustfmt + ]; + + inputsFrom = builtins.attrValues self.packages.${system}; + + buildInputs = [ + pkgs.openssl + pkgs.libiconv + ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ + pkgs.darwin.apple_sdk.frameworks.CoreServices + pkgs.darwin.Security + ]; + + RUST_SRC_PATH = pkgs.rustPlatform.rustLibSrc; + }; + + formatter = pkgs.nixpkgs-fmt; + } + ); +} From 32aab193d0ae6902768e648edfaa3741e312dabf Mon Sep 17 00:00:00 2001 From: Peter Mahon <pdmahon1@gmail.com> Date: Fri, 9 Jun 2023 01:17:59 -0700 Subject: [PATCH 378/630] Add Output Templates section in README.md Added a section for output templates so that users of the application have an easy reference as opposed to searching within the code. I also updated the Output Templates subsection in the downloads section to mention both .ts and .mp4 files since the default changes in version crunchy-cli v3.0.0-dev.9 of the binaries. --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0858123..5a52424 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any ```shell $ crunchy download -o "ditf.ts" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` - Default is `{title}.ts`. + Default is `{title}.ts` or `{title}.mp4`, depending on the version of binary you're using. See the Template Options section below for more options. - Resolution @@ -187,7 +187,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any ```shell $ crunchy archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Default is `{title}.mkv`. + Default is `{title}.mkv`. See the Template Options section below for more options. - Resolution @@ -217,6 +217,28 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any $ crunchy archive --default-subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is none. + +### Output Template Options + +You can use various template options to change how the filename is processed. The following tags are available: + +- {title} โ†’ Title of the video +- {series_name} โ†’ Name of the series +- {season_name} โ†’ Name of the season +- {audio} โ†’ Audio language of the video +- {resolution} โ†’ Resolution of the video +- {season_number} โ†’ Number of the season +- {episode_number} โ†’ Number of the episode +- {relative_episode_number} โ†’ Number of the episode relative to its season +- {series_id} โ†’ ID of the series +- {season_id} โ†’ ID of the season +- {episode_id} โ†’ ID of the episode + +When including a file extension in the output, use the `.mp4` extension when using a binary crunchy-cli v3.0.0-dev.9 or newer and using the `download` option. `.mkv` is the default file extension for `archive` downloads. Here is an example of using output templates: +```shell +$ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball +# Outputs: '[S01E01] Secret of the Dragon Ball.mkv' +``` ### Episode filtering From b55ac9a51acc9ada716feec64d5db9a3f66d4308 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 16 Jun 2023 10:14:36 +0200 Subject: [PATCH 379/630] Update README --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5a52424..64b2f00 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,9 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Define an output template by using the `-o` / `--output` flag. If you want to use any other file format than [`.ts`](https://en.wikipedia.org/wiki/MPEG_transport_stream) you need [ffmpeg](https://ffmpeg.org/). ```shell - $ crunchy download -o "ditf.ts" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy download -o "ditf.mp4" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` - Default is `{title}.ts` or `{title}.mp4`, depending on the version of binary you're using. See the Template Options section below for more options. + Default is `{title}.mp4`. See the [Template Options section](#output-template-options) below for more options. - Resolution @@ -187,7 +187,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any ```shell $ crunchy archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Default is `{title}.mkv`. See the Template Options section below for more options. + Default is `{title}.mkv`. See the [Template Options section](#output-template-options) below for more options. - Resolution @@ -222,22 +222,22 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any You can use various template options to change how the filename is processed. The following tags are available: -- {title} โ†’ Title of the video -- {series_name} โ†’ Name of the series -- {season_name} โ†’ Name of the season -- {audio} โ†’ Audio language of the video -- {resolution} โ†’ Resolution of the video -- {season_number} โ†’ Number of the season -- {episode_number} โ†’ Number of the episode -- {relative_episode_number} โ†’ Number of the episode relative to its season -- {series_id} โ†’ ID of the series -- {season_id} โ†’ ID of the season -- {episode_id} โ†’ ID of the episode +- `{title}` โ†’ Title of the video +- `{series_name}` โ†’ Name of the series +- `{season_name}` โ†’ Name of the season +- `{audio}` โ†’ Audio language of the video +- `{resolution}` โ†’ Resolution of the video +- `{season_number}` โ†’ Number of the season +- `{episode_number}` โ†’ Number of the episode +- `{relative_episode_number}` โ†’ Number of the episode relative to its season +- `{series_id}` โ†’ ID of the series +- `{season_id}` โ†’ ID of the season +- `{episode_id}` โ†’ ID of the episode -When including a file extension in the output, use the `.mp4` extension when using a binary crunchy-cli v3.0.0-dev.9 or newer and using the `download` option. `.mkv` is the default file extension for `archive` downloads. Here is an example of using output templates: +Example: ```shell $ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball -# Outputs: '[S01E01] Secret of the Dragon Ball.mkv' +# Output file: '[S01E01] Secret of the Dragon Ball.mkv' ``` ### Episode filtering From 7ed1158339f96b5df459f26c5c2f1c7b0602fa96 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 19 Jun 2023 01:34:30 +0200 Subject: [PATCH 380/630] Fix subtitle sorting (#208) --- crunchy-cli-core/src/utils/download.rs | 88 +++++++++++++------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 0fbd792..dcfcf0a 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -475,8 +475,7 @@ impl Downloader { let mut buf = vec![]; subtitle.write_to(&mut buf).await?; - fix_subtitle_look_and_feel(&mut buf); - fix_subtitle_length(&mut buf, max_length); + fix_subtitles(&mut buf, max_length); file.write_all(buf.as_slice())?; @@ -670,27 +669,6 @@ fn estimate_variant_file_size(variant_data: &VariantData, segments: &Vec<Variant (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() } -/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video -/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) -/// for more information. -fn fix_subtitle_look_and_feel(raw: &mut Vec<u8>) { - let mut script_info = false; - let mut new = String::new(); - - for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { - if line.trim().starts_with('[') && script_info { - new.push_str("ScaledBorderAndShadow: yes\n"); - script_info = false - } else if line.trim() == "[Script Info]" { - script_info = true - } - new.push_str(line); - new.push('\n') - } - - *raw = new.into_bytes() -} - /// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry /// long after the actual video ends with artificially extends the video length on some video players. /// To prevent this, the video length must be hard set. See @@ -712,12 +690,22 @@ pub fn get_video_length(path: &Path) -> Result<NaiveTime> { Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) } -/// Fix the length of subtitles to a specified maximum amount. This is required because sometimes -/// subtitles have an unnecessary entry long after the actual video ends with artificially extends -/// the video length on some video players. To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more +/// Fix the subtitles in multiple ways as Crunchyroll sometimes delivers them malformed. +/// +/// Look and feel fix: Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very +/// messy on some video players. See +/// [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) for more /// information. -fn fix_subtitle_length(raw: &mut Vec<u8>, max_length: NaiveTime) { +/// Length fix: Sometimes subtitles have an unnecessary long entry which exceeds the video length, +/// some video players can't handle this correctly. To prevent this, the subtitles must be checked +/// if any entry is longer than the video length and if so the entry ending must be hard set to not +/// exceed the video length. See [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) +/// for more information. +/// Sort fix: Sometimes subtitle entries aren't sorted correctly by time which confuses some video +/// players. To prevent this, the subtitle entries must be manually sorted. See +/// [crunchy-labs/crunchy-cli#208](https://github.com/crunchy-labs/crunchy-cli/issues/208) for more +/// information. +fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { let re = Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) .unwrap(); @@ -738,10 +726,17 @@ fn fix_subtitle_length(raw: &mut Vec<u8>, max_length: NaiveTime) { } let length_as_string = format_naive_time(max_length); - let mut new = String::new(); + let mut entries = (vec![], vec![]); - for line in String::from_utf8_lossy(raw.as_slice()).split('\n') { - if let Some(capture) = re.captures(line) { + let mut as_lines: Vec<String> = String::from_utf8_lossy(raw.as_slice()) + .split('\n') + .map(|s| s.to_string()) + .collect(); + + for (i, line) in as_lines.iter_mut().enumerate() { + if line.trim() == "[Script Info]" { + line.push_str("\nScaledBorderAndShadow: yes") + } else if let Some(capture) = re.captures(line) { let start = capture.name("start").map_or(NaiveTime::default(), |s| { NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() }); @@ -749,29 +744,32 @@ fn fix_subtitle_length(raw: &mut Vec<u8>, max_length: NaiveTime) { NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() }); - if start > max_length { - continue; - } else if end > max_length { - new.push_str( - re.replace( + if end > max_length { + *line = re + .replace( line, format!( "Dialogue: {},{},", - format_naive_time(start), + format_naive_time(start.clone()), &length_as_string ), ) .to_string() - .as_str(), - ) - } else { - new.push_str(line) } - } else { - new.push_str(line) + entries.0.push((start, i)); + entries.1.push(i) } - new.push('\n') } - *raw = new.into_bytes() + entries.0.sort_by(|(a, _), (b, _)| a.cmp(b)); + for i in 0..entries.0.len() { + let (_, original_position) = entries.0[i]; + let new_position = entries.1[i]; + + if original_position != new_position { + as_lines.swap(original_position, new_position) + } + } + + *raw = as_lines.join("\n").into_bytes() } From 0beaa99bfd109eab82f07de25a90800d5a753e20 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:08:08 +0200 Subject: [PATCH 381/630] Update supported urls in README --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 64b2f00..a3cbc9a 100644 --- a/README.md +++ b/README.md @@ -108,11 +108,11 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any ### Download **Supported urls** -- Single episode +- Single episode (with [episode filtering](#episode-filtering)) ```shell $ crunchy download https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- Series +- Series (with [episode filtering](#episode-filtering)) ```shell $ crunchy download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` @@ -156,9 +156,11 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any ### Archive **Supported urls** -- Series - - Only series urls are supported, because episode urls are locked to a single audio language. +- Single episode (with [episode filtering](#episode-filtering)) + ```shell + $ crunchy archive https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` +- Series (with [episode filtering](#episode-filtering)) ```shell $ crunchy archive https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` From 0aa648b1a5239bf4338cd7aebcea33c886a4012e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 25 May 2023 18:53:56 +0200 Subject: [PATCH 382/630] Add basic search command --- Cargo.lock | 579 +++++++++-------------- Cargo.toml | 6 +- crunchy-cli-core/Cargo.lock | 572 +++++++++------------- crunchy-cli-core/Cargo.toml | 5 +- crunchy-cli-core/src/lib.rs | 5 + crunchy-cli-core/src/search/command.rs | 189 ++++++++ crunchy-cli-core/src/search/filter.rs | 56 +++ crunchy-cli-core/src/search/format.rs | 625 +++++++++++++++++++++++++ crunchy-cli-core/src/search/mod.rs | 5 + crunchy-cli-core/src/utils/parse.rs | 12 +- 10 files changed, 1352 insertions(+), 702 deletions(-) create mode 100644 crunchy-cli-core/src/search/command.rs create mode 100644 crunchy-cli-core/src/search/filter.rs create mode 100644 crunchy-cli-core/src/search/format.rs create mode 100644 crunchy-cli-core/src/search/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4635644..5fde41d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,13 +15,19 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -33,9 +39,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", @@ -82,9 +88,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-trait" @@ -94,7 +100,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -105,15 +111,19 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] -name = "base64" -version = "0.21.0" +name = "base64-serde" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" +dependencies = [ + "base64", + "serde", +] [[package]] name = "bitflags" @@ -132,9 +142,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytes" @@ -165,13 +175,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", "time 0.1.45", @@ -191,9 +201,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.4" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" dependencies = [ "clap_builder", "clap_derive", @@ -202,64 +212,54 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.4" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" dependencies = [ "anstream", "anstyle", "bitflags", "clap_lex", - "strsim 0.10.0", + "strsim", ] [[package]] name = "clap_complete" -version = "4.2.1" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a19591b2ab0e3c04b588a0e04ddde7b9eaa423646d1b4a8092879216bf47473" +checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "clap_mangen" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4237e29de9c6949982ba87d51709204504fb8ed2fd38232fcb1e5bf7d4ba48c8" +checksum = "8f2e32b579dae093c2424a8b7e2bea09c89da01e1ce5065eb2f0a6f1cc15cc1f" dependencies = [ "clap", "roff", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "colorchoice" version = "1.0.0" @@ -268,15 +268,15 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -286,7 +286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.20", + "time 0.3.21", "version_check", ] @@ -302,7 +302,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.3.20", + "time 0.3.21", "url", ] @@ -365,6 +365,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", + "serde_plain", "shlex", "sys-locale", "tempfile", @@ -374,9 +375,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75b403a64b4cc8ed9a96cb16588475a93cb5c8643cf960907a54ae6f6ac3f0d" +checksum = "510ab662065c5ff28678e66fc1ad243727044642f2dd02a5bbadc12aa2717779" dependencies = [ "aes", "async-trait", @@ -396,18 +397,18 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.23.0", + "webpki-roots 0.23.1", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91a9978a505750f606a1fcf1efd5970d20521310e886839f2ad95c31354e54e" +checksum = "2a1e71fd50850102f81e439c08ffcb69ae98b64d4eb292c359a33ac2253aaa91" dependencies = [ - "darling 0.14.4", + "darling", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -422,134 +423,57 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.5" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix", - "windows-sys 0.45.0", -] - -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", + "windows-sys 0.48.0", ] [[package]] name = "darling" -version = "0.10.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", + "darling_core", + "darling_macro", ] [[package]] name = "darling_core" -version = "0.10.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.9.3", - "syn 1.0.109", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "strsim", + "syn", ] [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ - "darling_core 0.10.2", + "darling_core", "quote", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "dash-mpd" -version = "0.7.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4937210c839d6f764b196afa05349927a8a2cc204ad613086e81defcddd4ade1" +checksum = "306d758462ad461116dfd0680c824691449974c6ab697464a3ccdad925dde7c0" dependencies = [ + "base64", + "base64-serde", "chrono", "fs-err", "iso8601", @@ -566,34 +490,35 @@ dependencies = [ [[package]] name = "derive_setters" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cf41b4580a37cca5ef2ada2cc43cf5d6be3983f4522e83010d67ab6925e84b" +checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" dependencies = [ - "darling 0.10.2", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "dirs" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -670,9 +595,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -753,9 +678,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -764,9 +689,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -874,9 +799,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", @@ -914,12 +839,11 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -949,6 +873,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -962,11 +896,12 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", @@ -993,9 +928,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -1037,9 +972,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1052,39 +987,27 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" - -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "m3u8-rs" -version = "5.0.3" +version = "5.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3b9f971e885044cb57330d3c89a4f3bd45fe97d01c93b3dcec57096efacffc" +checksum = "d39af8845edca961e3286dcbafeb9e6407d3df6a616ef086847162d46f438d75" dependencies = [ "chrono", "nom", @@ -1116,14 +1039,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1166,16 +1088,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -1203,15 +1115,15 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags", "cfg-if", @@ -1230,7 +1142,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1241,9 +1153,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ "cc", "libc", @@ -1252,10 +1164,16 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" @@ -1271,21 +1189,21 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -1318,9 +1236,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1356,9 +1274,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -1367,17 +1285,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64", "bytes", "cookie", "cookie_store", @@ -1439,9 +1357,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.37.14" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", @@ -1453,14 +1371,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] @@ -1469,7 +1387,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64", ] [[package]] @@ -1507,12 +1425,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "sct" version = "0.7.0" @@ -1525,9 +1437,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", @@ -1538,9 +1450,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -1548,22 +1460,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1577,6 +1489,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1591,30 +1512,30 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" dependencies = [ - "base64 0.13.1", + "base64", "chrono", "hex", "indexmap", "serde", "serde_json", "serde_with_macros", - "time 0.3.20", + "time 0.3.21", ] [[package]] name = "serde_with_macros" -version = "2.3.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1634,13 +1555,13 @@ dependencies = [ [[package]] name = "smart-default" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1665,12 +1586,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "strsim" version = "0.10.0" @@ -1679,20 +1594,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -1722,15 +1626,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.2.6" @@ -1758,7 +1653,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1774,9 +1669,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -1786,15 +1681,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -1816,9 +1711,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -1828,18 +1723,18 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1854,13 +1749,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] @@ -1877,9 +1771,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -1908,9 +1802,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -1935,9 +1829,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1962,12 +1856,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna 0.4.0", "percent-encoding", ] @@ -2013,9 +1907,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2023,24 +1917,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -2050,9 +1944,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2060,28 +1954,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2108,9 +2002,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" dependencies = [ "rustls-webpki", ] @@ -2131,15 +2025,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index bad4772..4273955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,14 +5,14 @@ version = "3.0.0-dev.13" edition = "2021" [dependencies] -tokio = { version = "1.27", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } [build-dependencies] chrono = "0.4" -clap = { version = "4.2", features = ["string"] } -clap_complete = "4.2" +clap = { version = "4.3", features = ["string"] } +clap_complete = "4.3" clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 3904323..c5b7300 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -15,13 +15,19 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -33,9 +39,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", @@ -82,9 +88,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-trait" @@ -94,7 +100,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -105,15 +111,19 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] -name = "base64" -version = "0.21.0" +name = "base64-serde" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" +dependencies = [ + "base64", + "serde", +] [[package]] name = "bitflags" @@ -132,9 +142,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytes" @@ -165,13 +175,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", "time 0.1.45", @@ -191,9 +201,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.4" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" +checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0" dependencies = [ "clap_builder", "clap_derive", @@ -202,44 +212,34 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.4" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" +checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab" dependencies = [ "anstream", "anstyle", "bitflags", "clap_lex", - "strsim 0.10.0", + "strsim", ] [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" @@ -249,15 +249,15 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -267,7 +267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.20", + "time 0.3.22", "version_check", ] @@ -283,7 +283,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.3.20", + "time 0.3.22", "url", ] @@ -343,9 +343,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75b403a64b4cc8ed9a96cb16588475a93cb5c8643cf960907a54ae6f6ac3f0d" +checksum = "510ab662065c5ff28678e66fc1ad243727044642f2dd02a5bbadc12aa2717779" dependencies = [ "aes", "async-trait", @@ -365,18 +365,18 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.23.0", + "webpki-roots 0.23.1", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91a9978a505750f606a1fcf1efd5970d20521310e886839f2ad95c31354e54e" +checksum = "2a1e71fd50850102f81e439c08ffcb69ae98b64d4eb292c359a33ac2253aaa91" dependencies = [ - "darling 0.14.4", + "darling", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -391,134 +391,57 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.5" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix", - "windows-sys 0.45.0", -] - -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", + "windows-sys 0.48.0", ] [[package]] name = "darling" -version = "0.10.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", + "darling_core", + "darling_macro", ] [[package]] name = "darling_core" -version = "0.10.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.9.3", - "syn 1.0.109", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "strsim", + "syn", ] [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ - "darling_core 0.10.2", + "darling_core", "quote", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "dash-mpd" -version = "0.7.3" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4937210c839d6f764b196afa05349927a8a2cc204ad613086e81defcddd4ade1" +checksum = "8b97b7a11cc6dcf0c803554cbd7f7d8dfef99c598e6a310403bb5699d9a94076" dependencies = [ + "base64", + "base64-serde", "chrono", "fs-err", "iso8601", @@ -535,34 +458,35 @@ dependencies = [ [[package]] name = "derive_setters" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cf41b4580a37cca5ef2ada2cc43cf5d6be3983f4522e83010d67ab6925e84b" +checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" dependencies = [ - "darling 0.10.2", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "dirs" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -639,9 +563,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -722,9 +646,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -733,9 +657,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -843,9 +767,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", @@ -869,9 +793,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -883,12 +807,11 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -918,6 +841,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -931,11 +864,12 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", @@ -962,9 +896,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -1006,9 +940,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1021,39 +955,27 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" - -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "m3u8-rs" -version = "5.0.3" +version = "5.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3b9f971e885044cb57330d3c89a4f3bd45fe97d01c93b3dcec57096efacffc" +checksum = "d39af8845edca961e3286dcbafeb9e6407d3df6a616ef086847162d46f438d75" dependencies = [ "chrono", "nom", @@ -1085,14 +1007,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1135,16 +1056,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -1172,15 +1083,15 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags", "cfg-if", @@ -1199,7 +1110,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1210,9 +1121,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ "cc", "libc", @@ -1221,10 +1132,16 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" @@ -1240,21 +1157,21 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1287,9 +1204,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1325,9 +1242,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -1336,17 +1253,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64", "bytes", "cookie", "cookie_store", @@ -1402,9 +1319,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.14" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", @@ -1416,14 +1333,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] @@ -1432,7 +1349,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64", ] [[package]] @@ -1470,12 +1387,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "sct" version = "0.7.0" @@ -1488,9 +1399,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", @@ -1501,9 +1412,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -1511,22 +1422,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1554,30 +1465,30 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" dependencies = [ - "base64 0.13.1", + "base64", "chrono", "hex", "indexmap", "serde", "serde_json", "serde_with_macros", - "time 0.3.20", + "time 0.3.22", ] [[package]] name = "serde_with_macros" -version = "2.3.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1597,13 +1508,13 @@ dependencies = [ [[package]] name = "smart-default" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1628,12 +1539,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "strsim" version = "0.10.0" @@ -1642,20 +1547,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -1674,24 +1568,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", + "windows-sys 0.48.0", ] [[package]] @@ -1721,7 +1607,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1737,9 +1623,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa", "serde", @@ -1749,15 +1635,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -1779,9 +1665,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -1791,18 +1677,18 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1817,13 +1703,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] @@ -1840,9 +1725,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -1871,9 +1756,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -1898,9 +1783,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1925,12 +1810,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna 0.4.0", "percent-encoding", ] @@ -1976,9 +1861,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1986,24 +1871,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -2013,9 +1898,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2023,28 +1908,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2071,9 +1956,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" dependencies = [ "rustls-webpki", ] @@ -2094,15 +1979,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 62f83ec..5cadd08 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.2", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.3.4", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.3.6", features = ["dash-stream"] } ctrlc = "3.2" dirs = "5.0" derive_setters = "0.1" @@ -23,10 +23,11 @@ reqwest = { version = "0.11", default-features = false, features = ["socks"] } sanitize-filename = "0.4" serde = "1.0" serde_json = "1.0" +serde_plain = "1.0" shlex = "1.1" tempfile = "3.5" terminal_size = "0.2" -tokio = { version = "1.27", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" [build-dependencies] diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 9ae8e83..4de5826 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -14,11 +14,13 @@ use std::{env, fs}; mod archive; mod download; mod login; +mod search; mod utils; pub use archive::Archive; pub use download::Download; pub use login::Login; +pub use search::Search; #[async_trait::async_trait(?Send)] trait Execute { @@ -81,6 +83,7 @@ enum Command { Archive(Archive), Download(Download), Login(Login), + Search(Search), } #[derive(Debug, Parser)] @@ -128,6 +131,7 @@ pub async fn cli_entrypoint() { pre_check_executor(login).await } } + Command::Search(search) => pre_check_executor(search).await, }; let ctx = match create_ctx(&mut cli).await { @@ -173,6 +177,7 @@ pub async fn cli_entrypoint() { Command::Archive(archive) => execute_executor(archive, ctx).await, Command::Download(download) => execute_executor(download, ctx).await, Command::Login(login) => execute_executor(login, ctx).await, + Command::Search(search) => execute_executor(search, ctx).await, }; } diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs new file mode 100644 index 0000000..442dea5 --- /dev/null +++ b/crunchy-cli-core/src/search/command.rs @@ -0,0 +1,189 @@ +use crate::search::filter::FilterOptions; +use crate::search::format::Format; +use crate::utils::context::Context; +use crate::utils::parse::{parse_url, UrlFilter}; +use crate::Execute; +use anyhow::{bail, Result}; +use crunchyroll_rs::common::StreamExt; +use crunchyroll_rs::search::QueryResults; +use crunchyroll_rs::{Episode, Locale, MediaCollection, MovieListing, MusicVideo, Series}; + +#[derive(Debug, clap::Parser)] +pub struct Search { + #[arg(help = "Audio languages to include")] + #[arg(long, default_values_t = vec![crate::utils::locale::system_locale()])] + audio: Vec<Locale>, + + #[arg(help = "Filter the locale/language of subtitles according to the value of `--audio`")] + #[arg(long, default_value_t = false)] + filter_subtitles: bool, + + #[arg(help = "Limit of search top search results")] + #[arg(long, default_value_t = 5)] + search_top_results_limit: u32, + #[arg(help = "Limit of search series results")] + #[arg(long, default_value_t = 0)] + search_series_limit: u32, + #[arg(help = "Limit of search movie listing results")] + #[arg(long, default_value_t = 0)] + search_movie_listing_limit: u32, + #[arg(help = "Limit of search episode results")] + #[arg(long, default_value_t = 0)] + search_episode_limit: u32, + #[arg(help = "Limit of search music results")] + #[arg(long, default_value_t = 0)] + search_music_limit: u32, + + /// Format of the output text. + /// + /// You can specify keywords in a specific pattern and they will get replaced in the output text. + /// The required pattern for this begins with `{{`, then the keyword, and closes with `}}` (e.g. `{{episode.title}}`). + /// For example, if you want to get the title of an episode, you can use `Title {{episode.title}}` and `{{episode.title}}` will be replaced with the episode title + /// + /// See the following list for all keywords and their meaning: + /// series.title โ†’ Series title + /// series.description โ†’ Series description + /// + /// season.title โ†’ Season title + /// season.description โ†’ Season description + /// season.number โ†’ Season number + /// + /// episode.title โ†’ Episode title + /// episode.description โ†’ Episode description + /// episode.locale โ†’ Episode locale/language + /// episode.number โ†’ Episode number + /// episode.sequence_number โ†’ Episode number. This number is unique unlike `episode.number` which sometimes can be duplicated + /// + /// movie_listing.title โ†’ Movie listing title + /// movie_listing.description โ†’ Movie listing description + /// + /// movie.title โ†’ Movie title + /// movie.description โ†’ Movie description + /// + /// music_video.title โ†’ Music video title + /// music_video.description โ†’ Music video description + /// + /// concert.title โ†’ Concert title + /// concert.description โ†’ Concert description + /// + /// stream.locale โ†’ Stream locale/language + /// stream.dash_url โ†’ Stream url in DASH format + /// stream.hls_url โ†’ Stream url in HLS format + /// + /// subtitle.locale โ†’ Subtitle locale/language + /// subtitle.url โ†’ Subtitle url + #[arg(short, long, verbatim_doc_comment)] + #[arg(default_value = "S{{season.number}}E{{episode.number}} - {{episode.title}}")] + output: String, + + input: String, +} + +#[async_trait::async_trait(?Send)] +impl Execute for Search { + async fn execute(self, ctx: Context) -> Result<()> { + let input = if crunchyroll_rs::parse::parse_url(&self.input).is_some() { + match parse_url(&ctx.crunchy, self.input.clone(), true).await { + Ok(ok) => vec![ok], + Err(e) => bail!("url {} could not be parsed: {}", self.input, e), + } + } else { + let mut output = vec![]; + + let query = resolve_query(&self, ctx.crunchy.query(&self.input)).await?; + output.extend(query.0.into_iter().map(|m| (m, UrlFilter::default()))); + output.extend( + query + .1 + .into_iter() + .map(|s| (s.into(), UrlFilter::default())), + ); + output.extend( + query + .2 + .into_iter() + .map(|m| (m.into(), UrlFilter::default())), + ); + output.extend( + query + .3 + .into_iter() + .map(|e| (e.into(), UrlFilter::default())), + ); + output.extend( + query + .4 + .into_iter() + .map(|m| (m.into(), UrlFilter::default())), + ); + + output + }; + + for (media_collection, url_filter) in input { + let filter_options = FilterOptions { + audio: self.audio.clone(), + filter_subtitles: self.filter_subtitles, + url_filter, + }; + + let format = Format::new(self.output.clone(), filter_options)?; + println!("{}", format.parse(media_collection).await?); + } + + Ok(()) + } +} + +macro_rules! resolve_query { + ($limit:expr, $vec:expr, $item:expr) => { + if $limit > 0 { + let mut item_results = $item; + while let Some(item) = item_results.next().await { + $vec.push(item?); + if $vec.len() >= $limit as usize { + break; + } + } + } + }; +} + +async fn resolve_query( + search: &Search, + query_results: QueryResults, +) -> Result<( + Vec<MediaCollection>, + Vec<Series>, + Vec<MovieListing>, + Vec<Episode>, + Vec<MusicVideo>, +)> { + let mut media_collection = vec![]; + let mut series = vec![]; + let mut movie_listing = vec![]; + let mut episode = vec![]; + let mut music_video = vec![]; + + resolve_query!( + search.search_top_results_limit, + media_collection, + query_results.top_results + ); + resolve_query!(search.search_series_limit, series, query_results.series); + resolve_query!( + search.search_movie_listing_limit, + movie_listing, + query_results.movie_listing + ); + resolve_query!(search.search_episode_limit, episode, query_results.episode); + resolve_query!(search.search_music_limit, music_video, query_results.music); + + Ok(( + media_collection, + series, + movie_listing, + episode, + music_video, + )) +} diff --git a/crunchy-cli-core/src/search/filter.rs b/crunchy-cli-core/src/search/filter.rs new file mode 100644 index 0000000..6a8e4ee --- /dev/null +++ b/crunchy-cli-core/src/search/filter.rs @@ -0,0 +1,56 @@ +use crate::utils::parse::UrlFilter; +use crunchyroll_rs::media::Subtitle; +use crunchyroll_rs::{Episode, Locale, MovieListing, Season, Series}; + +pub struct FilterOptions { + pub audio: Vec<Locale>, + pub filter_subtitles: bool, + pub url_filter: UrlFilter, +} + +impl FilterOptions { + pub fn check_series(&self, series: &Series) -> bool { + self.check_audio_language(&series.audio_locales) + } + + pub fn filter_seasons(&self, mut seasons: Vec<Season>) -> Vec<Season> { + seasons.retain(|s| { + self.check_audio_language(&s.audio_locales) + && self.url_filter.is_season_valid(s.season_number) + }); + seasons + } + + pub fn filter_episodes(&self, mut episodes: Vec<Episode>) -> Vec<Episode> { + episodes.retain(|e| { + self.check_audio_language(&vec![e.audio_locale.clone()]) + && self + .url_filter + .is_episode_valid(e.episode_number, e.season_number) + }); + episodes + } + + pub fn check_movie_listing(&self, movie_listing: &MovieListing) -> bool { + self.check_audio_language( + &movie_listing + .audio_locale + .clone() + .map_or(vec![], |a| vec![a.clone()]), + ) + } + + pub fn filter_subtitles(&self, mut subtitles: Vec<Subtitle>) -> Vec<Subtitle> { + if self.filter_subtitles { + subtitles.retain(|s| self.check_audio_language(&vec![s.locale.clone()])) + } + subtitles + } + + fn check_audio_language(&self, audio: &Vec<Locale>) -> bool { + if !self.audio.is_empty() { + return self.audio.iter().any(|a| audio.contains(a)); + } + true + } +} diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs new file mode 100644 index 0000000..946bfd5 --- /dev/null +++ b/crunchy-cli-core/src/search/format.rs @@ -0,0 +1,625 @@ +use crate::search::filter::FilterOptions; +use anyhow::{bail, Result}; +use crunchyroll_rs::media::{Stream, Subtitle}; +use crunchyroll_rs::{ + Concert, Episode, Locale, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series, +}; +use regex::Regex; +use serde::Serialize; +use serde_json::{Map, Value}; +use std::collections::HashMap; +use std::ops::Range; + +#[derive(Default, Serialize)] +struct FormatSeries { + pub title: String, + pub description: String, +} + +impl From<&Series> for FormatSeries { + fn from(value: &Series) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + } + } +} + +#[derive(Default, Serialize)] +struct FormatSeason { + pub title: String, + pub description: String, + pub number: u32, +} + +impl From<&Season> for FormatSeason { + fn from(value: &Season) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + number: value.season_number, + } + } +} + +#[derive(Default, Serialize)] +struct FormatEpisode { + pub title: String, + pub description: String, + pub locale: Locale, + pub number: u32, + pub sequence_number: f32, +} + +impl From<&Episode> for FormatEpisode { + fn from(value: &Episode) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + locale: value.audio_locale.clone(), + number: value.episode_number, + sequence_number: value.sequence_number, + } + } +} + +#[derive(Default, Serialize)] +struct FormatMovieListing { + pub title: String, + pub description: String, +} + +impl From<&MovieListing> for FormatMovieListing { + fn from(value: &MovieListing) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + } + } +} + +#[derive(Default, Serialize)] +struct FormatMovie { + pub title: String, + pub description: String, +} + +impl From<&Movie> for FormatMovie { + fn from(value: &Movie) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + } + } +} + +#[derive(Default, Serialize)] +struct FormatMusicVideo { + pub title: String, + pub description: String, +} + +impl From<&MusicVideo> for FormatMusicVideo { + fn from(value: &MusicVideo) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + } + } +} + +#[derive(Default, Serialize)] +struct FormatConcert { + pub title: String, + pub description: String, +} + +impl From<&Concert> for FormatConcert { + fn from(value: &Concert) -> Self { + Self { + title: value.title.clone(), + description: value.description.clone(), + } + } +} + +#[derive(Default, Serialize)] +struct FormatStream { + pub locale: Locale, + pub dash_url: String, + pub hls_url: String, +} + +impl From<&Stream> for FormatStream { + fn from(value: &Stream) -> Self { + let (dash_url, hls_url) = value.variants.get(&Locale::Custom("".to_string())).map_or( + ("".to_string(), "".to_string()), + |v| { + ( + v.adaptive_dash.clone().unwrap_or_default().url, + v.adaptive_hls.clone().unwrap_or_default().url, + ) + }, + ); + + Self { + locale: value.audio_locale.clone(), + dash_url, + hls_url, + } + } +} + +#[derive(Default, Serialize)] +struct FormatSubtitle { + pub locale: Locale, + pub url: String, +} + +impl From<&Subtitle> for FormatSubtitle { + fn from(value: &Subtitle) -> Self { + Self { + locale: value.locale.clone(), + url: value.url.clone(), + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash)] +enum Scope { + Series, + Season, + Episode, + MovieListing, + Movie, + MusicVideo, + Concert, + Stream, + Subtitle, +} + +pub struct Format { + pattern: Vec<(Range<usize>, Scope, String)>, + pattern_count: HashMap<Scope, u32>, + input: String, + filter_options: FilterOptions, +} + +impl Format { + pub fn new(input: String, filter_options: FilterOptions) -> Result<Self> { + let scope_regex = Regex::new(r"(?m)\{\{\s*(?P<scope>\w+)\.(?P<field>\w+)\s*}}").unwrap(); + let mut pattern = vec![]; + let mut pattern_count = HashMap::new(); + + let field_check = HashMap::from([ + ( + Scope::Series, + serde_json::to_value(FormatSeries::default()).unwrap(), + ), + ( + Scope::Season, + serde_json::to_value(FormatSeason::default()).unwrap(), + ), + ( + Scope::Episode, + serde_json::to_value(FormatEpisode::default()).unwrap(), + ), + ( + Scope::MovieListing, + serde_json::to_value(FormatMovieListing::default()).unwrap(), + ), + ( + Scope::Movie, + serde_json::to_value(FormatMovie::default()).unwrap(), + ), + ( + Scope::MusicVideo, + serde_json::to_value(FormatMusicVideo::default()).unwrap(), + ), + ( + Scope::Concert, + serde_json::to_value(FormatConcert::default()).unwrap(), + ), + ( + Scope::Stream, + serde_json::to_value(FormatStream::default()).unwrap(), + ), + ( + Scope::Subtitle, + serde_json::to_value(FormatSubtitle::default()).unwrap(), + ), + ]); + + for capture in scope_regex.captures_iter(&input) { + let full = capture.get(0).unwrap(); + let scope = capture.name("scope").unwrap().as_str(); + let field = capture.name("field").unwrap().as_str(); + + let format_pattern_scope = match scope { + "series" => Scope::Series, + "season" => Scope::Season, + "episode" => Scope::Episode, + "movie_listing" => Scope::MovieListing, + "movie" => Scope::Movie, + "music_video" => Scope::MusicVideo, + "concert" => Scope::Concert, + "stream" => Scope::Stream, + "subtitle" => Scope::Subtitle, + _ => bail!("'{}.{}' is not a valid keyword", scope, field), + }; + + if field_check + .get(&format_pattern_scope) + .unwrap() + .as_object() + .unwrap() + .get(field) + .is_none() + { + bail!("'{}.{}' is not a valid keyword", scope, field) + } + + pattern.push(( + full.start()..full.end(), + format_pattern_scope.clone(), + field.to_string(), + )); + *pattern_count.entry(format_pattern_scope).or_default() += 1 + } + + Ok(Self { + pattern, + pattern_count, + input, + filter_options, + }) + } + + fn check_pattern_count_empty(&self, scope: Scope) -> bool { + self.pattern_count.get(&scope).cloned().unwrap_or_default() == 0 + } + + pub async fn parse(&self, media_collection: MediaCollection) -> Result<String> { + match &media_collection { + MediaCollection::Series(_) + | MediaCollection::Season(_) + | MediaCollection::Episode(_) => self.parse_series(media_collection).await, + MediaCollection::MovieListing(_) | MediaCollection::Movie(_) => { + self.parse_movie_listing(media_collection).await + } + MediaCollection::MusicVideo(_) => self.parse_music_video(media_collection).await, + MediaCollection::Concert(_) => self.parse_concert(media_collection).await, + } + } + + async fn parse_series(&self, media_collection: MediaCollection) -> Result<String> { + let series_empty = self.check_pattern_count_empty(Scope::Series); + let season_empty = self.check_pattern_count_empty(Scope::Season); + let episode_empty = self.check_pattern_count_empty(Scope::Episode); + let stream_empty = self.check_pattern_count_empty(Scope::Stream) + && self.check_pattern_count_empty(Scope::Subtitle); + + let mut tree: Vec<(Season, Vec<(Episode, Vec<Stream>)>)> = vec![]; + + let series = if !series_empty { + let series = match &media_collection { + MediaCollection::Series(series) => series.clone(), + MediaCollection::Season(season) => season.series().await?, + MediaCollection::Episode(episode) => episode.series().await?, + _ => panic!(), + }; + if !self.filter_options.check_series(&series) { + return Ok("".to_string()); + } + series + } else { + Series::default() + }; + if !season_empty || !episode_empty || !stream_empty { + let tmp_seasons = match &media_collection { + MediaCollection::Series(series) => series.seasons().await?, + MediaCollection::Season(season) => vec![season.clone()], + MediaCollection::Episode(_) => vec![], + _ => panic!(), + }; + let mut seasons = vec![]; + for mut season in tmp_seasons { + if self + .filter_options + .audio + .iter() + .any(|a| season.audio_locales.contains(a)) + { + seasons.push(season.clone()) + } + seasons.extend(season.version(self.filter_options.audio.clone()).await?); + } + tree.extend( + self.filter_options + .filter_seasons(seasons) + .into_iter() + .map(|s| (s, vec![])), + ) + } else { + tree.push((Season::default(), vec![])) + } + if !episode_empty || !stream_empty { + match &media_collection { + MediaCollection::Episode(episode) => { + let mut episodes = vec![]; + if self.filter_options.audio.contains(&episode.audio_locale) { + episodes.push(episode.clone()) + } + episodes.extend( + episode + .clone() + .version(self.filter_options.audio.clone()) + .await?, + ); + + tree.push(( + Season::default(), + episodes + .into_iter() + .filter(|e| self.filter_options.audio.contains(&e.audio_locale)) + .map(|e| (e, vec![])) + .collect(), + )) + } + _ => { + for (season, episodes) in tree.iter_mut() { + episodes.extend( + self.filter_options + .filter_episodes(season.episodes().await?) + .into_iter() + .map(|e| (e, vec![])), + ) + } + } + }; + } else { + for (_, episodes) in tree.iter_mut() { + episodes.push((Episode::default(), vec![])) + } + } + if !stream_empty { + for (_, episodes) in tree.iter_mut() { + for (episode, streams) in episodes { + streams.push(episode.streams().await?) + } + } + } else { + for (_, episodes) in tree.iter_mut() { + for (_, streams) in episodes { + streams.push(Stream::default()) + } + } + } + + let mut output = vec![]; + let series_map = self.serializable_to_json_map(FormatSeries::from(&series)); + for (season, episodes) in tree { + let season_map = self.serializable_to_json_map(FormatSeason::from(&season)); + for (episode, streams) in episodes { + let episode_map = self.serializable_to_json_map(FormatEpisode::from(&episode)); + for mut stream in streams { + let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); + if stream.subtitles.is_empty() { + if !self.check_pattern_count_empty(Scope::Subtitle) { + continue; + } + stream + .subtitles + .insert(Locale::Custom("".to_string()), Subtitle::default()); + } + for subtitle in self + .filter_options + .filter_subtitles(stream.subtitles.into_values().collect()) + { + let subtitle_map = + self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); + let replace_map = HashMap::from([ + (Scope::Series, &series_map), + (Scope::Season, &season_map), + (Scope::Episode, &episode_map), + (Scope::Stream, &stream_map), + (Scope::Subtitle, &subtitle_map), + ]); + output.push(self.replace(replace_map)) + } + } + } + } + + Ok(output.join("\n")) + } + + async fn parse_movie_listing(&self, media_collection: MediaCollection) -> Result<String> { + let movie_listing_empty = self.check_pattern_count_empty(Scope::MovieListing); + let movie_empty = self.check_pattern_count_empty(Scope::Movie); + let stream_empty = self.check_pattern_count_empty(Scope::Stream); + + let mut tree: Vec<(Movie, Vec<Stream>)> = vec![]; + + let movie_listing = if !movie_listing_empty { + let movie_listing = match &media_collection { + MediaCollection::MovieListing(movie_listing) => movie_listing.clone(), + MediaCollection::Movie(movie) => movie.movie_listing().await?, + _ => panic!(), + }; + if !self.filter_options.check_movie_listing(&movie_listing) { + return Ok("".to_string()); + } + movie_listing + } else { + MovieListing::default() + }; + if !movie_empty || !stream_empty { + let movies = match &media_collection { + MediaCollection::MovieListing(movie_listing) => movie_listing.movies().await?, + MediaCollection::Movie(movie) => vec![movie.clone()], + _ => panic!(), + }; + tree.extend(movies.into_iter().map(|m| (m, vec![]))) + } + if !stream_empty { + for (movie, streams) in tree.iter_mut() { + streams.push(movie.streams().await?) + } + } else { + for (_, streams) in tree.iter_mut() { + streams.push(Stream::default()) + } + } + + let mut output = vec![]; + let movie_listing_map = + self.serializable_to_json_map(FormatMovieListing::from(&movie_listing)); + for (movie, streams) in tree { + let movie_map = self.serializable_to_json_map(FormatMovie::from(&movie)); + for mut stream in streams { + let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); + if stream.subtitles.is_empty() { + if !self.check_pattern_count_empty(Scope::Subtitle) { + continue; + } + stream + .subtitles + .insert(Locale::Custom("".to_string()), Subtitle::default()); + } + for subtitle in self + .filter_options + .filter_subtitles(stream.subtitles.into_values().collect()) + { + let subtitle_map = + self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); + let replace_map = HashMap::from([ + (Scope::MovieListing, &movie_listing_map), + (Scope::Movie, &movie_map), + (Scope::Stream, &stream_map), + (Scope::Subtitle, &subtitle_map), + ]); + output.push(self.replace(replace_map)) + } + } + } + + Ok(output.join("\n")) + } + + async fn parse_music_video(&self, media_collection: MediaCollection) -> Result<String> { + let music_video_empty = self.check_pattern_count_empty(Scope::MusicVideo); + let stream_empty = self.check_pattern_count_empty(Scope::Stream); + + let music_video = if !music_video_empty { + match &media_collection { + MediaCollection::MusicVideo(music_video) => music_video.clone(), + _ => panic!(), + } + } else { + MusicVideo::default() + }; + let mut stream = if !stream_empty { + match &media_collection { + MediaCollection::MusicVideo(music_video) => music_video.streams().await?, + _ => panic!(), + } + } else { + Stream::default() + }; + + let mut output = vec![]; + let music_video_map = self.serializable_to_json_map(FormatMusicVideo::from(&music_video)); + let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); + if stream.subtitles.is_empty() { + if !self.check_pattern_count_empty(Scope::Subtitle) { + return Ok("".to_string()); + } + stream + .subtitles + .insert(Locale::Custom("".to_string()), Subtitle::default()); + } + for subtitle in self + .filter_options + .filter_subtitles(stream.subtitles.into_values().collect()) + { + let subtitle_map = self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); + let replace_map = HashMap::from([ + (Scope::MusicVideo, &music_video_map), + (Scope::Stream, &stream_map), + (Scope::Subtitle, &subtitle_map), + ]); + output.push(self.replace(replace_map)) + } + + Ok(output.join("\n")) + } + + async fn parse_concert(&self, media_collection: MediaCollection) -> Result<String> { + let concert_empty = self.check_pattern_count_empty(Scope::Concert); + let stream_empty = self.check_pattern_count_empty(Scope::Stream); + + let concert = if !concert_empty { + match &media_collection { + MediaCollection::Concert(concert) => concert.clone(), + _ => panic!(), + } + } else { + Concert::default() + }; + let mut stream = if !stream_empty { + match &media_collection { + MediaCollection::Concert(concert) => concert.streams().await?, + _ => panic!(), + } + } else { + Stream::default() + }; + + let mut output = vec![]; + let concert_map = self.serializable_to_json_map(FormatConcert::from(&concert)); + let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); + if stream.subtitles.is_empty() { + if !self.check_pattern_count_empty(Scope::Subtitle) { + return Ok("".to_string()); + } + stream + .subtitles + .insert(Locale::Custom("".to_string()), Subtitle::default()); + } + for subtitle in self + .filter_options + .filter_subtitles(stream.subtitles.into_values().collect()) + { + let subtitle_map = self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); + let replace_map = HashMap::from([ + (Scope::MusicVideo, &concert_map), + (Scope::Stream, &stream_map), + (Scope::Subtitle, &subtitle_map), + ]); + output.push(self.replace(replace_map)) + } + + Ok(output.join("\n")) + } + + fn serializable_to_json_map<S: Serialize>(&self, s: S) -> Map<String, Value> { + serde_json::from_value(serde_json::to_value(s).unwrap()).unwrap() + } + + fn replace(&self, values: HashMap<Scope, &Map<String, Value>>) -> String { + let mut output = self.input.clone(); + let mut offset = 0; + for (range, scope, field) in &self.pattern { + let item = + serde_plain::to_string(values.get(scope).unwrap().get(field.as_str()).unwrap()) + .unwrap(); + let start = (range.start as i32 + offset) as usize; + let end = (range.end as i32 + offset) as usize; + output.replace_range(start..end, &item); + offset += item.len() as i32 - range.len() as i32; + } + + output + } +} diff --git a/crunchy-cli-core/src/search/mod.rs b/crunchy-cli-core/src/search/mod.rs new file mode 100644 index 0000000..839c844 --- /dev/null +++ b/crunchy-cli-core/src/search/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod filter; +mod format; + +pub use command::Search; diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index fbcba20..1bf9364 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -8,7 +8,7 @@ use regex::Regex; /// If a struct instance equals the [`Default::default()`] it's considered that no find is applied. /// If `from_*` is [`None`] they're set to [`u32::MIN`]. /// If `to_*` is [`None`] they're set to [`u32::MAX`]. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct InnerUrlFilter { from_episode: Option<u32>, to_episode: Option<u32>, @@ -16,11 +16,19 @@ pub struct InnerUrlFilter { to_season: Option<u32>, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct UrlFilter { inner: Vec<InnerUrlFilter>, } +impl Default for UrlFilter { + fn default() -> Self { + Self { + inner: vec![InnerUrlFilter::default()], + } + } +} + impl UrlFilter { pub fn is_season_valid(&self, season: u32) -> bool { self.inner.iter().any(|f| { From e9b4837f448f74628480e8991ffc5455c09d1163 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 19 Jun 2023 17:23:52 +0200 Subject: [PATCH 383/630] Clean up search a bit --- crunchy-cli-core/src/search/format.rs | 297 +++++++++++--------------- 1 file changed, 119 insertions(+), 178 deletions(-) diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 946bfd5..195b82b 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -178,6 +178,29 @@ enum Scope { Subtitle, } +macro_rules! must_match_if_true { + ($condition:expr => $media_collection:ident | $field:pat => $expr:expr) => { + if $condition { + match &$media_collection { + $field => Some($expr), + _ => panic!(), + } + } else { + None + } + }; +} + +macro_rules! self_and_versions { + ($var:expr => $audio:expr) => { + { + let mut items = vec![$var.clone()]; + items.extend($var.clone().version($audio).await?); + items + } + }; +} + pub struct Format { pattern: Vec<(Range<usize>, Scope, String)>, pattern_count: HashMap<Scope, u32>, @@ -191,44 +214,29 @@ impl Format { let mut pattern = vec![]; let mut pattern_count = HashMap::new(); - let field_check = HashMap::from([ - ( - Scope::Series, - serde_json::to_value(FormatSeries::default()).unwrap(), - ), - ( - Scope::Season, - serde_json::to_value(FormatSeason::default()).unwrap(), - ), - ( - Scope::Episode, - serde_json::to_value(FormatEpisode::default()).unwrap(), - ), - ( - Scope::MovieListing, - serde_json::to_value(FormatMovieListing::default()).unwrap(), - ), - ( - Scope::Movie, - serde_json::to_value(FormatMovie::default()).unwrap(), - ), - ( - Scope::MusicVideo, - serde_json::to_value(FormatMusicVideo::default()).unwrap(), - ), - ( - Scope::Concert, - serde_json::to_value(FormatConcert::default()).unwrap(), - ), - ( - Scope::Stream, - serde_json::to_value(FormatStream::default()).unwrap(), - ), - ( - Scope::Subtitle, - serde_json::to_value(FormatSubtitle::default()).unwrap(), - ), - ]); + macro_rules! generate_field_check { + ($($scope:expr => $struct_:ident)+) => { + HashMap::from([ + $( + ( + $scope, + serde_json::from_value::<Map<String, Value>>(serde_json::to_value($struct_::default()).unwrap()).unwrap() + ) + ),+ + ]) + }; + } + let field_check = generate_field_check!( + Scope::Series => FormatSeries + Scope::Season => FormatSeason + Scope::Episode => FormatEpisode + Scope::MovieListing => FormatMovieListing + Scope::Movie => FormatMovie + Scope::MusicVideo => FormatMusicVideo + Scope::Concert => FormatConcert + Scope::Stream => FormatStream + Scope::Subtitle => FormatSubtitle + ); for capture in scope_regex.captures_iter(&input) { let full = capture.get(0).unwrap(); @@ -251,8 +259,6 @@ impl Format { if field_check .get(&format_pattern_scope) .unwrap() - .as_object() - .unwrap() .get(field) .is_none() { @@ -323,16 +329,8 @@ impl Format { _ => panic!(), }; let mut seasons = vec![]; - for mut season in tmp_seasons { - if self - .filter_options - .audio - .iter() - .any(|a| season.audio_locales.contains(a)) - { - seasons.push(season.clone()) - } - seasons.extend(season.version(self.filter_options.audio.clone()).await?); + for season in tmp_seasons { + seasons.extend(self_and_versions!(season => self.filter_options.audio.clone())) } tree.extend( self.filter_options @@ -346,17 +344,7 @@ impl Format { if !episode_empty || !stream_empty { match &media_collection { MediaCollection::Episode(episode) => { - let mut episodes = vec![]; - if self.filter_options.audio.contains(&episode.audio_locale) { - episodes.push(episode.clone()) - } - episodes.extend( - episode - .clone() - .version(self.filter_options.audio.clone()) - .await?, - ); - + let episodes = self_and_versions!(episode => self.filter_options.audio.clone()); tree.push(( Season::default(), episodes @@ -402,31 +390,21 @@ impl Format { let season_map = self.serializable_to_json_map(FormatSeason::from(&season)); for (episode, streams) in episodes { let episode_map = self.serializable_to_json_map(FormatEpisode::from(&episode)); - for mut stream in streams { + for stream in streams { let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); - if stream.subtitles.is_empty() { - if !self.check_pattern_count_empty(Scope::Subtitle) { - continue; - } - stream - .subtitles - .insert(Locale::Custom("".to_string()), Subtitle::default()); - } - for subtitle in self - .filter_options - .filter_subtitles(stream.subtitles.into_values().collect()) - { - let subtitle_map = - self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); - let replace_map = HashMap::from([ - (Scope::Series, &series_map), - (Scope::Season, &season_map), - (Scope::Episode, &episode_map), - (Scope::Stream, &stream_map), - (Scope::Subtitle, &subtitle_map), - ]); - output.push(self.replace(replace_map)) - } + + output.push( + self.replace_all( + HashMap::from([ + (Scope::Series, &series_map), + (Scope::Season, &season_map), + (Scope::Episode, &episode_map), + (Scope::Stream, &stream_map), + ]), + stream, + ) + .unwrap_or_default(), + ) } } } @@ -477,30 +455,20 @@ impl Format { self.serializable_to_json_map(FormatMovieListing::from(&movie_listing)); for (movie, streams) in tree { let movie_map = self.serializable_to_json_map(FormatMovie::from(&movie)); - for mut stream in streams { + for stream in streams { let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); - if stream.subtitles.is_empty() { - if !self.check_pattern_count_empty(Scope::Subtitle) { - continue; - } - stream - .subtitles - .insert(Locale::Custom("".to_string()), Subtitle::default()); - } - for subtitle in self - .filter_options - .filter_subtitles(stream.subtitles.into_values().collect()) - { - let subtitle_map = - self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); - let replace_map = HashMap::from([ - (Scope::MovieListing, &movie_listing_map), - (Scope::Movie, &movie_map), - (Scope::Stream, &stream_map), - (Scope::Subtitle, &subtitle_map), - ]); - output.push(self.replace(replace_map)) - } + + output.push( + self.replace_all( + HashMap::from([ + (Scope::MovieListing, &movie_listing_map), + (Scope::Movie, &movie_map), + (Scope::Stream, &stream_map), + ]), + stream, + ) + .unwrap_or_default(), + ) } } @@ -511,100 +479,73 @@ impl Format { let music_video_empty = self.check_pattern_count_empty(Scope::MusicVideo); let stream_empty = self.check_pattern_count_empty(Scope::Stream); - let music_video = if !music_video_empty { - match &media_collection { - MediaCollection::MusicVideo(music_video) => music_video.clone(), - _ => panic!(), - } - } else { - MusicVideo::default() - }; - let mut stream = if !stream_empty { - match &media_collection { - MediaCollection::MusicVideo(music_video) => music_video.streams().await?, - _ => panic!(), - } - } else { - Stream::default() - }; + let music_video = must_match_if_true!(!music_video_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.clone()).unwrap_or_default(); + let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.streams().await?).unwrap_or_default(); - let mut output = vec![]; let music_video_map = self.serializable_to_json_map(FormatMusicVideo::from(&music_video)); let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); - if stream.subtitles.is_empty() { - if !self.check_pattern_count_empty(Scope::Subtitle) { - return Ok("".to_string()); - } - stream - .subtitles - .insert(Locale::Custom("".to_string()), Subtitle::default()); - } - for subtitle in self - .filter_options - .filter_subtitles(stream.subtitles.into_values().collect()) - { - let subtitle_map = self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); - let replace_map = HashMap::from([ - (Scope::MusicVideo, &music_video_map), - (Scope::Stream, &stream_map), - (Scope::Subtitle, &subtitle_map), - ]); - output.push(self.replace(replace_map)) - } - Ok(output.join("\n")) + let output = self + .replace_all( + HashMap::from([ + (Scope::MusicVideo, &music_video_map), + (Scope::Stream, &stream_map), + ]), + stream, + ) + .unwrap_or_default(); + Ok(output) } async fn parse_concert(&self, media_collection: MediaCollection) -> Result<String> { let concert_empty = self.check_pattern_count_empty(Scope::Concert); let stream_empty = self.check_pattern_count_empty(Scope::Stream); - let concert = if !concert_empty { - match &media_collection { - MediaCollection::Concert(concert) => concert.clone(), - _ => panic!(), - } - } else { - Concert::default() - }; - let mut stream = if !stream_empty { - match &media_collection { - MediaCollection::Concert(concert) => concert.streams().await?, - _ => panic!(), - } - } else { - Stream::default() - }; + let concert = must_match_if_true!(!concert_empty => media_collection|MediaCollection::Concert(concert) => concert.clone()).unwrap_or_default(); + let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::Concert(concert) => concert.streams().await?).unwrap_or_default(); - let mut output = vec![]; let concert_map = self.serializable_to_json_map(FormatConcert::from(&concert)); let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); + + let output = self + .replace_all( + HashMap::from([(Scope::Concert, &concert_map), (Scope::Stream, &stream_map)]), + stream, + ) + .unwrap_or_default(); + Ok(output) + } + + fn serializable_to_json_map<S: Serialize>(&self, s: S) -> Map<String, Value> { + serde_json::from_value(serde_json::to_value(s).unwrap()).unwrap() + } + + fn replace_all( + &self, + values: HashMap<Scope, &Map<String, Value>>, + mut stream: Stream, + ) -> Option<String> { if stream.subtitles.is_empty() { if !self.check_pattern_count_empty(Scope::Subtitle) { - return Ok("".to_string()); + return None; } stream .subtitles .insert(Locale::Custom("".to_string()), Subtitle::default()); } - for subtitle in self + let subtitles = self .filter_options - .filter_subtitles(stream.subtitles.into_values().collect()) - { + .filter_subtitles(stream.subtitles.into_values().collect()); + + let mut output = vec![]; + for subtitle in subtitles { let subtitle_map = self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); - let replace_map = HashMap::from([ - (Scope::MusicVideo, &concert_map), - (Scope::Stream, &stream_map), - (Scope::Subtitle, &subtitle_map), - ]); - output.push(self.replace(replace_map)) + let mut tmp_values = values.clone(); + tmp_values.insert(Scope::Subtitle, &subtitle_map); + output.push(self.replace(tmp_values)) } - Ok(output.join("\n")) - } - - fn serializable_to_json_map<S: Serialize>(&self, s: S) -> Map<String, Value> { - serde_json::from_value(serde_json::to_value(s).unwrap()).unwrap() + Some(output.join("\n")) } fn replace(&self, values: HashMap<Scope, &Map<String, Value>>) -> String { From 26ca3ca65c70edf0ea024113ac3b7f361e3be755 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 19 Jun 2023 17:25:57 +0200 Subject: [PATCH 384/630] Remove usage of deprecated functions --- crunchy-cli-core/src/utils/format.rs | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 2f546df..ca7df94 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -5,7 +5,7 @@ use anyhow::Result; use chrono::Duration; use crunchyroll_rs::media::{Resolution, Stream, Subtitle, VariantData}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; -use log::{debug, info, warn}; +use log::{debug, info}; use std::cmp::Ordering; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; @@ -126,24 +126,8 @@ impl SingleFormat { pub async fn stream(&self) -> Result<Stream> { let stream = match &self.source { - MediaCollection::Episode(e) => { - if let Ok(stream) = e.legacy_streams().await { - stream - } else { - let stream = e.streams().await?; - warn!("Failed to get stream via legacy endpoint"); - stream - } - } - MediaCollection::Movie(m) => { - if let Ok(stream) = m.legacy_streams().await { - stream - } else { - let stream = m.streams().await?; - warn!("Failed to get stream via legacy endpoint"); - stream - } - } + MediaCollection::Episode(e) => e.streams().await?, + MediaCollection::Movie(m) => m.streams().await?, MediaCollection::MusicVideo(mv) => mv.streams().await?, MediaCollection::Concert(c) => c.streams().await?, _ => unreachable!(), From 0b044ba27e3012370f5ecfc5954e99e41b0af066 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 19 Jun 2023 17:45:54 +0200 Subject: [PATCH 385/630] Check search scopes before replacing --- crunchy-cli-core/src/search/format.rs | 66 +++++++++++++++++++++------ 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 195b82b..6273895 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -165,7 +165,7 @@ impl From<&Subtitle> for FormatSubtitle { } } -#[derive(Clone, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] enum Scope { Series, Season, @@ -192,13 +192,11 @@ macro_rules! must_match_if_true { } macro_rules! self_and_versions { - ($var:expr => $audio:expr) => { - { - let mut items = vec![$var.clone()]; - items.extend($var.clone().version($audio).await?); - items - } - }; + ($var:expr => $audio:expr) => {{ + let mut items = vec![$var.clone()]; + items.extend($var.clone().version($audio).await?); + items + }}; } pub struct Format { @@ -281,20 +279,41 @@ impl Format { }) } - fn check_pattern_count_empty(&self, scope: Scope) -> bool { - self.pattern_count.get(&scope).cloned().unwrap_or_default() == 0 - } - pub async fn parse(&self, media_collection: MediaCollection) -> Result<String> { match &media_collection { MediaCollection::Series(_) | MediaCollection::Season(_) - | MediaCollection::Episode(_) => self.parse_series(media_collection).await, + | MediaCollection::Episode(_) => { + self.check_scopes(vec![ + Scope::Series, + Scope::Season, + Scope::Episode, + Scope::Stream, + Scope::Subtitle, + ])?; + + self.parse_series(media_collection).await + } MediaCollection::MovieListing(_) | MediaCollection::Movie(_) => { + self.check_scopes(vec![ + Scope::MovieListing, + Scope::Movie, + Scope::Stream, + Scope::Subtitle, + ])?; + self.parse_movie_listing(media_collection).await } - MediaCollection::MusicVideo(_) => self.parse_music_video(media_collection).await, - MediaCollection::Concert(_) => self.parse_concert(media_collection).await, + MediaCollection::MusicVideo(_) => { + self.check_scopes(vec![Scope::MusicVideo, Scope::Stream, Scope::Subtitle])?; + + self.parse_music_video(media_collection).await + } + MediaCollection::Concert(_) => { + self.check_scopes(vec![Scope::Concert, Scope::Stream, Scope::Subtitle])?; + + self.parse_concert(media_collection).await + } } } @@ -520,6 +539,23 @@ impl Format { serde_json::from_value(serde_json::to_value(s).unwrap()).unwrap() } + fn check_pattern_count_empty(&self, scope: Scope) -> bool { + self.pattern_count.get(&scope).cloned().unwrap_or_default() == 0 + } + + fn check_scopes(&self, available_scopes: Vec<Scope>) -> Result<()> { + for (_, scope, field) in self.pattern.iter() { + if !available_scopes.contains(scope) { + bail!( + "'{}.{}' is not a valid keyword", + format!("{:?}", scope).to_lowercase(), + field + ) + } + } + Ok(()) + } + fn replace_all( &self, values: HashMap<Scope, &Map<String, Value>>, From 4e4a4355f55a27f94827bfc93d6c8ac9ac2ecae1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:06:09 +0200 Subject: [PATCH 386/630] Add more search replace fields --- crunchy-cli-core/src/search/command.rs | 25 ++++++++++++++++-- crunchy-cli-core/src/search/format.rs | 36 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index 442dea5..ac82d8c 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -10,7 +10,10 @@ use crunchyroll_rs::{Episode, Locale, MediaCollection, MovieListing, MusicVideo, #[derive(Debug, clap::Parser)] pub struct Search { - #[arg(help = "Audio languages to include")] + #[arg(help = format!("Audio languages to include. \ + Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + #[arg(long_help = format!("Audio languages to include. \ + Available languages are:\n {}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(long, default_values_t = vec![crate::utils::locale::system_locale()])] audio: Vec<Locale>, @@ -41,37 +44,55 @@ pub struct Search { /// For example, if you want to get the title of an episode, you can use `Title {{episode.title}}` and `{{episode.title}}` will be replaced with the episode title /// /// See the following list for all keywords and their meaning: + /// series.id โ†’ Series id /// series.title โ†’ Series title /// series.description โ†’ Series description + /// series.release_year โ†’ Series release year /// + /// season.id โ†’ Season id /// season.title โ†’ Season title /// season.description โ†’ Season description /// season.number โ†’ Season number + /// season.episodes โ†’ Number of episodes the season has /// + /// episode.id โ†’ Episode id /// episode.title โ†’ Episode title /// episode.description โ†’ Episode description /// episode.locale โ†’ Episode locale/language /// episode.number โ†’ Episode number /// episode.sequence_number โ†’ Episode number. This number is unique unlike `episode.number` which sometimes can be duplicated + /// episode.duration โ†’ Episode duration in milliseconds + /// episode.air_date โ†’ Episode air date as unix timestamp + /// episode.premium_only โ†’ If the episode is only available with Crunchyroll premium /// + /// movie_listing.id โ†’ Movie listing id /// movie_listing.title โ†’ Movie listing title /// movie_listing.description โ†’ Movie listing description /// + /// movie.id โ†’ Movie id /// movie.title โ†’ Movie title /// movie.description โ†’ Movie description + /// movie.duration โ†’ Movie duration in milliseconds + /// movie.premium_only โ†’ If the movie is only available with Crunchyroll premium /// + /// music_video.id โ†’ Music video id /// music_video.title โ†’ Music video title /// music_video.description โ†’ Music video description + /// music_video.duration โ†’ Music video duration in milliseconds + /// music_video.premium_only โ†’ If the music video is only available with Crunchyroll premium /// + /// concert.id โ†’ Concert id /// concert.title โ†’ Concert title /// concert.description โ†’ Concert description + /// concert.duration โ†’ Concert duration in milliseconds + /// concert.premium_only โ†’ If the concert is only available with Crunchyroll premium /// /// stream.locale โ†’ Stream locale/language /// stream.dash_url โ†’ Stream url in DASH format /// stream.hls_url โ†’ Stream url in HLS format /// /// subtitle.locale โ†’ Subtitle locale/language - /// subtitle.url โ†’ Subtitle url + /// subtitle.url โ†’ Url to the subtitle #[arg(short, long, verbatim_doc_comment)] #[arg(default_value = "S{{season.number}}E{{episode.number}} - {{episode.title}}")] output: String, diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 6273895..cfdba3a 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -12,59 +12,76 @@ use std::ops::Range; #[derive(Default, Serialize)] struct FormatSeries { + pub id: String, pub title: String, pub description: String, + pub release_year: u32, } impl From<&Series> for FormatSeries { fn from(value: &Series) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), + release_year: value.series_launch_year.unwrap_or_default(), } } } #[derive(Default, Serialize)] struct FormatSeason { + pub id: String, pub title: String, pub description: String, pub number: u32, + pub episodes: u32, } impl From<&Season> for FormatSeason { fn from(value: &Season) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), number: value.season_number, + episodes: value.number_of_episodes, } } } #[derive(Default, Serialize)] struct FormatEpisode { + pub id: String, pub title: String, pub description: String, pub locale: Locale, pub number: u32, pub sequence_number: f32, + pub duration: i64, + pub air_date: i64, + pub premium_only: bool, } impl From<&Episode> for FormatEpisode { fn from(value: &Episode) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), locale: value.audio_locale.clone(), number: value.episode_number, sequence_number: value.sequence_number, + duration: value.duration.num_milliseconds(), + air_date: value.episode_air_date.timestamp(), + premium_only: value.is_premium_only, } } } #[derive(Default, Serialize)] struct FormatMovieListing { + pub id: String, pub title: String, pub description: String, } @@ -72,6 +89,7 @@ struct FormatMovieListing { impl From<&MovieListing> for FormatMovieListing { fn from(value: &MovieListing) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), } @@ -80,45 +98,63 @@ impl From<&MovieListing> for FormatMovieListing { #[derive(Default, Serialize)] struct FormatMovie { + pub id: String, pub title: String, pub description: String, + pub duration: i64, + pub premium_only: bool, } impl From<&Movie> for FormatMovie { fn from(value: &Movie) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), + duration: value.duration.num_milliseconds(), + premium_only: value.is_premium_only, } } } #[derive(Default, Serialize)] struct FormatMusicVideo { + pub id: String, pub title: String, pub description: String, + pub duration: i64, + pub premium_only: bool, } impl From<&MusicVideo> for FormatMusicVideo { fn from(value: &MusicVideo) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), + duration: value.duration.num_milliseconds(), + premium_only: value.is_premium_only, } } } #[derive(Default, Serialize)] struct FormatConcert { + pub id: String, pub title: String, pub description: String, + pub duration: i64, + pub premium_only: bool, } impl From<&Concert> for FormatConcert { fn from(value: &Concert) -> Self { Self { + id: value.id.clone(), title: value.title.clone(), description: value.description.clone(), + duration: value.duration.num_milliseconds(), + premium_only: value.is_premium_only, } } } From 0cd647fb14ec48da1cab70122770c53b527b50b7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:09:35 +0200 Subject: [PATCH 387/630] Add search documentation --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index a3cbc9a..44756e7 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,31 @@ $ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://w # Output file: '[S01E01] Secret of the Dragon Ball.mkv' ``` +### Search + +**Supported urls/input** +- Single episode (with [episode filtering](#episode-filtering)) + ```shell + $ crunchy search https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` +- Series (with [episode filtering](#episode-filtering)) + ```shell + $ crunchy search https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` +- Search input + ```shell + $ crunchy search "darling in the franxx" + ``` + +**Options** +- Audio + + Set the audio language to search via the `--audio` flag. Can be used multiple times. + ```shell + $ crunchy search --audio en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is your system locale. + ### Episode filtering Filters patterns can be used to download a specific range of episodes from a single series. From f7af9835263457ff5e69611fe2a1a597fbb0fb82 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:19:59 +0200 Subject: [PATCH 388/630] Remove search --filter-subtitles flag --- crunchy-cli-core/src/search/command.rs | 5 ----- crunchy-cli-core/src/search/filter.rs | 9 --------- crunchy-cli-core/src/search/format.rs | 5 +---- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index ac82d8c..e81ef8d 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -17,10 +17,6 @@ pub struct Search { #[arg(long, default_values_t = vec![crate::utils::locale::system_locale()])] audio: Vec<Locale>, - #[arg(help = "Filter the locale/language of subtitles according to the value of `--audio`")] - #[arg(long, default_value_t = false)] - filter_subtitles: bool, - #[arg(help = "Limit of search top search results")] #[arg(long, default_value_t = 5)] search_top_results_limit: u32, @@ -144,7 +140,6 @@ impl Execute for Search { for (media_collection, url_filter) in input { let filter_options = FilterOptions { audio: self.audio.clone(), - filter_subtitles: self.filter_subtitles, url_filter, }; diff --git a/crunchy-cli-core/src/search/filter.rs b/crunchy-cli-core/src/search/filter.rs index 6a8e4ee..0b31823 100644 --- a/crunchy-cli-core/src/search/filter.rs +++ b/crunchy-cli-core/src/search/filter.rs @@ -1,10 +1,8 @@ use crate::utils::parse::UrlFilter; -use crunchyroll_rs::media::Subtitle; use crunchyroll_rs::{Episode, Locale, MovieListing, Season, Series}; pub struct FilterOptions { pub audio: Vec<Locale>, - pub filter_subtitles: bool, pub url_filter: UrlFilter, } @@ -40,13 +38,6 @@ impl FilterOptions { ) } - pub fn filter_subtitles(&self, mut subtitles: Vec<Subtitle>) -> Vec<Subtitle> { - if self.filter_subtitles { - subtitles.retain(|s| self.check_audio_language(&vec![s.locale.clone()])) - } - subtitles - } - fn check_audio_language(&self, audio: &Vec<Locale>) -> bool { if !self.audio.is_empty() { return self.audio.iter().any(|a| audio.contains(a)); diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index cfdba3a..6769c4c 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -605,12 +605,9 @@ impl Format { .subtitles .insert(Locale::Custom("".to_string()), Subtitle::default()); } - let subtitles = self - .filter_options - .filter_subtitles(stream.subtitles.into_values().collect()); let mut output = vec![]; - for subtitle in subtitles { + for (_, subtitle) in stream.subtitles { let subtitle_map = self.serializable_to_json_map(FormatSubtitle::from(&subtitle)); let mut tmp_values = values.clone(); tmp_values.insert(Scope::Subtitle, &subtitle_map); From 2ebc76a0dfe952ae7e5828a5a30d1f46ccfb79a2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:33:31 +0200 Subject: [PATCH 389/630] Change search README documentation position --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 44756e7..ca87732 100644 --- a/README.md +++ b/README.md @@ -219,28 +219,6 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any $ crunchy archive --default-subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is none. - -### Output Template Options - -You can use various template options to change how the filename is processed. The following tags are available: - -- `{title}` โ†’ Title of the video -- `{series_name}` โ†’ Name of the series -- `{season_name}` โ†’ Name of the season -- `{audio}` โ†’ Audio language of the video -- `{resolution}` โ†’ Resolution of the video -- `{season_number}` โ†’ Number of the season -- `{episode_number}` โ†’ Number of the episode -- `{relative_episode_number}` โ†’ Number of the episode relative to its season -- `{series_id}` โ†’ ID of the series -- `{season_id}` โ†’ ID of the season -- `{episode_id}` โ†’ ID of the episode - -Example: -```shell -$ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball -# Output file: '[S01E01] Secret of the Dragon Ball.mkv' -``` ### Search @@ -267,6 +245,28 @@ $ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://w ``` Default is your system locale. +### Output Template Options + +You can use various template options to change how the filename is processed. The following tags are available: + +- `{title}` โ†’ Title of the video +- `{series_name}` โ†’ Name of the series +- `{season_name}` โ†’ Name of the season +- `{audio}` โ†’ Audio language of the video +- `{resolution}` โ†’ Resolution of the video +- `{season_number}` โ†’ Number of the season +- `{episode_number}` โ†’ Number of the episode +- `{relative_episode_number}` โ†’ Number of the episode relative to its season +- `{series_id}` โ†’ ID of the series +- `{season_id}` โ†’ ID of the season +- `{episode_id}` โ†’ ID of the episode + +Example: +```shell +$ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball +# Output file: '[S01E01] Secret of the Dragon Ball.mkv' +``` + ### Episode filtering Filters patterns can be used to download a specific range of episodes from a single series. From 0ef4980ab3a529d11da0add7180f94d1f4c5d6dc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:35:37 +0200 Subject: [PATCH 390/630] Update dependencies and version --- Cargo.lock | 119 ++++++++++++++++++------------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 82 ++++++++++++++----------- crunchy-cli-core/Cargo.toml | 8 +-- 4 files changed, 111 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fde41d..4a77d79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.2" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" dependencies = [ "clap_builder", "clap_derive", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.1" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" dependencies = [ "anstream", "anstyle", @@ -286,23 +286,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.21", + "time 0.3.22", "version_check", ] [[package]] name = "cookie_store" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" +checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" dependencies = [ "cookie", "idna 0.2.3", "log", "publicsuffix", "serde", + "serde_derive", "serde_json", - "time 0.3.21", + "time 0.3.22", "url", ] @@ -324,16 +325,16 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] [[package]] name = "crunchy-cli" -version = "3.0.0-dev.13" +version = "3.0.0-dev.14" dependencies = [ "chrono", "clap", @@ -345,7 +346,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.13" +version = "3.0.0-dev.14" dependencies = [ "anyhow", "async-trait", @@ -468,9 +469,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306d758462ad461116dfd0680c824691449974c6ab697464a3ccdad925dde7c0" +checksum = "8b97b7a11cc6dcf0c803554cbd7f7d8dfef99c598e6a310403bb5699d9a94076" dependencies = [ "base64", "base64-serde", @@ -825,9 +826,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -972,9 +973,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -999,9 +1000,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "m3u8-rs" @@ -1201,9 +1202,9 @@ checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1357,9 +1358,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", @@ -1371,9 +1372,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" dependencies = [ "log", "ring", @@ -1460,18 +1461,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -1480,9 +1481,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "itoa", "ryu", @@ -1523,7 +1524,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.21", + "time 0.3.22", ] [[package]] @@ -1615,15 +1616,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1669,9 +1671,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa", "serde", @@ -1749,9 +1751,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", @@ -1885,11 +1887,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -1907,9 +1908,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1917,9 +1918,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -1932,9 +1933,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -1944,9 +1945,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1954,9 +1955,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -1967,15 +1968,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 4273955..beddc9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.13" +version = "3.0.0-dev.14" edition = "2021" [dependencies] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index c5b7300..e794609 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.3" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" dependencies = [ "clap_builder", "clap_derive", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.3" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" dependencies = [ "anstream", "anstyle", @@ -273,15 +273,16 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" +checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" dependencies = [ "cookie", "idna 0.2.3", "log", "publicsuffix", "serde", + "serde_derive", "serde_json", "time 0.3.22", "url", @@ -305,16 +306,16 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.13" +version = "3.0.0-dev.14" dependencies = [ "anyhow", "async-trait", @@ -334,6 +335,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", + "serde_plain", "shlex", "sys-locale", "tempfile", @@ -940,9 +942,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1319,9 +1321,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", @@ -1333,9 +1335,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" dependencies = [ "log", "ring", @@ -1442,15 +1444,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1839,11 +1850,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -1861,9 +1871,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1871,9 +1881,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -1886,9 +1896,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -1898,9 +1908,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1908,9 +1918,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -1921,15 +1931,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5cadd08..10b69fe 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.13" +version = "3.0.0-dev.14" edition = "2021" [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = { version = "4.2", features = ["derive", "string"] } +clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { version = "0.3.6", features = ["dash-stream"] } -ctrlc = "3.2" +ctrlc = "3.4" dirs = "5.0" derive_setters = "0.1" fs2 = "0.4" @@ -25,7 +25,7 @@ serde = "1.0" serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" -tempfile = "3.5" +tempfile = "3.6" terminal_size = "0.2" tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" From 5b8a4b9969715ae4b60121300aea354ac13deec5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:53:12 +0200 Subject: [PATCH 391/630] Add simple search command description --- crunchy-cli-core/src/archive/command.rs | 1 - crunchy-cli-core/src/search/command.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 9af14fb..bb2f30d 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -22,7 +22,6 @@ use std::path::PathBuf; #[derive(Clone, Debug, clap::Parser)] #[clap(about = "Archive a video")] #[command(arg_required_else_help(true))] -#[command()] pub struct Archive { #[arg(help = format!("Audio languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index e81ef8d..0159c05 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -9,6 +9,8 @@ use crunchyroll_rs::search::QueryResults; use crunchyroll_rs::{Episode, Locale, MediaCollection, MovieListing, MusicVideo, Series}; #[derive(Debug, clap::Parser)] +#[clap(about = "Search in videos")] +#[command(arg_required_else_help(true))] pub struct Search { #[arg(help = format!("Audio languages to include. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] From f4682e0f290406cb938b3db8f355396dfb9c3bd8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 20 Jun 2023 00:53:19 +0200 Subject: [PATCH 392/630] Add search command to man pages --- build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/build.rs b/build.rs index 1e4d71f..7056c67 100644 --- a/build.rs +++ b/build.rs @@ -100,6 +100,7 @@ fn generate_manpages(out_dir: PathBuf) -> std::io::Result<()> { generate_command_manpage(crunchy_cli_core::Archive::command(), &out_dir, "archive")?; generate_command_manpage(crunchy_cli_core::Download::command(), &out_dir, "download")?; generate_command_manpage(crunchy_cli_core::Login::command(), &out_dir, "login")?; + generate_command_manpage(crunchy_cli_core::Search::command(), &out_dir, "search")?; Ok(()) } From d75c04fbb6c93d0ed6dd4ea85f383b774e956532 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 23 Jun 2023 15:28:09 +0200 Subject: [PATCH 393/630] Download all seasons if season number is duplicated --- crunchy-cli-core/src/archive/filter.rs | 20 +++++++-- crunchy-cli-core/src/utils/format.rs | 58 ++++++++++++++++++++------ 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 514b540..b181ae6 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -18,6 +18,7 @@ pub(crate) struct ArchiveFilter { archive: Archive, season_episode_count: HashMap<u32, Vec<String>>, season_subtitles_missing: Vec<u32>, + season_sorting: Vec<String>, visited: Visited, } @@ -28,6 +29,7 @@ impl ArchiveFilter { archive, season_episode_count: HashMap::new(), season_subtitles_missing: vec![], + season_sorting: vec![], visited: Visited::None, } } @@ -129,6 +131,7 @@ impl Filter for ArchiveFilter { let mut episodes = vec![]; for season in seasons { + self.season_sorting.push(season.id.clone()); let season_locale = season .audio_locales .get(0) @@ -296,15 +299,24 @@ impl Filter for ArchiveFilter { let mut single_format_collection = SingleFormatCollection::new(); - let mut sorted: BTreeMap<(u32, String), Self::T> = BTreeMap::new(); + let mut pre_sorted: BTreeMap<(String, String), Self::T> = BTreeMap::new(); for data in flatten_input { - sorted - .entry((data.season_number, data.sequence_number.to_string())) + pre_sorted + .entry((data.season_id.clone(), data.sequence_number.to_string())) .or_insert(vec![]) .push(data) } - for data in sorted.into_values() { + let mut sorted: Vec<((String, String), Self::T)> = pre_sorted.into_iter().collect(); + sorted.sort_by(|((a, _), _), ((b, _), _)| { + self.season_sorting + .iter() + .position(|p| p == a) + .unwrap() + .cmp(&self.season_sorting.iter().position(|p| p == b).unwrap()) + }); + + for (_, data) in sorted { single_format_collection.add_single_formats(data) } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index ca7df94..c67386d 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -173,8 +173,42 @@ impl PartialEq for SingleFormatCollectionEpisodeKey { } impl Eq for SingleFormatCollectionEpisodeKey {} +struct SingleFormatCollectionSeasonKey((u32, String)); + +impl PartialOrd for SingleFormatCollectionSeasonKey { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + let mut cmp = self.0 .0.partial_cmp(&other.0 .0); + if let Some(ordering) = cmp { + if matches!(ordering, Ordering::Equal) && self.0 .1 != other.0 .1 { + // first come first serve + cmp = Some(Ordering::Greater) + } + } + cmp + } +} +impl Ord for SingleFormatCollectionSeasonKey { + fn cmp(&self, other: &Self) -> Ordering { + let mut cmp = self.0 .0.cmp(&other.0 .0); + if matches!(cmp, Ordering::Equal) && self.0 .1 != other.0 .1 { + // first come first serve + cmp = Ordering::Greater + } + cmp + } +} +impl PartialEq for SingleFormatCollectionSeasonKey { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} +impl Eq for SingleFormatCollectionSeasonKey {} + pub struct SingleFormatCollection( - BTreeMap<u32, BTreeMap<SingleFormatCollectionEpisodeKey, Vec<SingleFormat>>>, + BTreeMap< + SingleFormatCollectionSeasonKey, + BTreeMap<SingleFormatCollectionEpisodeKey, Vec<SingleFormat>>, + >, ); impl SingleFormatCollection { @@ -189,7 +223,10 @@ impl SingleFormatCollection { pub fn add_single_formats(&mut self, single_formats: Vec<SingleFormat>) { let format = single_formats.first().unwrap(); self.0 - .entry(format.season_number) + .entry(SingleFormatCollectionSeasonKey(( + format.season_number, + format.season_id.clone(), + ))) .or_insert(BTreeMap::new()) .insert( SingleFormatCollectionEpisodeKey(format.sequence_number), @@ -199,18 +236,13 @@ impl SingleFormatCollection { pub fn full_visual_output(&self) { debug!("Series has {} seasons", self.0.len()); - for (season_number, episodes) in &self.0 { + for (season_key, episodes) in &self.0 { + let first_episode = episodes.first_key_value().unwrap().1.first().unwrap(); info!( - "{} Season {}", - episodes - .first_key_value() - .unwrap() - .1 - .first() - .unwrap() - .series_name - .clone(), - season_number + "{} Season {} ({})", + first_episode.series_name.clone(), + season_key.0 .0, + first_episode.season_title.clone(), ); for (i, (_, formats)) in episodes.iter().enumerate() { let format = formats.first().unwrap(); From fc44b8af8a08df8a87fcac08fc8ccf07c59468e9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 23 Jun 2023 17:19:16 +0200 Subject: [PATCH 394/630] Add option to select seasons when season number is duplicated (#199) --- Cargo.lock | 28 +++--- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/archive/command.rs | 6 +- crunchy-cli-core/src/archive/filter.rs | 46 +++++++++- crunchy-cli-core/src/download/command.rs | 6 +- crunchy-cli-core/src/download/filter.rs | 90 ++++++++++++------- crunchy-cli-core/src/lib.rs | 4 + .../src/utils/interactive_select.rs | 73 +++++++++++++++ crunchy-cli-core/src/utils/log.rs | 23 ++++- crunchy-cli-core/src/utils/mod.rs | 1 + 10 files changed, 227 insertions(+), 52 deletions(-) create mode 100644 crunchy-cli-core/src/utils/interactive_select.rs diff --git a/Cargo.lock b/Cargo.lock index 4a77d79..dedc0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,6 +355,7 @@ dependencies = [ "crunchyroll-rs", "ctrlc", "derive_setters", + "dialoguer", "dirs", "fs2", "indicatif", @@ -370,7 +371,6 @@ dependencies = [ "shlex", "sys-locale", "tempfile", - "terminal_size", "tokio", ] @@ -501,6 +501,16 @@ dependencies = [ "syn", ] +[[package]] +name = "dialoguer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +dependencies = [ + "console", + "shell-words", +] + [[package]] name = "dirs" version = "5.0.1" @@ -1539,6 +1549,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.1.0" @@ -1628,16 +1644,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "terminal_size" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "thiserror" version = "1.0.40" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 10b69fe..cf2b7c3 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -11,6 +11,7 @@ clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { version = "0.3.6", features = ["dash-stream"] } ctrlc = "3.4" +dialoguer = { version = "0.10", default-features = false } dirs = "5.0" derive_setters = "0.1" fs2 = "0.4" @@ -26,7 +27,6 @@ serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" tempfile = "3.6" -terminal_size = "0.2" tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index bb2f30d..9a2ee21 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -98,6 +98,10 @@ pub struct Archive { #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, + #[arg(help = "Skip any interactive input")] + #[arg(short, long, default_value_t = false)] + pub(crate) yes: bool, + #[arg(help = "Crunchyroll series url(s)")] pub(crate) urls: Vec<String>, } @@ -149,7 +153,7 @@ impl Execute for Archive { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = ArchiveFilter::new(url_filter, self.clone()) + let single_format_collection = ArchiveFilter::new(url_filter, self.clone(), !self.yes) .visit(media_collection) .await?; diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index b181ae6..d6593e5 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -1,10 +1,11 @@ use crate::archive::command::Archive; use crate::utils::filter::{real_dedup_vec, Filter}; use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; +use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; use crate::utils::parse::UrlFilter; use anyhow::Result; use crunchyroll_rs::{Concert, Episode, Locale, Movie, MovieListing, MusicVideo, Season, Series}; -use log::warn; +use log::{info, warn}; use std::collections::{BTreeMap, HashMap}; enum Visited { @@ -16,6 +17,7 @@ enum Visited { pub(crate) struct ArchiveFilter { url_filter: UrlFilter, archive: Archive, + interactive_input: bool, season_episode_count: HashMap<u32, Vec<String>>, season_subtitles_missing: Vec<u32>, season_sorting: Vec<String>, @@ -23,10 +25,11 @@ pub(crate) struct ArchiveFilter { } impl ArchiveFilter { - pub(crate) fn new(url_filter: UrlFilter, archive: Archive) -> Self { + pub(crate) fn new(url_filter: UrlFilter, archive: Archive, interactive_input: bool) -> Self { Self { url_filter, archive, + interactive_input, season_episode_count: HashMap::new(), season_subtitles_missing: vec![], season_sorting: vec![], @@ -71,7 +74,44 @@ impl Filter for ArchiveFilter { } self.visited = Visited::Series } - Ok(series.seasons().await?) + + let mut seasons = series.seasons().await?; + let mut remove_ids = vec![]; + for season in seasons.iter_mut() { + if !self.url_filter.is_season_valid(season.season_number) + && !season + .audio_locales + .iter() + .any(|l| self.archive.audio.contains(l)) + && !season + .available_versions() + .await? + .iter() + .any(|l| self.archive.audio.contains(l)) + { + remove_ids.push(season.id.clone()); + } + } + + seasons.retain(|s| !remove_ids.contains(&s.id)); + + let duplicated_seasons = get_duplicated_seasons(&seasons); + if duplicated_seasons.len() > 0 { + if self.interactive_input { + check_for_duplicated_seasons(&mut seasons); + } else { + info!( + "Found duplicated seasons: {}", + duplicated_seasons + .iter() + .map(|d| d.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + } + + Ok(seasons) } async fn visit_season(&mut self, mut season: Season) -> Result<Vec<Episode>> { diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 644e4e5..0167f49 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -73,6 +73,10 @@ pub struct Download { #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, + #[arg(help = "Skip any interactive input")] + #[arg(short, long, default_value_t = false)] + pub(crate) yes: bool, + #[arg(help = "Url(s) to Crunchyroll episodes or series")] pub(crate) urls: Vec<String>, } @@ -119,7 +123,7 @@ impl Execute for Download { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = DownloadFilter::new(url_filter, self.clone()) + let single_format_collection = DownloadFilter::new(url_filter, self.clone(), !self.yes) .visit(media_collection) .await?; diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index f6a4a2e..b04aef8 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -1,24 +1,27 @@ use crate::download::Download; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; +use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; use crate::utils::parse::UrlFilter; use anyhow::{bail, Result}; use crunchyroll_rs::{Concert, Episode, Movie, MovieListing, MusicVideo, Season, Series}; -use log::{error, warn}; +use log::{error, info, warn}; use std::collections::HashMap; pub(crate) struct DownloadFilter { url_filter: UrlFilter, download: Download, + interactive_input: bool, season_episode_count: HashMap<u32, Vec<String>>, season_subtitles_missing: Vec<u32>, } impl DownloadFilter { - pub(crate) fn new(url_filter: UrlFilter, download: Download) -> Self { + pub(crate) fn new(url_filter: UrlFilter, download: Download, interactive_input: bool) -> Self { Self { url_filter, download, + interactive_input, season_episode_count: HashMap::new(), season_subtitles_missing: vec![], } @@ -43,42 +46,61 @@ impl Filter for DownloadFilter { } } - let seasons = series.seasons().await?; + let mut seasons = vec![]; + for mut season in series.seasons().await? { + if !self.url_filter.is_season_valid(season.season_number) { + continue; + } + + if !season + .audio_locales + .iter() + .any(|l| l == &self.download.audio) + { + if season + .available_versions() + .await? + .iter() + .any(|l| l == &self.download.audio) + { + season = season + .version(vec![self.download.audio.clone()]) + .await? + .remove(0) + } else { + error!( + "Season {} - '{}' is not available with {} audio", + season.season_number, + season.title, + self.download.audio.clone(), + ); + continue; + } + } + + seasons.push(season) + } + + let duplicated_seasons = get_duplicated_seasons(&seasons); + if duplicated_seasons.len() > 0 { + if self.interactive_input { + check_for_duplicated_seasons(&mut seasons); + } else { + info!( + "Found duplicated seasons: {}", + duplicated_seasons + .iter() + .map(|d| d.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + } Ok(seasons) } - async fn visit_season(&mut self, mut season: Season) -> Result<Vec<Episode>> { - if !self.url_filter.is_season_valid(season.season_number) { - return Ok(vec![]); - } - - if !season - .audio_locales - .iter() - .any(|l| l == &self.download.audio) - { - if season - .available_versions() - .await? - .iter() - .any(|l| l == &self.download.audio) - { - season = season - .version(vec![self.download.audio.clone()]) - .await? - .remove(0) - } else { - error!( - "Season {} - '{}' is not available with {} audio", - season.season_number, - season.title, - self.download.audio.clone(), - ); - return Ok(vec![]); - } - } - + async fn visit_season(&mut self, season: Season) -> Result<Vec<Episode>> { let mut episodes = season.episodes().await?; if Format::has_relative_episodes_fmt(&self.download.output) { diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 4de5826..7cb68ce 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -18,6 +18,7 @@ mod search; mod utils; pub use archive::Archive; +use dialoguer::console::Term; pub use download::Download; pub use login::Login; pub use search::Search; @@ -168,6 +169,9 @@ pub async fn cli_entrypoint() { } } } + // when pressing ctrl-c while interactively choosing seasons the cursor stays hidden, this + // line shows it again + let _ = Term::stdout().show_cursor(); std::process::exit(1) }) .unwrap(); diff --git a/crunchy-cli-core/src/utils/interactive_select.rs b/crunchy-cli-core/src/utils/interactive_select.rs new file mode 100644 index 0000000..85116e6 --- /dev/null +++ b/crunchy-cli-core/src/utils/interactive_select.rs @@ -0,0 +1,73 @@ +use crate::utils::log::progress_pause; +use crunchyroll_rs::Season; +use dialoguer::console::Term; +use dialoguer::MultiSelect; +use std::collections::BTreeMap; + +pub fn get_duplicated_seasons(seasons: &Vec<Season>) -> Vec<u32> { + let mut season_number_counter = BTreeMap::<u32, u32>::new(); + for season in seasons { + season_number_counter + .entry(season.season_number) + .and_modify(|c| *c += 1) + .or_default(); + } + season_number_counter + .into_iter() + .filter_map(|(k, v)| if v > 0 { Some(k) } else { None }) + .collect() +} + +pub fn check_for_duplicated_seasons(seasons: &mut Vec<Season>) { + let mut as_map = BTreeMap::new(); + for season in seasons.iter() { + as_map + .entry(season.season_number) + .or_insert(vec![]) + .push(season) + } + + let duplicates: Vec<&Season> = as_map + .into_values() + .filter(|s| s.len() > 1) + .flatten() + .collect(); + progress_pause!(); + let _ = Term::stdout().clear_line(); + let keep = select( + "Duplicated seasons were found. Select the one you want to download (space to select/deselect; enter to continue)", + duplicates + .iter() + .map(|s| format!("Season {} ({})", s.season_number, s.title)) + .collect(), + ); + progress_pause!(); + + let mut remove_ids = vec![]; + for (i, duplicate) in duplicates.into_iter().enumerate() { + if !keep.contains(&i) { + remove_ids.push(duplicate.id.clone()) + } + } + + seasons.retain(|s| !remove_ids.contains(&s.id)); +} + +pub fn select(prompt: &str, input: Vec<String>) -> Vec<usize> { + if input.is_empty() { + return vec![]; + } + + let def: Vec<bool> = (0..input.len()).map(|_| true).collect(); + + let selection = MultiSelect::new() + .with_prompt(prompt) + .items(&input[..]) + .defaults(&def[..]) + .clear(false) + .report(false) + .interact_on(&Term::stdout()) + .unwrap_or_default(); + + selection +} diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs index 37bc5a9..c74b73d 100644 --- a/crunchy-cli-core/src/utils/log.rs +++ b/crunchy-cli-core/src/utils/log.rs @@ -1,4 +1,4 @@ -use indicatif::{ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; use log::{ info, set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, SetLoggerError, @@ -37,6 +37,15 @@ macro_rules! progress { } pub(crate) use progress; +macro_rules! progress_pause { + () => { + { + log::info!(target: "progress_pause", "") + } + } +} +pub(crate) use progress_pause; + macro_rules! tab_info { ($($arg:tt)+) => { if log::max_level() == log::LevelFilter::Debug { @@ -62,6 +71,7 @@ impl Log for CliLogger { fn log(&self, record: &Record) { if !self.enabled(record.metadata()) || (record.target() != "progress" + && record.target() != "progress_pause" && record.target() != "progress_end" && !record.target().starts_with("crunchy_cli")) { @@ -75,6 +85,16 @@ impl Log for CliLogger { match record.target() { "progress" => self.progress(record, false), + "progress_pause" => { + let progress = self.progress.lock().unwrap(); + if let Some(p) = &*progress { + p.set_draw_target(if p.is_hidden() { + ProgressDrawTarget::stdout() + } else { + ProgressDrawTarget::hidden() + }) + } + } "progress_end" => self.progress(record, true), _ => { if self.progress.lock().unwrap().is_some() { @@ -158,6 +178,7 @@ impl CliLogger { .unwrap() .tick_strings(&["โ€”", "\\", "|", "/", finish_str]), ); + pb.set_draw_target(ProgressDrawTarget::stdout()); pb.enable_steady_tick(Duration::from_millis(200)); pb.set_message(msg); *progress = Some(pb) diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index c991cd8..d46cc33 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -4,6 +4,7 @@ pub mod download; pub mod ffmpeg; pub mod filter; pub mod format; +pub mod interactive_select; pub mod locale; pub mod log; pub mod os; From 75b6e7b4523444cb3efdd2f400de1db661859992 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 24 Jun 2023 12:57:22 +0200 Subject: [PATCH 395/630] Add long flag options to -v and -q (--verbose and --quiet) --- crunchy-cli-core/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 7cb68ce..5aef69a 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -90,27 +90,27 @@ enum Command { #[derive(Debug, Parser)] struct Verbosity { #[arg(help = "Verbose output")] - #[arg(short)] - v: bool, + #[arg(short, long)] + verbose: bool, #[arg(help = "Quiet output. Does not print anything unless it's a error")] #[arg( long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout" )] - #[arg(short)] - q: bool, + #[arg(short, long)] + quiet: bool, } pub async fn cli_entrypoint() { let mut cli: Cli = Cli::parse(); if let Some(verbosity) = &cli.verbosity { - if verbosity.v as u8 + verbosity.q as u8 > 1 { + if verbosity.verbose as u8 + verbosity.quiet as u8 > 1 { eprintln!("Output cannot be verbose ('-v') and quiet ('-q') at the same time"); std::process::exit(1) - } else if verbosity.v { + } else if verbosity.verbose { CliLogger::init(LevelFilter::Debug).unwrap() - } else if verbosity.q { + } else if verbosity.quiet { CliLogger::init(LevelFilter::Error).unwrap() } } else { From 618d2206a2c40d1ddf5bea4c1cfa8f5894560c2f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 24 Jun 2023 13:05:30 +0200 Subject: [PATCH 396/630] Hide interactive output when -q flag is set --- crunchy-cli-core/src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 5aef69a..982aa17 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -120,8 +120,20 @@ pub async fn cli_entrypoint() { debug!("cli input: {:?}", cli); match &mut cli.command { - Command::Archive(archive) => pre_check_executor(archive).await, - Command::Download(download) => pre_check_executor(download).await, + Command::Archive(archive) => { + // prevent interactive select to be shown when output should be quiet + if cli.verbosity.is_some() && cli.verbosity.as_ref().unwrap().quiet { + archive.yes = true; + } + pre_check_executor(archive).await + } + Command::Download(download) => { + // prevent interactive select to be shown when output should be quiet + if cli.verbosity.is_some() && cli.verbosity.as_ref().unwrap().quiet { + download.yes = true; + } + pre_check_executor(download).await + } Command::Login(login) => { if login.remove { if let Some(session_file) = login::session_file_path() { From 0234d46bf9377ce63311fba00383d1e9dfa86741 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 25 Jun 2023 17:47:40 +0200 Subject: [PATCH 397/630] Fix duplicated download with archive --- crunchy-cli-core/src/archive/filter.rs | 89 +++++++++++++++++++------- crunchy-cli-core/src/utils/format.rs | 6 ++ 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index d6593e5..38fed32 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -79,15 +79,15 @@ impl Filter for ArchiveFilter { let mut remove_ids = vec![]; for season in seasons.iter_mut() { if !self.url_filter.is_season_valid(season.season_number) - && !season + || (!season .audio_locales .iter() .any(|l| self.archive.audio.contains(l)) - && !season - .available_versions() - .await? - .iter() - .any(|l| self.archive.audio.contains(l)) + && !season + .available_versions() + .await? + .iter() + .any(|l| self.archive.audio.contains(l))) { remove_ids.push(season.id.clone()); } @@ -172,20 +172,44 @@ impl Filter for ArchiveFilter { let mut episodes = vec![]; for season in seasons { self.season_sorting.push(season.id.clone()); - let season_locale = season - .audio_locales - .get(0) - .cloned() - .unwrap_or(Locale::ja_JP); + let season_locale = if season.audio_locales.len() < 2 { + Some( + season + .audio_locales + .get(0) + .cloned() + .unwrap_or(Locale::ja_JP), + ) + } else { + None + }; let mut eps = season.episodes().await?; let before_len = eps.len(); - eps.retain(|e| e.audio_locale == season_locale); - if eps.len() != before_len { + + for mut ep in eps.clone() { + if let Some(l) = &season_locale { + if &ep.audio_locale == l { + continue; + } + eps.remove(eps.iter().position(|p| p.id == ep.id).unwrap()); + } else { + let mut requested_locales = self.archive.audio.clone(); + if let Some(idx) = requested_locales.iter().position(|p| p == &ep.audio_locale) + { + requested_locales.remove(idx); + } else { + eps.remove(eps.iter().position(|p| p.id == ep.id).unwrap()); + } + eps.extend(ep.version(self.archive.audio.clone()).await?); + } + } + if eps.len() < before_len { if eps.len() == 0 { if matches!(self.visited, Visited::Series) { warn!( "Season {} is not available with {} audio", - season.season_number, season_locale + season.season_number, + season_locale.unwrap_or(Locale::ja_JP) ) } } else { @@ -193,7 +217,7 @@ impl Filter for ArchiveFilter { warn!( "Season {} is only available with {} audio until episode {} ({})", season.season_number, - season_locale, + season_locale.unwrap_or(Locale::ja_JP), last_episode.episode_number, last_episode.title ) @@ -339,24 +363,45 @@ impl Filter for ArchiveFilter { let mut single_format_collection = SingleFormatCollection::new(); - let mut pre_sorted: BTreeMap<(String, String), Self::T> = BTreeMap::new(); + let mut pre_sorted: BTreeMap<String, Self::T> = BTreeMap::new(); for data in flatten_input { pre_sorted - .entry((data.season_id.clone(), data.sequence_number.to_string())) + .entry(data.identifier.clone()) .or_insert(vec![]) .push(data) } - let mut sorted: Vec<((String, String), Self::T)> = pre_sorted.into_iter().collect(); - sorted.sort_by(|((a, _), _), ((b, _), _)| { + let mut sorted: Vec<(String, Self::T)> = pre_sorted.into_iter().collect(); + sorted.sort_by(|(_, a), (_, b)| { self.season_sorting .iter() - .position(|p| p == a) + .position(|p| p == &a.first().unwrap().season_id) .unwrap() - .cmp(&self.season_sorting.iter().position(|p| p == b).unwrap()) + .cmp( + &self + .season_sorting + .iter() + .position(|p| p == &b.first().unwrap().season_id) + .unwrap(), + ) }); - for (_, data) in sorted { + for (_, mut data) in sorted { + data.sort_by(|a, b| { + self.archive + .audio + .iter() + .position(|p| p == &a.audio) + .unwrap_or(usize::MAX) + .cmp( + &self + .archive + .audio + .iter() + .position(|p| p == &b.audio) + .unwrap_or(usize::MAX), + ) + }); single_format_collection.add_single_formats(data) } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index c67386d..16d268e 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -12,6 +12,8 @@ use std::path::{Path, PathBuf}; #[derive(Clone)] pub struct SingleFormat { + pub identifier: String, + pub title: String, pub description: String, @@ -42,6 +44,7 @@ impl SingleFormat { relative_episode_number: Option<u32>, ) -> Self { Self { + identifier: episode.identifier.clone(), title: episode.title.clone(), description: episode.description.clone(), audio: episode.audio_locale.clone(), @@ -66,6 +69,7 @@ impl SingleFormat { pub fn new_from_movie(movie: Movie, subtitles: Vec<Locale>) -> Self { Self { + identifier: movie.id.clone(), title: movie.title.clone(), description: movie.description.clone(), audio: Locale::ja_JP, @@ -86,6 +90,7 @@ impl SingleFormat { pub fn new_from_music_video(music_video: MusicVideo) -> Self { Self { + identifier: music_video.id.clone(), title: music_video.title.clone(), description: music_video.description.clone(), audio: Locale::ja_JP, @@ -106,6 +111,7 @@ impl SingleFormat { pub fn new_from_concert(concert: Concert) -> Self { Self { + identifier: concert.id.clone(), title: concert.title.clone(), description: concert.description.clone(), audio: Locale::ja_JP, From f40dc0dd1c63b695cdc2b75adef3db6320439309 Mon Sep 17 00:00:00 2001 From: Bastian Venz <Serverfrog@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:59:55 +0200 Subject: [PATCH 398/630] Update crunchyroll-rs to 0.3.7 (#220) --- Cargo.lock | 188 +--------------------- crunchy-cli-core/Cargo.lock | 216 +++----------------------- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/search/format.rs | 8 +- crunchy-cli-core/src/utils/format.rs | 8 +- 5 files changed, 34 insertions(+), 388 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dedc0ff..7a724a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,16 +307,6 @@ dependencies = [ "url", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -376,9 +366,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510ab662065c5ff28678e66fc1ad243727044642f2dd02a5bbadc12aa2717779" +checksum = "921f8aefa462be97067128bad89e273d3808320bf4a3c9d5be368e2cc9878c50" dependencies = [ "aes", "async-trait", @@ -403,9 +393,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e71fd50850102f81e439c08ffcb69ae98b64d4eb292c359a33ac2253aaa91" +checksum = "d1700be68ab7e7098ac1b9ced729d92d66743c8a2697047284b5b73af0f8b328" dependencies = [ "darling", "quote", @@ -589,21 +579,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.0" @@ -644,12 +619,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - [[package]] name = "futures-sink" version = "0.3.28" @@ -669,9 +638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", - "futures-io", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -821,19 +788,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1059,24 +1013,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.26.2" @@ -1130,50 +1066,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "openssl" -version = "0.10.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -1198,12 +1090,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "portable-atomic" version = "1.3.3" @@ -1318,12 +1204,10 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1333,7 +1217,6 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-socks", "tower-service", @@ -1427,15 +1310,6 @@ dependencies = [ "regex", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "sct" version = "0.7.0" @@ -1446,29 +1320,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.164" @@ -1745,16 +1596,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1879,12 +1720,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -2047,21 +1882,6 @@ dependencies = [ "windows-targets 0.48.0", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index e794609..48bd37e 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -288,16 +288,6 @@ dependencies = [ "url", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -324,6 +314,7 @@ dependencies = [ "crunchyroll-rs", "ctrlc", "derive_setters", + "dialoguer", "dirs", "fs2", "indicatif", @@ -339,15 +330,14 @@ dependencies = [ "shlex", "sys-locale", "tempfile", - "terminal_size", "tokio", ] [[package]] name = "crunchyroll-rs" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510ab662065c5ff28678e66fc1ad243727044642f2dd02a5bbadc12aa2717779" +checksum = "921f8aefa462be97067128bad89e273d3808320bf4a3c9d5be368e2cc9878c50" dependencies = [ "aes", "async-trait", @@ -372,9 +362,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e71fd50850102f81e439c08ffcb69ae98b64d4eb292c359a33ac2253aaa91" +checksum = "d1700be68ab7e7098ac1b9ced729d92d66743c8a2697047284b5b73af0f8b328" dependencies = [ "darling", "quote", @@ -470,6 +460,16 @@ dependencies = [ "syn", ] +[[package]] +name = "dialoguer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +dependencies = [ + "console", + "shell-words", +] + [[package]] name = "dirs" version = "5.0.1" @@ -548,21 +548,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.0" @@ -603,12 +588,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - [[package]] name = "futures-sink" version = "0.3.28" @@ -628,9 +607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", - "futures-io", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -780,19 +757,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1018,24 +982,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.26.2" @@ -1089,50 +1035,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "openssl" -version = "0.10.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -1157,12 +1059,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "portable-atomic" version = "1.3.3" @@ -1277,12 +1173,10 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1292,7 +1186,6 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-socks", "tower-service", @@ -1380,15 +1273,6 @@ dependencies = [ "regex", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "sct" version = "0.7.0" @@ -1399,29 +1283,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.164" @@ -1502,6 +1363,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.1.0" @@ -1591,16 +1458,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "terminal_size" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "thiserror" version = "1.0.40" @@ -1702,16 +1559,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1836,12 +1683,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -2004,21 +1845,6 @@ dependencies = [ "windows-targets 0.48.0", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index cf2b7c3..27e3846 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.3.6", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.3.7", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.10", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 6769c4c..3574597 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -428,7 +428,7 @@ impl Format { if !stream_empty { for (_, episodes) in tree.iter_mut() { for (episode, streams) in episodes { - streams.push(episode.streams().await?) + streams.push(episode.stream().await?) } } } else { @@ -497,7 +497,7 @@ impl Format { } if !stream_empty { for (movie, streams) in tree.iter_mut() { - streams.push(movie.streams().await?) + streams.push(movie.stream().await?) } } else { for (_, streams) in tree.iter_mut() { @@ -535,7 +535,7 @@ impl Format { let stream_empty = self.check_pattern_count_empty(Scope::Stream); let music_video = must_match_if_true!(!music_video_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.clone()).unwrap_or_default(); - let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.streams().await?).unwrap_or_default(); + let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.stream().await?).unwrap_or_default(); let music_video_map = self.serializable_to_json_map(FormatMusicVideo::from(&music_video)); let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); @@ -557,7 +557,7 @@ impl Format { let stream_empty = self.check_pattern_count_empty(Scope::Stream); let concert = must_match_if_true!(!concert_empty => media_collection|MediaCollection::Concert(concert) => concert.clone()).unwrap_or_default(); - let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::Concert(concert) => concert.streams().await?).unwrap_or_default(); + let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::Concert(concert) => concert.stream().await?).unwrap_or_default(); let concert_map = self.serializable_to_json_map(FormatConcert::from(&concert)); let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 16d268e..0b0f46e 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -132,10 +132,10 @@ impl SingleFormat { pub async fn stream(&self) -> Result<Stream> { let stream = match &self.source { - MediaCollection::Episode(e) => e.streams().await?, - MediaCollection::Movie(m) => m.streams().await?, - MediaCollection::MusicVideo(mv) => mv.streams().await?, - MediaCollection::Concert(c) => c.streams().await?, + MediaCollection::Episode(e) => e.stream().await?, + MediaCollection::Movie(m) => m.stream().await?, + MediaCollection::MusicVideo(mv) => mv.stream().await?, + MediaCollection::Concert(c) => c.stream().await?, _ => unreachable!(), }; Ok(stream) From af8a88a7927f330b51b805d58adfa717013a59a8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 5 Jul 2023 01:57:56 +0200 Subject: [PATCH 399/630] Add option to force subtitle burn with download (#221) --- crunchy-cli-core/src/download/command.rs | 11 +++- crunchy-cli-core/src/utils/download.rs | 74 +++++++++++++----------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 0167f49..ac1e391 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -77,6 +77,10 @@ pub struct Download { #[arg(short, long, default_value_t = false)] pub(crate) yes: bool, + #[arg(help = "Force subtitles to be always burnt-in")] + #[arg(long, default_value_t = false)] + pub(crate) force_hardsub: bool, + #[arg(help = "Url(s) to Crunchyroll episodes or series")] pub(crate) urls: Vec<String>, } @@ -98,8 +102,10 @@ impl Execute for Download { if self.subtitle.is_some() { if let Some(ext) = Path::new(&self.output).extension() { - if ext.to_string_lossy() != "mp4" { - warn!("Detected a non mp4 output container. Adding subtitles may take a while") + if self.force_hardsub { + warn!("Hardsubs are forced. Adding subtitles may take a while") + } else if !["mkv", "mov", "mp4"].contains(&ext.to_string_lossy().as_ref()) { + warn!("Detected a container which does not support softsubs. Adding subtitles may take a while") } } } @@ -137,6 +143,7 @@ impl Execute for Download { let download_builder = DownloadBuilder::new() .default_subtitle(self.subtitle.clone()) + .force_hardsub(self.force_hardsub) .output_format(if is_special_file(&self.output) || self.output == "-" { Some("mpegts".to_string()) } else { diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index dcfcf0a..1e1b207 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -47,6 +47,7 @@ pub struct DownloadBuilder { output_format: Option<String>, audio_sort: Option<Vec<Locale>>, subtitle_sort: Option<Vec<Locale>>, + force_hardsub: bool, } impl DownloadBuilder { @@ -57,6 +58,7 @@ impl DownloadBuilder { output_format: None, audio_sort: None, subtitle_sort: None, + force_hardsub: false, } } @@ -68,6 +70,8 @@ impl DownloadBuilder { audio_sort: self.audio_sort, subtitle_sort: self.subtitle_sort, + force_hardsub: self.force_hardsub, + formats: vec![], } } @@ -92,6 +96,8 @@ pub struct Downloader { audio_sort: Option<Vec<Locale>>, subtitle_sort: Option<Vec<Locale>>, + force_hardsub: bool, + formats: Vec<DownloadFormat>, } @@ -255,8 +261,9 @@ impl Downloader { // this formats are supporting embedding subtitles into the video container instead of // burning it into the video stream directly - let container_supports_softsubs = - ["mkv", "mov", "mp4"].contains(&dst.extension().unwrap_or_default().to_str().unwrap()); + let container_supports_softsubs = !self.force_hardsub + && ["mkv", "mov", "mp4"] + .contains(&dst.extension().unwrap_or_default().to_str().unwrap()); if container_supports_softsubs { for (i, meta) in subtitles.iter().enumerate() { @@ -290,38 +297,39 @@ impl Downloader { .iter() .position(|m| m.language == default_subtitle) { - match dst.extension().unwrap_or_default().to_str().unwrap() { - "mkv" => (), - "mov" | "mp4" => output_presets.extend([ - "-movflags".to_string(), - "faststart".to_string(), - "-c:s".to_string(), - "mov_text".to_string(), - ]), - _ => { - // remove '-c:v copy' and '-c:a copy' from output presets as its causes issues with - // burning subs into the video - let mut last = String::new(); - let mut remove_count = 0; - for (i, s) in output_presets.clone().iter().enumerate() { - if (last == "-c:v" || last == "-c:a") && s == "copy" { - // remove last - output_presets.remove(i - remove_count - 1); - remove_count += 1; - output_presets.remove(i - remove_count); - remove_count += 1; - } - last = s.clone(); - } - - output_presets.extend([ - "-vf".to_string(), - format!( - "ass={}", - subtitles.get(position).unwrap().path.to_str().unwrap() - ), - ]) + if container_supports_softsubs { + match dst.extension().unwrap_or_default().to_str().unwrap() { + "mov" | "mp4" => output_presets.extend([ + "-movflags".to_string(), + "faststart".to_string(), + "-c:s".to_string(), + "mov_text".to_string(), + ]), + _ => (), } + } else { + // remove '-c:v copy' and '-c:a copy' from output presets as its causes issues with + // burning subs into the video + let mut last = String::new(); + let mut remove_count = 0; + for (i, s) in output_presets.clone().iter().enumerate() { + if (last == "-c:v" || last == "-c:a") && s == "copy" { + // remove last + output_presets.remove(i - remove_count - 1); + remove_count += 1; + output_presets.remove(i - remove_count); + remove_count += 1; + } + last = s.clone(); + } + + output_presets.extend([ + "-vf".to_string(), + format!( + "ass={}", + subtitles.get(position).unwrap().path.to_str().unwrap() + ), + ]) } } From 1fe8746dda317f117b878a6c18c583b416737993 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 5 Jul 2023 16:01:55 +0200 Subject: [PATCH 400/630] Add support for old url scheme (#224) --- crunchy-cli-core/src/utils/parse.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index 1bf9364..f85d8c2 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -130,6 +130,20 @@ pub async fn parse_url( UrlFilter::default() }; + // check if the url is the old series/episode scheme which still occurs in some places (like the + // rss) + let old_url_regex = Regex::new(r"https?://(www\.)?crunchyroll\.com/.+").unwrap(); + if old_url_regex.is_match(&url) { + debug!("Detected maybe old url"); + // replace the 'http' prefix with 'https' as https is not supported by the reqwest client + if url.starts_with("http://") { + url.replace_range(0..4, "https") + } + // the old url redirects to the new url. request the old url, follow the redirects and + // extract the final url + url = crunchy.client().get(&url).send().await?.url().to_string() + } + let parsed_url = crunchyroll_rs::parse_url(url).map_or(Err(anyhow!("Invalid url")), Ok)?; debug!("Url type: {:?}", parsed_url); let media_collection = match parsed_url { From 751735477cdac22edddcbf74dac059f2d996d73d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 8 Jul 2023 16:36:35 +0200 Subject: [PATCH 401/630] Update dependencies --- Cargo.lock | 312 ++++++++++++++++++++++++------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 308 +++++++++++++++++++++++------------ crunchy-cli-core/Cargo.toml | 8 +- crunchy-cli-core/src/lib.rs | 14 +- 5 files changed, 423 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a724a7..cc02df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aes" version = "0.8.3" @@ -54,15 +69,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -94,9 +109,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", @@ -109,6 +124,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.2" @@ -131,6 +161,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "block-padding" version = "0.3.3" @@ -201,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.4" +version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" dependencies = [ "clap_builder", "clap_derive", @@ -212,22 +248,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] [[package]] name = "clap_complete" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" dependencies = [ "clap", ] @@ -315,9 +350,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -366,9 +401,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921f8aefa462be97067128bad89e273d3808320bf4a3c9d5be368e2cc9878c50" +checksum = "3be9aa4589c26829667cc6f32f7238b93cdc0c6c130b295b3714df7ea6c28ef1" dependencies = [ "aes", "async-trait", @@ -388,14 +423,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.23.1", + "webpki-roots 0.24.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1700be68ab7e7098ac1b9ced729d92d66743c8a2697047284b5b73af0f8b328" +checksum = "b718f70c2825f2310e2301295a3a66f1723bbdd39f6cfbe6148df00556e9c75e" dependencies = [ "darling", "quote", @@ -459,9 +494,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.9.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b97b7a11cc6dcf0c803554cbd7f7d8dfef99c598e6a310403bb5699d9a94076" +checksum = "ef11db94edb7974469f90fe64eaa95b3a2b36fdd5f72420717f7ee56a1922988" dependencies = [ "base64", "base64-serde", @@ -619,6 +654,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -638,6 +684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", @@ -666,10 +713,16 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.19" +name = "gimli" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -698,18 +751,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -753,9 +797,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -777,10 +821,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ + "futures-util", "http", "hyper", "rustls", @@ -897,26 +942,25 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", + "hermit-abi", + "rustix 0.38.3", "windows-sys 0.48.0", ] @@ -931,9 +975,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" [[package]] name = "js-sys" @@ -952,9 +996,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" @@ -962,6 +1006,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + [[package]] name = "log" version = "0.4.19" @@ -1002,6 +1052,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.8" @@ -1019,7 +1078,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -1046,11 +1105,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -1060,6 +1119,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1080,9 +1148,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -1098,9 +1166,9 @@ checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] @@ -1123,9 +1191,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", "serde", @@ -1133,9 +1201,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -1146,7 +1214,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1155,7 +1223,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1171,9 +1239,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ "aho-corasick", "memchr", @@ -1182,9 +1262,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" [[package]] name = "reqwest" @@ -1250,24 +1330,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] -name = "rustix" -version = "0.37.20" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys 0.4.3", "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed" dependencies = [ "log", "ring", @@ -1277,18 +1376,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" dependencies = [ "ring", "untrusted", @@ -1296,9 +1395,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" [[package]] name = "sanitize-filename" @@ -1322,18 +1421,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.164" +version = "1.0.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" dependencies = [ "proc-macro2", "quote", @@ -1342,9 +1441,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" +checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" dependencies = [ "itoa", "ryu", @@ -1462,9 +1561,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.18" +version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ "proc-macro2", "quote", @@ -1491,24 +1590,24 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", + "rustix 0.37.23", "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", @@ -1570,11 +1669,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -1678,9 +1778,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-normalization" @@ -1844,9 +1944,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ "rustls-webpki", ] @@ -1879,7 +1979,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -1897,7 +1997,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -1917,9 +2017,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", diff --git a/Cargo.toml b/Cargo.toml index beddc9e..4deb9d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ version = "3.0.0-dev.14" edition = "2021" [dependencies] -tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 48bd37e..5f9347b 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aes" version = "0.8.3" @@ -54,15 +69,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -94,9 +109,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", @@ -109,6 +124,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.2" @@ -131,6 +161,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "block-padding" version = "0.3.3" @@ -201,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.4" +version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" dependencies = [ "clap_builder", "clap_derive", @@ -212,13 +248,12 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] @@ -296,9 +331,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -335,9 +370,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921f8aefa462be97067128bad89e273d3808320bf4a3c9d5be368e2cc9878c50" +checksum = "3be9aa4589c26829667cc6f32f7238b93cdc0c6c130b295b3714df7ea6c28ef1" dependencies = [ "aes", "async-trait", @@ -357,14 +392,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.23.1", + "webpki-roots 0.24.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1700be68ab7e7098ac1b9ced729d92d66743c8a2697047284b5b73af0f8b328" +checksum = "b718f70c2825f2310e2301295a3a66f1723bbdd39f6cfbe6148df00556e9c75e" dependencies = [ "darling", "quote", @@ -428,9 +463,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.9.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b97b7a11cc6dcf0c803554cbd7f7d8dfef99c598e6a310403bb5699d9a94076" +checksum = "ef11db94edb7974469f90fe64eaa95b3a2b36fdd5f72420717f7ee56a1922988" dependencies = [ "base64", "base64-serde", @@ -588,6 +623,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -607,6 +653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", @@ -635,10 +682,16 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.19" +name = "gimli" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -667,18 +720,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -722,9 +766,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -746,10 +790,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ + "futures-util", "http", "hyper", "rustls", @@ -866,26 +911,25 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", + "hermit-abi", + "rustix 0.38.3", "windows-sys 0.48.0", ] @@ -900,9 +944,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" [[package]] name = "js-sys" @@ -921,9 +965,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" @@ -931,6 +975,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + [[package]] name = "log" version = "0.4.19" @@ -971,6 +1021,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.8" @@ -988,7 +1047,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -1015,11 +1074,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -1029,6 +1088,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1049,9 +1117,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -1067,9 +1135,9 @@ checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] @@ -1092,9 +1160,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", "serde", @@ -1102,9 +1170,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -1115,7 +1183,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1124,7 +1192,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1140,9 +1208,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ "aho-corasick", "memchr", @@ -1151,9 +1231,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" [[package]] name = "reqwest" @@ -1213,24 +1293,43 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.37.20" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys 0.4.3", "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed" dependencies = [ "log", "ring", @@ -1240,18 +1339,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" dependencies = [ "ring", "untrusted", @@ -1259,9 +1358,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" [[package]] name = "sanitize-filename" @@ -1285,18 +1384,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.164" +version = "1.0.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" dependencies = [ "proc-macro2", "quote", @@ -1305,9 +1404,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" +checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" dependencies = [ "itoa", "ryu", @@ -1425,9 +1524,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.18" +version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ "proc-macro2", "quote", @@ -1454,24 +1553,24 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", + "rustix 0.37.23", "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", @@ -1533,11 +1632,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -1641,9 +1741,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-normalization" @@ -1807,9 +1907,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ "rustls-webpki", ] @@ -1842,7 +1942,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -1860,7 +1960,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -1880,9 +1980,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 27e3846..2ba5322 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.3.7", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.4.0", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.10", default-features = false } dirs = "5.0" @@ -18,8 +18,8 @@ fs2 = "0.4" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } -num_cpus = "1.15" -regex = "1.8" +num_cpus = "1.16" +regex = "1.9" reqwest = { version = "0.11", default-features = false, features = ["socks"] } sanitize-filename = "0.4" serde = "1.0" @@ -27,7 +27,7 @@ serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" tempfile = "3.6" -tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" [build-dependencies] diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 982aa17..fbfb409 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -264,13 +264,15 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { lang }; - let mut client_builder = CrunchyrollBuilder::predefined_client_builder(); - if let Some(proxy) = &cli.proxy { - client_builder = client_builder.proxy(proxy.clone()) - } - + let proxy = cli.proxy.clone(); let mut builder = Crunchyroll::builder() - .client(client_builder.build()?) + .client_builder(move || { + let mut client_builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(proxy) = &proxy { + client_builder = client_builder.proxy(proxy.clone()) + } + client_builder + }) .locale(locale) .stabilization_locales(cli.experimental_fixes) .stabilization_season_number(cli.experimental_fixes); From 49de7bbba93f96968ecb9d009d44499e8b930bfc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 12 Jul 2023 21:15:01 +0200 Subject: [PATCH 402/630] Add progress indicator for subtitle download --- crunchy-cli-core/src/utils/download.rs | 67 ++++++++++++++++++-------- crunchy-cli-core/src/utils/log.rs | 2 +- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 1e1b207..f50570a 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -204,21 +204,50 @@ impl Downloader { }, }) } - let len = get_video_length(&video_path)?; - for (subtitle, not_cc) in format.subtitles.iter() { - let subtitle_path = self.download_subtitle(subtitle.clone(), len).await?; - let mut subtitle_title = subtitle.locale.to_human_readable(); - if !not_cc { - subtitle_title += " (CC)" + if !format.subtitles.is_empty() { + #[cfg(not(windows))] + let pb = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template( + format!( + ":: {:<1$} {{msg}} {{spinner}}", + "Downloading subtitles", fmt_space + ) + .as_str(), + ) + .unwrap() + .tick_strings(&["โ€”", "\\", "|", "/", ""]), + ) + .with_finish(ProgressFinish::Abandon); + pb.enable_steady_tick(Duration::from_millis(100)); + + let len = get_video_length(&video_path)?; + for (subtitle, not_cc) in format.subtitles.iter() { + let mut progress_message = pb.message(); + if !progress_message.is_empty() { + progress_message += ", " + } + progress_message += &subtitle.locale.to_string(); + let mut subtitle_title = subtitle.locale.to_human_readable(); + + if !not_cc { + progress_message += " (CC)"; + subtitle_title += " (CC)" + } + if i != 0 { + progress_message += &format!(" [Video: #{}]", i + 1); + subtitle_title += &format!(" [Video: #{}]", i + 1) + } + + pb.set_message(progress_message); + + let subtitle_path = self.download_subtitle(subtitle.clone(), len).await?; + subtitles.push(FFmpegMeta { + path: subtitle_path, + language: subtitle.locale.clone(), + title: subtitle_title, + }) } - if i != 0 { - subtitle_title += &format!(" [Video: #{}]", i + 1) - } - subtitles.push(FFmpegMeta { - path: subtitle_path, - language: subtitle.locale.clone(), - title: subtitle_title, - }) } videos.push(FFmpegMeta { path: video_path, @@ -454,7 +483,7 @@ impl Downloader { let tempfile = tempfile(".mp4")?; let (mut file, path) = tempfile.into_parts(); - download_segments(ctx, &mut file, Some(message), variant_data).await?; + download_segments(ctx, &mut file, message, variant_data).await?; Ok(path) } @@ -468,7 +497,7 @@ impl Downloader { let tempfile = tempfile(".m4a")?; let (mut file, path) = tempfile.into_parts(); - download_segments(ctx, &mut file, Some(message), variant_data).await?; + download_segments(ctx, &mut file, message, variant_data).await?; Ok(path) } @@ -494,7 +523,7 @@ impl Downloader { pub async fn download_segments( ctx: &Context, writer: &mut impl Write, - message: Option<String>, + message: String, variant_data: &VariantData, ) -> Result<()> { let segments = variant_data.segments().await?; @@ -509,12 +538,12 @@ pub async fn download_segments( let progress = ProgressBar::new(estimated_file_size) .with_style( ProgressStyle::with_template( - ":: {msg}{bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", + ":: {msg} {bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", ) .unwrap() .progress_chars("##-"), ) - .with_message(message.map(|m| m + " ").unwrap_or_default()) + .with_message(message) .with_finish(ProgressFinish::Abandon); Some(progress) } else { diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs index c74b73d..6650e58 100644 --- a/crunchy-cli-core/src/utils/log.rs +++ b/crunchy-cli-core/src/utils/log.rs @@ -169,7 +169,7 @@ impl CliLogger { let finish_str = "โœ”"; #[cfg(windows)] // windows does not support all unicode characters by default in their consoles, so - // we're using this (square root?) symbol instead. microsoft. + // we're using this (square root) symbol instead. microsoft. let finish_str = "โˆš"; let pb = ProgressBar::new_spinner(); From 513353890da7c0b8494ccdfcebf41b87c8d1e59b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 12 Jul 2023 21:34:49 +0200 Subject: [PATCH 403/630] Respect debug output when showing subtitle download spinner --- crunchy-cli-core/src/utils/download.rs | 61 ++++++++++++++++---------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index f50570a..4531c69 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -205,43 +205,60 @@ impl Downloader { }) } if !format.subtitles.is_empty() { - #[cfg(not(windows))] - let pb = ProgressBar::new_spinner() - .with_style( - ProgressStyle::with_template( - format!( - ":: {:<1$} {{msg}} {{spinner}}", - "Downloading subtitles", fmt_space + let progress_spinner = if log::max_level() == LevelFilter::Info { + let progress_spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template( + format!( + ":: {:<1$} {{msg}} {{spinner}}", + "Downloading subtitles", fmt_space + ) + .as_str(), ) - .as_str(), + .unwrap() + .tick_strings(&["โ€”", "\\", "|", "/", ""]), ) - .unwrap() - .tick_strings(&["โ€”", "\\", "|", "/", ""]), - ) - .with_finish(ProgressFinish::Abandon); - pb.enable_steady_tick(Duration::from_millis(100)); + .with_finish(ProgressFinish::Abandon); + progress_spinner.enable_steady_tick(Duration::from_millis(100)); + Some(progress_spinner) + } else { + None + }; let len = get_video_length(&video_path)?; for (subtitle, not_cc) in format.subtitles.iter() { - let mut progress_message = pb.message(); - if !progress_message.is_empty() { - progress_message += ", " + if let Some(pb) = &progress_spinner { + let mut progress_message = pb.message(); + if !progress_message.is_empty() { + progress_message += ", " + } + progress_message += &subtitle.locale.to_string(); + if !not_cc { + progress_message += " (CC)"; + } + if i != 0 { + progress_message += &format!(" [Video: #{}]", i + 1); + } + pb.set_message(progress_message) } - progress_message += &subtitle.locale.to_string(); - let mut subtitle_title = subtitle.locale.to_human_readable(); + let mut subtitle_title = subtitle.locale.to_human_readable(); if !not_cc { - progress_message += " (CC)"; subtitle_title += " (CC)" } if i != 0 { - progress_message += &format!(" [Video: #{}]", i + 1); subtitle_title += &format!(" [Video: #{}]", i + 1) } - pb.set_message(progress_message); - let subtitle_path = self.download_subtitle(subtitle.clone(), len).await?; + debug!( + "Downloaded {} subtitles{}{}", + subtitle.locale, + (!not_cc).then_some(" (cc)").unwrap_or_default(), + (i != 0) + .then_some(format!(" for video {}", i)) + .unwrap_or_default() + ); subtitles.push(FFmpegMeta { path: subtitle_path, language: subtitle.locale.clone(), From 9ad27102fc14a0044fd2ded48453e831b3369655 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 12 Jul 2023 21:59:41 +0200 Subject: [PATCH 404/630] Fix missing identifier on newer simulcast titles (#227) --- crunchy-cli-core/src/utils/format.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 0b0f46e..d975042 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -44,7 +44,18 @@ impl SingleFormat { relative_episode_number: Option<u32>, ) -> Self { Self { - identifier: episode.identifier.clone(), + identifier: if episode.identifier.is_empty() { + // crunchyroll sometimes leafs the identifier field empty so we have to build it + // ourself. it's not 100% save that the identifier which is built here is the same + // as if crunchyroll would deliver it (because the variables used here may also be + // wrong delivered by crunchy), but it's the best thing i can do at the moment + format!( + "{}|S{}|E{}", + episode.series_id, episode.season_number, episode.sequence_number + ) + } else { + episode.identifier.clone() + }, title: episode.title.clone(), description: episode.description.clone(), audio: episode.audio_locale.clone(), From 850aa7a969768992820d8a9142ed01ccb27adec8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 13 Jul 2023 13:51:02 +0200 Subject: [PATCH 405/630] Use config file to store sessions --- Cargo.lock | 82 ++++++++++++++++++++++++++- crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/lib.rs | 44 +++++++------- crunchy-cli-core/src/login/command.rs | 33 +++-------- crunchy-cli-core/src/login/mod.rs | 2 +- crunchy-cli-core/src/utils/config.rs | 59 +++++++++++++++++++ crunchy-cli-core/src/utils/context.rs | 2 + crunchy-cli-core/src/utils/mod.rs | 1 + 8 files changed, 177 insertions(+), 47 deletions(-) create mode 100644 crunchy-cli-core/src/utils/config.rs diff --git a/Cargo.lock b/Cargo.lock index cc02df5..7468e73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,7 @@ dependencies = [ "sys-locale", "tempfile", "tokio", + "toml", ] [[package]] @@ -578,6 +579,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "errno" version = "0.3.1" @@ -730,7 +737,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -743,6 +750,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -900,10 +913,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "indicatif" version = "0.17.5" @@ -1459,6 +1482,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1480,7 +1512,7 @@ dependencies = [ "base64", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", "serde", "serde_json", "serde_with_macros", @@ -1732,6 +1764,41 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -2114,6 +2181,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 2ba5322..32c2ec4 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -27,6 +27,7 @@ serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" tempfile = "3.6" +toml = { version = "0.7", features = ["display", "parse", "preserve_order"] } tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index fbfb409..303d38e 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -17,6 +17,7 @@ mod login; mod search; mod utils; +use crate::utils::config::{Auth, Config}; pub use archive::Archive; use dialoguer::console::Term; pub use download::Download; @@ -136,8 +137,20 @@ pub async fn cli_entrypoint() { } Command::Login(login) => { if login.remove { - if let Some(session_file) = login::session_file_path() { - let _ = fs::remove_file(session_file); + match Config::load() { + Ok(config) => { + if let Some(mut c) = config { + c.auth = None; + if let Err(e) = c.write() { + error!("{}", e); + std::process::exit(1) + } + } + } + Err(e) => { + error!("{}", e); + std::process::exit(1) + } } return; } else { @@ -226,11 +239,12 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { } async fn create_ctx(cli: &mut Cli) -> Result<Context> { - let crunchy = crunchyroll_session(cli).await?; - Ok(Context { crunchy }) + let mut config = Config::load()?.unwrap_or_default(); + let crunchy = crunchyroll_session(cli, &mut config).await?; + Ok(Context { crunchy, config }) } -async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { +async fn crunchyroll_session(cli: &mut Cli, config: &mut Config) -> Result<Crunchyroll> { let supported_langs = vec![ Locale::ar_ME, Locale::de_DE, @@ -293,20 +307,12 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { let progress_handler = progress!("Logging in"); if root_login_methods_count + login_login_methods_count == 0 { - if let Some(login_file_path) = login::session_file_path() { - if login_file_path.exists() { - let session = fs::read_to_string(login_file_path)?; - if let Some((token_type, token)) = session.split_once(':') { - match token_type { - "refresh_token" => { - return Ok(builder.login_with_refresh_token(token).await?) - } - "etp_rt" => return Ok(builder.login_with_etp_rt(token).await?), - _ => (), - } - } - bail!("Could not read stored session ('{}')", session) - } + if let Some(auth) = &config.auth { + return match auth { + Auth::RefreshToken { token } => Ok(builder.login_with_refresh_token(token).await?), + Auth::EtpRt { token } => Ok(builder.login_with_etp_rt(token).await?), + Auth::Anonymous => Ok(builder.login_anonymously().await?), + }; } bail!("Please use a login method ('--credentials', '--etp-rt' or '--anonymous')") } else if root_login_methods_count + login_login_methods_count > 1 { diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index ab16e06..076e1b7 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -1,11 +1,9 @@ +use crate::utils::config::Auth; use crate::utils::context::Context; use crate::Execute; -use anyhow::bail; use anyhow::Result; use clap::Parser; use crunchyroll_rs::crunchyroll::SessionToken; -use std::fs; -use std::path::PathBuf; #[derive(Debug, clap::Parser)] #[clap(about = "Save your login credentials persistent on disk")] @@ -19,23 +17,14 @@ pub struct Login { #[async_trait::async_trait(?Send)] impl Execute for Login { - async fn execute(self, ctx: Context) -> Result<()> { - if let Some(login_file_path) = session_file_path() { - fs::create_dir_all(login_file_path.parent().unwrap())?; - - match ctx.crunchy.session_token().await { - SessionToken::RefreshToken(refresh_token) => Ok(fs::write( - login_file_path, - format!("refresh_token:{}", refresh_token), - )?), - SessionToken::EtpRt(etp_rt) => { - Ok(fs::write(login_file_path, format!("etp_rt:{}", etp_rt))?) - } - SessionToken::Anonymous => bail!("Anonymous login cannot be saved"), - } - } else { - bail!("Cannot find config path") - } + async fn execute(self, mut ctx: Context) -> Result<()> { + let auth = match ctx.crunchy.session_token().await { + SessionToken::RefreshToken(token) => Auth::RefreshToken { token }, + SessionToken::EtpRt(token) => Auth::EtpRt { token }, + SessionToken::Anonymous => Auth::Anonymous, + }; + ctx.config.auth = Some(auth); + Ok(ctx.config.write()?) } } @@ -56,7 +45,3 @@ pub struct LoginMethod { #[arg(long, default_value_t = false)] pub anonymous: bool, } - -pub fn session_file_path() -> Option<PathBuf> { - dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli").join("session")) -} diff --git a/crunchy-cli-core/src/login/mod.rs b/crunchy-cli-core/src/login/mod.rs index 8c1220a..8c79d61 100644 --- a/crunchy-cli-core/src/login/mod.rs +++ b/crunchy-cli-core/src/login/mod.rs @@ -1,3 +1,3 @@ mod command; -pub use command::{session_file_path, Login, LoginMethod}; +pub use command::{Login, LoginMethod}; diff --git a/crunchy-cli-core/src/utils/config.rs b/crunchy-cli-core/src/utils/config.rs new file mode 100644 index 0000000..3206ca8 --- /dev/null +++ b/crunchy-cli-core/src/utils/config.rs @@ -0,0 +1,59 @@ +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "method")] +pub enum Auth { + RefreshToken { token: String }, + EtpRt { token: String }, + Anonymous, +} + +#[derive(Default, Deserialize, Serialize)] +pub struct Config { + pub auth: Option<Auth>, +} + +impl Config { + pub fn load() -> Result<Option<Self>> { + let path = Config::assert_config_file_path(true)?; + + if let Some(p) = path { + if p.exists() { + let content = fs::read_to_string(p)?; + return Ok(Some(toml::from_str(&content)?)); + } + } + Ok(None) + } + + pub fn write(&self) -> Result<()> { + let path = Config::assert_config_file_path(false)?.unwrap(); + Ok(fs::write(path, toml::to_string(self)?)?) + } + + pub fn config_file_path() -> Option<PathBuf> { + dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli.conf")) + } + + fn assert_config_file_path(ignore_non_existing_config_dir: bool) -> Result<Option<PathBuf>> { + let Some(path) = Config::config_file_path() else { + if ignore_non_existing_config_dir { + return Ok(None) + } + bail!("Cannot find config directory") + }; + + if path.exists() && path.is_dir() { + bail!( + "Config path ({}) is a directory (must be a normal file)", + path.to_string_lossy() + ) + } + + Ok(Some(path)) + } +} diff --git a/crunchy-cli-core/src/utils/context.rs b/crunchy-cli-core/src/utils/context.rs index f8df024..3e9af73 100644 --- a/crunchy-cli-core/src/utils/context.rs +++ b/crunchy-cli-core/src/utils/context.rs @@ -1,5 +1,7 @@ +use crate::utils::config::Config; use crunchyroll_rs::Crunchyroll; pub struct Context { pub crunchy: Crunchyroll, + pub config: Config, } diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index d46cc33..510eeec 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -1,4 +1,5 @@ pub mod clap; +pub mod config; pub mod context; pub mod download; pub mod ffmpeg; From 6b768879786047d4bdc46efb4c658c297c1c2327 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 13 Jul 2023 14:08:11 +0200 Subject: [PATCH 406/630] Fix error when using etp-rt or anonymous flag after login command --- crunchy-cli-core/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 303d38e..8595f62 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -335,9 +335,9 @@ async fn crunchyroll_session(cli: &mut Cli, config: &mut Config) -> Result<Crunc } else { bail!("Invalid credentials format. Please provide your credentials as user:password") } - } else if let Some(etp_rt) = &cli.login_method.etp_rt { + } else if let Some(etp_rt) = &login_method.etp_rt { builder.login_with_etp_rt(etp_rt).await? - } else if cli.login_method.anonymous { + } else if login_method.anonymous { builder.login_anonymously().await? } else { bail!("should never happen") From 8490263e8417dffb1e872babc0ef69f3bec65352 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 13 Jul 2023 14:09:18 +0200 Subject: [PATCH 407/630] Add info output that login command saved the session --- crunchy-cli-core/src/login/command.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index 076e1b7..f7994e5 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -4,6 +4,7 @@ use crate::Execute; use anyhow::Result; use clap::Parser; use crunchyroll_rs::crunchyroll::SessionToken; +use log::info; #[derive(Debug, clap::Parser)] #[clap(about = "Save your login credentials persistent on disk")] @@ -24,7 +25,11 @@ impl Execute for Login { SessionToken::Anonymous => Auth::Anonymous, }; ctx.config.auth = Some(auth); - Ok(ctx.config.write()?) + ctx.config.write()?; + + info!("Saved login"); + + Ok(()) } } From dd2033d32311bb6f8243b1d5f4f43dc3d017614a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 13 Jul 2023 16:07:05 +0200 Subject: [PATCH 408/630] Revert "Use config file to store sessions" This reverts commit 850aa7a9 --- Cargo.lock | 82 +-------------------------- crunchy-cli-core/Cargo.toml | 1 - crunchy-cli-core/src/lib.rs | 44 +++++++------- crunchy-cli-core/src/login/command.rs | 36 ++++++++---- crunchy-cli-core/src/login/mod.rs | 2 +- crunchy-cli-core/src/utils/config.rs | 59 ------------------- crunchy-cli-core/src/utils/context.rs | 2 - crunchy-cli-core/src/utils/mod.rs | 1 - 8 files changed, 48 insertions(+), 179 deletions(-) delete mode 100644 crunchy-cli-core/src/utils/config.rs diff --git a/Cargo.lock b/Cargo.lock index 7468e73..cc02df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,7 +397,6 @@ dependencies = [ "sys-locale", "tempfile", "tokio", - "toml", ] [[package]] @@ -579,12 +578,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "equivalent" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" - [[package]] name = "errno" version = "0.3.1" @@ -737,7 +730,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap", "slab", "tokio", "tokio-util", @@ -750,12 +743,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - [[package]] name = "heck" version = "0.4.1" @@ -913,20 +900,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", "serde", ] -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown 0.14.0", -] - [[package]] name = "indicatif" version = "0.17.5" @@ -1482,15 +1459,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1512,7 +1480,7 @@ dependencies = [ "base64", "chrono", "hex", - "indexmap 1.9.3", + "indexmap", "serde", "serde_json", "serde_with_macros", @@ -1764,41 +1732,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" -dependencies = [ - "indexmap 2.0.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" -dependencies = [ - "indexmap 2.0.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "tower-service" version = "0.3.2" @@ -2181,15 +2114,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "winnow" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" -dependencies = [ - "memchr", -] - [[package]] name = "winreg" version = "0.10.1" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 32c2ec4..2ba5322 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -27,7 +27,6 @@ serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" tempfile = "3.6" -toml = { version = "0.7", features = ["display", "parse", "preserve_order"] } tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 8595f62..c85df85 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -17,7 +17,6 @@ mod login; mod search; mod utils; -use crate::utils::config::{Auth, Config}; pub use archive::Archive; use dialoguer::console::Term; pub use download::Download; @@ -137,20 +136,8 @@ pub async fn cli_entrypoint() { } Command::Login(login) => { if login.remove { - match Config::load() { - Ok(config) => { - if let Some(mut c) = config { - c.auth = None; - if let Err(e) = c.write() { - error!("{}", e); - std::process::exit(1) - } - } - } - Err(e) => { - error!("{}", e); - std::process::exit(1) - } + if let Some(session_file) = login::session_file_path() { + let _ = fs::remove_file(session_file); } return; } else { @@ -239,12 +226,11 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { } async fn create_ctx(cli: &mut Cli) -> Result<Context> { - let mut config = Config::load()?.unwrap_or_default(); - let crunchy = crunchyroll_session(cli, &mut config).await?; - Ok(Context { crunchy, config }) + let crunchy = crunchyroll_session(cli).await?; + Ok(Context { crunchy }) } -async fn crunchyroll_session(cli: &mut Cli, config: &mut Config) -> Result<Crunchyroll> { +async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { let supported_langs = vec![ Locale::ar_ME, Locale::de_DE, @@ -307,12 +293,20 @@ async fn crunchyroll_session(cli: &mut Cli, config: &mut Config) -> Result<Crunc let progress_handler = progress!("Logging in"); if root_login_methods_count + login_login_methods_count == 0 { - if let Some(auth) = &config.auth { - return match auth { - Auth::RefreshToken { token } => Ok(builder.login_with_refresh_token(token).await?), - Auth::EtpRt { token } => Ok(builder.login_with_etp_rt(token).await?), - Auth::Anonymous => Ok(builder.login_anonymously().await?), - }; + if let Some(login_file_path) = login::session_file_path() { + if login_file_path.exists() { + let session = fs::read_to_string(login_file_path)?; + if let Some((token_type, token)) = session.split_once(':') { + match token_type { + "refresh_token" => { + return Ok(builder.login_with_refresh_token(token).await?) + } + "etp_rt" => return Ok(builder.login_with_etp_rt(token).await?), + _ => (), + } + } + bail!("Could not read stored session ('{}')", session) + } } bail!("Please use a login method ('--credentials', '--etp-rt' or '--anonymous')") } else if root_login_methods_count + login_login_methods_count > 1 { diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index f7994e5..f5099d7 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -1,10 +1,12 @@ -use crate::utils::config::Auth; use crate::utils::context::Context; use crate::Execute; +use anyhow::bail; use anyhow::Result; use clap::Parser; use crunchyroll_rs::crunchyroll::SessionToken; use log::info; +use std::fs; +use std::path::PathBuf; #[derive(Debug, clap::Parser)] #[clap(about = "Save your login credentials persistent on disk")] @@ -18,18 +20,26 @@ pub struct Login { #[async_trait::async_trait(?Send)] impl Execute for Login { - async fn execute(self, mut ctx: Context) -> Result<()> { - let auth = match ctx.crunchy.session_token().await { - SessionToken::RefreshToken(token) => Auth::RefreshToken { token }, - SessionToken::EtpRt(token) => Auth::EtpRt { token }, - SessionToken::Anonymous => Auth::Anonymous, - }; - ctx.config.auth = Some(auth); - ctx.config.write()?; + async fn execute(self, ctx: Context) -> Result<()> { + if let Some(login_file_path) = session_file_path() { + fs::create_dir_all(login_file_path.parent().unwrap())?; - info!("Saved login"); + match ctx.crunchy.session_token().await { + SessionToken::RefreshToken(refresh_token) => { + fs::write(login_file_path, format!("refresh_token:{}", refresh_token))? + } + SessionToken::EtpRt(etp_rt) => { + fs::write(login_file_path, format!("etp_rt:{}", etp_rt))? + } + SessionToken::Anonymous => bail!("Anonymous login cannot be saved"), + } - Ok(()) + info!("Saved login"); + + Ok(()) + } else { + bail!("Cannot find config path") + } } } @@ -50,3 +60,7 @@ pub struct LoginMethod { #[arg(long, default_value_t = false)] pub anonymous: bool, } + +pub fn session_file_path() -> Option<PathBuf> { + dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli").join("session")) +} diff --git a/crunchy-cli-core/src/login/mod.rs b/crunchy-cli-core/src/login/mod.rs index 8c79d61..8c1220a 100644 --- a/crunchy-cli-core/src/login/mod.rs +++ b/crunchy-cli-core/src/login/mod.rs @@ -1,3 +1,3 @@ mod command; -pub use command::{Login, LoginMethod}; +pub use command::{session_file_path, Login, LoginMethod}; diff --git a/crunchy-cli-core/src/utils/config.rs b/crunchy-cli-core/src/utils/config.rs deleted file mode 100644 index 3206ca8..0000000 --- a/crunchy-cli-core/src/utils/config.rs +++ /dev/null @@ -1,59 +0,0 @@ -use anyhow::{bail, Result}; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::PathBuf; - -#[derive(Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -#[serde(tag = "method")] -pub enum Auth { - RefreshToken { token: String }, - EtpRt { token: String }, - Anonymous, -} - -#[derive(Default, Deserialize, Serialize)] -pub struct Config { - pub auth: Option<Auth>, -} - -impl Config { - pub fn load() -> Result<Option<Self>> { - let path = Config::assert_config_file_path(true)?; - - if let Some(p) = path { - if p.exists() { - let content = fs::read_to_string(p)?; - return Ok(Some(toml::from_str(&content)?)); - } - } - Ok(None) - } - - pub fn write(&self) -> Result<()> { - let path = Config::assert_config_file_path(false)?.unwrap(); - Ok(fs::write(path, toml::to_string(self)?)?) - } - - pub fn config_file_path() -> Option<PathBuf> { - dirs::config_dir().map(|config_dir| config_dir.join("crunchy-cli.conf")) - } - - fn assert_config_file_path(ignore_non_existing_config_dir: bool) -> Result<Option<PathBuf>> { - let Some(path) = Config::config_file_path() else { - if ignore_non_existing_config_dir { - return Ok(None) - } - bail!("Cannot find config directory") - }; - - if path.exists() && path.is_dir() { - bail!( - "Config path ({}) is a directory (must be a normal file)", - path.to_string_lossy() - ) - } - - Ok(Some(path)) - } -} diff --git a/crunchy-cli-core/src/utils/context.rs b/crunchy-cli-core/src/utils/context.rs index 3e9af73..f8df024 100644 --- a/crunchy-cli-core/src/utils/context.rs +++ b/crunchy-cli-core/src/utils/context.rs @@ -1,7 +1,5 @@ -use crate::utils::config::Config; use crunchyroll_rs::Crunchyroll; pub struct Context { pub crunchy: Crunchyroll, - pub config: Config, } diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index 510eeec..d46cc33 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -1,5 +1,4 @@ pub mod clap; -pub mod config; pub mod context; pub mod download; pub mod ffmpeg; From 566422cb06255c3af85ab3c3ac204d26f42633f0 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 17 Jul 2023 14:32:10 +0200 Subject: [PATCH 409/630] Update dependencies --- Cargo.lock | 112 ++++++++++++++++++------------------ crunchy-cli-core/Cargo.lock | 112 ++++++++++++++++++------------------ crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/lib.rs | 17 +++--- 4 files changed, 121 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc02df5..042c0f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "async-trait" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.11" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.11" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" dependencies = [ "anstream", "anstyle", @@ -269,9 +269,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", @@ -321,7 +321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.22", + "time 0.3.23", "version_check", ] @@ -338,7 +338,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.22", + "time 0.3.23", "url", ] @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be9aa4589c26829667cc6f32f7238b93cdc0c6c130b295b3714df7ea6c28ef1" +checksum = "a1fc76ad1ab97992a987dd2a5fadfa4e90fc69d337704f42b7eeb30f7fda1eb1" dependencies = [ "aes", "async-trait", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b718f70c2825f2310e2301295a3a66f1723bbdd39f6cfbe6148df00556e9c75e" +checksum = "2f9581dc7276f1c327dcaa91fa6d3b3f09c46018dc5a0d7815be3f8027780a07" dependencies = [ "darling", "quote", @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -469,9 +469,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", @@ -483,9 +483,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", @@ -494,9 +494,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef11db94edb7974469f90fe64eaa95b3a2b36fdd5f72420717f7ee56a1922988" +checksum = "58b55cd4a2bd4b541906e88adbd2242bda9a697517f638c844fa47fb56fe2f17" dependencies = [ "base64", "base64-serde", @@ -960,7 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.3", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -975,9 +975,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1160,15 +1160,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" +checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6" [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -1201,9 +1201,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] @@ -1251,9 +1251,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", @@ -1262,9 +1262,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -1351,9 +1351,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags 2.3.3", "errno", @@ -1364,9 +1364,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed" +checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" dependencies = [ "log", "ring", @@ -1395,9 +1395,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sanitize-filename" @@ -1421,18 +1421,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", @@ -1441,9 +1441,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -1484,7 +1484,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.22", + "time 0.3.23", ] [[package]] @@ -1561,9 +1561,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.23" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -1627,9 +1627,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "serde", @@ -1645,9 +1645,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -1778,9 +1778,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 5f9347b..f8e0970 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "async-trait" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.11" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.11" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" dependencies = [ "anstream", "anstyle", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", @@ -302,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.22", + "time 0.3.23", "version_check", ] @@ -319,7 +319,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.22", + "time 0.3.23", "url", ] @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be9aa4589c26829667cc6f32f7238b93cdc0c6c130b295b3714df7ea6c28ef1" +checksum = "a1fc76ad1ab97992a987dd2a5fadfa4e90fc69d337704f42b7eeb30f7fda1eb1" dependencies = [ "aes", "async-trait", @@ -397,9 +397,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b718f70c2825f2310e2301295a3a66f1723bbdd39f6cfbe6148df00556e9c75e" +checksum = "2f9581dc7276f1c327dcaa91fa6d3b3f09c46018dc5a0d7815be3f8027780a07" dependencies = [ "darling", "quote", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -438,9 +438,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", @@ -463,9 +463,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef11db94edb7974469f90fe64eaa95b3a2b36fdd5f72420717f7ee56a1922988" +checksum = "58b55cd4a2bd4b541906e88adbd2242bda9a697517f638c844fa47fb56fe2f17" dependencies = [ "base64", "base64-serde", @@ -929,7 +929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.3", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -944,9 +944,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1129,15 +1129,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" +checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6" [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -1170,9 +1170,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] @@ -1220,9 +1220,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", @@ -1231,9 +1231,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -1314,9 +1314,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags 2.3.3", "errno", @@ -1327,9 +1327,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed" +checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" dependencies = [ "log", "ring", @@ -1358,9 +1358,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sanitize-filename" @@ -1384,18 +1384,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", @@ -1404,9 +1404,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -1447,7 +1447,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.22", + "time 0.3.23", ] [[package]] @@ -1524,9 +1524,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.23" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -1590,9 +1590,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "serde", @@ -1608,9 +1608,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -1741,9 +1741,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 2ba5322..c35787e 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.4.0", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.5.0", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.10", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index c85df85..2b11f81 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -264,22 +264,21 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { lang }; - let proxy = cli.proxy.clone(); let mut builder = Crunchyroll::builder() - .client_builder(move || { - let mut client_builder = CrunchyrollBuilder::predefined_client_builder(); - if let Some(proxy) = &proxy { - client_builder = client_builder.proxy(proxy.clone()) - } - client_builder - }) .locale(locale) .stabilization_locales(cli.experimental_fixes) .stabilization_season_number(cli.experimental_fixes); - if let Command::Download(download) = &cli.command { builder = builder.preferred_audio_locale(download.audio.clone()) } + if let Some(p) = &cli.proxy { + builder = builder.client( + CrunchyrollBuilder::predefined_client_builder() + .proxy(p.clone()) + .build() + .unwrap(), + ) + } let root_login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 From 4ec9a0d309dab8904dc2fcc00b8d847eca153a88 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 17 Jul 2023 16:08:54 +0200 Subject: [PATCH 410/630] Add native-tls/openssl tls backend for linux --- Cargo.lock | 167 ++++++++++++++++++++++++++++++++++++ crunchy-cli-core/Cargo.toml | 6 ++ crunchy-cli-core/src/lib.rs | 35 +++++++- 3 files changed, 207 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 042c0f0..e0b4ab4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,6 +342,16 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -614,6 +624,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -833,6 +858,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1072,6 +1110,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.26.2" @@ -1134,6 +1190,60 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.26.0+1.1.1u" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1158,6 +1268,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "portable-atomic" version = "1.4.1" @@ -1284,10 +1400,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1297,6 +1415,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-socks", "tower-service", @@ -1409,6 +1528,15 @@ dependencies = [ "regex", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "sct" version = "0.7.0" @@ -1419,6 +1547,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.171" @@ -1696,6 +1847,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1820,6 +1981,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index c35787e..5f8f4bc 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -30,5 +30,11 @@ tempfile = "3.6" tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" +[target.'cfg(target_os = "linux")'.dependencies] +reqwest = { version = "0.11", default-features = false, features = ["socks", "native-tls-alpn", "native-tls-vendored"] } + +[target.'cfg(not(target_os = "linux"))'.dependencies] +reqwest = { version = "0.11", default-features = false, features = ["socks"] } + [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 2b11f81..7d0fbfa 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -8,7 +8,7 @@ use crunchyroll_rs::crunchyroll::CrunchyrollBuilder; use crunchyroll_rs::error::CrunchyrollError; use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; -use reqwest::Proxy; +use reqwest::{Client, ClientBuilder, Proxy, StatusCode}; use std::{env, fs}; mod archive; @@ -264,8 +264,19 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { lang }; + let proxy = cli.proxy.clone(); let mut builder = Crunchyroll::builder() .locale(locale) + .client( + get_client(|| { + if let Some(p) = &proxy { + CrunchyrollBuilder::predefined_client_builder().proxy(p.clone()) + } else { + CrunchyrollBuilder::predefined_client_builder() + } + }) + .await?, + ) .stabilization_locales(cli.experimental_fixes) .stabilization_season_number(cli.experimental_fixes); if let Command::Download(download) = &cli.command { @@ -340,3 +351,25 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { Ok(crunchy) } + +#[cfg(target_os = "linux")] +async fn get_client<F: Fn() -> ClientBuilder>(f: F) -> Result<Client> { + let client = f().build().unwrap(); + if client + .get("https://www.crunchyroll.com") + .send() + .await? + .status() + != StatusCode::FORBIDDEN + { + return Ok(client); + } + + debug!("rustls tls backend probably triggered the cloudflare bot check, using openssl instead"); + Ok(f().use_native_tls().build().unwrap()) +} + +#[cfg(not(target_os = "linux"))] +async fn get_client<F: Fn() -> ClientBuilder>(f: F) -> Result<Client> { + Ok(f().build().unwrap()) +} From dc6bc0d9516e8d7bb19d1f8a02189987a4c53643 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 20 Jul 2023 13:46:09 +0200 Subject: [PATCH 411/630] Add openssl tls backend for all platforms --- Cargo.lock | 18 +++- Cargo.toml | 6 ++ crunchy-cli-core/Cargo.lock | 179 ++++++++++++++++++++++++++++++++++++ crunchy-cli-core/Cargo.toml | 12 ++- crunchy-cli-core/src/lib.rs | 47 +++------- 5 files changed, 221 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0b4ab4..34b7d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -396,6 +396,7 @@ dependencies = [ "indicatif", "lazy_static", "log", + "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git)", "num_cpus", "regex", "reqwest", @@ -866,7 +867,7 @@ checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", - "native-tls", + "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio", "tokio-native-tls", ] @@ -1128,6 +1129,17 @@ dependencies = [ "tempfile", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git#bdedf02f48372efeccdf4323920c21bb1a044788" +dependencies = [ + "log", + "openssl", + "openssl-probe", + "openssl-sys", +] + [[package]] name = "nix" version = "0.26.2" @@ -1405,7 +1417,7 @@ dependencies = [ "js-sys", "log", "mime", - "native-tls", + "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "percent-encoding", "pin-project-lite", @@ -1853,7 +1865,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "native-tls", + "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 4deb9d7..cab33ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,12 @@ authors = ["Crunchy Labs Maintainers"] version = "3.0.0-dev.14" edition = "2021" +[features] +default = ["openssl-static"] + +openssl = ["crunchy-cli-core/openssl"] +openssl-static = ["crunchy-cli-core/openssl-static"] + [dependencies] tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"], default-features = false } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index f8e0970..6297bf2 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -323,6 +323,16 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -355,6 +365,7 @@ dependencies = [ "indicatif", "lazy_static", "log", + "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git)", "num_cpus", "regex", "reqwest", @@ -583,6 +594,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -802,6 +828,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1041,6 +1080,35 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git#bdedf02f48372efeccdf4323920c21bb1a044788" +dependencies = [ + "log", + "openssl", + "openssl-probe", + "openssl-sys", +] + [[package]] name = "nix" version = "0.26.2" @@ -1103,6 +1171,60 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.26.0+1.1.1u" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1127,6 +1249,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "portable-atomic" version = "1.4.1" @@ -1253,10 +1381,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "percent-encoding", "pin-project-lite", @@ -1266,6 +1396,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-socks", "tower-service", @@ -1372,6 +1503,15 @@ dependencies = [ "regex", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "sct" version = "0.7.0" @@ -1382,6 +1522,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.171" @@ -1659,6 +1822,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1783,6 +1956,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5f8f4bc..522a4bd 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -4,6 +4,10 @@ authors = ["Crunchy Labs Maintainers"] version = "3.0.0-dev.14" edition = "2021" +[features] +openssl = ["dep:native-tls", "reqwest/native-tls"] +openssl-static = ["dep:native-tls", "native-tls?/vendored", "reqwest/native-tls-vendored"] + [dependencies] anyhow = "1.0" async-trait = "0.1" @@ -30,11 +34,9 @@ tempfile = "3.6" tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" -[target.'cfg(target_os = "linux")'.dependencies] -reqwest = { version = "0.11", default-features = false, features = ["socks", "native-tls-alpn", "native-tls-vendored"] } - -[target.'cfg(not(target_os = "linux"))'.dependencies] -reqwest = { version = "0.11", default-features = false, features = ["socks"] } +# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports +# `rustls` and `native-tls` as tls backend +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", optional = true } [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 7d0fbfa..9132204 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -8,7 +8,7 @@ use crunchyroll_rs::crunchyroll::CrunchyrollBuilder; use crunchyroll_rs::error::CrunchyrollError; use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; -use reqwest::{Client, ClientBuilder, Proxy, StatusCode}; +use reqwest::Proxy; use std::{env, fs}; mod archive; @@ -267,16 +267,19 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { let proxy = cli.proxy.clone(); let mut builder = Crunchyroll::builder() .locale(locale) - .client( - get_client(|| { - if let Some(p) = &proxy { - CrunchyrollBuilder::predefined_client_builder().proxy(p.clone()) - } else { - CrunchyrollBuilder::predefined_client_builder() - } - }) - .await?, - ) + .client({ + let builder = if let Some(p) = &proxy { + CrunchyrollBuilder::predefined_client_builder().proxy(p.clone()) + } else { + CrunchyrollBuilder::predefined_client_builder() + }; + #[cfg(any(feature = "openssl", feature = "openssl-static"))] + let client = builder.use_native_tls().build().unwrap(); + #[cfg(not(any(feature = "openssl", feature = "openssl-static")))] + let client = builder.build().unwrap(); + + client + }) .stabilization_locales(cli.experimental_fixes) .stabilization_season_number(cli.experimental_fixes); if let Command::Download(download) = &cli.command { @@ -351,25 +354,3 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { Ok(crunchy) } - -#[cfg(target_os = "linux")] -async fn get_client<F: Fn() -> ClientBuilder>(f: F) -> Result<Client> { - let client = f().build().unwrap(); - if client - .get("https://www.crunchyroll.com") - .send() - .await? - .status() - != StatusCode::FORBIDDEN - { - return Ok(client); - } - - debug!("rustls tls backend probably triggered the cloudflare bot check, using openssl instead"); - Ok(f().use_native_tls().build().unwrap()) -} - -#[cfg(not(target_os = "linux"))] -async fn get_client<F: Fn() -> ClientBuilder>(f: F) -> Result<Client> { - Ok(f().build().unwrap()) -} From 00e8082e660a8242053fb53e4ea4abd1e24cf765 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 20 Jul 2023 14:21:57 +0200 Subject: [PATCH 412/630] Remove test ci step --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb4c0c0..478ee43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,9 +50,6 @@ jobs: toolchain: stable target: ${{ matrix.toolchain }} - - name: Test - run: cargo test --release --all-features --target ${{ matrix.toolchain }} - - name: Build run: cargo build --release --all-features --target ${{ matrix.toolchain }} From 068c0fcac1f77cf4d555cd9dc72f3879f79cc30c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 20 Jul 2023 18:07:32 +0200 Subject: [PATCH 413/630] Split ci platforms in separate jobs --- .github/workflows/ci.yml | 98 ++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 478ee43..753ea7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,23 +8,8 @@ on: workflow_dispatch: jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: ubuntu-latest - toolchain: x86_64-unknown-linux-musl - platform: linux - ext: - - os: windows-latest - toolchain: x86_64-pc-windows-gnu - platform: windows - ext: .exe - - os: macos-latest - toolchain: x86_64-apple-darwin - platform: darwin - ext: + build-linux: + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 @@ -41,35 +26,100 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install system dependencies - if: matrix.platform == 'linux' run: sudo apt-get install musl-tools - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable - target: ${{ matrix.toolchain }} + target: x86_64-unknown-linux-musl - name: Build - run: cargo build --release --all-features --target ${{ matrix.toolchain }} + run: cargo build --release --all-features --target x86_64-unknown-linux-musl - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli_${{ matrix.platform }} - path: ./target/${{ matrix.toolchain }}/release/crunchy-cli${{ matrix.ext }} + name: crunchy-cli_linux + path: ./target/x86_64-unknown-linux-musl/release/crunchy-cli if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: name: manpages - path: ./target/${{ matrix.toolchain }}/release/manpages + path: ./target/x86_64-unknown-linux-musl/release/manpages if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: name: completions - path: ./target/${{ matrix.toolchain }}/release/completions + path: ./target/x86_64-unknown-linux-musl/release/completions + if-no-files-found: error + + build-mac: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + target: x86_64-apple-darwin + + - name: Build + run: cargo build --release --all-features --target x86_64-apple-darwin + + - name: Upload binary artifact + uses: actions/upload-artifact@v3 + with: + name: crunchy-cli_darwin + path: ./target/x86_64-apple-darwin/release/crunchy-cli + if-no-files-found: error + + build-windows: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install system dependencies + uses: msys2/setup-msys2@v2 + with: + install: mingw-w64-x86_64-rust base-devel + + - name: Build + shell: msys2 {0} + run: cargo build --release --all-features --target x86_64-pc-windows-gnu + + - name: Upload binary artifact + uses: actions/upload-artifact@v3 + with: + name: crunchy-cli_windows + path: ./target/x86_64-pc-windows-gnu/release/crunchy-cli.exe if-no-files-found: error From 5afda0b5f17027e6bf4a54d9d952e4fec07bdcdb Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 20 Jul 2023 19:59:45 +0200 Subject: [PATCH 414/630] Add openssl alpn support --- crunchy-cli-core/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 522a4bd..8b9f7f4 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -5,8 +5,8 @@ version = "3.0.0-dev.14" edition = "2021" [features] -openssl = ["dep:native-tls", "reqwest/native-tls"] -openssl-static = ["dep:native-tls", "native-tls?/vendored", "reqwest/native-tls-vendored"] +openssl = ["dep:native-tls", "reqwest/native-tls-alpn"] +openssl-static = ["dep:native-tls", "native-tls?/vendored", "reqwest/native-tls-alpn", "reqwest/native-tls-vendored"] [dependencies] anyhow = "1.0" From db156d361fe9bcce70453c7fcd8b084549c766f5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 21 Jul 2023 13:56:07 +0200 Subject: [PATCH 415/630] Update dependencies and version --- Cargo.lock | 101 ++++++++++++------------------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.lock | 99 ++++++++++++----------------------- crunchy-cli-core/Cargo.toml | 4 +- 4 files changed, 68 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34b7d0b..362e019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.12" +version = "4.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" +checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.12" +version = "4.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" +checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" dependencies = [ "anstream", "anstyle", @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.14" +version = "3.0.0-dev.15" dependencies = [ "chrono", "clap", @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.14" +version = "3.0.0-dev.15" dependencies = [ "anyhow", "async-trait", @@ -396,7 +396,7 @@ dependencies = [ "indicatif", "lazy_static", "log", - "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git)", + "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d)", "num_cpus", "regex", "reqwest", @@ -612,12 +612,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fnv" @@ -975,17 +972,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.8.0" @@ -999,7 +985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.4", + "rustix", "windows-sys 0.48.0", ] @@ -1039,12 +1025,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.3" @@ -1132,7 +1112,7 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git#bdedf02f48372efeccdf4323920c21bb1a044788" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" dependencies = [ "log", "openssl", @@ -1164,9 +1144,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] @@ -1466,20 +1446,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.4" @@ -1489,7 +1455,7 @@ dependencies = [ "bitflags 2.3.3", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys", "windows-sys 0.48.0", ] @@ -1584,18 +1550,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.171" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" dependencies = [ "proc-macro2", "quote", @@ -1636,9 +1602,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" dependencies = [ "base64", "chrono", @@ -1652,9 +1618,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" dependencies = [ "darling", "proc-macro2", @@ -1724,9 +1690,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.26" +version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" dependencies = [ "proc-macro2", "quote", @@ -1745,32 +1711,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.37.23", + "rustix", "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index cab33ae..aa52a9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.14" +version = "3.0.0-dev.15" edition = "2021" [features] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 6297bf2..f3acc59 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -109,9 +109,9 @@ checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.12" +version = "4.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" +checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.12" +version = "4.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" +checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" dependencies = [ "anstream", "anstyle", @@ -350,7 +350,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.14" +version = "3.0.0-dev.15" dependencies = [ "anyhow", "async-trait", @@ -365,7 +365,7 @@ dependencies = [ "indicatif", "lazy_static", "log", - "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git)", + "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d)", "num_cpus", "regex", "reqwest", @@ -581,12 +581,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fnv" @@ -944,17 +941,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.8.0" @@ -968,7 +954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.4", + "rustix", "windows-sys 0.48.0", ] @@ -1008,12 +994,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.3" @@ -1101,7 +1081,7 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git#bdedf02f48372efeccdf4323920c21bb1a044788" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" dependencies = [ "log", "openssl", @@ -1133,9 +1113,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] @@ -1429,20 +1409,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.4" @@ -1452,7 +1418,7 @@ dependencies = [ "bitflags 2.3.3", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys", "windows-sys 0.48.0", ] @@ -1547,18 +1513,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.171" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" dependencies = [ "proc-macro2", "quote", @@ -1599,9 +1565,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" dependencies = [ "base64", "chrono", @@ -1615,9 +1581,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" dependencies = [ "darling", "proc-macro2", @@ -1687,9 +1653,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.26" +version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" dependencies = [ "proc-macro2", "quote", @@ -1708,32 +1674,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.37.23", + "rustix", "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 8b9f7f4..4a6a229 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.14" +version = "3.0.0-dev.15" edition = "2021" [features] @@ -36,7 +36,7 @@ sys-locale = "0.3" # fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports # `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", optional = true } +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d", features = ["alpn"], optional = true } [build-dependencies] chrono = "0.4" From 9ced3483d87416441cf1e0d6a1e8f5c61c8032c4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 22 Jul 2023 15:13:24 +0200 Subject: [PATCH 416/630] Error if download series has an episode in an unexpected language and input url is a series url (#225) --- crunchy-cli-core/src/download/filter.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index b04aef8..31c0db6 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -14,6 +14,7 @@ pub(crate) struct DownloadFilter { interactive_input: bool, season_episode_count: HashMap<u32, Vec<String>>, season_subtitles_missing: Vec<u32>, + season_visited: bool, } impl DownloadFilter { @@ -24,6 +25,7 @@ impl DownloadFilter { interactive_input, season_episode_count: HashMap::new(), season_subtitles_missing: vec![], + season_visited: false, } } } @@ -101,6 +103,8 @@ impl Filter for DownloadFilter { } async fn visit_season(&mut self, season: Season) -> Result<Vec<Episode>> { + self.season_visited = true; + let mut episodes = season.episodes().await?; if Format::has_relative_episodes_fmt(&self.download.output) { @@ -139,14 +143,22 @@ impl Filter for DownloadFilter { .await? .contains(&self.download.audio) { - bail!( + let error_message = format!( "Episode {} ({}) of {} season {} is not available with {} audio", episode.episode_number, episode.title, episode.series_title, episode.season_number, self.download.audio - ) + ); + // sometimes a series randomly has episode in an other language. if this is the case, + // only error if the input url was a episode url + if self.season_visited { + warn!("{}", error_message); + return Ok(None); + } else { + bail!("{}", error_message) + } } // overwrite the current episode with the other version episode episode = episode From 4c396a9e4a2c107fc5a237ba8869412446ccc016 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 26 Jul 2023 19:17:10 +0200 Subject: [PATCH 417/630] Remove option to configure ffmpeg args with env variables --- crunchy-cli-core/src/utils/ffmpeg.rs | 32 ++++++---------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 3336fca..210b0f7 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -1,12 +1,11 @@ use lazy_static::lazy_static; use regex::Regex; -use std::env; use std::str::FromStr; #[derive(Clone, Debug, Eq, PartialEq)] pub enum FFmpegPreset { Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality), - Custom(Option<String>, Option<String>), + Custom(Option<String>), } lazy_static! { @@ -81,7 +80,7 @@ ffmpeg_enum! { impl Default for FFmpegPreset { fn default() -> Self { - Self::Custom(None, Some("-c:v copy -c:a copy".to_string())) + Self::Custom(Some("-c:v copy -c:a copy".to_string())) } } @@ -181,27 +180,8 @@ impl FFmpegPreset { } pub(crate) fn parse(s: &str) -> Result<FFmpegPreset, String> { - let env_ffmpeg_input_args = env::var("FFMPEG_INPUT_ARGS").ok(); - let env_ffmpeg_output_args = env::var("FFMPEG_OUTPUT_ARGS").ok(); - - if env_ffmpeg_input_args.is_some() || env_ffmpeg_output_args.is_some() { - if let Some(input) = &env_ffmpeg_input_args { - if shlex::split(input).is_none() { - return Err(format!("Failed to find custom ffmpeg input '{}' (`FFMPEG_INPUT_ARGS` env variable)", input)); - } - } - if let Some(output) = &env_ffmpeg_output_args { - if shlex::split(output).is_none() { - return Err(format!("Failed to find custom ffmpeg output '{}' (`FFMPEG_INPUT_ARGS` env variable)", output)); - } - } - - return Ok(FFmpegPreset::Custom( - env_ffmpeg_input_args, - env_ffmpeg_output_args, - )); - } else if !PREDEFINED_PRESET.is_match(s) { - return Ok(FFmpegPreset::Custom(None, Some(s.to_string()))); + if !PREDEFINED_PRESET.is_match(s) { + return Ok(FFmpegPreset::Custom(Some(s.to_string()))); } let mut codec: Option<FFmpegCodec> = None; @@ -272,8 +252,8 @@ impl FFmpegPreset { pub(crate) fn into_input_output_args(self) -> (Vec<String>, Vec<String>) { match self { - FFmpegPreset::Custom(input, output) => ( - input.map_or(vec![], |i| shlex::split(&i).unwrap_or_default()), + FFmpegPreset::Custom(output) => ( + vec![], output.map_or(vec![], |o| shlex::split(&o).unwrap_or_default()), ), FFmpegPreset::Predefined(codec, hwaccel_opt, quality) => { From 84c70f2bee6525b49b87e34bc5cf1209f4a7a105 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 26 Jul 2023 20:51:34 +0200 Subject: [PATCH 418/630] Add workaround for incorrect hardsub labeling (#231) --- Cargo.lock | 17 +++++++---------- crunchy-cli-core/Cargo.lock | 17 +++++++---------- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/utils/video.rs | 23 ++++++++++++++++++++++- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 362e019..56ec987 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fc76ad1ab97992a987dd2a5fadfa4e90fc69d337704f42b7eeb30f7fda1eb1" +checksum = "a0b33d2464e990dec5d3e6265cc892a88ab89971cfd177b7d7c7d0e9f8cde817" dependencies = [ "aes", "async-trait", @@ -434,14 +434,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.24.0", + "webpki-roots 0.25.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9581dc7276f1c327dcaa91fa6d3b3f09c46018dc5a0d7815be3f8027780a07" +checksum = "cab3e4af975066a3dc3dd0bb50b1a29c4a3cdee5365e8b6559d21aa15d9ace6a" dependencies = [ "darling", "quote", @@ -2088,12 +2088,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" -dependencies = [ - "rustls-webpki", -] +checksum = "1a4ac452058d835c2b7ff6d74f0ad9f40e172bb1ce661b1444f397eeb1d19e6d" [[package]] name = "winapi" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index f3acc59..a5f9b1d 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fc76ad1ab97992a987dd2a5fadfa4e90fc69d337704f42b7eeb30f7fda1eb1" +checksum = "a0b33d2464e990dec5d3e6265cc892a88ab89971cfd177b7d7c7d0e9f8cde817" dependencies = [ "aes", "async-trait", @@ -403,14 +403,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.24.0", + "webpki-roots 0.25.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9581dc7276f1c327dcaa91fa6d3b3f09c46018dc5a0d7815be3f8027780a07" +checksum = "cab3e4af975066a3dc3dd0bb50b1a29c4a3cdee5365e8b6559d21aa15d9ace6a" dependencies = [ "darling", "quote", @@ -2051,12 +2051,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" -dependencies = [ - "rustls-webpki", -] +checksum = "1a4ac452058d835c2b7ff6d74f0ad9f40e172bb1ce661b1444f397eeb1d19e6d" [[package]] name = "winapi" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 4a6a229..f7a3f43 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -13,7 +13,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.5.0", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.5.1", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.10", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index f2fabd4..5b3eaeb 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -1,11 +1,32 @@ use anyhow::Result; use crunchyroll_rs::media::{Resolution, Stream, VariantData}; +use crunchyroll_rs::Locale; pub async fn variant_data_from_stream( stream: &Stream, resolution: &Resolution, ) -> Result<Option<(VariantData, VariantData)>> { - let mut streaming_data = stream.dash_streaming_data(None).await?; + // sometimes Crunchyroll marks episodes without real subtitles that they have subtitles and + // reports that only hardsub episode are existing. the following lines are trying to prevent + // potential errors which might get caused by this incorrect reporting + // (https://github.com/crunchy-labs/crunchy-cli/issues/231) + let mut hardsub_locales = stream.streaming_hardsub_locales(); + let hardsub_locale = if !hardsub_locales.contains(&Locale::Custom("".to_string())) + && !hardsub_locales.contains(&Locale::Custom(":".to_string())) + { + // if only one hardsub locale exists, assume that this stream doesn't really contains hardsubs + if hardsub_locales.len() == 1 { + Some(hardsub_locales.remove(0)) + } else { + // fallback to `None`. this should trigger an error message in `stream.dash_streaming_data` + // that the requested stream is not available + None + } + } else { + None + }; + + let mut streaming_data = stream.dash_streaming_data(hardsub_locale).await?; streaming_data .0 .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); From 700b041f9a2a9c882e1cb6d3a008eeb461e66c0f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 27 Jul 2023 14:18:53 +0200 Subject: [PATCH 419/630] Remove deprecated archive --locale flag --- crunchy-cli-core/src/archive/command.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 9a2ee21..398f6c7 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -15,7 +15,7 @@ use anyhow::Result; use chrono::Duration; use crunchyroll_rs::media::{Resolution, Subtitle}; use crunchyroll_rs::Locale; -use log::{debug, warn}; +use log::debug; use std::collections::HashMap; use std::path::PathBuf; @@ -29,9 +29,6 @@ pub struct Archive { Available languages are:\n {}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] pub(crate) audio: Vec<Locale>, - #[arg(help = "Deprecated. Use '-a' / '--audio' instead")] - #[arg(short, long)] - locale: Vec<Locale>, #[arg(help = format!("Subtitle languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ @@ -122,15 +119,6 @@ impl Execute for Archive { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") } - if !self.locale.is_empty() { - warn!("The '-l' / '--locale' flag is deprecated, use '-a' / '--audio' instead"); - for locale in &self.locale { - if !self.audio.contains(locale) { - self.audio.push(locale.clone()) - } - } - } - self.audio = all_locale_in_locales(self.audio.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); From b1342d54f33dd8b11bfbbacec9dbc0edc4c3c769 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 27 Jul 2023 21:25:51 +0200 Subject: [PATCH 420/630] Change name of output artifacts --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 753ea7c..870c99e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli_linux + name: crunchy-cli-linux-x86_64 path: ./target/x86_64-unknown-linux-musl/release/crunchy-cli if-no-files-found: error @@ -87,7 +87,7 @@ jobs: - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli_darwin + name: crunchy-cli-darwin-x86_64 path: ./target/x86_64-apple-darwin/release/crunchy-cli if-no-files-found: error @@ -120,6 +120,6 @@ jobs: - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli_windows + name: crunchy-cli-windows-x86_64 path: ./target/x86_64-pc-windows-gnu/release/crunchy-cli.exe if-no-files-found: error From 435b75bbf9c80c4a8aac911825779df747f2f8e7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 27 Jul 2023 22:05:25 +0200 Subject: [PATCH 421/630] Add aarch64 architecture to linux ci --- .github/workflows/ci.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 870c99e..3cdfd77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,13 @@ on: jobs: build-linux: runs-on: ubuntu-latest + strategy: + matrix: + include: + - arch: x86_64 + toolchain: x86_64-unknown-linux-musl + - arch: aarch64 + toolchain: aarch64-unknown-linux-musl steps: - name: Checkout uses: actions/checkout@v3 @@ -25,37 +32,31 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install system dependencies - run: sudo apt-get install musl-tools - - - name: Install toolchain - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - target: x86_64-unknown-linux-musl + - name: Install cross + run: cargo install cross - name: Build - run: cargo build --release --all-features --target x86_64-unknown-linux-musl + run: cross build --release --all-features --target ${{ matrix.toolchain }} - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli-linux-x86_64 - path: ./target/x86_64-unknown-linux-musl/release/crunchy-cli + name: crunchy-cli-linux-${{ matrix.arch }} + path: ./target/${{ matrix.toolchain }}/release/crunchy-cli if-no-files-found: error - name: Upload manpages artifact uses: actions/upload-artifact@v3 with: name: manpages - path: ./target/x86_64-unknown-linux-musl/release/manpages + path: ./target/${{ matrix.toolchain }}/release/manpages if-no-files-found: error - name: Upload completions artifact uses: actions/upload-artifact@v3 with: name: completions - path: ./target/x86_64-unknown-linux-musl/release/completions + path: ./target/${{ matrix.toolchain }}/release/completions if-no-files-found: error build-mac: From 0586f38cdc1bed3a1bad0c579f4b728590036a7d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 7 Aug 2023 15:33:42 +0200 Subject: [PATCH 422/630] Update ffmpeg preset help message --- crunchy-cli-core/src/archive/command.rs | 7 +++---- crunchy-cli-core/src/download/command.rs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 398f6c7..10133f2 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -75,11 +75,10 @@ pub struct Archive { #[arg(value_parser = MergeBehavior::parse)] pub(crate) merge: MergeBehavior, - #[arg(help = format!("Presets for video converting. Can be used multiple times. \ + #[arg(help = format!("Presets for converting the video to a specific coding format. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] - #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ - Generally used to minify the file size with keeping (nearly) the same quality. \ - It is recommended to only use this if you archive videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ + #[arg(long_help = format!("Presets for converting the video to a specific coding format. \ + If you need more specific ffmpeg customizations you can pass ffmpeg output arguments instead of a preset as value. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index ac1e391..1ce731d 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -59,11 +59,10 @@ pub struct Download { #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] pub(crate) resolution: Resolution, - #[arg(help = format!("Presets for video converting. Can be used multiple times. \ + #[arg(help = format!("Presets for converting the video to a specific coding format. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] - #[arg(long_help = format!("Presets for video converting. Can be used multiple times. \ - Generally used to minify the file size with keeping (nearly) the same quality. \ - It is recommended to only use this if you download videos with high resolutions since low resolution videos tend to result in a larger file with any of the provided presets. \ + #[arg(long_help = format!("Presets for converting the video to a specific coding format. \ + If you need more specific ffmpeg customizations you can pass ffmpeg output arguments instead of a preset as value. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] From 2bcaa6e4d5fce93be98c629f5604e5dca2fa3c9d Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:40:05 +0100 Subject: [PATCH 423/630] Add ci to publish AUR package on release (#233) --- .github/scripts/PKGBUILD.binary | 39 +++++++++++++++++++ .github/scripts/PKGBUILD.source | 34 ++++++++++++++++ .github/workflows/publish.yml | 69 +++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 .github/scripts/PKGBUILD.binary create mode 100644 .github/scripts/PKGBUILD.source create mode 100644 .github/workflows/publish.yml diff --git a/.github/scripts/PKGBUILD.binary b/.github/scripts/PKGBUILD.binary new file mode 100644 index 0000000..c9cc76e --- /dev/null +++ b/.github/scripts/PKGBUILD.binary @@ -0,0 +1,39 @@ +# Maintainer: ByteDream +pkgname=crunchy-cli-bin +pkgdesc="Command-line downloader for Crunchyroll" +arch=('x86_64') +url="https://github.com/crunchy-labs/crunchy-cli" +license=('MIT') + +pkgver=$CI_PKG_VERSION +pkgrel=1 + +depends=('ffmpeg') +source=( + "crunchy-cli::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-linux-x86_64" + "manpages.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-manpages.zip" + "completions.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-completions.zip" + "LICENSE::https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/v${pkgver}/LICENSE" +) +noextract=("manpages.zip" "completions.zip") +sha256sums=('$CI_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') + +package() { + cd "$srcdir" + + # all files in manpages.zip and completions.zip are stored in root of the archive, makepkg extracts them all to $srcdir + # which makes it pretty messy. so the extraction is done manually to keep the content of $srcdir structured + mkdir manpages completions + cd manpages + bsdtar -xf ../manpages.zip + cd ../completions + bsdtar -xf ../completions.zip + cd .. + + install -Dm755 crunchy-cli $pkgdir/usr/bin/crunchy-cli + install -Dm644 manpages/* -t $pkgdir/usr/share/man/man1 + install -Dm644 completions/crunchy-cli.bash $pkgdir/usr/share/bash-completions/completions/crunchy-cli + install -Dm644 completions/_crunchy-cli $pkgdir/usr/share/zsh/site-functions/_crunchy-cli + install -Dm644 completions/crunchy-cli.fish $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish + install -Dm644 LICENSE $pkgdir/usr/share/licenses/crunchy-cli/LICENSE +} diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source new file mode 100644 index 0000000..737a699 --- /dev/null +++ b/.github/scripts/PKGBUILD.source @@ -0,0 +1,34 @@ +# Maintainer: ByteDream +pkgname=crunchy-cli +pkgdesc="Command-line downloader for Crunchyroll" +arch=('x86_64' 'i686' 'arm' 'armv6h' 'armv7h' 'aarch64') +url="https://github.com/crunchy-labs/crunchy-cli" +license=('MIT') + +pkgver=$CI_PKG_VERSION +pkgrel=1 + +depends=('ffmpeg' 'openssl') +makedepends=('cargo') +source=("${pkgname}-${pkgver}.tar.gz::https://github.com/crunchy-labs/crunchy-cli/archive/refs/tags/v${pkgver}.tar.gz") +sha256sums=('$CI_SHA_SUM') + +build() { + cd "$srcdir/${pkgname}-$pkgver" + + export CARGO_HOME="$srcdir/cargo-home" + export RUSTUP_TOOLCHAIN=stable + + cargo build --release --no-default-features --features openssl +} + +package() { + cd "$srcdir/${pkgname}-$pkgver" + + install -Dm755 target/release/crunchy-cli $pkgdir/usr/bin/crunchy-cli + install -Dm644 target/release/manpages/* $pkgdir/usr/share/man/man1 + install -Dm644 target/release/completions/crunchy-cli.bash $pkgdir/usr/share/bash-completions/completions/crunchy-cli + install -Dm644 target/release/completions/_crunchy-cli $pkgdir/usr/share/zsh/site-functions/_crunchy-cli + install -Dm644 target/release/completions/crunchy-cli.fish $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish + install -Dm644 LICENSE $pkgdir/usr/share/licenses/crunchy-cli/LICENSE +} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..37f4379 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,69 @@ +name: publish + +on: + push: + tags: + - v* + +jobs: + publish-aur: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get version + run: echo "RELEASE_VERSION=$(echo ${{ github.ref_name }} | cut -c 2-)" >> $GITHUB_ENV + + - name: Generate crunchy-cli sha sum + run: | + curl -LO https://github.com/crunchy-labs/crunchy-cli/archive/refs/tags/${{ github.ref_name }}.tar.gz + echo "CRUNCHY_CLI_SHA256=$(sha256sum ${{ github.ref_name }}.tar.gz | cut -f 1 -d ' ')" >> $GITHUB_ENV + + - name: Generate crunchy-cli PKGBUILD + env: + CI_PKG_VERSION: ${{ env.RELEASE_VERSION }} + CI_SHA_SUM: ${{ env.CRUNCHY_CLI_SHA256 }} + run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM' < .github/scripts/PKGBUILD.source > PKGBUILD + + - name: Publish crunchy-cli to AUR + uses: KSXGitHub/github-actions-deploy-aur@2.7.0 + with: + pkgname: crunchy-cli + pkgbuild: ./PKGBUILD + commit_username: release-action + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: Update to version {{ env.RELEASE_VERSION }} + test: true + + - name: Generate crunchy-cli-bin sha sums + run: | + curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-x86_64-linux + curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-completions.zip + curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-manpages.zip + curl -LO https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/${{ github.ref_name }}/LICENSE + echo "CRUNCHY_CLI_BIN_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-x86_64-linux | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_COMPLETIONS_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-completions.zip | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_MANPAGES_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-manpages.zip | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_LICENSE_SHA256=$(sha256sum LICENSE | cut -f 1 -d ' ')" >> $GITHUB_ENV + + - name: Generate crunchy-cli-bin PKGBUILD + env: + CI_PKG_VERSION: ${{ env.RELEASE_VERSION }} + CI_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_SHA256 }} + CI_MANPAGES_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_MANPAGES_SHA256 }} + CI_COMPLETIONS_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_COMPLETIONS_SHA256 }} + CI_LICENSE_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_LICENSE_SHA256 }} + run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD + + - name: Publish crunchy-cli-bin to AUR + uses: KSXGitHub/github-actions-deploy-aur@2.7.0 + with: + pkgname: crunchy-cli-bin + pkgbuild: ./PKGBUILD + commit_username: release-action + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: Update to version {{ env.RELEASE_VERSION }} + test: true From 6f40ffacec966268de3888de3e550fdcd8384ec6 Mon Sep 17 00:00:00 2001 From: ByteDream <63594396+ByteDream@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:58:51 +0100 Subject: [PATCH 424/630] Change license (#223) --- LICENSE | 699 ++---------------------------------------------------- README.md | 2 +- 2 files changed, 26 insertions(+), 675 deletions(-) diff --git a/LICENSE b/LICENSE index f288702..512eb1b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,25 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - <program> Copyright (C) <year> <name of author> - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -<https://www.gnu.org/licenses/>. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -<https://www.gnu.org/licenses/why-not-lgpl.html>. +Copyright (c) 2023-NOW Crunchy Labs Team + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index ca87732..1b2a37b 100644 --- a/README.md +++ b/README.md @@ -293,4 +293,4 @@ This tool is **ONLY** meant for private use. You need a subscription to [`๐Ÿ’ณ C # โš– License -This project is licensed under the GNU General Public License v3.0 (GPL-3.0) - see the [LICENSE](LICENSE) file for more details. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details. From a12a8bc3661cc9263fa76f35644a9dc3e32a9ab7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 7 Aug 2023 16:19:18 +0200 Subject: [PATCH 425/630] Update README --- README.md | 269 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 213 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 1b2a37b..a8ec76e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # crunchy-cli -A pure [Rust](https://www.rust-lang.org/) CLI for [Crunchyroll](https://www.crunchyroll.com). +๐Ÿ‘‡ A Command-line downloader for Crunchyroll [Crunchyroll](https://www.crunchyroll.com). <p align="center"> <a href="https://github.com/crunchy-labs/crunchy-cli"> @@ -26,23 +26,20 @@ A pure [Rust](https://www.rust-lang.org/) CLI for [Crunchyroll](https://www.crun <p align="center"> <a href="#%EF%B8%8F-usage">Usage ๐Ÿ–ฅ๏ธ</a> โ€ข - <a href="#%EF%B8%8F-disclaimer">Disclaimer ๐Ÿ“œ</a> + <a href="#-disclaimer">Disclaimer ๐Ÿ“œ</a> โ€ข <a href="#-license">License โš–</a> </p> > We are in no way affiliated with, maintained, authorized, sponsored, or officially associated with Crunchyroll LLC or any of its subsidiaries or affiliates. -> The official Crunchyroll website can be found at [crunchyroll.com](https://crunchyroll.com/). - -> This README belongs to the _master_ branch which is currently under heavy development towards the next major version (3.0). -> It is mostly stable but some issues may still occur. -> If you do not want to use an under-development / pre-release version, head over to the _[golang](https://github.com/crunchy-labs/crunchy-cli/tree/golang)_ branch which contains the EOL but last stable version (and documentation for it). +> The official Crunchyroll website can be found at [www.crunchyroll.com](https://www.crunchyroll.com/). ## โœจ Features - Download single videos and entire series from [Crunchyroll](https://www.crunchyroll.com). - Archive episodes or seasons in an `.mkv` file with multiple subtitles and audios. - Specify a range of episodes to download from an anime. +- Search through the Crunchyroll collection and return metadata (title, duration, direct stream link, ...) of all media types. ## ๐Ÿ’พ Get the executable @@ -50,12 +47,25 @@ A pure [Rust](https://www.rust-lang.org/) CLI for [Crunchyroll](https://www.crun Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) tab and get the binary from the latest (pre-)release. -### โ„๏ธ The nix way +### ๐Ÿ“ฆ Get it via a package manager -This requires [nix](https://nixos.org) and you'll probably need `--extra-experimental-features "nix-command flakes"` depending on your configurations. -```shell -$ nix <run|shell|develop> github:crunchy-labs/crunchy-cli -``` +- [AUR](https://aur.archlinux.org/) + + If you're using Arch or an Arch based Linux distribution you are able to install our [AUR](https://aur.archlinux.org/) package. + You need an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like [yay](https://github.com/Jguer/yay) to install it. + ```shell + # this package builds crunchy-cli manually (recommended) + $ yay -S crunchy-cli + # this package installs the latest pre-compiled release binary + $ yay -S crunchy-cli-bin + ``` + +- [Nix](https://nixos.org/) + + This requires [nix](https://nixos.org) and you'll probably need `--extra-experimental-features "nix-command flakes"`, depending on your configurations. + ```shell + $ nix <run|shell|develop> github:crunchy-labs/crunchy-cli + ``` ### ๐Ÿ›  Build it yourself @@ -64,7 +74,9 @@ This requires [git](https://git-scm.com/) and [Cargo](https://doc.rust-lang.org/ ```shell $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli +# either just build it (will be available in ./target/release/crunchy-cli)... $ cargo build --release +# ... or install it globally $ cargo install --force --path . ``` @@ -74,47 +86,101 @@ $ cargo install --force --path . crunchy-cli requires you to log in. Though you can use a non-premium account, you will not have access to premium content without a subscription. -You can authenticate with your credentials (username:password) or by using a refresh token. +You can authenticate with your credentials (user:password) or by using a refresh token. - Credentials - - ```shell - $ crunchy --credentials "user:password" - ``` + + ```shell + $ crunchy-cli --credentials "user:password" + ``` - Refresh Token - - To obtain a refresh token, you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. - The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). - When installed, look for the `etp_rt` entry and extract its value. - - ```shell - $ crunchy --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" - ``` + + To obtain a refresh token, you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. + The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). + When installed, look for the `etp_rt` entry and extract its value. + ```shell + $ crunchy-cli --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" + ``` - Stay Anonymous - - Skip the login check: - - ```shell - $ crunchy --anonymous - ``` + + Login without an account (you won't be able to access premium content): + ```shell + $ crunchy-cli --anonymous + ``` + +### Global settings + +You can set specific settings which will be + +- Verbose output + + If you want to include debug information in the output, use the `-v` / `--verbose` flag to show it. + ```shell + $ crunchy-cli -v + ``` + This flag can't be used with `-q` / `--quiet`. + +- Quiet output + + If you want to hide all output, use the `-q` / `--quiet` flag to do so. + This is especially useful if you want to pipe the output video to an external program (like a video player). + ```shell + $ crunchy-cli -q + ``` + +- Language + + By default, the resulting metadata like title or description are shown in your system language (if Crunchyroll supports it, else in English). + If you want to show the results in another language, use the `--lang` flag to set it. + ```shell + $ crunchy-cli --lang de-DE + ``` + +- Experimental fixes + + Crunchyroll constantly changes and breaks its services or just delivers incorrect answers. + The `--experimental-fixes` flag tries to fix some of those issues. + As the *experimental* in `--experimental-fixes` states, these fixes may or may not break other functionality. + ```shell + $ crunchy-cli --experimental-fixes + ``` + For an overview which parts this flag affects, see the [documentation](https://docs.rs/crunchyroll-rs/latest/crunchyroll_rs/crunchyroll/struct.CrunchyrollBuilder.html) of the underlying Crunchyroll library, all functions beginning with `stabilization_` are applied. + +- Proxy + + The `--proxy` flag supports https and socks5 proxies to route all your traffic through. + This may be helpful to bypass the geo-restrictions Crunchyroll has on certain series. + ```shell + $ crunchy-cli --proxy socks5://127.0.0.1:8080 + ``` + Make sure that proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. ### Login -crunchy-cli can store your session, so you don't have to authenticate every time you execute a command. - -Note that the `login` keyword has to be used *last*. +The `login` command can store your session, so you don't have to authenticate every time you execute a command. ```shell -$ crunchy --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" login +# save the refresh token which gets generated when login with credentials. +# your username/email and password won't be stored at any time on disk +$ crunchy-cli login --credentials "user:password" +# save etp-rt cookie +$ crunchy-cli login --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" ``` -With the session stored, you do not need to use `--credentials` / `--etp-rt` anymore. This does not work with `--anonymous`. +With the session stored, you do not need to pass `--credentials` / `--etp-rt` / `--anonymous` anymore when you want to execute a command. ### Download +The `download` command lets you download episodes with a specific audio language and optional subtitles. + **Supported urls** - Single episode (with [episode filtering](#episode-filtering)) ```shell - $ crunchy download https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy-cli download https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` - Series (with [episode filtering](#episode-filtering)) ```shell - $ crunchy download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli download https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` **Options** @@ -123,7 +189,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Set the audio language with the `-a` / `--audio` flag. This only works if the url points to a series since episode urls are language specific. ```shell - $ crunchy download -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli download -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is your system locale. If not supported by Crunchyroll, `en-US` (American English) is the default. @@ -132,16 +198,15 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. The subtitles will be burned into the video track (cf. [hardsub](https://www.urbandictionary.com/define.php?term=hardsub)) and thus can not be turned off. ```shell - $ crunchy download -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli download -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is none. - Output template Define an output template by using the `-o` / `--output` flag. - If you want to use any other file format than [`.ts`](https://en.wikipedia.org/wiki/MPEG_transport_stream) you need [ffmpeg](https://ffmpeg.org/). ```shell - $ crunchy download -o "ditf.mp4" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy-cli download -o "ditf.mp4" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` Default is `{title}.mp4`. See the [Template Options section](#output-template-options) below for more options. @@ -149,20 +214,54 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any The resolution for videos can be set via the `-r` / `--resolution` flag. ```shell - $ crunchy download -r worst https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy-cli download -r worst https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` Default is `best`. +- FFmpeg Preset + + You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. + Multiple predefined presets how videos should be encoded (h264, h265, av1, ...) are available, you can see them with `crunchy-cli download --help`. + If you need more specific ffmpeg customizations you could either convert the output file manually or use ffmpeg output arguments as value for this flag. + ```shell + $ crunchy-cli downlaod --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + +- Skip existing + + If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. + ```shell + $ crunchy-cli download --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + +- Yes + + Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. + The `--yes` flag suppresses this interactive prompt and just downloads all seasons. + ```shell + $ crunchy-cli download --yes https://www.crunchyroll.com/series/GR49G9VP6/sword-art-online + ``` + If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. + +- Force hardsub + + If you want to burn-in the subtitles, even if the output format/container supports soft-subs (e.g. `.mp4`), use the `--force-hardsub` flag to do so. + ```shell + $ crunchy-cli download --force-hardsub -s en-US https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + ### Archive +The `archive` command lets you download episodes with multiple audios and subtitles and merges it into a `.mkv` file. + **Supported urls** - Single episode (with [episode filtering](#episode-filtering)) ```shell - $ crunchy archive https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy-cli archive https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` - Series (with [episode filtering](#episode-filtering)) ```shell - $ crunchy archive https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` **Options** @@ -170,7 +269,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Set the audio language with the `-a` / `--audio` flag. Can be used multiple times. ```shell - $ crunchy archive -a ja-JP -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive -a ja-JP -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is your system locale (if not supported by Crunchyroll, `en-US` (American English) and `ja-JP` (Japanese) are used). @@ -178,7 +277,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. ```shell - $ crunchy archive -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is `all` subtitles. @@ -187,7 +286,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Define an output template by using the `-o` / `--output` flag. crunchy-cli uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of it's ability to store multiple audio, video and subtitle tracks at once. ```shell - $ crunchy archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is `{title}.mkv`. See the [Template Options section](#output-template-options) below for more options. @@ -195,7 +294,7 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any The resolution for videos can be set via the `-r` / `--resolution` flag. ```shell - $ crunchy archive -r worst https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive -r worst https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is `best`. @@ -208,32 +307,57 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` - otherwise like `audio`. Subtitles will always match the primary audio and video. ```shell - $ crunchy archive -m audio https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive -m audio https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is `auto`. +- FFmpeg Preset + + You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. + Multiple predefined presets how videos should be encoded (h264, h265, av1, ...) are available, you can see them with `crunchy-cli archive --help`. + If you need more specific ffmpeg customizations you could either convert the output file manually or use ffmpeg output arguments as value for this flag. + ```shell + $ crunchy-cli archive --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + - Default subtitle `--default-subtitle` Set which subtitle language is to be flagged as **default** and **forced**. ```shell - $ crunchy archive --default-subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive --default-subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is none. +- Skip existing + + If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. + ```shell + $ crunchy-cli archive --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + +- Yes + + Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. + The `--yes` flag suppresses this interactive prompt and just downloads all seasons. + ```shell + $ crunchy-cli archive --yes https://www.crunchyroll.com/series/GR49G9VP6/sword-art-online + ``` + If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. + ### Search **Supported urls/input** - Single episode (with [episode filtering](#episode-filtering)) ```shell - $ crunchy search https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy-cli search https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` - Series (with [episode filtering](#episode-filtering)) ```shell - $ crunchy search https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli search https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` - Search input ```shell - $ crunchy search "darling in the franxx" + $ crunchy-cli search "darling in the franxx" ``` **Options** @@ -241,11 +365,40 @@ With the session stored, you do not need to use `--credentials` / `--etp-rt` any Set the audio language to search via the `--audio` flag. Can be used multiple times. ```shell - $ crunchy search --audio en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli search --audio en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default is your system locale. -### Output Template Options +- Result limit + + If your input is a search term instead of an url, you have multiple options to control which results to process. + The `--search-top-results-limit` flag sets the limit of top search results to process. + `--search-series-limit` sets the limit of only series, `--search-movie-listing-limit` of only movie listings, `--search-episode-limit` of only episodes and `--search-music-limit` of only concerts and music videos. + ```shell + $ crunchy-cli search --search-top-results-limit 10 "darling in the franxx" + # only return series which have 'darling' in it. do not return top results which might also be non-series items + $ crunchy-cli search --search-top-results-limit 0 --search-series-limit 10 "darling" + # this returns 2 top results, 3 movie listings, 5 episodes and 1 music item as result + $ crunchy-cli search --search-top-results-limit 2 --search-movie-listing-limit 3 --search-episode-limit 5 --search-music-limit 1 "test" + ``` + Default is `5` for `--search-top-results-limit`, `0` for all others. + +- Output template + + The search command is designed to show only the specific information you want. + This is done with the `-o`/`--output` flag. + You can specify keywords in a specific pattern, and they will get replaced in the output text. + The required pattern for this begins with `{{`, then the keyword, and closes with `}}` (e.g. `{{episode.title}}`). + For example, if you want to get the title of an episode, you can use `Title: {{episode.title}}` and `{{episode.title}}` will be replaced with the episode title. + You can see all supported keywords with `crunchy-cli search --help`. + ```shell + $ crunchy-cli search -o "{{series.title}}" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + Default is `S{{season.number}}E{{episode.number}} - {{episode.title}}`. + +--- + +#### Output Template Options You can use various template options to change how the filename is processed. The following tags are available: @@ -263,11 +416,11 @@ You can use various template options to change how the filename is processed. Th Example: ```shell -$ crunchy archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball +$ crunchy-cli archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball # Output file: '[S01E01] Secret of the Dragon Ball.mkv' ``` -### Episode filtering +#### Episode filtering Filters patterns can be used to download a specific range of episodes from a single series. @@ -283,13 +436,17 @@ There are many possible patterns, for example: - `...[S3,S5]` - Download season three and five. - `...[S1-S3,S4E2-S4E6]` - Download season one to three, then episodes two to six from season four. -In practice, it would look like this: `https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E5]` +In practice, it would look like this: +``` +https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E5] +``` # ๐Ÿ“œ Disclaimer -This tool is **ONLY** meant for private use. You need a subscription to [`๐Ÿ’ณ Crunchyroll Premium ๐Ÿ’ณ`](https://www.crunchyroll.com/welcome#plans) to download premium content. +This tool is meant for private use only. +You need a [Crunchyroll Premium](https://www.crunchyroll.com/welcome#plans) subscription to access premium content. -**You are entirely responsible for what happens to files you downloaded through crunchy-cli.** +**You are entirely responsible for what happens when you use crunchy-cli.** # โš– License From aef2fddff7529a7d3731c9be40ea39cacadea76c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 7 Aug 2023 16:24:47 +0200 Subject: [PATCH 426/630] Update version --- Cargo.lock | 8 +- Cargo.toml | 3 +- crunchy-cli-core/Cargo.lock | 183 ++++++++++++++++++++++-------------- crunchy-cli-core/Cargo.toml | 7 +- 4 files changed, 120 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56ec987..e56716b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0-dev.15" +version = "3.0.0" dependencies = [ "chrono", "clap", @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.15" +version = "3.0.0" dependencies = [ "anyhow", "async-trait", @@ -1498,9 +1498,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sanitize-filename" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c" +checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" dependencies = [ "lazy_static", "regex", diff --git a/Cargo.toml b/Cargo.toml index aa52a9e..d1c1868 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.15" +version = "3.0.0" edition = "2021" +license = "MIT" [features] default = ["openssl-static"] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index a5f9b1d..b26b645 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -199,9 +199,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -237,9 +240,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.17" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.17" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", @@ -302,7 +305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.23", + "time 0.3.25", "version_check", ] @@ -319,7 +322,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.23", + "time 0.3.25", "url", ] @@ -350,7 +353,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0-dev.15" +version = "3.0.0" dependencies = [ "anyhow", "async-trait", @@ -403,7 +406,7 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.25.0", + "webpki-roots 0.25.1", ] [[package]] @@ -494,6 +497,15 @@ dependencies = [ "xattr", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", +] + [[package]] name = "derive_setters" version = "0.1.6" @@ -539,9 +551,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -559,10 +571,16 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -722,7 +740,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -735,6 +753,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -905,15 +929,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", "serde", ] [[package]] name = "indicatif" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" +checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" dependencies = [ "console", "instant", @@ -996,9 +1031,9 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" @@ -1153,9 +1188,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -1185,18 +1220,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.26.0+1.1.1u" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -1219,9 +1254,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -1237,9 +1272,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6" +checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" [[package]] name = "proc-macro2" @@ -1278,9 +1313,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -1316,9 +1351,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", @@ -1328,9 +1363,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -1411,9 +1446,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", @@ -1424,9 +1459,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring", @@ -1445,9 +1480,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.1" +version = "0.101.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" dependencies = [ "ring", "untrusted", @@ -1461,9 +1496,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sanitize-filename" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c502bdb638f1396509467cb0580ef3b29aa2a45c5d43e5d84928241280296c" +checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" dependencies = [ "lazy_static", "regex", @@ -1490,9 +1525,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1503,9 +1538,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -1513,18 +1548,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.174" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.174" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -1533,9 +1568,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -1565,25 +1600,26 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" +checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" dependencies = [ "base64", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", + "indexmap 2.0.0", "serde", "serde_json", "serde_with_macros", - "time 0.3.23", + "time 0.3.25", ] [[package]] name = "serde_with_macros" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" +checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" dependencies = [ "darling", "proc-macro2", @@ -1653,9 +1689,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -1674,9 +1710,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", @@ -1718,10 +1754,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -1736,9 +1773,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -2051,9 +2088,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a4ac452058d835c2b7ff6d74f0ad9f40e172bb1ce661b1444f397eeb1d19e6d" +checksum = "c9c6eda1c830a36f361e7721c87fd79ea84293b54f8c48c959f85ec636f0f196" [[package]] name = "winapi" @@ -2229,9 +2266,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f7a3f43..c9ebb25 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0-dev.15" +version = "3.0.0" edition = "2021" +license = "MIT" [features] openssl = ["dep:native-tls", "reqwest/native-tls-alpn"] @@ -25,12 +26,12 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.9" reqwest = { version = "0.11", default-features = false, features = ["socks"] } -sanitize-filename = "0.4" +sanitize-filename = "0.5" serde = "1.0" serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" -tempfile = "3.6" +tempfile = "3.7" tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" From 40397e96a379d6b8f1ac5332a82bf89e739b2e5f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 7 Aug 2023 17:08:04 +0200 Subject: [PATCH 427/630] Fix arch release pipeline --- .github/scripts/PKGBUILD.binary | 10 ++++++---- .github/scripts/PKGBUILD.source | 10 +++++----- .github/workflows/publish.yml | 8 ++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/scripts/PKGBUILD.binary b/.github/scripts/PKGBUILD.binary index c9cc76e..832f4d0 100644 --- a/.github/scripts/PKGBUILD.binary +++ b/.github/scripts/PKGBUILD.binary @@ -9,6 +9,8 @@ pkgver=$CI_PKG_VERSION pkgrel=1 depends=('ffmpeg') +provides=('crunchy-cli') +conflicts=('crunchy-cli') source=( "crunchy-cli::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-linux-x86_64" "manpages.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-manpages.zip" @@ -32,8 +34,8 @@ package() { install -Dm755 crunchy-cli $pkgdir/usr/bin/crunchy-cli install -Dm644 manpages/* -t $pkgdir/usr/share/man/man1 - install -Dm644 completions/crunchy-cli.bash $pkgdir/usr/share/bash-completions/completions/crunchy-cli - install -Dm644 completions/_crunchy-cli $pkgdir/usr/share/zsh/site-functions/_crunchy-cli - install -Dm644 completions/crunchy-cli.fish $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish - install -Dm644 LICENSE $pkgdir/usr/share/licenses/crunchy-cli/LICENSE + install -Dm644 completions/crunchy-cli.bash -t $pkgdir/usr/share/bash-completions/completions/crunchy-cli + install -Dm644 completions/_crunchy-cli -t $pkgdir/usr/share/zsh/site-functions/_crunchy-cli + install -Dm644 completions/crunchy-cli.fish -t $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish + install -Dm644 LICENSE -t $pkgdir/usr/share/licenses/crunchy-cli/LICENSE } diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source index 737a699..beffe74 100644 --- a/.github/scripts/PKGBUILD.source +++ b/.github/scripts/PKGBUILD.source @@ -26,9 +26,9 @@ package() { cd "$srcdir/${pkgname}-$pkgver" install -Dm755 target/release/crunchy-cli $pkgdir/usr/bin/crunchy-cli - install -Dm644 target/release/manpages/* $pkgdir/usr/share/man/man1 - install -Dm644 target/release/completions/crunchy-cli.bash $pkgdir/usr/share/bash-completions/completions/crunchy-cli - install -Dm644 target/release/completions/_crunchy-cli $pkgdir/usr/share/zsh/site-functions/_crunchy-cli - install -Dm644 target/release/completions/crunchy-cli.fish $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish - install -Dm644 LICENSE $pkgdir/usr/share/licenses/crunchy-cli/LICENSE + install -Dm644 target/release/manpages/* -t $pkgdir/usr/share/man/man1 + install -Dm644 target/release/completions/crunchy-cli.bash -t $pkgdir/usr/share/bash-completions/completions/crunchy-cli + install -Dm644 target/release/completions/_crunchy-cli -t $pkgdir/usr/share/zsh/site-functions/_crunchy-cli + install -Dm644 target/release/completions/crunchy-cli.fish -t $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish + install -Dm644 LICENSE -t $pkgdir/usr/share/licenses/crunchy-cli/LICENSE } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 37f4379..ddf4136 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,7 +27,7 @@ jobs: run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM' < .github/scripts/PKGBUILD.source > PKGBUILD - name: Publish crunchy-cli to AUR - uses: KSXGitHub/github-actions-deploy-aur@2.7.0 + uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 with: pkgname: crunchy-cli pkgbuild: ./PKGBUILD @@ -39,7 +39,7 @@ jobs: - name: Generate crunchy-cli-bin sha sums run: | - curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-x86_64-linux + curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-linux-x86_64 curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-completions.zip curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-manpages.zip curl -LO https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/${{ github.ref_name }}/LICENSE @@ -55,10 +55,10 @@ jobs: CI_MANPAGES_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_MANPAGES_SHA256 }} CI_COMPLETIONS_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_COMPLETIONS_SHA256 }} CI_LICENSE_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_LICENSE_SHA256 }} - run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD + run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD - name: Publish crunchy-cli-bin to AUR - uses: KSXGitHub/github-actions-deploy-aur@2.7.0 + uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 with: pkgname: crunchy-cli-bin pkgbuild: ./PKGBUILD From b98332eae4096832bbfd5b5c7e7aee962df2493b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 7 Aug 2023 17:47:06 +0200 Subject: [PATCH 428/630] Add aarch64 support for arch release pipeline --- .github/scripts/PKGBUILD.binary | 13 ++++++++++--- .github/workflows/publish.yml | 9 ++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/scripts/PKGBUILD.binary b/.github/scripts/PKGBUILD.binary index 832f4d0..08a0569 100644 --- a/.github/scripts/PKGBUILD.binary +++ b/.github/scripts/PKGBUILD.binary @@ -1,7 +1,7 @@ # Maintainer: ByteDream pkgname=crunchy-cli-bin pkgdesc="Command-line downloader for Crunchyroll" -arch=('x86_64') +arch=('x86_64', 'aarch64') url="https://github.com/crunchy-labs/crunchy-cli" license=('MIT') @@ -11,14 +11,21 @@ pkgrel=1 depends=('ffmpeg') provides=('crunchy-cli') conflicts=('crunchy-cli') -source=( +source_x86_64=( "crunchy-cli::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-linux-x86_64" "manpages.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-manpages.zip" "completions.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-completions.zip" "LICENSE::https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/v${pkgver}/LICENSE" ) +source_aarch64=( + "crunchy-cli::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-linux-aarch64" + "manpages.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-manpages.zip" + "completions.zip::https://github.com/crunchy-labs/crunchy-cli/releases/download/v${pkgver}/crunchy-cli-v${pkgver}-completions.zip" + "LICENSE::https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/v${pkgver}/LICENSE" +) noextract=("manpages.zip" "completions.zip") -sha256sums=('$CI_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') +sha256sums_x86_64=('$CI_x86_64_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') +sha256sums_aarch64=('$CI_aarch64_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') package() { cd "$srcdir" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ddf4136..c2857a9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -40,10 +40,12 @@ jobs: - name: Generate crunchy-cli-bin sha sums run: | curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-linux-x86_64 + curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-linux-aarch64 curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-completions.zip curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-manpages.zip curl -LO https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/${{ github.ref_name }}/LICENSE - echo "CRUNCHY_CLI_BIN_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-x86_64-linux | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_x86_64_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-x86_64-linux | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_aarch64_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-aarch64-linux | cut -f 1 -d ' ')" >> $GITHUB_ENV echo "CRUNCHY_CLI_BIN_COMPLETIONS_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-completions.zip | cut -f 1 -d ' ')" >> $GITHUB_ENV echo "CRUNCHY_CLI_BIN_MANPAGES_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-manpages.zip | cut -f 1 -d ' ')" >> $GITHUB_ENV echo "CRUNCHY_CLI_BIN_LICENSE_SHA256=$(sha256sum LICENSE | cut -f 1 -d ' ')" >> $GITHUB_ENV @@ -51,11 +53,12 @@ jobs: - name: Generate crunchy-cli-bin PKGBUILD env: CI_PKG_VERSION: ${{ env.RELEASE_VERSION }} - CI_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_SHA256 }} + CI_x86_64_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_x86_64_SHA256 }} + CI_aarch64_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_aarch64_SHA256 }} CI_MANPAGES_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_MANPAGES_SHA256 }} CI_COMPLETIONS_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_COMPLETIONS_SHA256 }} CI_LICENSE_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_LICENSE_SHA256 }} - run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD + run: envsubst '$CI_PKG_VERSION,$CI_x86_64_SHA_SUM,$CI_aarch64_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD - name: Publish crunchy-cli-bin to AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 From 9f9aec1f8a542af14a573933a1a930dd447b84c7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 7 Aug 2023 17:48:30 +0200 Subject: [PATCH 429/630] Fix cross installation if cache is present --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cdfd77..e34097d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install cross - run: cargo install cross + run: cargo install --force cross - name: Build run: cross build --release --all-features --target ${{ matrix.toolchain }} From 800df5ca6c8d339945181eec894a1db831a15fa2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 8 Aug 2023 11:06:07 +0200 Subject: [PATCH 430/630] (Re-)add scoop install example --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a8ec76e..967eecf 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,14 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t $ nix <run|shell|develop> github:crunchy-labs/crunchy-cli ``` +- [Scoop](https://scoop.sh/) + + For Windows users, we support the [scoop](https://scoop.sh/#/) command-line installer. + ```shell + $ scoop bucket add extras + $ scoop install extras/crunchy-cli + ``` + ### ๐Ÿ›  Build it yourself Since we do not support every platform and architecture you may have to build the project yourself. From 448b633be853f8eca017c9b158a4c96e9b912044 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 8 Aug 2023 17:33:08 +0200 Subject: [PATCH 431/630] Fix AUR completion and license directory --- .github/scripts/PKGBUILD.binary | 8 ++++---- .github/scripts/PKGBUILD.source | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/scripts/PKGBUILD.binary b/.github/scripts/PKGBUILD.binary index 08a0569..0117723 100644 --- a/.github/scripts/PKGBUILD.binary +++ b/.github/scripts/PKGBUILD.binary @@ -41,8 +41,8 @@ package() { install -Dm755 crunchy-cli $pkgdir/usr/bin/crunchy-cli install -Dm644 manpages/* -t $pkgdir/usr/share/man/man1 - install -Dm644 completions/crunchy-cli.bash -t $pkgdir/usr/share/bash-completions/completions/crunchy-cli - install -Dm644 completions/_crunchy-cli -t $pkgdir/usr/share/zsh/site-functions/_crunchy-cli - install -Dm644 completions/crunchy-cli.fish -t $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish - install -Dm644 LICENSE -t $pkgdir/usr/share/licenses/crunchy-cli/LICENSE + install -Dm644 completions/crunchy-cli.bash $pkgdir/usr/share/bash-completion/completions/crunchy-cli + install -Dm644 completions/_crunchy-cli $pkgdir/usr/share/zsh/site-functions/_crunchy-cli + install -Dm644 completions/crunchy-cli.fish $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish + install -Dm644 LICENSE $pkgdir/usr/share/licenses/crunchy-cli/LICENSE } diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source index beffe74..015efe3 100644 --- a/.github/scripts/PKGBUILD.source +++ b/.github/scripts/PKGBUILD.source @@ -27,8 +27,8 @@ package() { install -Dm755 target/release/crunchy-cli $pkgdir/usr/bin/crunchy-cli install -Dm644 target/release/manpages/* -t $pkgdir/usr/share/man/man1 - install -Dm644 target/release/completions/crunchy-cli.bash -t $pkgdir/usr/share/bash-completions/completions/crunchy-cli - install -Dm644 target/release/completions/_crunchy-cli -t $pkgdir/usr/share/zsh/site-functions/_crunchy-cli - install -Dm644 target/release/completions/crunchy-cli.fish -t $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish - install -Dm644 LICENSE -t $pkgdir/usr/share/licenses/crunchy-cli/LICENSE + install -Dm644 target/release/completions/crunchy-cli.bash $pkgdir/usr/share/bash-completion/completions/crunchy-cli + install -Dm644 target/release/completions/_crunchy-cli $pkgdir/usr/share/zsh/site-functions/_crunchy-cli + install -Dm644 target/release/completions/crunchy-cli.fish $pkgdir/usr/share/fish/vendor_completions.d/crunchy-cli.fish + install -Dm644 LICENSE $pkgdir/usr/share/licenses/crunchy-cli/LICENSE } From a45833f5a2234ec4750d6b9a7c4c6c17bb534b03 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 14 Aug 2023 00:45:18 +0200 Subject: [PATCH 432/630] Update dependencies and version --- Cargo.lock | 242 +++++++++++++++++++++--------------- Cargo.toml | 4 +- crunchy-cli-core/Cargo.lock | 157 ++++++++--------------- crunchy-cli-core/Cargo.toml | 9 +- crunchy-cli-core/src/lib.rs | 25 ++-- 5 files changed, 216 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e56716b..47b0102 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -109,9 +109,9 @@ checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", @@ -163,9 +163,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block-padding" @@ -199,9 +199,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -237,9 +240,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.17" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.17" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", @@ -321,7 +324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.23", + "time 0.3.25", "version_check", ] @@ -338,7 +341,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.23", + "time 0.3.25", "url", ] @@ -369,7 +372,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.0" +version = "3.0.1" dependencies = [ "chrono", "clap", @@ -381,7 +384,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0" +version = "3.0.1" dependencies = [ "anyhow", "async-trait", @@ -412,9 +415,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b33d2464e990dec5d3e6265cc892a88ab89971cfd177b7d7c7d0e9f8cde817" +checksum = "6560e2f721420854d34b94b8bc35b316e3ff6bc83cb7cbf24750ddf55a21d45a" dependencies = [ "aes", "async-trait", @@ -434,14 +437,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.25.0", + "webpki-roots 0.25.2", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab3e4af975066a3dc3dd0bb50b1a29c4a3cdee5365e8b6559d21aa15d9ace6a" +checksum = "b1398807cd10094f08c1d31423f7979e74c25f772c203d477dfa6ddc4ceb81f2" dependencies = [ "darling", "quote", @@ -505,9 +508,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b55cd4a2bd4b541906e88adbd2242bda9a697517f638c844fa47fb56fe2f17" +checksum = "5d0c74b03285fe95649f588140b6009dc10bc4f747bd774818ed8e9cc6b5cbb6" dependencies = [ "base64", "base64-serde", @@ -525,6 +528,15 @@ dependencies = [ "xattr", ] +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +dependencies = [ + "serde", +] + [[package]] name = "derive_setters" version = "0.1.6" @@ -570,9 +582,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -590,10 +602,16 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -753,7 +771,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -766,6 +784,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -814,9 +838,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -835,7 +859,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -936,15 +960,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", "serde", ] [[package]] name = "indicatif" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" +checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" dependencies = [ "console", "instant", @@ -1027,15 +1062,15 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "m3u8-rs" @@ -1184,9 +1219,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -1216,18 +1251,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.26.0+1.1.1u" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -1250,9 +1285,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -1268,9 +1303,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6" +checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" [[package]] name = "proc-macro2" @@ -1299,9 +1334,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", "serde", @@ -1309,9 +1344,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -1347,9 +1382,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", @@ -1359,9 +1394,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -1448,11 +1483,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", @@ -1461,9 +1496,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring", @@ -1482,9 +1517,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.1" +version = "0.101.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" dependencies = [ "ring", "untrusted", @@ -1527,9 +1562,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1540,9 +1575,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -1550,18 +1585,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.174" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.174" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -1570,9 +1605,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -1602,25 +1637,26 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" +checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" dependencies = [ "base64", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", + "indexmap 2.0.0", "serde", "serde_json", "serde_with_macros", - "time 0.3.23", + "time 0.3.25", ] [[package]] name = "serde_with_macros" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" +checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" dependencies = [ "darling", "proc-macro2", @@ -1670,6 +1706,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -1690,9 +1736,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -1711,9 +1757,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", @@ -1755,10 +1801,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -1773,9 +1820,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -1797,18 +1844,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys 0.48.0", ] @@ -2088,9 +2134,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a4ac452058d835c2b7ff6d74f0ad9f40e172bb1ce661b1444f397eeb1d19e6d" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "winapi" @@ -2266,9 +2312,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index d1c1868..eb15425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0" +version = "3.0.1" edition = "2021" license = "MIT" @@ -12,7 +12,7 @@ openssl = ["crunchy-cli-core/openssl"] openssl-static = ["crunchy-cli-core/openssl-static"] [dependencies] -tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.31", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index b26b645..5ba4511 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -109,9 +109,9 @@ checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", @@ -163,9 +163,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block-padding" @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", @@ -251,9 +251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", @@ -326,16 +326,6 @@ dependencies = [ "url", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -353,7 +343,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.0" +version = "3.0.1" dependencies = [ "anyhow", "async-trait", @@ -368,7 +358,7 @@ dependencies = [ "indicatif", "lazy_static", "log", - "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d)", + "native-tls", "num_cpus", "regex", "reqwest", @@ -384,9 +374,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b33d2464e990dec5d3e6265cc892a88ab89971cfd177b7d7c7d0e9f8cde817" +checksum = "6560e2f721420854d34b94b8bc35b316e3ff6bc83cb7cbf24750ddf55a21d45a" dependencies = [ "aes", "async-trait", @@ -406,14 +396,14 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.25.1", + "webpki-roots 0.25.2", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab3e4af975066a3dc3dd0bb50b1a29c4a3cdee5365e8b6559d21aa15d9ace6a" +checksum = "b1398807cd10094f08c1d31423f7979e74c25f772c203d477dfa6ddc4ceb81f2" dependencies = [ "darling", "quote", @@ -477,9 +467,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b55cd4a2bd4b541906e88adbd2242bda9a697517f638c844fa47fb56fe2f17" +checksum = "5d0c74b03285fe95649f588140b6009dc10bc4f747bd774818ed8e9cc6b5cbb6" dependencies = [ "base64", "base64-serde", @@ -807,9 +797,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -828,7 +818,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -857,7 +847,7 @@ checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", - "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", "tokio", "tokio-native-tls", ] @@ -1037,9 +1027,9 @@ checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "m3u8-rs" @@ -1095,24 +1085,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -1254,9 +1226,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -1303,9 +1275,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", "serde", @@ -1401,7 +1373,7 @@ dependencies = [ "js-sys", "log", "mime", - "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1446,11 +1418,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.7" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", @@ -1480,9 +1452,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.2" +version = "0.101.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" dependencies = [ "ring", "untrusted", @@ -1504,15 +1476,6 @@ dependencies = [ "regex", ] -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "sct" version = "0.7.0" @@ -1523,29 +1486,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.183" @@ -1669,6 +1609,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -1797,18 +1747,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys 0.48.0", ] @@ -1830,7 +1779,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", "tokio", ] @@ -2088,9 +2037,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c6eda1c830a36f361e7721c87fd79ea84293b54f8c48c959f85ec636f0f196" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "winapi" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index c9ebb25..5f60474 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.0" +version = "3.0.1" edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.5.1", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.6.1", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.10", default-features = false } dirs = "5.0" @@ -32,7 +32,7 @@ serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" tempfile = "3.7" -tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.31", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" # fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports @@ -41,3 +41,6 @@ native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git [build-dependencies] chrono = "0.4" + +[patch.crates-io] +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 9132204..793e946 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -5,7 +5,7 @@ use anyhow::bail; use anyhow::Result; use clap::{Parser, Subcommand}; use crunchyroll_rs::crunchyroll::CrunchyrollBuilder; -use crunchyroll_rs::error::CrunchyrollError; +use crunchyroll_rs::error::Error; use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; use reqwest::Proxy; @@ -205,20 +205,17 @@ async fn pre_check_executor(executor: &mut impl Execute) { } async fn execute_executor(executor: impl Execute, ctx: Context) { - if let Err(err) = executor.execute(ctx).await { - error!("a unexpected error occurred: {}", err); - - if let Some(crunchy_error) = err.downcast_ref::<CrunchyrollError>() { - let message = match crunchy_error { - CrunchyrollError::Internal(i) => &i.message, - CrunchyrollError::Request(r) => &r.message, - CrunchyrollError::Decode(d) => &d.message, - CrunchyrollError::Authentication(a) => &a.message, - CrunchyrollError::Input(i) => &i.message, - }; - if message.contains("content.get_video_streams_v2.cms_service_error") { - error!("You've probably hit a rate limit. Try again later, generally after 10-20 minutes the rate limit is over and you can continue to use the cli") + if let Err(mut err) = executor.execute(ctx).await { + if let Some(crunchy_error) = err.downcast_mut::<Error>() { + if let Error::Block { message, .. } = crunchy_error { + *message = "Triggered Cloudflare bot protection. Try again later or use a VPN or proxy to spoof your location".to_string() + } else if let Error::Request { message, .. } = crunchy_error { + *message = "You've probably hit a rate limit. Try again later, generally after 10-20 minutes the rate limit is over and you can continue to use the cli".to_string() } + + error!("An error occurred: {}", crunchy_error) + } else { + error!("An error occurred: {}", err) } std::process::exit(1) From 6da292f0135a5fad6da0ff99632aeb78d48c0b1f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 14 Aug 2023 15:46:14 +0200 Subject: [PATCH 433/630] Do not test PKGBUILD on arch release pipeline --- .github/workflows/publish.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c2857a9..80cb5bb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,7 +35,6 @@ jobs: commit_email: ${{ secrets.AUR_EMAIL }} ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} commit_message: Update to version {{ env.RELEASE_VERSION }} - test: true - name: Generate crunchy-cli-bin sha sums run: | @@ -69,4 +68,3 @@ jobs: commit_email: ${{ secrets.AUR_EMAIL }} ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} commit_message: Update to version {{ env.RELEASE_VERSION }} - test: true From f45bb19cd79f766ed3664e20cbc017f4d40bccfd Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 16 Aug 2023 16:55:16 +0200 Subject: [PATCH 434/630] Remove duplicated native-tls entry (#235) --- Cargo.lock | 18 +++--------------- crunchy-cli-core/Cargo.lock | 1 - crunchy-cli-core/Cargo.toml | 10 ++++------ 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47b0102..a9119d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,6 @@ dependencies = [ "indicatif", "lazy_static", "log", - "native-tls 0.2.11 (git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d)", "num_cpus", "regex", "reqwest", @@ -888,7 +887,7 @@ checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", - "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", "tokio", "tokio-native-tls", ] @@ -1144,17 +1143,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" -dependencies = [ - "log", - "openssl", - "openssl-probe", - "openssl-sys", -] - [[package]] name = "nix" version = "0.26.2" @@ -1432,7 +1420,7 @@ dependencies = [ "js-sys", "log", "mime", - "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1876,7 +1864,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "native-tls 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", "tokio", ] diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock index 5ba4511..0a14466 100644 --- a/crunchy-cli-core/Cargo.lock +++ b/crunchy-cli-core/Cargo.lock @@ -358,7 +358,6 @@ dependencies = [ "indicatif", "lazy_static", "log", - "native-tls", "num_cpus", "regex", "reqwest", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5f60474..5787f39 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" license = "MIT" [features] -openssl = ["dep:native-tls", "reqwest/native-tls-alpn"] -openssl-static = ["dep:native-tls", "native-tls?/vendored", "reqwest/native-tls-alpn", "reqwest/native-tls-vendored"] +openssl = ["reqwest/native-tls-alpn"] +openssl-static = ["reqwest/native-tls-alpn", "reqwest/native-tls-vendored"] [dependencies] anyhow = "1.0" @@ -35,12 +35,10 @@ tempfile = "3.7" tokio = { version = "1.31", features = ["macros", "rt-multi-thread", "time"] } sys-locale = "0.3" -# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports -# `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d", features = ["alpn"], optional = true } - [build-dependencies] chrono = "0.4" [patch.crates-io] +# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports +# `rustls` and `native-tls` as tls backend native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } From 31fe1460f19b0b928c40e9cbba5ad5ea6c033278 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 16 Aug 2023 16:57:35 +0200 Subject: [PATCH 435/630] Replace native-tls its internal fork in the root crate --- Cargo.lock | 51 +-------------------------------------------------- Cargo.toml | 5 +++++ 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9119d3..d307294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,16 +345,6 @@ dependencies = [ "url", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -1128,19 +1118,12 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" dependencies = [ - "lazy_static", - "libc", "log", "openssl", "openssl-probe", "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", ] [[package]] @@ -1529,15 +1512,6 @@ dependencies = [ "regex", ] -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "sct" version = "0.7.0" @@ -1548,29 +1522,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.183" diff --git a/Cargo.toml b/Cargo.toml index eb15425..114de49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,11 @@ clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } +[patch.crates-io] +# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports +# `rustls` and `native-tls` as tls backend +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } + [profile.release] strip = true opt-level = "z" From d295a57f842f0fdeb348884c5b3e3d1dabc350f4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 16 Aug 2023 17:26:03 +0200 Subject: [PATCH 436/630] Use dynamically linked openssl when running with nix --- flake.lock | 6 +++--- flake.nix | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 91bbf3d..90e07bf 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1685866647, - "narHash": "sha256-4jKguNHY/edLYImB+uL8jKPL/vpfOvMmSlLAGfxSrnY=", + "lastModified": 1692128808, + "narHash": "sha256-Di1Zm/P042NuwThMiZNrtmaAjd4Tm2qBOKHX7xUOfMk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a53a3bec10deef6e1cc1caba5bc60f53b959b1e8", + "rev": "4ed9856be002a730234a1a1ed9dcd9dd10cbdb40", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 4a48dec..2c297f7 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,9 @@ allowBuiltinFetchGit = true; }; + buildNoDefaultFeatures = true; + buildFeatures = [ "openssl" ]; + nativeBuildInputs = [ pkgs.pkg-config ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ From 92ed4bd87d0cb137e48398134eeaa5e4a0b3eb99 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 16 Aug 2023 19:08:13 +0200 Subject: [PATCH 437/630] Revert "Replace native-tls its internal fork in the root crate" This reverts commit 31fe1460f19b0b928c40e9cbba5ad5ea6c033278. --- Cargo.lock | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 5 ----- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d307294..a9119d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,16 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -1118,12 +1128,19 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ + "lazy_static", + "libc", "log", "openssl", "openssl-probe", "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1512,6 +1529,15 @@ dependencies = [ "regex", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "sct" version = "0.7.0" @@ -1522,6 +1548,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.183" diff --git a/Cargo.toml b/Cargo.toml index 114de49..eb15425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,6 @@ clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } -[patch.crates-io] -# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports -# `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } - [profile.release] strip = true opt-level = "z" From 6a6b981979bfd62d46eae40ef81831bd3b5c2741 Mon Sep 17 00:00:00 2001 From: StepBroBD <Hi@StepBroBD.com> Date: Wed, 16 Aug 2023 14:29:39 -0400 Subject: [PATCH 438/630] Fix nix flake overlays (#236) --- flake.lock | 6 +++--- flake.nix | 23 +++++++++-------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/flake.lock b/flake.lock index 90e07bf..b9c63db 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2c297f7..2ca105a 100644 --- a/flake.nix +++ b/flake.nix @@ -4,17 +4,12 @@ utils.url = "flake:flake-utils"; }; - outputs = { self, nixpkgs, utils, ... }: utils.lib.eachSystem [ - "aarch64-darwin" - "x86_64-darwin" - "aarch64-linux" - "x86_64-linux" - ] + outputs = { self, nixpkgs, utils }: utils.lib.eachDefaultSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; - # enable musl on Linux makes the build time 100x slower - # since it will trigger a toolchain rebuild + # enable musl on Linux will trigger a toolchain rebuild + # making the build very slow + pkgs = import nixpkgs { inherit system; }; # if nixpkgs.legacyPackages.${system}.stdenv.hostPlatform.isLinux # then nixpkgs.legacyPackages.${system}.pkgsMusl # else nixpkgs.legacyPackages.${system}; @@ -49,10 +44,6 @@ { packages.default = crunchy-cli; - overlays.default = _: prev: { - crunchy-cli = prev.crunchy-cli.override { }; - }; - devShells.default = pkgs.mkShell { packages = with pkgs; [ cargo @@ -77,5 +68,9 @@ formatter = pkgs.nixpkgs-fmt; } - ); + ) // { + overlays.default = final: prev: { + inherit (self.packages.${final.system}) crunchy-cli; + }; + }; } From 70b41b4dd5881d66208e1ec2ac8bcfc5ef236e03 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 17 Aug 2023 11:53:57 +0200 Subject: [PATCH 439/630] Show an error message if no url was given --- crunchy-cli-core/src/archive/command.rs | 1 + crunchy-cli-core/src/download/command.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 10133f2..b7cd8b7 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -99,6 +99,7 @@ pub struct Archive { pub(crate) yes: bool, #[arg(help = "Crunchyroll series url(s)")] + #[arg(required = true)] pub(crate) urls: Vec<String>, } diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 1ce731d..df4e616 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -81,6 +81,7 @@ pub struct Download { pub(crate) force_hardsub: bool, #[arg(help = "Url(s) to Crunchyroll episodes or series")] + #[arg(required = true)] pub(crate) urls: Vec<String>, } From 596fcc2342bdc8feec64c0fdc0da99afe6e983c2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 17 Aug 2023 11:54:00 +0200 Subject: [PATCH 440/630] Fix relative episode number exceeding actual episode count (#238) --- crunchy-cli-core/src/archive/filter.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 38fed32..851b4b5 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -18,7 +18,7 @@ pub(crate) struct ArchiveFilter { url_filter: UrlFilter, archive: Archive, interactive_input: bool, - season_episode_count: HashMap<u32, Vec<String>>, + season_episode_count: HashMap<String, Vec<String>>, season_subtitles_missing: Vec<u32>, season_sorting: Vec<String>, visited: Visited, @@ -229,7 +229,7 @@ impl Filter for ArchiveFilter { if Format::has_relative_episodes_fmt(&self.archive.output) { for episode in episodes.iter() { self.season_episode_count - .entry(episode.season_number) + .entry(episode.season_id.clone()) .or_insert(vec![]) .push(episode.id.clone()) } @@ -300,20 +300,16 @@ impl Filter for ArchiveFilter { } let relative_episode_number = if Format::has_relative_episodes_fmt(&self.archive.output) { - if self - .season_episode_count - .get(&episode.season_number) - .is_none() - { + if self.season_episode_count.get(&episode.season_id).is_none() { let season_episodes = episode.season().await?.episodes().await?; self.season_episode_count.insert( - episode.season_number, + episode.season_id.clone(), season_episodes.into_iter().map(|e| e.id).collect(), ); } let relative_episode_number = self .season_episode_count - .get(&episode.season_number) + .get(&episode.season_id) .unwrap() .iter() .position(|id| id == &episode.id) From 2f57b075591b2906992f32b451dbec90f30a0e69 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 17 Aug 2023 11:58:11 +0200 Subject: [PATCH 441/630] Change ci cargo cache key --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e34097d..bb2f797 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + key: ${{ matrix.toolchain }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install cross run: cargo install --force cross @@ -74,7 +74,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + key: x86_64-apple-darwin-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install toolchain uses: dtolnay/rust-toolchain@stable @@ -107,7 +107,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + key: x86_64-pc-windows-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install system dependencies uses: msys2/setup-msys2@v2 From a80f6e5df484a14008fd46d787cef773531ab84a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 25 Aug 2023 14:24:12 +0200 Subject: [PATCH 442/630] Use workspace instead of separate Cargo.lock file --- Cargo.lock | 293 ++--- Cargo.toml | 8 + crunchy-cli-core/Cargo.lock | 2222 ----------------------------------- crunchy-cli-core/Cargo.toml | 5 - 4 files changed, 115 insertions(+), 2413 deletions(-) delete mode 100644 crunchy-cli-core/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index a9119d3..b07d41b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -54,24 +54,23 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" @@ -93,9 +92,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -103,9 +102,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-trait" @@ -126,9 +125,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -199,9 +198,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] @@ -240,9 +239,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.21" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d" dependencies = [ "clap_builder", "clap_derive", @@ -251,9 +250,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6" dependencies = [ "anstream", "anstyle", @@ -263,18 +262,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.3.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" dependencies = [ "heck", "proc-macro2", @@ -284,15 +283,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clap_mangen" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f2e32b579dae093c2424a8b7e2bea09c89da01e1ce5065eb2f0a6f1cc15cc1f" +checksum = "cf8e5f34d85d9e0bbe2491d100a7a7c1007bb2467b518080bfe311e8947197a9" dependencies = [ "clap", "roff", @@ -324,7 +323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.25", + "time 0.3.27", "version_check", ] @@ -341,20 +340,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.25", + "time 0.3.27", "url", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -436,7 +425,7 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots 0.25.2", + "webpki-roots", ] [[package]] @@ -507,9 +496,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0c74b03285fe95649f588140b6009dc10bc4f747bd774818ed8e9cc6b5cbb6" +checksum = "b18de4d072c5be455129422f6a69eb17c032467750d857820cb0c9a92e86a4ed" dependencies = [ "base64", "base64-serde", @@ -529,9 +518,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" dependencies = [ "serde", ] @@ -593,9 +582,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -754,15 +743,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -1012,17 +1001,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "iso8601" version = "0.6.1" @@ -1128,19 +1106,12 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" dependencies = [ - "lazy_static", - "libc", "log", "openssl", "openssl-probe", "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", ] [[package]] @@ -1192,9 +1163,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] @@ -1332,9 +1303,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1399,9 +1370,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ "base64", "bytes", @@ -1438,7 +1409,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.22.6", + "webpki-roots", "winreg", ] @@ -1505,9 +1476,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.3" +version = "0.101.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ "ring", "untrusted", @@ -1529,15 +1500,6 @@ dependencies = [ "regex", ] -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "sct" version = "0.7.0" @@ -1548,43 +1510,20 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" -version = "1.0.183" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" dependencies = [ "proc-macro2", "quote", @@ -1593,9 +1532,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -1625,9 +1564,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" +checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" dependencies = [ "base64", "chrono", @@ -1637,14 +1576,14 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.25", + "time 0.3.27", ] [[package]] name = "serde_with_macros" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" +checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" dependencies = [ "darling", "proc-macro2", @@ -1666,9 +1605,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -1724,9 +1663,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1745,9 +1684,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", @@ -1758,18 +1697,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", @@ -1789,9 +1728,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07" dependencies = [ "deranged", "itoa", @@ -1808,9 +1747,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9" dependencies = [ "time-core", ] @@ -1832,9 +1771,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ "backtrace", "bytes", @@ -2101,25 +2040,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.25.2" @@ -2154,7 +2074,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -2172,7 +2092,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -2192,17 +2112,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -2213,9 +2133,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" @@ -2225,9 +2145,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" @@ -2237,9 +2157,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" @@ -2249,9 +2169,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" @@ -2261,9 +2181,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" @@ -2273,9 +2193,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" @@ -2285,17 +2205,18 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index eb15425..6aa5ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,14 @@ clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } +[workspace] +members = ["crunchy-cli-core"] + +[patch.crates-io] +# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports +# `rustls` and `native-tls` as tls backend +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } + [profile.release] strip = true opt-level = "z" diff --git a/crunchy-cli-core/Cargo.lock b/crunchy-cli-core/Cargo.lock deleted file mode 100644 index 0a14466..0000000 --- a/crunchy-cli-core/Cargo.lock +++ /dev/null @@ -1,2222 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aes" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aho-corasick" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" -dependencies = [ - "memchr", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" - -[[package]] -name = "anstyle-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "anstyle-wincon" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" -dependencies = [ - "anstyle", - "windows-sys 0.48.0", -] - -[[package]] -name = "anyhow" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" - -[[package]] -name = "async-trait" -version = "0.1.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - -[[package]] -name = "base64-serde" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" -dependencies = [ - "base64", - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - -[[package]] -name = "cc" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "time 0.1.45", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clap" -version = "4.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" -dependencies = [ - "clap_builder", - "clap_derive", - "once_cell", -] - -[[package]] -name = "clap_builder" -version = "4.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "console" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.45.0", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time 0.3.25", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" -dependencies = [ - "cookie", - "idna 0.2.3", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time 0.3.25", - "url", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - -[[package]] -name = "crunchy-cli-core" -version = "3.0.1" -dependencies = [ - "anyhow", - "async-trait", - "chrono", - "clap", - "crunchyroll-rs", - "ctrlc", - "derive_setters", - "dialoguer", - "dirs", - "fs2", - "indicatif", - "lazy_static", - "log", - "num_cpus", - "regex", - "reqwest", - "sanitize-filename", - "serde", - "serde_json", - "serde_plain", - "shlex", - "sys-locale", - "tempfile", - "tokio", -] - -[[package]] -name = "crunchyroll-rs" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6560e2f721420854d34b94b8bc35b316e3ff6bc83cb7cbf24750ddf55a21d45a" -dependencies = [ - "aes", - "async-trait", - "cbc", - "chrono", - "crunchyroll-rs-internal", - "dash-mpd", - "futures-util", - "http", - "lazy_static", - "m3u8-rs", - "regex", - "reqwest", - "rustls", - "serde", - "serde_json", - "serde_urlencoded", - "smart-default", - "tokio", - "webpki-roots 0.25.2", -] - -[[package]] -name = "crunchyroll-rs-internal" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1398807cd10094f08c1d31423f7979e74c25f772c203d477dfa6ddc4ceb81f2" -dependencies = [ - "darling", - "quote", - "syn", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctrlc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" -dependencies = [ - "nix", - "windows-sys 0.48.0", -] - -[[package]] -name = "darling" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "dash-mpd" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0c74b03285fe95649f588140b6009dc10bc4f747bd774818ed8e9cc6b5cbb6" -dependencies = [ - "base64", - "base64-serde", - "chrono", - "fs-err", - "iso8601", - "log", - "num-traits", - "quick-xml", - "regex", - "serde", - "serde_with", - "thiserror", - "tokio", - "xattr", -] - -[[package]] -name = "deranged" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" -dependencies = [ - "serde", -] - -[[package]] -name = "derive_setters" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dialoguer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" -dependencies = [ - "console", - "shell-words", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fastrand" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs-err" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "h2" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 1.9.3", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.4.9", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" -dependencies = [ - "futures-util", - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown 0.14.0", - "serde", -] - -[[package]] -name = "indicatif" -version = "0.17.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" -dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "unicode-width", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" - -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "iso8601" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" -dependencies = [ - "nom", -] - -[[package]] -name = "itoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" - -[[package]] -name = "js-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "linux-raw-sys" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "m3u8-rs" -version = "5.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39af8845edca961e3286dcbafeb9e6407d3df6a616ef086847162d46f438d75" -dependencies = [ - "chrono", - "nom", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" -dependencies = [ - "log", - "openssl", - "openssl-probe", - "openssl-sys", -] - -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "static_assertions", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-traits" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "openssl" -version = "0.10.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "111.27.0+1.1.1v" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pin-project-lite" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "portable-atomic" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" - -[[package]] -name = "proc-macro2" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" -dependencies = [ - "idna 0.3.0", - "psl-types", -] - -[[package]] -name = "quick-xml" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quote" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "reqwest" -version = "0.11.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" -dependencies = [ - "base64", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tokio-socks", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.22.6", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustix" -version = "0.38.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustls" -version = "0.21.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" -dependencies = [ - "base64", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "ryu" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - -[[package]] -name = "sanitize-filename" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "serde" -version = "1.0.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_plain" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1402f54f9a3b9e2efe71c1cea24e648acce55887983553eeb858cf3115acfd49" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.0.0", - "serde", - "serde_json", - "serde_with_macros", - "time 0.3.25", -] - -[[package]] -name = "serde_with_macros" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9197f1ad0e3c173a0222d3c4404fb04c3afe87e962bcb327af73e8301fa203c7" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smart-default" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "2.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sys-locale" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b9eefabb91675082b41eb94c3ecd91af7656caee3fb4961a07c0ec8c7ca6f" -dependencies = [ - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "tempfile" -version = "3.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "thiserror" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" -dependencies = [ - "deranged", - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" -dependencies = [ - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "socket2 0.5.3", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" -dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna 0.4.0", - "percent-encoding", -] - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "web-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "xattr" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" -dependencies = [ - "libc", -] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5787f39..c66aa1e 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -37,8 +37,3 @@ sys-locale = "0.3" [build-dependencies] chrono = "0.4" - -[patch.crates-io] -# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports -# `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } From 70b3a7a3e1d0c935ccc6c8f3decc8e5948114b7a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 25 Aug 2023 14:34:16 +0200 Subject: [PATCH 443/630] Remove toolchain setup step from apple build action --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb2f797..f020b68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,12 +76,6 @@ jobs: target/ key: x86_64-apple-darwin-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install toolchain - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - target: x86_64-apple-darwin - - name: Build run: cargo build --release --all-features --target x86_64-apple-darwin From e06e6b2b01b13eac73e714e2bef8e6b5f71d5f14 Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:21:37 +0200 Subject: [PATCH 444/630] Update README.md (#240) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 967eecf..7a620cb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # crunchy-cli -๐Ÿ‘‡ A Command-line downloader for Crunchyroll [Crunchyroll](https://www.crunchyroll.com). +๐Ÿ‘‡ A Command-line downloader for [Crunchyroll](https://www.crunchyroll.com). <p align="center"> <a href="https://github.com/crunchy-labs/crunchy-cli"> From 3ae6fe4a1a43b034a9e9fe17c67441eed5b5ba8e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 25 Aug 2023 17:14:44 +0200 Subject: [PATCH 445/630] Fmt --- crunchy-cli-core/src/archive/command.rs | 3 ++- crunchy-cli-core/src/download/command.rs | 3 ++- crunchy-cli-core/src/utils/format.rs | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index b7cd8b7..7ce1658 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -213,7 +213,8 @@ async fn get_format( for single_format in single_formats { let stream = single_format.stream().await?; - let Some((video, audio)) = variant_data_from_stream(&stream, &archive.resolution).await? else { + let Some((video, audio)) = variant_data_from_stream(&stream, &archive.resolution).await? + else { if single_format.is_episode() { bail!( "Resolution ({}) is not available for episode {} ({}) of {} season {}", diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index df4e616..031ed04 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -185,7 +185,8 @@ async fn get_format( single_format: &SingleFormat, ) -> Result<(DownloadFormat, Format)> { let stream = single_format.stream().await?; - let Some((video, audio)) = variant_data_from_stream(&stream, &download.resolution).await? else { + let Some((video, audio)) = variant_data_from_stream(&stream, &download.resolution).await? + else { if single_format.is_episode() { bail!( "Resolution ({}) is not available for episode {} ({}) of {} season {}", diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index d975042..b6dcf35 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -297,8 +297,8 @@ impl Iterator for SingleFormatCollectionIterator { type Item = Vec<SingleFormat>; fn next(&mut self) -> Option<Self::Item> { - let Some((_, episodes)) = self.0.0.iter_mut().next() else { - return None + let Some((_, episodes)) = self.0 .0.iter_mut().next() else { + return None; }; let value = episodes.pop_first().unwrap().1; From 18f891efd2aa7d91a944c2620adb9c66014388a1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 25 Aug 2023 17:15:06 +0200 Subject: [PATCH 446/630] Use system certs when using openssl --- Cargo.lock | 67 +++++++++++++++++++++++++++++++++---- crunchy-cli-core/Cargo.toml | 9 ++--- crunchy-cli-core/src/lib.rs | 12 ++++++- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b07d41b..5fccedf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,16 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -391,6 +401,7 @@ dependencies = [ "num_cpus", "regex", "reqwest", + "rustls-native-certs", "sanitize-filename", "serde", "serde_json", @@ -403,9 +414,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6560e2f721420854d34b94b8bc35b316e3ff6bc83cb7cbf24750ddf55a21d45a" +checksum = "771cd92c5a4cc050f301674d77bca6c23f8f260ef346bd06c11b4b05ab9e0166" dependencies = [ "aes", "async-trait", @@ -430,9 +441,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1398807cd10094f08c1d31423f7979e74c25f772c203d477dfa6ddc4ceb81f2" +checksum = "b260191f1125c7ba31e35071524938de5c6b0c2d248e5a35c62c4605e2da427e" dependencies = [ "darling", "quote", @@ -1244,9 +1255,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1465,6 +1476,18 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.3" @@ -1500,6 +1523,15 @@ dependencies = [ "regex", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "sct" version = "0.7.0" @@ -1510,6 +1542,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.186" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index c66aa1e..6c9b59c 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -6,15 +6,15 @@ edition = "2021" license = "MIT" [features] -openssl = ["reqwest/native-tls-alpn"] -openssl-static = ["reqwest/native-tls-alpn", "reqwest/native-tls-vendored"] +openssl = ["reqwest/native-tls-alpn", "dep:rustls-native-certs"] +openssl-static = ["reqwest/native-tls-alpn", "reqwest/native-tls-vendored", "dep:rustls-native-certs"] [dependencies] anyhow = "1.0" async-trait = "0.1" clap = { version = "4.3", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.6.1", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.6.2", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.10", default-features = false } dirs = "5.0" @@ -31,9 +31,10 @@ serde = "1.0" serde_json = "1.0" serde_plain = "1.0" shlex = "1.1" +sys-locale = "0.3" tempfile = "3.7" tokio = { version = "1.31", features = ["macros", "rt-multi-thread", "time"] } -sys-locale = "0.3" +rustls-native-certs = { version = "0.6", optional = true } [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 793e946..bae3bed 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -271,7 +271,17 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { CrunchyrollBuilder::predefined_client_builder() }; #[cfg(any(feature = "openssl", feature = "openssl-static"))] - let client = builder.use_native_tls().build().unwrap(); + let client = { + let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); + + for certificate in rustls_native_certs::load_native_certs().unwrap() { + builder = builder.add_root_certificate( + reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), + ) + } + + builder.build().unwrap() + }; #[cfg(not(any(feature = "openssl", feature = "openssl-static")))] let client = builder.build().unwrap(); From b477ca982cd25d009f80bb4aa631128a906635dd Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 6 Sep 2023 02:55:04 +0200 Subject: [PATCH 447/630] Add options to get drm dash and hls url with search --- crunchy-cli-core/src/search/command.rs | 2 ++ crunchy-cli-core/src/search/format.rs | 28 ++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index 0159c05..1ec4cb9 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -87,7 +87,9 @@ pub struct Search { /// /// stream.locale โ†’ Stream locale/language /// stream.dash_url โ†’ Stream url in DASH format + /// stream.drm_dash_url โ†’ Stream url in DRM protected DASH format /// stream.hls_url โ†’ Stream url in HLS format + /// stream.drm_hls_url โ†’ Stream url in DRM protected HLS format /// /// subtitle.locale โ†’ Subtitle locale/language /// subtitle.url โ†’ Url to the subtitle diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 3574597..55cba7c 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -163,25 +163,37 @@ impl From<&Concert> for FormatConcert { struct FormatStream { pub locale: Locale, pub dash_url: String, + pub drm_dash_url: String, pub hls_url: String, + pub drm_hls_url: String, } impl From<&Stream> for FormatStream { fn from(value: &Stream) -> Self { - let (dash_url, hls_url) = value.variants.get(&Locale::Custom("".to_string())).map_or( - ("".to_string(), "".to_string()), - |v| { + let (dash_url, drm_dash_url, hls_url, drm_hls_url) = + value.variants.get(&Locale::Custom("".to_string())).map_or( ( - v.adaptive_dash.clone().unwrap_or_default().url, - v.adaptive_hls.clone().unwrap_or_default().url, - ) - }, - ); + "".to_string(), + "".to_string(), + "".to_string(), + "".to_string(), + ), + |v| { + ( + v.adaptive_dash.clone().unwrap_or_default().url, + v.drm_adaptive_dash.clone().unwrap_or_default().url, + v.adaptive_hls.clone().unwrap_or_default().url, + v.drm_adaptive_hls.clone().unwrap_or_default().url, + ) + }, + ); Self { locale: value.audio_locale.clone(), dash_url, + drm_dash_url, hls_url, + drm_hls_url, } } } From 0f7e6c9120506ba502e653e64928176e66d31108 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 6 Sep 2023 03:02:40 +0200 Subject: [PATCH 448/630] Add root flag to set custom user agent --- crunchy-cli-core/src/lib.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index bae3bed..50b1e4c 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -63,6 +63,10 @@ pub struct Cli { #[arg(value_parser = crate::utils::clap::clap_parse_proxy)] proxy: Option<Proxy>, + #[arg(help = "Use custom user agent")] + #[clap(long)] + user_agent: Option<String>, + #[clap(subcommand)] command: Command, } @@ -261,15 +265,17 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { lang }; - let proxy = cli.proxy.clone(); let mut builder = Crunchyroll::builder() .locale(locale) .client({ - let builder = if let Some(p) = &proxy { - CrunchyrollBuilder::predefined_client_builder().proxy(p.clone()) - } else { - CrunchyrollBuilder::predefined_client_builder() - }; + let mut builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(p) = &cli.proxy { + builder = builder.proxy(p.clone()) + } + if let Some(ua) = &cli.user_agent { + builder = builder.user_agent(ua) + } + #[cfg(any(feature = "openssl", feature = "openssl-static"))] let client = { let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); @@ -292,14 +298,6 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { if let Command::Download(download) = &cli.command { builder = builder.preferred_audio_locale(download.audio.clone()) } - if let Some(p) = &cli.proxy { - builder = builder.client( - CrunchyrollBuilder::predefined_client_builder() - .proxy(p.clone()) - .build() - .unwrap(), - ) - } let root_login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 From 7485bd8e76aa6e5fda7e5850c4342556b46d57d1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 8 Sep 2023 21:41:25 +0200 Subject: [PATCH 449/630] Update dependencies and version --- Cargo.lock | 179 +++++++++++++++--------------------- Cargo.toml | 8 +- crunchy-cli-core/Cargo.toml | 10 +- 3 files changed, 85 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fccedf..b7e3911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64-serde" @@ -183,9 +183,9 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cbc" @@ -213,18 +213,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -239,20 +238,19 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", @@ -262,18 +260,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.0" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" +checksum = "4110a1e6af615a9e6d0a36f805d5c99099f8bab9b8042f5bc1fa220a4a89e36f" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", @@ -323,7 +321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.27", + "time", "version_check", ] @@ -340,7 +338,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.27", + "time", "url", ] @@ -371,7 +369,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.1" +version = "3.0.2" dependencies = [ "chrono", "clap", @@ -383,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.1" +version = "3.0.2" dependencies = [ "anyhow", "async-trait", @@ -462,9 +460,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ "nix", "windows-sys 0.48.0", @@ -608,9 +606,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -749,7 +747,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1078,9 +1076,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mime" @@ -1110,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -1127,14 +1125,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "cfg-if", "libc", - "static_assertions", ] [[package]] @@ -1174,9 +1171,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1189,11 +1186,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.56" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -1221,18 +1218,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.27.0+1.1.1v" +version = "300.1.3+3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" +checksum = "cd2c101a165fff9935e34def4669595ab1c7847943c42be86e21503e482be107" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.91" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -1273,9 +1270,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "proc-macro2" @@ -1352,9 +1349,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", @@ -1364,9 +1361,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -1375,9 +1372,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" @@ -1453,9 +1450,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" dependencies = [ "bitflags 2.4.0", "errno", @@ -1466,9 +1463,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.6" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", @@ -1567,18 +1564,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.186" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.186" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -1598,9 +1595,9 @@ dependencies = [ [[package]] name = "serde_plain" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" dependencies = [ "serde", ] @@ -1631,7 +1628,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.27", + "time", ] [[package]] @@ -1654,9 +1651,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "slab" @@ -1704,12 +1701,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" @@ -1718,9 +1709,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.29" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -1729,12 +1720,11 @@ dependencies = [ [[package]] name = "sys-locale" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b9eefabb91675082b41eb94c3ecd91af7656caee3fb4961a07c0ec8c7ca6f" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" dependencies = [ "libc", - "windows-sys 0.45.0", ] [[package]] @@ -1752,18 +1742,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", @@ -1772,20 +1762,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ "deranged", "itoa", @@ -1802,9 +1781,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] @@ -1971,9 +1950,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna 0.4.0", @@ -2007,12 +1986,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 6aa5ce0..be15b04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.1" +version = "3.0.2" edition = "2021" license = "MIT" @@ -12,14 +12,14 @@ openssl = ["crunchy-cli-core/openssl"] openssl-static = ["crunchy-cli-core/openssl-static"] [dependencies] -tokio = { version = "1.31", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.32", features = ["macros", "rt-multi-thread", "time"], default-features = false } crunchy-cli-core = { path = "./crunchy-cli-core" } [build-dependencies] chrono = "0.4" -clap = { version = "4.3", features = ["string"] } -clap_complete = "4.3" +clap = { version = "4.4", features = ["string"] } +clap_complete = "4.4" clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 6c9b59c..f75c57d 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.1" +version = "3.0.2" edition = "2021" license = "MIT" @@ -12,7 +12,7 @@ openssl-static = ["reqwest/native-tls-alpn", "reqwest/native-tls-vendored", "dep [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = { version = "4.3", features = ["derive", "string"] } +clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { version = "0.6.2", features = ["dash-stream"] } ctrlc = "3.4" @@ -30,10 +30,10 @@ sanitize-filename = "0.5" serde = "1.0" serde_json = "1.0" serde_plain = "1.0" -shlex = "1.1" +shlex = "1.2" sys-locale = "0.3" -tempfile = "3.7" -tokio = { version = "1.31", features = ["macros", "rt-multi-thread", "time"] } +tempfile = "3.8" +tokio = { version = "1.32", features = ["macros", "rt-multi-thread", "time"] } rustls-native-certs = { version = "0.6", optional = true } [build-dependencies] From 185b65fc9b58a5085d1fe538f1c2519eb7e63de5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 8 Sep 2023 22:49:58 +0200 Subject: [PATCH 450/630] Remove invalid character from AUR binary PKGBUILD --- .github/scripts/PKGBUILD.binary | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/PKGBUILD.binary b/.github/scripts/PKGBUILD.binary index 0117723..3493ee6 100644 --- a/.github/scripts/PKGBUILD.binary +++ b/.github/scripts/PKGBUILD.binary @@ -1,7 +1,7 @@ # Maintainer: ByteDream pkgname=crunchy-cli-bin pkgdesc="Command-line downloader for Crunchyroll" -arch=('x86_64', 'aarch64') +arch=('x86_64' 'aarch64') url="https://github.com/crunchy-labs/crunchy-cli" license=('MIT') From 8eda8df3f7dd048f9c5aae672f4cbb242d05b1bc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 21 Sep 2023 13:46:23 +0200 Subject: [PATCH 451/630] Use native tls as default tls backend, add features to use rustls or openssl instead --- Cargo.lock | 98 ++++++++++++++++++++----------------- Cargo.toml | 20 +++++--- build.rs | 23 +++++++++ crunchy-cli-core/Cargo.toml | 6 ++- crunchy-cli-core/src/lib.rs | 4 +- src/main.rs | 8 +++ 6 files changed, 103 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7e3911..506097c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64-serde" @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytes" @@ -213,9 +213,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", @@ -238,9 +238,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.2" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" dependencies = [ "anstream", "anstyle", @@ -287,9 +287,9 @@ checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clap_mangen" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8e5f34d85d9e0bbe2491d100a7a7c1007bb2467b518080bfe311e8947197a9" +checksum = "b44f35c514163027542f7147797ff930523eea288e03642727348ef1a9666f6b" dependencies = [ "clap", "roff", @@ -376,6 +376,7 @@ dependencies = [ "clap_complete", "clap_mangen", "crunchy-cli-core", + "native-tls", "tokio", ] @@ -795,9 +796,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -1042,15 +1043,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "log" @@ -1115,12 +1116,17 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=570100d#570100d3391bd9aab7a390cfef0d1a28e8efe200" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=fdba246#fdba246a79986607cbdf573733445498bb6da2a9" dependencies = [ + "libc", "log", "openssl", "openssl-probe", "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1218,9 +1224,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.3+3.1.2" +version = "300.1.5+3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd2c101a165fff9935e34def4669595ab1c7847943c42be86e21503e482be107" +checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" dependencies = [ "cc", ] @@ -1276,9 +1282,9 @@ checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -1450,9 +1456,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ "bitflags 2.4.0", "errno", @@ -1496,9 +1502,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted", @@ -1584,9 +1590,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -1687,9 +1693,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -1709,9 +1715,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.31" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -1815,7 +1821,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "socket2 0.5.4", "tokio-macros", "windows-sys 0.48.0", ] @@ -1865,9 +1871,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -1911,9 +1917,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" @@ -1923,9 +1929,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1938,9 +1944,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "untrusted" diff --git a/Cargo.toml b/Cargo.toml index be15b04..099bc6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,22 @@ edition = "2021" license = "MIT" [features] -default = ["openssl-static"] +default = ["native-tls"] -openssl = ["crunchy-cli-core/openssl"] -openssl-static = ["crunchy-cli-core/openssl-static"] +rustls-tls = ["crunchy-cli-core/rustls-tls"] +native-tls = ["crunchy-cli-core/native-tls"] +openssl-tls = ["dep:native-tls", "native-tls/openssl", "crunchy-cli-core/openssl-tls"] +openssl-tls-static = ["dep:native-tls", "native-tls/openssl", "crunchy-cli-core/openssl-tls-static"] + +# deprecated +openssl = ["openssl-tls"] +openssl-static = ["openssl-tls-static"] [dependencies] tokio = { version = "1.32", features = ["macros", "rt-multi-thread", "time"], default-features = false } +native-tls = { version = "0.2.11", optional = true } + crunchy-cli-core = { path = "./crunchy-cli-core" } [build-dependencies] @@ -28,9 +36,9 @@ crunchy-cli-core = { path = "./crunchy-cli-core" } members = ["crunchy-cli-core"] [patch.crates-io] -# fork of the `native-tls` crate which uses openssl as backend on every platform. this is done as `reqwest` only supports -# `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "570100d" } +# fork of the `native-tls` crate which can use openssl as backend on every platform. this is done as `reqwest` only +# supports `rustls` and `native-tls` as tls backend +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "fdba246" } [profile.release] strip = true diff --git a/build.rs b/build.rs index 7056c67..5b464c4 100644 --- a/build.rs +++ b/build.rs @@ -3,6 +3,29 @@ use clap_complete::shells; use std::path::{Path, PathBuf}; fn main() -> std::io::Result<()> { + let rustls_tls = cfg!(feature = "rustls-tls"); + let native_tls = cfg!(feature = "native-tls"); + let openssl_tls = cfg!(any(feature = "openssl-tls", feature = "openssl-tls-static")); + + if rustls_tls as u8 + native_tls as u8 + openssl_tls as u8 > 1 { + let active_tls_backend = if openssl_tls { + "openssl" + } else if native_tls { + "native tls" + } else { + "rustls" + }; + + println!("cargo:warning=Multiple tls backends are activated (through the '*-tls' features). Consider to activate only one as it is not possible to change the backend during runtime. The active backend for this build will be '{}'.", active_tls_backend) + } + + if cfg!(feature = "openssl") { + println!("cargo:warning=The 'openssl' feature is deprecated and will be removed in a future version. Use the 'openssl-tls' feature instead.") + } + if cfg!(feature = "openssl-static") { + println!("cargo:warning=The 'openssl-static' feature is deprecated and will be removed in a future version. Use the 'openssl-tls-static' feature instead.") + } + // note that we're using an anti-pattern here / violate the rust conventions. build script are // not supposed to write outside of 'OUT_DIR'. to have the generated files in the build "root" // (the same directory where the output binary lives) is much simpler than in 'OUT_DIR' since diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f75c57d..8288bf1 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -6,8 +6,10 @@ edition = "2021" license = "MIT" [features] -openssl = ["reqwest/native-tls-alpn", "dep:rustls-native-certs"] -openssl-static = ["reqwest/native-tls-alpn", "reqwest/native-tls-vendored", "dep:rustls-native-certs"] +rustls-tls = ["reqwest/rustls-tls"] +native-tls = ["reqwest/native-tls", "reqwest/native-tls-alpn"] +openssl-tls = ["reqwest/native-tls", "reqwest/native-tls-alpn", "dep:rustls-native-certs"] +openssl-tls-static = ["reqwest/native-tls", "reqwest/native-tls-alpn", "reqwest/native-tls-vendored", "dep:rustls-native-certs"] [dependencies] anyhow = "1.0" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 50b1e4c..02bef6f 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -276,7 +276,7 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { builder = builder.user_agent(ua) } - #[cfg(any(feature = "openssl", feature = "openssl-static"))] + #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] let client = { let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); @@ -288,7 +288,7 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { builder.build().unwrap() }; - #[cfg(not(any(feature = "openssl", feature = "openssl-static")))] + #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] let client = builder.build().unwrap(); client diff --git a/src/main.rs b/src/main.rs index 5e1d695..da3c699 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,11 @@ +#[cfg(not(any( + feature = "rustls-tls", + feature = "native-tls", + feature = "openssl-tls", + feature = "openssl-tls-static" +)))] +compile_error!("At least one tls feature must be activated"); + #[tokio::main] async fn main() { crunchy_cli_core::cli_entrypoint().await From 64428ea7d175a2137b755456662914adc73f0c6a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 21 Sep 2023 19:18:29 +0200 Subject: [PATCH 452/630] Rename native-tls crate to prevent false-positive build warnings --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 099bc6f..b5ae0a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ default = ["native-tls"] rustls-tls = ["crunchy-cli-core/rustls-tls"] native-tls = ["crunchy-cli-core/native-tls"] -openssl-tls = ["dep:native-tls", "native-tls/openssl", "crunchy-cli-core/openssl-tls"] -openssl-tls-static = ["dep:native-tls", "native-tls/openssl", "crunchy-cli-core/openssl-tls-static"] +openssl-tls = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls"] +openssl-tls-static = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls-static"] # deprecated openssl = ["openssl-tls"] @@ -20,7 +20,7 @@ openssl-static = ["openssl-tls-static"] [dependencies] tokio = { version = "1.32", features = ["macros", "rt-multi-thread", "time"], default-features = false } -native-tls = { version = "0.2.11", optional = true } +native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } crunchy-cli-core = { path = "./crunchy-cli-core" } From 01913e0db3ba9f9dc78f1759734ce59f1c6c8790 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 21 Sep 2023 19:20:00 +0200 Subject: [PATCH 453/630] Adjust ci and PKGBUILD build args to feature changes --- .github/scripts/PKGBUILD.source | 2 +- .github/workflows/ci.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source index 015efe3..af6b8e1 100644 --- a/.github/scripts/PKGBUILD.source +++ b/.github/scripts/PKGBUILD.source @@ -19,7 +19,7 @@ build() { export CARGO_HOME="$srcdir/cargo-home" export RUSTUP_TOOLCHAIN=stable - cargo build --release --no-default-features --features openssl + cargo build --release } package() { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f020b68..7493041 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: cargo install --force cross - name: Build - run: cross build --release --all-features --target ${{ matrix.toolchain }} + run: cross build --release --no-default-features --features openssl-tls-static --target ${{ matrix.toolchain }} - name: Upload binary artifact uses: actions/upload-artifact@v3 @@ -77,7 +77,7 @@ jobs: key: x86_64-apple-darwin-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Build - run: cargo build --release --all-features --target x86_64-apple-darwin + run: cargo build --release --target x86_64-apple-darwin - name: Upload binary artifact uses: actions/upload-artifact@v3 @@ -110,7 +110,7 @@ jobs: - name: Build shell: msys2 {0} - run: cargo build --release --all-features --target x86_64-pc-windows-gnu + run: cargo build --release --target x86_64-pc-windows-gnu - name: Upload binary artifact uses: actions/upload-artifact@v3 From 3e21ca4fe7c1831e857118a391bd01e2146c36ab Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 22 Sep 2023 11:52:39 +0200 Subject: [PATCH 454/630] Update dependencies and version --- Cargo.lock | 17 +++++++++-------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 506097c..09cad3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.0.2" +version = "3.0.3" dependencies = [ "chrono", "clap", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.2" +version = "3.0.3" dependencies = [ "anyhow", "async-trait", @@ -549,12 +549,13 @@ dependencies = [ [[package]] name = "dialoguer" -version = "0.10.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ "console", "shell-words", + "thiserror", ] [[package]] @@ -975,9 +976,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", "instant", @@ -1502,9 +1503,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.5" +version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ "ring", "untrusted", diff --git a/Cargo.toml b/Cargo.toml index b5ae0a6..e5882d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.2" +version = "3.0.3" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 8288bf1..fc298f3 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.2" +version = "3.0.3" edition = "2021" license = "MIT" @@ -18,7 +18,7 @@ clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { version = "0.6.2", features = ["dash-stream"] } ctrlc = "3.4" -dialoguer = { version = "0.10", default-features = false } +dialoguer = { version = "0.11", default-features = false } dirs = "5.0" derive_setters = "0.1" fs2 = "0.4" From a93a1fa807758cf41829233281ca5832a52c6151 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 22 Sep 2023 12:11:00 +0200 Subject: [PATCH 455/630] Fix env variable resolving in publish pipeline --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 80cb5bb..1599111 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: commit_username: release-action commit_email: ${{ secrets.AUR_EMAIL }} ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: Update to version {{ env.RELEASE_VERSION }} + commit_message: Update to version ${{ env.RELEASE_VERSION }} - name: Generate crunchy-cli-bin sha sums run: | @@ -67,4 +67,4 @@ jobs: commit_username: release-action commit_email: ${{ secrets.AUR_EMAIL }} ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: Update to version {{ env.RELEASE_VERSION }} + commit_message: Update to version ${{ env.RELEASE_VERSION }} From d79197edc6fb9bf2bac699d2f6b55bfae4d7dea7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 23 Sep 2023 16:56:38 +0200 Subject: [PATCH 456/630] Use async mutex and channel instead of the std equivalents --- crunchy-cli-core/src/utils/download.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 4531c69..29f160a 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -17,9 +17,11 @@ use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -use std::sync::{mpsc, Arc, Mutex}; +use std::sync::Arc; use std::time::Duration; use tempfile::TempPath; +use tokio::sync::mpsc::unbounded_channel; +use tokio::sync::Mutex; use tokio::task::JoinSet; #[derive(Clone, Debug)] @@ -576,7 +578,7 @@ pub async fn download_segments( segs[i - ((i / cpus) * cpus)].push(segment); } - let (sender, receiver) = mpsc::channel(); + let (sender, mut receiver) = unbounded_channel(); let mut join_set: JoinSet<Result<()>> = JoinSet::new(); for num in 0..cpus { @@ -629,7 +631,7 @@ pub async fn download_segments( buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); - let mut c = thread_count.lock().unwrap(); + let mut c = thread_count.lock().await; debug!( "Downloaded and decrypted segment [{}/{} {:.2}%] {}", num + (i * cpus) + 1, @@ -663,7 +665,7 @@ pub async fn download_segments( // the segment number and the values the corresponding bytes let mut data_pos = 0; let mut buf: BTreeMap<i32, Vec<u8>> = BTreeMap::new(); - for (pos, bytes) in receiver.iter() { + while let Some((pos, bytes)) = receiver.recv().await { // if the position is lower than 0, an error occurred in the sending download thread if pos < 0 { break; From f48474ba776bce8a6d00e6f0c97f38cf047b8eb4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 27 Sep 2023 00:03:26 +0200 Subject: [PATCH 457/630] Remove numbers from binary PKGBUILD env variables --- .github/scripts/PKGBUILD.binary | 4 ++-- .github/workflows/publish.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/scripts/PKGBUILD.binary b/.github/scripts/PKGBUILD.binary index 3493ee6..00fee47 100644 --- a/.github/scripts/PKGBUILD.binary +++ b/.github/scripts/PKGBUILD.binary @@ -24,8 +24,8 @@ source_aarch64=( "LICENSE::https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/v${pkgver}/LICENSE" ) noextract=("manpages.zip" "completions.zip") -sha256sums_x86_64=('$CI_x86_64_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') -sha256sums_aarch64=('$CI_aarch64_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') +sha256sums_x86_64=('$CI_AMD_BINARY_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') +sha256sums_aarch64=('$CI_ARM_BINARY_SHA_SUM' '$CI_MANPAGES_SHA_SUM' '$CI_COMPLETIONS_SHA_SUM' '$CI_LICENSE_SHA_SUM') package() { cd "$srcdir" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1599111..f777f9e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -52,12 +52,12 @@ jobs: - name: Generate crunchy-cli-bin PKGBUILD env: CI_PKG_VERSION: ${{ env.RELEASE_VERSION }} - CI_x86_64_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_x86_64_SHA256 }} - CI_aarch64_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_aarch64_SHA256 }} + CI_AMD_BINARY_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_x86_64_SHA256 }} + CI_ARM_BINARY_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_aarch64_SHA256 }} CI_MANPAGES_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_MANPAGES_SHA256 }} CI_COMPLETIONS_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_COMPLETIONS_SHA256 }} CI_LICENSE_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_LICENSE_SHA256 }} - run: envsubst '$CI_PKG_VERSION,$CI_x86_64_SHA_SUM,$CI_aarch64_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD + run: envsubst '$CI_PKG_VERSION,$CI_AMD_BINARY_SHA_SUM,$CI_ARM_BINARY_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD - name: Publish crunchy-cli-bin to AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 From 9596175f7fe2a40fe2f0c20772d373aa13150a0f Mon Sep 17 00:00:00 2001 From: Valentine Briese <valentinegb@icloud.com> Date: Wed, 11 Oct 2023 18:24:45 -0700 Subject: [PATCH 458/630] Add FFmpeg Apple hardware acceleration --- crunchy-cli-core/src/utils/ffmpeg.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 210b0f7..3407d91 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -66,7 +66,8 @@ ffmpeg_enum! { ffmpeg_enum! { enum FFmpegHwAccel { - Nvidia + Nvidia, + Apple } } @@ -275,6 +276,9 @@ impl FFmpegPreset { ]); output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } + FFmpegHwAccel::Apple => { + output.extend(["-c:v", "h264_videotoolbox", "-c:a", "copy"]) + } } } else { output.extend(["-c:v", "libx264", "-c:a", "copy"]) @@ -300,6 +304,9 @@ impl FFmpegPreset { ]); output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) } + FFmpegHwAccel::Apple => { + output.extend(["-c:v", "hevc_videotoolbox", "-c:a", "copy"]) + } } } else { output.extend(["-c:v", "libx265", "-c:a", "copy"]) From 610593a79547b577b46707f996adefa7cf27db15 Mon Sep 17 00:00:00 2001 From: Valentine Briese <valentinegb@icloud.com> Date: Wed, 11 Oct 2023 18:26:51 -0700 Subject: [PATCH 459/630] Make H265 codec compatible with Apple HEVC standards --- crunchy-cli-core/src/utils/ffmpeg.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 3407d91..6145fcf 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -302,14 +302,26 @@ impl FFmpegPreset { "-c:v", "h264_cuvid", ]); - output.extend(["-c:v", "hevc_nvenc", "-c:a", "copy"]) - } - FFmpegHwAccel::Apple => { - output.extend(["-c:v", "hevc_videotoolbox", "-c:a", "copy"]) + output.extend([ + "-c:v", + "hevc_nvenc", + "-c:a", + "copy", + "-tag:v", + "hvc1", + ]) } + FFmpegHwAccel::Apple => output.extend([ + "-c:v", + "hevc_videotoolbox", + "-c:a", + "copy", + "-tag:v", + "hvc1", + ]), } } else { - output.extend(["-c:v", "libx265", "-c:a", "copy"]) + output.extend(["-c:v", "libx265", "-c:a", "copy", "-tag:v", "hvc1"]) } match quality { From 7095e2b8b6464edeb9d79a21540365d02133a93b Mon Sep 17 00:00:00 2001 From: Valentine Briese <valentinegb@icloud.com> Date: Wed, 11 Oct 2023 18:54:47 -0700 Subject: [PATCH 460/630] Use `-q:v` FFmpeg option for Apple hardware acceleration --- crunchy-cli-core/src/utils/ffmpeg.rs | 65 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 6145fcf..af29bfd 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -263,6 +263,12 @@ impl FFmpegPreset { match codec { FFmpegCodec::H264 => { + let mut crf_quality = || match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "18"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + }; + if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { @@ -274,23 +280,37 @@ impl FFmpegPreset { "-c:v", "h264_cuvid", ]); + crf_quality(); output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } FFmpegHwAccel::Apple => { + // Apple's Video Toolbox encoders ignore `-crf`, + // use `-q:v` instead. It's on a scale of 1-100, + // 100 being lossless. Just did some math + // ((-a/51+1)*99+1 where `a` is the old crf value) + // so these settings very likely need some more + // tweeking. + match quality { + FFmpegQuality::Lossless => output.extend(["-q:v", "65"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-q:v", "32"]), + } + output.extend(["-c:v", "h264_videotoolbox", "-c:a", "copy"]) } } } else { + crf_quality(); output.extend(["-c:v", "libx264", "-c:a", "copy"]) } - - match quality { - FFmpegQuality::Lossless => output.extend(["-crf", "18"]), - FFmpegQuality::Normal => (), - FFmpegQuality::Low => output.extend(["-crf", "35"]), - } } FFmpegCodec::H265 => { + let mut crf_quality = || match quality { + FFmpegQuality::Lossless => output.extend(["-crf", "20"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-crf", "35"]), + }; + if let Some(hwaccel) = hwaccel_opt { match hwaccel { FFmpegHwAccel::Nvidia => { @@ -302,6 +322,7 @@ impl FFmpegPreset { "-c:v", "h264_cuvid", ]); + crf_quality(); output.extend([ "-c:v", "hevc_nvenc", @@ -311,24 +332,28 @@ impl FFmpegPreset { "hvc1", ]) } - FFmpegHwAccel::Apple => output.extend([ - "-c:v", - "hevc_videotoolbox", - "-c:a", - "copy", - "-tag:v", - "hvc1", - ]), + FFmpegHwAccel::Apple => { + // See the comment that starts on line 287. + match quality { + FFmpegQuality::Lossless => output.extend(["-q:v", "61"]), + FFmpegQuality::Normal => (), + FFmpegQuality::Low => output.extend(["-q:v", "32"]), + } + + output.extend([ + "-c:v", + "hevc_videotoolbox", + "-c:a", + "copy", + "-tag:v", + "hvc1", + ]) + } } } else { + crf_quality(); output.extend(["-c:v", "libx265", "-c:a", "copy", "-tag:v", "hvc1"]) } - - match quality { - FFmpegQuality::Lossless => output.extend(["-crf", "20"]), - FFmpegQuality::Normal => (), - FFmpegQuality::Low => output.extend(["-crf", "35"]), - } } FFmpegCodec::Av1 => { output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); From e5db8e95043c7d9dabeb6e05c0f2f6311563a58d Mon Sep 17 00:00:00 2001 From: Valentine Briese <valentinegb@icloud.com> Date: Thu, 12 Oct 2023 12:20:06 -0700 Subject: [PATCH 461/630] Fix `ffmpeg-preset` option in `download` command (#254) --- crunchy-cli-core/src/download/command.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 031ed04..baf27bf 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -148,7 +148,8 @@ impl Execute for Download { Some("mpegts".to_string()) } else { None - }); + }) + .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()); for mut single_formats in single_format_collection.into_iter() { // the vec contains always only one item From 13335c020b19f80cde7e56455d4380c9921a681f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 13 Oct 2023 11:41:56 +0200 Subject: [PATCH 462/630] Sanitize the full output filename (#253) --- crunchy-cli-core/src/utils/format.rs | 80 +++++++++++++--------------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index b6dcf35..618f7d5 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -371,51 +371,43 @@ impl Format { /// Formats the given string if it has specific pattern in it. It's possible to sanitize it which /// removes characters which can cause failures if the output string is used as a file name. pub fn format_path(&self, path: PathBuf, sanitize: bool) -> PathBuf { - let sanitize_func = if sanitize { - |s: &str| sanitize_filename::sanitize(s) + let path = path + .to_string_lossy() + .to_string() + .replace("{title}", &self.title) + .replace( + "{audio}", + &self + .locales + .iter() + .map(|(a, _)| a.to_string()) + .collect::<Vec<String>>() + .join("|"), + ) + .replace("{resolution}", &self.resolution.to_string()) + .replace("{series_id}", &self.series_id) + .replace("{series_name}", &self.series_name) + .replace("{season_id}", &self.season_id) + .replace("{season_name}", &self.season_title) + .replace( + "{season_number}", + &format!("{:0>2}", self.season_number.to_string()), + ) + .replace("{episode_id}", &self.episode_id) + .replace( + "{episode_number}", + &format!("{:0>2}", self.episode_number.to_string()), + ) + .replace( + "{relative_episode_number}", + &self.relative_episode_number.unwrap_or_default().to_string(), + ); + + if sanitize { + PathBuf::from(sanitize_filename::sanitize(path)) } else { - // converting this to a string is actually unnecessary - |s: &str| s.to_string() - }; - - let as_string = path.to_string_lossy().to_string(); - - PathBuf::from( - as_string - .replace("{title}", &sanitize_func(&self.title)) - .replace( - "{audio}", - &sanitize_func( - &self - .locales - .iter() - .map(|(a, _)| a.to_string()) - .collect::<Vec<String>>() - .join("|"), - ), - ) - .replace("{resolution}", &sanitize_func(&self.resolution.to_string())) - .replace("{series_id}", &sanitize_func(&self.series_id)) - .replace("{series_name}", &sanitize_func(&self.series_name)) - .replace("{season_id}", &sanitize_func(&self.season_id)) - .replace("{season_name}", &sanitize_func(&self.season_title)) - .replace( - "{season_number}", - &sanitize_func(&format!("{:0>2}", self.season_number.to_string())), - ) - .replace("{episode_id}", &sanitize_func(&self.episode_id)) - .replace( - "{episode_number}", - &sanitize_func(&format!("{:0>2}", self.episode_number.to_string())), - ) - .replace( - "{relative_episode_number}", - &sanitize_func(&format!( - "{:0>2}", - self.relative_episode_number.unwrap_or_default().to_string() - )), - ), - ) + PathBuf::from(path) + } } pub fn visual_output(&self, dst: &Path) { From 81385ef6ce257522dc197d2ce8dd9a252d2f99c7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 15 Oct 2023 20:49:03 +0200 Subject: [PATCH 463/630] Add relative_sequence_number format option (#206, #241, #246) --- crunchy-cli-core/src/archive/command.rs | 24 +++++---- crunchy-cli-core/src/archive/filter.rs | 64 +++++++++++++++--------- crunchy-cli-core/src/download/command.rs | 24 +++++---- crunchy-cli-core/src/download/filter.rs | 60 +++++++++++----------- crunchy-cli-core/src/utils/format.rs | 33 +++++++++--- 5 files changed, 122 insertions(+), 83 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7ce1658..ffdc00c 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -39,17 +39,19 @@ pub struct Archive { #[arg(help = "Name of the output file")] #[arg(long_help = "Name of the output file.\ If you use one of the following pattern they will get replaced:\n \ - {title} โ†’ Title of the video\n \ - {series_name} โ†’ Name of the series\n \ - {season_name} โ†’ Name of the season\n \ - {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ - {season_number} โ†’ Number of the season\n \ - {episode_number} โ†’ Number of the episode\n \ - {relative_episode_number} โ†’ Number of the episode relative to its season\n \ - {series_id} โ†’ ID of the series\n \ - {season_id} โ†’ ID of the season\n \ - {episode_id} โ†’ ID of the episode")] + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {season_number} โ†’ Number of the season\n \ + {episode_number} โ†’ Number of the episode\n \ + {relative_episode_number} โ†’ Number of the episode relative to its season\n \ + {sequence_number} โ†’ Like '{episode_number}' but without possible non-number characters\n \ + {relative_sequence_number} โ†’ Like '{relative_episode_number}' but with support for episode 0's and .5's\n \ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] #[arg(short, long, default_value = "{title}.mkv")] pub(crate) output: String, diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 851b4b5..c2cc206 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -18,7 +18,7 @@ pub(crate) struct ArchiveFilter { url_filter: UrlFilter, archive: Archive, interactive_input: bool, - season_episode_count: HashMap<String, Vec<String>>, + season_episodes: HashMap<String, Vec<Episode>>, season_subtitles_missing: Vec<u32>, season_sorting: Vec<String>, visited: Visited, @@ -30,7 +30,7 @@ impl ArchiveFilter { url_filter, archive, interactive_input, - season_episode_count: HashMap::new(), + season_episodes: HashMap::new(), season_subtitles_missing: vec![], season_sorting: vec![], visited: Visited::None, @@ -226,12 +226,12 @@ impl Filter for ArchiveFilter { episodes.extend(eps) } - if Format::has_relative_episodes_fmt(&self.archive.output) { + if Format::has_relative_fmt(&self.archive.output) { for episode in episodes.iter() { - self.season_episode_count + self.season_episodes .entry(episode.season_id.clone()) .or_insert(vec![]) - .push(episode.id.clone()) + .push(episode.clone()) } } @@ -299,22 +299,34 @@ impl Filter for ArchiveFilter { episodes.push((episode.clone(), episode.subtitle_locales.clone())) } - let relative_episode_number = if Format::has_relative_episodes_fmt(&self.archive.output) { - if self.season_episode_count.get(&episode.season_id).is_none() { - let season_episodes = episode.season().await?.episodes().await?; - self.season_episode_count.insert( - episode.season_id.clone(), - season_episodes.into_iter().map(|e| e.id).collect(), - ); + let mut relative_episode_number = None; + let mut relative_sequence_number = None; + // get the relative episode number. only done if the output string has the pattern to include + // the relative episode number as this requires some extra fetching + if Format::has_relative_fmt(&self.archive.output) { + let season_eps = match self.season_episodes.get(&episode.season_id) { + Some(eps) => eps, + None => { + self.season_episodes.insert( + episode.season_id.clone(), + episode.season().await?.episodes().await?, + ); + self.season_episodes.get(&episode.season_id).unwrap() + } + }; + let mut non_integer_sequence_number_count = 0; + for (i, ep) in season_eps.iter().enumerate() { + if ep.sequence_number.fract() != 0.0 || ep.sequence_number == 0.0 { + non_integer_sequence_number_count += 1; + } + if ep.id == episode.id { + relative_episode_number = Some(i + 1); + relative_sequence_number = + Some((i + 1 - non_integer_sequence_number_count) as f32); + break; + } } - let relative_episode_number = self - .season_episode_count - .get(&episode.season_id) - .unwrap() - .iter() - .position(|id| id == &episode.id) - .map(|index| index + 1); - if relative_episode_number.is_none() { + if relative_episode_number.is_none() || relative_sequence_number.is_none() { warn!( "Failed to get relative episode number for episode {} ({}) of {} season {}", episode.episode_number, @@ -323,16 +335,18 @@ impl Filter for ArchiveFilter { episode.season_number, ) } - relative_episode_number - } else { - None - }; + } Ok(Some( episodes .into_iter() .map(|(e, s)| { - SingleFormat::new_from_episode(e, s, relative_episode_number.map(|n| n as u32)) + SingleFormat::new_from_episode( + e, + s, + relative_episode_number.map(|n| n as u32), + relative_sequence_number, + ) }) .collect(), )) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index baf27bf..6d059ef 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -35,17 +35,19 @@ pub struct Download { #[arg(help = "Name of the output file")] #[arg(long_help = "Name of the output file.\ If you use one of the following pattern they will get replaced:\n \ - {title} โ†’ Title of the video\n \ - {series_name} โ†’ Name of the series\n \ - {season_name} โ†’ Name of the season\n \ - {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ - {season_number} โ†’ Number of the season\n \ - {episode_number} โ†’ Number of the episode\n \ - {relative_episode_number} โ†’ Number of the episode relative to its season\n \ - {series_id} โ†’ ID of the series\n \ - {season_id} โ†’ ID of the season\n \ - {episode_id} โ†’ ID of the episode")] + {title} โ†’ Title of the video\n \ + {series_name} โ†’ Name of the series\n \ + {season_name} โ†’ Name of the season\n \ + {audio} โ†’ Audio language of the video\n \ + {resolution} โ†’ Resolution of the video\n \ + {season_number} โ†’ Number of the season\n \ + {episode_number} โ†’ Number of the episode\n \ + {relative_episode_number} โ†’ Number of the episode relative to its season\n \ + {sequence_number} โ†’ Like '{episode_number}' but without possible non-number characters\n \ + {relative_sequence_number} โ†’ Like '{relative_episode_number}' but with support for episode 0's and .5's\n \ + {series_id} โ†’ ID of the series\n \ + {season_id} โ†’ ID of the season\n \ + {episode_id} โ†’ ID of the episode")] #[arg(short, long, default_value = "{title}.mp4")] pub(crate) output: String, diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 31c0db6..c5aef7e 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -12,7 +12,7 @@ pub(crate) struct DownloadFilter { url_filter: UrlFilter, download: Download, interactive_input: bool, - season_episode_count: HashMap<u32, Vec<String>>, + season_episodes: HashMap<u32, Vec<Episode>>, season_subtitles_missing: Vec<u32>, season_visited: bool, } @@ -23,7 +23,7 @@ impl DownloadFilter { url_filter, download, interactive_input, - season_episode_count: HashMap::new(), + season_episodes: HashMap::new(), season_subtitles_missing: vec![], season_visited: false, } @@ -107,12 +107,12 @@ impl Filter for DownloadFilter { let mut episodes = season.episodes().await?; - if Format::has_relative_episodes_fmt(&self.download.output) { + if Format::has_relative_fmt(&self.download.output) { for episode in episodes.iter() { - self.season_episode_count + self.season_episodes .entry(episode.season_number) .or_insert(vec![]) - .push(episode.id.clone()) + .push(episode.clone()) } } @@ -189,28 +189,34 @@ impl Filter for DownloadFilter { } } + let mut relative_episode_number = None; + let mut relative_sequence_number = None; // get the relative episode number. only done if the output string has the pattern to include // the relative episode number as this requires some extra fetching - let relative_episode_number = if Format::has_relative_episodes_fmt(&self.download.output) { - if self - .season_episode_count - .get(&episode.season_number) - .is_none() - { - let season_episodes = episode.season().await?.episodes().await?; - self.season_episode_count.insert( - episode.season_number, - season_episodes.into_iter().map(|e| e.id).collect(), - ); + if Format::has_relative_fmt(&self.download.output) { + let season_eps = match self.season_episodes.get(&episode.season_number) { + Some(eps) => eps, + None => { + self.season_episodes.insert( + episode.season_number, + episode.season().await?.episodes().await?, + ); + self.season_episodes.get(&episode.season_number).unwrap() + } + }; + let mut non_integer_sequence_number_count = 0; + for (i, ep) in season_eps.iter().enumerate() { + if ep.sequence_number.fract() != 0.0 || ep.sequence_number == 0.0 { + non_integer_sequence_number_count += 1; + } + if ep.id == episode.id { + relative_episode_number = Some(i + 1); + relative_sequence_number = + Some((i + 1 - non_integer_sequence_number_count) as f32); + break; + } } - let relative_episode_number = self - .season_episode_count - .get(&episode.season_number) - .unwrap() - .iter() - .position(|id| id == &episode.id) - .map(|index| index + 1); - if relative_episode_number.is_none() { + if relative_episode_number.is_none() || relative_sequence_number.is_none() { warn!( "Failed to get relative episode number for episode {} ({}) of {} season {}", episode.episode_number, @@ -219,10 +225,7 @@ impl Filter for DownloadFilter { episode.season_number, ) } - relative_episode_number - } else { - None - }; + } Ok(Some(SingleFormat::new_from_episode( episode.clone(), @@ -234,6 +237,7 @@ impl Filter for DownloadFilter { } }), relative_episode_number.map(|n| n as u32), + relative_sequence_number, ))) } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 618f7d5..f32d82a 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -29,8 +29,9 @@ pub struct SingleFormat { pub episode_id: String, pub episode_number: String, - pub sequence_number: f32, pub relative_episode_number: Option<u32>, + pub sequence_number: f32, + pub relative_sequence_number: Option<f32>, pub duration: Duration, @@ -42,6 +43,7 @@ impl SingleFormat { episode: Episode, subtitles: Vec<Locale>, relative_episode_number: Option<u32>, + relative_sequence_number: Option<f32>, ) -> Self { Self { identifier: if episode.identifier.is_empty() { @@ -73,6 +75,7 @@ impl SingleFormat { }, sequence_number: episode.sequence_number, relative_episode_number, + relative_sequence_number, duration: episode.duration, source: episode.into(), } @@ -92,8 +95,9 @@ impl SingleFormat { season_number: 1, episode_id: movie.id.clone(), episode_number: "1".to_string(), - sequence_number: 1.0, relative_episode_number: Some(1), + sequence_number: 1.0, + relative_sequence_number: Some(1.0), duration: movie.duration, source: movie.into(), } @@ -113,8 +117,9 @@ impl SingleFormat { season_number: 1, episode_id: music_video.id.clone(), episode_number: "1".to_string(), - sequence_number: 1.0, relative_episode_number: Some(1), + sequence_number: 1.0, + relative_sequence_number: Some(1.0), duration: music_video.duration, source: music_video.into(), } @@ -134,8 +139,9 @@ impl SingleFormat { season_number: 1, episode_id: concert.id.clone(), episode_number: "1".to_string(), - sequence_number: 1.0, relative_episode_number: Some(1), + sequence_number: 1.0, + relative_sequence_number: Some(1.0), duration: concert.duration, source: concert.into(), } @@ -328,8 +334,9 @@ pub struct Format { pub episode_id: String, pub episode_number: String, - pub sequence_number: f32, pub relative_episode_number: Option<u32>, + pub sequence_number: f32, + pub relative_sequence_number: Option<f32>, } impl Format { @@ -363,8 +370,9 @@ impl Format { season_number: first_format.season_number, episode_id: first_format.episode_id, episode_number: first_format.episode_number, - sequence_number: first_format.sequence_number, relative_episode_number: first_format.relative_episode_number, + sequence_number: first_format.sequence_number, + relative_sequence_number: first_format.relative_sequence_number, } } @@ -401,6 +409,14 @@ impl Format { .replace( "{relative_episode_number}", &self.relative_episode_number.unwrap_or_default().to_string(), + ) + .replace("{sequence_number}", &self.sequence_number.to_string()) + .replace( + "{relative_sequence_number}", + &self + .relative_sequence_number + .unwrap_or_default() + .to_string(), ); if sanitize { @@ -447,7 +463,8 @@ impl Format { tab_info!("FPS: {:.2}", self.fps) } - pub fn has_relative_episodes_fmt<S: AsRef<str>>(s: S) -> bool { - return s.as_ref().contains("{relative_episode_number}"); + pub fn has_relative_fmt<S: AsRef<str>>(s: S) -> bool { + return s.as_ref().contains("{relative_episode_number}") + || s.as_ref().contains("{relative_sequence_number}"); } } From bbb5a7876520384e2e7725d1adb6bb08faa9caa5 Mon Sep 17 00:00:00 2001 From: Valentine Briese <valentinegb@icloud.com> Date: Sun, 15 Oct 2023 11:52:53 -0700 Subject: [PATCH 464/630] Add `--threads` (`-t`) option to downloading commands (#256) * Add `single-threaded` option to downloading commands * Replace `--single-threaded` boolean option with `--threads` optional `usize` option * Simplify `threads` field unwrapping * Make `--threads` `usize` with a default value --- crunchy-cli-core/src/archive/command.rs | 7 +- crunchy-cli-core/src/download/command.rs | 7 +- crunchy-cli-core/src/utils/download.rs | 301 ++++++++++++----------- 3 files changed, 166 insertions(+), 149 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7ce1658..bb4794f 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -98,6 +98,10 @@ pub struct Archive { #[arg(short, long, default_value_t = false)] pub(crate) yes: bool, + #[arg(help = "The number of threads used to download")] + #[arg(short, long, default_value_t = num_cpus::get())] + pub(crate) threads: usize, + #[arg(help = "Crunchyroll series url(s)")] #[arg(required = true)] pub(crate) urls: Vec<String>, @@ -158,7 +162,8 @@ impl Execute for Archive { .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) .output_format(Some("matroska".to_string())) .audio_sort(Some(self.audio.clone())) - .subtitle_sort(Some(self.subtitle.clone())); + .subtitle_sort(Some(self.subtitle.clone())) + .threads(self.threads); for single_formats in single_format_collection.into_iter() { let (download_formats, mut format) = get_format(&self, &single_formats).await?; diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index baf27bf..760bf38 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -80,6 +80,10 @@ pub struct Download { #[arg(long, default_value_t = false)] pub(crate) force_hardsub: bool, + #[arg(help = "The number of threads used to download")] + #[arg(short, long, default_value_t = num_cpus::get())] + pub(crate) threads: usize, + #[arg(help = "Url(s) to Crunchyroll episodes or series")] #[arg(required = true)] pub(crate) urls: Vec<String>, @@ -149,7 +153,8 @@ impl Execute for Download { } else { None }) - .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()); + .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) + .threads(self.threads); for mut single_formats in single_format_collection.into_iter() { // the vec contains always only one item diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 29f160a..ff9f240 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -50,6 +50,7 @@ pub struct DownloadBuilder { audio_sort: Option<Vec<Locale>>, subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, + threads: usize, } impl DownloadBuilder { @@ -61,6 +62,7 @@ impl DownloadBuilder { audio_sort: None, subtitle_sort: None, force_hardsub: false, + threads: num_cpus::get(), } } @@ -73,6 +75,7 @@ impl DownloadBuilder { subtitle_sort: self.subtitle_sort, force_hardsub: self.force_hardsub, + threads: self.threads, formats: vec![], } @@ -99,6 +102,7 @@ pub struct Downloader { subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, + threads: usize, formats: Vec<DownloadFormat>, } @@ -502,7 +506,8 @@ impl Downloader { let tempfile = tempfile(".mp4")?; let (mut file, path) = tempfile.into_parts(); - download_segments(ctx, &mut file, message, variant_data).await?; + self.download_segments(ctx, &mut file, message, variant_data) + .await?; Ok(path) } @@ -516,7 +521,8 @@ impl Downloader { let tempfile = tempfile(".m4a")?; let (mut file, path) = tempfile.into_parts(); - download_segments(ctx, &mut file, message, variant_data).await?; + self.download_segments(ctx, &mut file, message, variant_data) + .await?; Ok(path) } @@ -537,188 +543,189 @@ impl Downloader { Ok(path) } -} -pub async fn download_segments( - ctx: &Context, - writer: &mut impl Write, - message: String, - variant_data: &VariantData, -) -> Result<()> { - let segments = variant_data.segments().await?; - let total_segments = segments.len(); + async fn download_segments( + &self, + ctx: &Context, + writer: &mut impl Write, + message: String, + variant_data: &VariantData, + ) -> Result<()> { + let segments = variant_data.segments().await?; + let total_segments = segments.len(); - let client = Arc::new(ctx.crunchy.client()); - let count = Arc::new(Mutex::new(0)); + let client = Arc::new(ctx.crunchy.client()); + let count = Arc::new(Mutex::new(0)); - let progress = if log::max_level() == LevelFilter::Info { - let estimated_file_size = estimate_variant_file_size(variant_data, &segments); + let progress = if log::max_level() == LevelFilter::Info { + let estimated_file_size = estimate_variant_file_size(variant_data, &segments); - let progress = ProgressBar::new(estimated_file_size) - .with_style( - ProgressStyle::with_template( - ":: {msg} {bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", + let progress = ProgressBar::new(estimated_file_size) + .with_style( + ProgressStyle::with_template( + ":: {msg} {bytes:>10} {bytes_per_sec:>12} [{wide_bar}] {percent:>3}%", + ) + .unwrap() + .progress_chars("##-"), ) - .unwrap() - .progress_chars("##-"), - ) - .with_message(message) - .with_finish(ProgressFinish::Abandon); - Some(progress) - } else { - None - }; + .with_message(message) + .with_finish(ProgressFinish::Abandon); + Some(progress) + } else { + None + }; - let cpus = num_cpus::get(); - let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); - for _ in 0..cpus { - segs.push(vec![]) - } - for (i, segment) in segments.clone().into_iter().enumerate() { - segs[i - ((i / cpus) * cpus)].push(segment); - } + let cpus = self.threads; + let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); + for _ in 0..cpus { + segs.push(vec![]) + } + for (i, segment) in segments.clone().into_iter().enumerate() { + segs[i - ((i / cpus) * cpus)].push(segment); + } - let (sender, mut receiver) = unbounded_channel(); + let (sender, mut receiver) = unbounded_channel(); - let mut join_set: JoinSet<Result<()>> = JoinSet::new(); - for num in 0..cpus { - let thread_client = client.clone(); - let thread_sender = sender.clone(); - let thread_segments = segs.remove(0); - let thread_count = count.clone(); - join_set.spawn(async move { - let after_download_sender = thread_sender.clone(); + let mut join_set: JoinSet<Result<()>> = JoinSet::new(); + for num in 0..cpus { + let thread_client = client.clone(); + let thread_sender = sender.clone(); + let thread_segments = segs.remove(0); + let thread_count = count.clone(); + join_set.spawn(async move { + let after_download_sender = thread_sender.clone(); - // the download process is encapsulated in its own function. this is done to easily - // catch errors which get returned with `...?` and `bail!(...)` and that the thread - // itself can report that an error has occurred - let download = || async move { - for (i, segment) in thread_segments.into_iter().enumerate() { - let mut retry_count = 0; - let mut buf = loop { - let request = thread_client - .get(&segment.url) - .timeout(Duration::from_secs(60)) - .send(); + // the download process is encapsulated in its own function. this is done to easily + // catch errors which get returned with `...?` and `bail!(...)` and that the thread + // itself can report that an error has occurred + let download = || async move { + for (i, segment) in thread_segments.into_iter().enumerate() { + let mut retry_count = 0; + let mut buf = loop { + let request = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60)) + .send(); - let response = match request.await { - Ok(r) => r, - Err(e) => { - if retry_count == 5 { - bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) - } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count); - continue - } - }; - - match response.bytes().await { - Ok(b) => break b.to_vec(), - Err(e) => { - if e.is_body() { + let response = match request.await { + Ok(r) => r, + Err(e) => { if retry_count == 5 { bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) - } else { - bail!("{}", e) + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count); + continue + } + }; + + match response.bytes().await { + Ok(b) => break b.to_vec(), + Err(e) => { + if e.is_body() { + if retry_count == 5 { + bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) + } + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) + } else { + bail!("{}", e) + } } } - } - retry_count += 1; - }; + retry_count += 1; + }; - buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); + buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); - let mut c = thread_count.lock().await; - debug!( - "Downloaded and decrypted segment [{}/{} {:.2}%] {}", - num + (i * cpus) + 1, - total_segments, - ((*c + 1) as f64 / total_segments as f64) * 100f64, - segment.url - ); + let mut c = thread_count.lock().await; + debug!( + "Downloaded and decrypted segment [{}/{} {:.2}%] {}", + num + (i * cpus) + 1, + total_segments, + ((*c + 1) as f64 / total_segments as f64) * 100f64, + segment.url + ); - thread_sender.send((num as i32 + (i * cpus) as i32, buf))?; + thread_sender.send((num as i32 + (i * cpus) as i32, buf))?; - *c += 1; + *c += 1; + } + Ok(()) + }; + + + let result = download().await; + if result.is_err() { + after_download_sender.send((-1 as i32, vec![]))?; } - Ok(()) - }; + result + }); + } + // drop the sender already here so it does not outlive all download threads which are the only + // real consumers of it + drop(sender); - let result = download().await; - if result.is_err() { - after_download_sender.send((-1 as i32, vec![]))?; + // this is the main loop which writes the data. it uses a BTreeMap as a buffer as the write + // happens synchronized. the download consist of multiple segments. the map keys are representing + // the segment number and the values the corresponding bytes + let mut data_pos = 0; + let mut buf: BTreeMap<i32, Vec<u8>> = BTreeMap::new(); + while let Some((pos, bytes)) = receiver.recv().await { + // if the position is lower than 0, an error occurred in the sending download thread + if pos < 0 { + break; } - result - }); - } - // drop the sender already here so it does not outlive all download threads which are the only - // real consumers of it - drop(sender); + if let Some(p) = &progress { + let progress_len = p.length().unwrap(); + let estimated_segment_len = (variant_data.bandwidth / 8) + * segments.get(pos as usize).unwrap().length.as_secs(); + let bytes_len = bytes.len() as u64; - // this is the main loop which writes the data. it uses a BTreeMap as a buffer as the write - // happens synchronized. the download consist of multiple segments. the map keys are representing - // the segment number and the values the corresponding bytes - let mut data_pos = 0; - let mut buf: BTreeMap<i32, Vec<u8>> = BTreeMap::new(); - while let Some((pos, bytes)) = receiver.recv().await { - // if the position is lower than 0, an error occurred in the sending download thread - if pos < 0 { - break; + p.set_length(progress_len - estimated_segment_len + bytes_len); + p.inc(bytes_len) + } + + // check if the currently sent bytes are the next in the buffer. if so, write them directly + // to the target without first adding them to the buffer. + // if not, add them to the buffer + if data_pos == pos { + writer.write_all(bytes.borrow())?; + data_pos += 1; + } else { + buf.insert(pos, bytes); + } + // check if the buffer contains the next segment(s) + while let Some(b) = buf.remove(&data_pos) { + writer.write_all(b.borrow())?; + data_pos += 1; + } } - if let Some(p) = &progress { - let progress_len = p.length().unwrap(); - let estimated_segment_len = - (variant_data.bandwidth / 8) * segments.get(pos as usize).unwrap().length.as_secs(); - let bytes_len = bytes.len() as u64; - - p.set_length(progress_len - estimated_segment_len + bytes_len); - p.inc(bytes_len) + // if any error has occurred while downloading it gets returned here + while let Some(joined) = join_set.join_next().await { + joined?? } - // check if the currently sent bytes are the next in the buffer. if so, write them directly - // to the target without first adding them to the buffer. - // if not, add them to the buffer - if data_pos == pos { - writer.write_all(bytes.borrow())?; - data_pos += 1; - } else { - buf.insert(pos, bytes); - } - // check if the buffer contains the next segment(s) + // write the remaining buffer, if existent while let Some(b) = buf.remove(&data_pos) { writer.write_all(b.borrow())?; data_pos += 1; } - } - // if any error has occurred while downloading it gets returned here - while let Some(joined) = join_set.join_next().await { - joined?? - } + if !buf.is_empty() { + bail!( + "Download buffer is not empty. Remaining segments: {}", + buf.into_keys() + .map(|k| k.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } - // write the remaining buffer, if existent - while let Some(b) = buf.remove(&data_pos) { - writer.write_all(b.borrow())?; - data_pos += 1; + Ok(()) } - - if !buf.is_empty() { - bail!( - "Download buffer is not empty. Remaining segments: {}", - buf.into_keys() - .map(|k| k.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - Ok(()) } fn estimate_variant_file_size(variant_data: &VariantData, segments: &Vec<VariantSegment>) -> u64 { From 568bce00085cc301029d0b39c39cf15b97aa1d3d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 15 Oct 2023 22:39:53 +0200 Subject: [PATCH 465/630] Manually implement filename sanitizing to allow the usage of file separators --- Cargo.lock | 11 ----- crunchy-cli-core/Cargo.toml | 1 - crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/download/command.rs | 2 +- crunchy-cli-core/src/utils/format.rs | 55 ++++++++++++------------ crunchy-cli-core/src/utils/os.rs | 48 +++++++++++++++++++++ 6 files changed, 77 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09cad3e..409a005 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,7 +401,6 @@ dependencies = [ "regex", "reqwest", "rustls-native-certs", - "sanitize-filename", "serde", "serde_json", "serde_plain", @@ -1517,16 +1516,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "sanitize-filename" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "schannel" version = "0.1.22" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index fc298f3..e38cb1a 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -28,7 +28,6 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.9" reqwest = { version = "0.11", default-features = false, features = ["socks"] } -sanitize-filename = "0.5" serde = "1.0" serde_json = "1.0" serde_plain = "1.0" diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index bb4794f..3f455ed 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -173,7 +173,7 @@ impl Execute for Archive { downloader.add_format(download_format) } - let formatted_path = format.format_path((&self.output).into(), true); + let formatted_path = format.format_path((&self.output).into()); let (path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 760bf38..9c9ecb4 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -165,7 +165,7 @@ impl Execute for Download { let mut downloader = download_builder.clone().build(); downloader.add_format(download_format); - let formatted_path = format.format_path((&self.output).into(), true); + let formatted_path = format.format_path((&self.output).into()); let (path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 618f7d5..f462263 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,6 +1,6 @@ use crate::utils::filter::real_dedup_vec; use crate::utils::log::tab_info; -use crate::utils::os::is_special_file; +use crate::utils::os::{is_special_file, sanitize}; use anyhow::Result; use chrono::Duration; use crunchyroll_rs::media::{Resolution, Stream, Subtitle, VariantData}; @@ -368,46 +368,45 @@ impl Format { } } - /// Formats the given string if it has specific pattern in it. It's possible to sanitize it which - /// removes characters which can cause failures if the output string is used as a file name. - pub fn format_path(&self, path: PathBuf, sanitize: bool) -> PathBuf { - let path = path - .to_string_lossy() - .to_string() - .replace("{title}", &self.title) + /// Formats the given string if it has specific pattern in it. It also sanitizes the filename. + pub fn format_path(&self, path: PathBuf) -> PathBuf { + let mut path = sanitize(path.to_string_lossy(), false); + path = path + .replace("{title}", &sanitize(&self.title, true)) .replace( "{audio}", - &self - .locales - .iter() - .map(|(a, _)| a.to_string()) - .collect::<Vec<String>>() - .join("|"), + &sanitize( + self.locales + .iter() + .map(|(a, _)| a.to_string()) + .collect::<Vec<String>>() + .join("|"), + true, + ), ) - .replace("{resolution}", &self.resolution.to_string()) - .replace("{series_id}", &self.series_id) - .replace("{series_name}", &self.series_name) - .replace("{season_id}", &self.season_id) - .replace("{season_name}", &self.season_title) + .replace("{resolution}", &sanitize(self.resolution.to_string(), true)) + .replace("{series_id}", &sanitize(&self.series_id, true)) + .replace("{series_name}", &sanitize(&self.series_name, true)) + .replace("{season_id}", &sanitize(&self.season_id, true)) + .replace("{season_name}", &sanitize(&self.season_title, true)) .replace( "{season_number}", - &format!("{:0>2}", self.season_number.to_string()), + &format!("{:0>2}", sanitize(self.season_number.to_string(), true)), ) - .replace("{episode_id}", &self.episode_id) + .replace("{episode_id}", &sanitize(&self.episode_id, true)) .replace( "{episode_number}", - &format!("{:0>2}", self.episode_number.to_string()), + &format!("{:0>2}", sanitize(&self.episode_number, true)), ) .replace( "{relative_episode_number}", - &self.relative_episode_number.unwrap_or_default().to_string(), + &sanitize( + self.relative_episode_number.unwrap_or_default().to_string(), + true, + ), ); - if sanitize { - PathBuf::from(sanitize_filename::sanitize(path)) - } else { - PathBuf::from(path) - } + PathBuf::from(path) } pub fn visual_output(&self, dst: &Path) { diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index f6d9ad0..1f76b90 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -1,4 +1,6 @@ use log::debug; +use regex::{Regex, RegexBuilder}; +use std::borrow::Cow; use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -78,3 +80,49 @@ pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { pub fn is_special_file<P: AsRef<Path>>(path: P) -> bool { path.as_ref().exists() && !path.as_ref().is_file() && !path.as_ref().is_dir() } + +lazy_static::lazy_static! { + static ref ILLEGAL_RE: Regex = Regex::new(r#"[\?<>:\*\|":]"#).unwrap(); + static ref CONTROL_RE: Regex = Regex::new(r"[\x00-\x1f\x80-\x9f]").unwrap(); + static ref RESERVED_RE: Regex = Regex::new(r"^\.+$").unwrap(); + static ref WINDOWS_RESERVED_RE: Regex = RegexBuilder::new(r"(?i)^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$") + .case_insensitive(true) + .build() + .unwrap(); + static ref WINDOWS_TRAILING_RE: Regex = Regex::new(r"[\. ]+$").unwrap(); +} + +/// Sanitizes a filename with the option to include/exclude the path separator from sanitizing. This +/// is based of the implementation of the +/// [`sanitize-filename`](https://crates.io/crates/sanitize-filename) crate. +pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String { + let path = Cow::from(path.as_ref()); + + let path = ILLEGAL_RE.replace_all(&path, ""); + let path = CONTROL_RE.replace_all(&path, ""); + let path = RESERVED_RE.replace(&path, ""); + + let collect = |name: String| { + if name.len() > 255 { + name[..255].to_string() + } else { + name + } + }; + + if cfg!(windows) { + let path = WINDOWS_RESERVED_RE.replace(&path, ""); + let path = WINDOWS_TRAILING_RE.replace(&path, ""); + let mut path = path.to_string(); + if include_path_separator { + path = path.replace(['\\', '/'], ""); + } + collect(path) + } else { + let mut path = path.to_string(); + if include_path_separator { + path = path.replace('/', ""); + } + collect(path) + } +} From 685c79d673538aa981515806595cbba2da4decbf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 15 Oct 2023 22:55:44 +0200 Subject: [PATCH 466/630] Add 2-digit padding to relative_episode_number, sequence_number and relative_sequence_number format option --- crunchy-cli-core/src/utils/format.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 1ba8136..4679878 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -408,22 +408,28 @@ impl Format { ) .replace( "{relative_episode_number}", - &sanitize( - self.relative_episode_number.unwrap_or_default().to_string(), - true, + &format!( + "{:0>2}", + sanitize( + self.relative_episode_number.unwrap_or_default().to_string(), + true, + ) ), ) .replace( "{sequence_number}", - &sanitize(self.sequence_number.to_string(), true), + &format!("{:0>2}", sanitize(self.sequence_number.to_string(), true)), ) .replace( "{relative_sequence_number}", - &sanitize( - self.relative_sequence_number - .unwrap_or_default() - .to_string(), - true, + &format!( + "{:0>2}", + sanitize( + self.relative_sequence_number + .unwrap_or_default() + .to_string(), + true, + ) ), ); From d0fe7f54f6c0e74f87d13a6d072599c12aad8367 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 15 Oct 2023 23:34:22 +0200 Subject: [PATCH 467/630] Show fractal in relative_sequence_number if present --- crunchy-cli-core/src/archive/filter.rs | 8 +++++--- crunchy-cli-core/src/download/filter.rs | 8 +++++--- crunchy-cli-core/src/utils/parse.rs | 10 ++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index c2cc206..024ad17 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -2,7 +2,7 @@ use crate::archive::command::Archive; use crate::utils::filter::{real_dedup_vec, Filter}; use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; -use crate::utils::parse::UrlFilter; +use crate::utils::parse::{fract, UrlFilter}; use anyhow::Result; use crunchyroll_rs::{Concert, Episode, Locale, Movie, MovieListing, MusicVideo, Season, Series}; use log::{info, warn}; @@ -321,8 +321,10 @@ impl Filter for ArchiveFilter { } if ep.id == episode.id { relative_episode_number = Some(i + 1); - relative_sequence_number = - Some((i + 1 - non_integer_sequence_number_count) as f32); + relative_sequence_number = Some( + (i + 1 - non_integer_sequence_number_count) as f32 + + fract(ep.sequence_number), + ); break; } } diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index c5aef7e..ff3c2ff 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -2,7 +2,7 @@ use crate::download::Download; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; -use crate::utils::parse::UrlFilter; +use crate::utils::parse::{fract, UrlFilter}; use anyhow::{bail, Result}; use crunchyroll_rs::{Concert, Episode, Movie, MovieListing, MusicVideo, Season, Series}; use log::{error, info, warn}; @@ -211,8 +211,10 @@ impl Filter for DownloadFilter { } if ep.id == episode.id { relative_episode_number = Some(i + 1); - relative_sequence_number = - Some((i + 1 - non_integer_sequence_number_count) as f32); + relative_sequence_number = Some( + (i + 1 - non_integer_sequence_number_count) as f32 + + fract(ep.sequence_number), + ); break; } } diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index f85d8c2..c0ac2b0 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -192,3 +192,13 @@ pub fn parse_resolution(mut resolution: String) -> Result<Resolution> { bail!("Could not find resolution") } } + +/// Dirty implementation of [`f32::fract`] with more accuracy. +pub fn fract(input: f32) -> f32 { + if input.fract() == 0.0 { + return 0.0; + } + format!("0.{}", input.to_string().split('.').last().unwrap()) + .parse::<f32>() + .unwrap() +} From 5a3a30444329548134ca998eebaa20e96eb88634 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 15 Oct 2023 23:52:44 +0200 Subject: [PATCH 468/630] Use episode sequence number as filter number for url episode filtering --- crunchy-cli-core/src/archive/filter.rs | 2 +- crunchy-cli-core/src/download/filter.rs | 4 ++-- crunchy-cli-core/src/search/filter.rs | 2 +- crunchy-cli-core/src/utils/parse.rs | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 024ad17..91023a3 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -241,7 +241,7 @@ impl Filter for ArchiveFilter { async fn visit_episode(&mut self, mut episode: Episode) -> Result<Option<Self::T>> { if !self .url_filter - .is_episode_valid(episode.episode_number, episode.season_number) + .is_episode_valid(episode.sequence_number, episode.season_number) { return Ok(None); } diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index ff3c2ff..55b1e8b 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -118,7 +118,7 @@ impl Filter for DownloadFilter { episodes.retain(|e| { self.url_filter - .is_episode_valid(e.episode_number, season.season_number) + .is_episode_valid(e.sequence_number, season.season_number) }); Ok(episodes) @@ -127,7 +127,7 @@ impl Filter for DownloadFilter { async fn visit_episode(&mut self, mut episode: Episode) -> Result<Option<Self::T>> { if !self .url_filter - .is_episode_valid(episode.episode_number, episode.season_number) + .is_episode_valid(episode.sequence_number, episode.season_number) { return Ok(None); } diff --git a/crunchy-cli-core/src/search/filter.rs b/crunchy-cli-core/src/search/filter.rs index 0b31823..264b31d 100644 --- a/crunchy-cli-core/src/search/filter.rs +++ b/crunchy-cli-core/src/search/filter.rs @@ -24,7 +24,7 @@ impl FilterOptions { self.check_audio_language(&vec![e.audio_locale.clone()]) && self .url_filter - .is_episode_valid(e.episode_number, e.season_number) + .is_episode_valid(e.sequence_number, e.season_number) }); episodes } diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index c0ac2b0..3fc7e7c 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -10,8 +10,8 @@ use regex::Regex; /// If `to_*` is [`None`] they're set to [`u32::MAX`]. #[derive(Debug, Default)] pub struct InnerUrlFilter { - from_episode: Option<u32>, - to_episode: Option<u32>, + from_episode: Option<f32>, + to_episode: Option<f32>, from_season: Option<u32>, to_season: Option<u32>, } @@ -39,10 +39,10 @@ impl UrlFilter { }) } - pub fn is_episode_valid(&self, episode: u32, season: u32) -> bool { + pub fn is_episode_valid(&self, episode: f32, season: u32) -> bool { self.inner.iter().any(|f| { - let from_episode = f.from_episode.unwrap_or(u32::MIN); - let to_episode = f.to_episode.unwrap_or(u32::MAX); + let from_episode = f.from_episode.unwrap_or(f32::MIN); + let to_episode = f.to_episode.unwrap_or(f32::MAX); let from_season = f.from_season.unwrap_or(u32::MIN); let to_season = f.to_season.unwrap_or(u32::MAX); From f56d9ecabf356ab9870c94f8ca5f3b46a2a39484 Mon Sep 17 00:00:00 2001 From: Catd <34575742+Nannk@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:04:45 +0000 Subject: [PATCH 469/630] Changes in Readme regarding subtitles and flag usage (#255) * Update README.md updated Flags and subtitles sections * Update README.md * Update README.md Comma in a better place --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7a620cb..df285cc 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ $ cargo install --force --path . > All shown commands are examples ๐Ÿง‘๐Ÿผโ€๐Ÿณ +### Global Flags + crunchy-cli requires you to log in. Though you can use a non-premium account, you will not have access to premium content without a subscription. You can authenticate with your credentials (user:password) or by using a refresh token. @@ -99,7 +101,7 @@ You can authenticate with your credentials (user:password) or by using a refresh - Credentials ```shell - $ crunchy-cli --credentials "user:password" + $ crunchy-cli --credentials "user:password" <command> ``` - Refresh Token @@ -107,13 +109,13 @@ You can authenticate with your credentials (user:password) or by using a refresh The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). When installed, look for the `etp_rt` entry and extract its value. ```shell - $ crunchy-cli --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" + $ crunchy-cli --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" <command> ``` - Stay Anonymous Login without an account (you won't be able to access premium content): ```shell - $ crunchy-cli --anonymous + $ crunchy-cli --anonymous <command> ``` ### Global settings @@ -124,7 +126,7 @@ You can set specific settings which will be If you want to include debug information in the output, use the `-v` / `--verbose` flag to show it. ```shell - $ crunchy-cli -v + $ crunchy-cli -v <command> ``` This flag can't be used with `-q` / `--quiet`. @@ -133,7 +135,7 @@ You can set specific settings which will be If you want to hide all output, use the `-q` / `--quiet` flag to do so. This is especially useful if you want to pipe the output video to an external program (like a video player). ```shell - $ crunchy-cli -q + $ crunchy-cli -q <command> ``` - Language @@ -141,7 +143,7 @@ You can set specific settings which will be By default, the resulting metadata like title or description are shown in your system language (if Crunchyroll supports it, else in English). If you want to show the results in another language, use the `--lang` flag to set it. ```shell - $ crunchy-cli --lang de-DE + $ crunchy-cli --lang de-DE <command> ``` - Experimental fixes @@ -150,7 +152,7 @@ You can set specific settings which will be The `--experimental-fixes` flag tries to fix some of those issues. As the *experimental* in `--experimental-fixes` states, these fixes may or may not break other functionality. ```shell - $ crunchy-cli --experimental-fixes + $ crunchy-cli --experimental-fixes <command> ``` For an overview which parts this flag affects, see the [documentation](https://docs.rs/crunchyroll-rs/latest/crunchyroll_rs/crunchyroll/struct.CrunchyrollBuilder.html) of the underlying Crunchyroll library, all functions beginning with `stabilization_` are applied. @@ -159,7 +161,7 @@ You can set specific settings which will be The `--proxy` flag supports https and socks5 proxies to route all your traffic through. This may be helpful to bypass the geo-restrictions Crunchyroll has on certain series. ```shell - $ crunchy-cli --proxy socks5://127.0.0.1:8080 + $ crunchy-cli --proxy socks5://127.0.0.1:8080 <command> ``` Make sure that proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. @@ -204,7 +206,7 @@ The `download` command lets you download episodes with a specific audio language - Subtitle language Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. - The subtitles will be burned into the video track (cf. [hardsub](https://www.urbandictionary.com/define.php?term=hardsub)) and thus can not be turned off. + In formats that support it (.mp4, .mov and .mkv ), subtitles are stored as soft-subs. All other formats are hardsubbed: the subtitles will be burned into the video track (cf. [hardsub](https://www.urbandictionary.com/define.php?term=hardsub)) and thus can not be turned off. ```shell $ crunchy-cli download -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` From d8b76f8cc7985d5c838d6081985e686042051bd5 Mon Sep 17 00:00:00 2001 From: kennedy <854543+kennedy@users.noreply.github.com> Date: Sun, 29 Oct 2023 05:12:25 +0000 Subject: [PATCH 470/630] Add homebrew instructions (#261) Added details about homebrew and what archs are supported. made minor style linting: add space surrounding shell code blocks, and headers. --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index df285cc..aa99c45 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t If you're using Arch or an Arch based Linux distribution you are able to install our [AUR](https://aur.archlinux.org/) package. You need an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like [yay](https://github.com/Jguer/yay) to install it. + ```shell # this package builds crunchy-cli manually (recommended) $ yay -S crunchy-cli @@ -63,6 +64,7 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t - [Nix](https://nixos.org/) This requires [nix](https://nixos.org) and you'll probably need `--extra-experimental-features "nix-command flakes"`, depending on your configurations. + ```shell $ nix <run|shell|develop> github:crunchy-labs/crunchy-cli ``` @@ -70,15 +72,27 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t - [Scoop](https://scoop.sh/) For Windows users, we support the [scoop](https://scoop.sh/#/) command-line installer. + ```shell $ scoop bucket add extras $ scoop install extras/crunchy-cli ``` +- [Homebrew](https://brew.sh/) + + For macOS/linux users, we support the [brew](https://brew.sh/#/) command-line installer. Packages are compile by the [homebrew project](https://github.com/Homebrew/homebrew-core/pkgs/container/core%2Fcrunchy-cli), and will also install the `openssl@3` and `ffmpeg` dependencies. + + ```shell + $ brew install crunchy-cli + ``` + + Supported archs: `x86_64_linux`, `arm64_monterey`, `sonoma`, `ventura` + ### ๐Ÿ›  Build it yourself Since we do not support every platform and architecture you may have to build the project yourself. This requires [git](https://git-scm.com/) and [Cargo](https://doc.rust-lang.org/cargo). + ```shell $ git clone https://github.com/crunchy-labs/crunchy-cli $ cd crunchy-cli @@ -103,18 +117,22 @@ You can authenticate with your credentials (user:password) or by using a refresh ```shell $ crunchy-cli --credentials "user:password" <command> ``` + - Refresh Token To obtain a refresh token, you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). When installed, look for the `etp_rt` entry and extract its value. + ```shell $ crunchy-cli --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" <command> ``` + - Stay Anonymous Login without an account (you won't be able to access premium content): - ```shell + + ```shell $ crunchy-cli --anonymous <command> ``` @@ -125,15 +143,18 @@ You can set specific settings which will be - Verbose output If you want to include debug information in the output, use the `-v` / `--verbose` flag to show it. + ```shell $ crunchy-cli -v <command> ``` + This flag can't be used with `-q` / `--quiet`. - Quiet output If you want to hide all output, use the `-q` / `--quiet` flag to do so. This is especially useful if you want to pipe the output video to an external program (like a video player). + ```shell $ crunchy-cli -q <command> ``` @@ -142,6 +163,7 @@ You can set specific settings which will be By default, the resulting metadata like title or description are shown in your system language (if Crunchyroll supports it, else in English). If you want to show the results in another language, use the `--lang` flag to set it. + ```shell $ crunchy-cli --lang de-DE <command> ``` @@ -151,18 +173,22 @@ You can set specific settings which will be Crunchyroll constantly changes and breaks its services or just delivers incorrect answers. The `--experimental-fixes` flag tries to fix some of those issues. As the *experimental* in `--experimental-fixes` states, these fixes may or may not break other functionality. + ```shell $ crunchy-cli --experimental-fixes <command> ``` + For an overview which parts this flag affects, see the [documentation](https://docs.rs/crunchyroll-rs/latest/crunchyroll_rs/crunchyroll/struct.CrunchyrollBuilder.html) of the underlying Crunchyroll library, all functions beginning with `stabilization_` are applied. - Proxy The `--proxy` flag supports https and socks5 proxies to route all your traffic through. This may be helpful to bypass the geo-restrictions Crunchyroll has on certain series. + ```shell $ crunchy-cli --proxy socks5://127.0.0.1:8080 <command> ``` + Make sure that proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. ### Login @@ -184,6 +210,7 @@ With the session stored, you do not need to pass `--credentials` / `--etp-rt` / The `download` command lets you download episodes with a specific audio language and optional subtitles. **Supported urls** + - Single episode (with [episode filtering](#episode-filtering)) ```shell $ crunchy-cli download https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome @@ -194,38 +221,47 @@ The `download` command lets you download episodes with a specific audio language ``` **Options** + - Audio language Set the audio language with the `-a` / `--audio` flag. This only works if the url points to a series since episode urls are language specific. + ```shell $ crunchy-cli download -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is your system locale. If not supported by Crunchyroll, `en-US` (American English) is the default. - Subtitle language Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. In formats that support it (.mp4, .mov and .mkv ), subtitles are stored as soft-subs. All other formats are hardsubbed: the subtitles will be burned into the video track (cf. [hardsub](https://www.urbandictionary.com/define.php?term=hardsub)) and thus can not be turned off. + ```shell $ crunchy-cli download -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is none. - Output template Define an output template by using the `-o` / `--output` flag. + ```shell $ crunchy-cli download -o "ditf.mp4" https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` + Default is `{title}.mp4`. See the [Template Options section](#output-template-options) below for more options. - Resolution The resolution for videos can be set via the `-r` / `--resolution` flag. + ```shell $ crunchy-cli download -r worst https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` + Default is `best`. - FFmpeg Preset @@ -233,6 +269,7 @@ The `download` command lets you download episodes with a specific audio language You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. Multiple predefined presets how videos should be encoded (h264, h265, av1, ...) are available, you can see them with `crunchy-cli download --help`. If you need more specific ffmpeg customizations you could either convert the output file manually or use ffmpeg output arguments as value for this flag. + ```shell $ crunchy-cli downlaod --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` @@ -240,6 +277,7 @@ The `download` command lets you download episodes with a specific audio language - Skip existing If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. + ```shell $ crunchy-cli download --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` @@ -248,14 +286,17 @@ The `download` command lets you download episodes with a specific audio language Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. The `--yes` flag suppresses this interactive prompt and just downloads all seasons. + ```shell $ crunchy-cli download --yes https://www.crunchyroll.com/series/GR49G9VP6/sword-art-online ``` + If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. - Force hardsub If you want to burn-in the subtitles, even if the output format/container supports soft-subs (e.g. `.mp4`), use the `--force-hardsub` flag to do so. + ```shell $ crunchy-cli download --force-hardsub -s en-US https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` @@ -265,6 +306,7 @@ The `download` command lets you download episodes with a specific audio language The `archive` command lets you download episodes with multiple audios and subtitles and merges it into a `.mkv` file. **Supported urls** + - Single episode (with [episode filtering](#episode-filtering)) ```shell $ crunchy-cli archive https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome @@ -275,37 +317,46 @@ The `archive` command lets you download episodes with multiple audios and subtit ``` **Options** + - Audio languages Set the audio language with the `-a` / `--audio` flag. Can be used multiple times. + ```shell $ crunchy-cli archive -a ja-JP -a de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is your system locale (if not supported by Crunchyroll, `en-US` (American English) and `ja-JP` (Japanese) are used). - Subtitle languages Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. + ```shell $ crunchy-cli archive -s de-DE https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is `all` subtitles. - Output template Define an output template by using the `-o` / `--output` flag. crunchy-cli uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of it's ability to store multiple audio, video and subtitle tracks at once. + ```shell $ crunchy-cli archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is `{title}.mkv`. See the [Template Options section](#output-template-options) below for more options. - Resolution The resolution for videos can be set via the `-r` / `--resolution` flag. + ```shell $ crunchy-cli archive -r worst https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is `best`. - Merge behavior @@ -316,9 +367,11 @@ The `archive` command lets you download episodes with multiple audios and subtit With the `-m` / `--merge` flag you can define the behaviour when an episodes' video tracks differ in length. Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` - otherwise like `audio`. Subtitles will always match the primary audio and video. + ```shell $ crunchy-cli archive -m audio https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is `auto`. - FFmpeg Preset @@ -326,6 +379,7 @@ The `archive` command lets you download episodes with multiple audios and subtit You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. Multiple predefined presets how videos should be encoded (h264, h265, av1, ...) are available, you can see them with `crunchy-cli archive --help`. If you need more specific ffmpeg customizations you could either convert the output file manually or use ffmpeg output arguments as value for this flag. + ```shell $ crunchy-cli archive --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` @@ -333,14 +387,17 @@ The `archive` command lets you download episodes with multiple audios and subtit - Default subtitle `--default-subtitle` Set which subtitle language is to be flagged as **default** and **forced**. + ```shell $ crunchy-cli archive --default-subtitle en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is none. - Skip existing If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. + ```shell $ crunchy-cli archive --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` @@ -349,14 +406,17 @@ The `archive` command lets you download episodes with multiple audios and subtit Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. The `--yes` flag suppresses this interactive prompt and just downloads all seasons. + ```shell $ crunchy-cli archive --yes https://www.crunchyroll.com/series/GR49G9VP6/sword-art-online ``` + If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. ### Search **Supported urls/input** + - Single episode (with [episode filtering](#episode-filtering)) ```shell $ crunchy-cli search https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome @@ -371,12 +431,15 @@ The `archive` command lets you download episodes with multiple audios and subtit ``` **Options** + - Audio Set the audio language to search via the `--audio` flag. Can be used multiple times. + ```shell $ crunchy-cli search --audio en-US https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is your system locale. - Result limit @@ -384,6 +447,7 @@ The `archive` command lets you download episodes with multiple audios and subtit If your input is a search term instead of an url, you have multiple options to control which results to process. The `--search-top-results-limit` flag sets the limit of top search results to process. `--search-series-limit` sets the limit of only series, `--search-movie-listing-limit` of only movie listings, `--search-episode-limit` of only episodes and `--search-music-limit` of only concerts and music videos. + ```shell $ crunchy-cli search --search-top-results-limit 10 "darling in the franxx" # only return series which have 'darling' in it. do not return top results which might also be non-series items @@ -391,6 +455,7 @@ The `archive` command lets you download episodes with multiple audios and subtit # this returns 2 top results, 3 movie listings, 5 episodes and 1 music item as result $ crunchy-cli search --search-top-results-limit 2 --search-movie-listing-limit 3 --search-episode-limit 5 --search-music-limit 1 "test" ``` + Default is `5` for `--search-top-results-limit`, `0` for all others. - Output template @@ -401,9 +466,11 @@ The `archive` command lets you download episodes with multiple audios and subtit The required pattern for this begins with `{{`, then the keyword, and closes with `}}` (e.g. `{{episode.title}}`). For example, if you want to get the title of an episode, you can use `Title: {{episode.title}}` and `{{episode.title}}` will be replaced with the episode title. You can see all supported keywords with `crunchy-cli search --help`. + ```shell $ crunchy-cli search -o "{{series.title}}" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` + Default is `S{{season.number}}E{{episode.number}} - {{episode.title}}`. --- @@ -425,6 +492,7 @@ You can use various template options to change how the filename is processed. Th - `{episode_id}` โ†’ ID of the episode Example: + ```shell $ crunchy-cli archive -o "[S{season_number}E{episode_number}] {title}.mkv" https://www.crunchyroll.com/series/G8DHV7W21/dragon-ball # Output file: '[S01E01] Secret of the Dragon Ball.mkv' @@ -438,6 +506,7 @@ A filter pattern may consist of either a season, an episode, or a combination of When used in combination, seasons `S` must be defined before episodes `E`. There are many possible patterns, for example: + - `...[E5]` - Download the fifth episode. - `...[S1]` - Download the whole first season. - `...[-S2]` - Download the first two seasons. @@ -447,6 +516,7 @@ There are many possible patterns, for example: - `...[S1-S3,S4E2-S4E6]` - Download season one to three, then episodes two to six from season four. In practice, it would look like this: + ``` https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx[E1-E5] ``` From 7594412f5845d0746df755b9536413432ed3a36a Mon Sep 17 00:00:00 2001 From: kennedy <854543+kennedy@users.noreply.github.com> Date: Thu, 2 Nov 2023 08:37:40 -0400 Subject: [PATCH 471/630] updated brew url (#263) * updated brew url Its most appropriate to forward users to the brew's information page generated for crunchy-cli. There are stats on amount of downloads, see where the manifest is location, and what architectures are built for it. * Update README.md Co-authored-by: ByteDream <63594396+ByteDream@users.noreply.github.com> --------- Co-authored-by: ByteDream <63594396+ByteDream@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa99c45..3cbb8c9 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t - [Homebrew](https://brew.sh/) - For macOS/linux users, we support the [brew](https://brew.sh/#/) command-line installer. Packages are compile by the [homebrew project](https://github.com/Homebrew/homebrew-core/pkgs/container/core%2Fcrunchy-cli), and will also install the `openssl@3` and `ffmpeg` dependencies. + For macOS/linux users, we support the [brew](https://brew.sh/#/) command-line installer. Packages are compiled by the [homebrew project](https://formulae.brew.sh/formula/crunchy-cli), and will also install the `openssl@3` and `ffmpeg` dependencies. ```shell $ brew install crunchy-cli From 787d8ab02c206a763b2e5a81b413aee11bfe11b5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 4 Nov 2023 15:24:14 +0100 Subject: [PATCH 472/630] Add --special-output and --skip-specials flag --- crunchy-cli-core/src/archive/command.rs | 36 ++++++++++++++++--- crunchy-cli-core/src/archive/filter.rs | 16 ++++++++- crunchy-cli-core/src/download/command.rs | 44 +++++++++++++++++++++--- crunchy-cli-core/src/download/filter.rs | 16 ++++++++- crunchy-cli-core/src/utils/format.rs | 4 +++ 5 files changed, 106 insertions(+), 10 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7d28e2e..f6da256 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -54,6 +54,11 @@ pub struct Archive { {episode_id} โ†’ ID of the episode")] #[arg(short, long, default_value = "{title}.mkv")] pub(crate) output: String, + #[arg(help = "Name of the output file if the episode is a special")] + #[arg(long_help = "Name of the output file if the episode is a special. \ + If not set, the '-o'/'--output' flag will be used as name template")] + #[arg(long)] + pub(crate) special_output: Option<String>, #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution.\ @@ -95,6 +100,9 @@ pub struct Archive { #[arg(help = "Skip files which are already existing")] #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, + #[arg(help = "Skip special episodes")] + #[arg(long, default_value_t = false)] + pub(crate) skip_specials: bool, #[arg(help = "Skip any interactive input")] #[arg(short, long, default_value_t = false)] @@ -123,6 +131,17 @@ impl Execute for Archive { && self.output != "-" { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") + } else if let Some(special_output) = &self.special_output { + if PathBuf::from(special_output) + .extension() + .unwrap_or_default() + .to_string_lossy() + != "mkv" + && !is_special_file(special_output) + && special_output != "-" + { + bail!("File extension for special episodes is not '.mkv'. Currently only matroska / '.mkv' files are supported") + } } self.audio = all_locale_in_locales(self.audio.clone()); @@ -147,9 +166,10 @@ impl Execute for Archive { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = ArchiveFilter::new(url_filter, self.clone(), !self.yes) - .visit(media_collection) - .await?; + let single_format_collection = + ArchiveFilter::new(url_filter, self.clone(), !self.yes, self.skip_specials) + .visit(media_collection) + .await?; if single_format_collection.is_empty() { progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); @@ -175,7 +195,15 @@ impl Execute for Archive { downloader.add_format(download_format) } - let formatted_path = format.format_path((&self.output).into()); + let formatted_path = if format.is_special() { + format.format_path( + self.special_output + .as_ref() + .map_or((&self.output).into(), |so| so.into()), + ) + } else { + format.format_path((&self.output).into()) + }; let (path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 91023a3..a4e3188 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -18,6 +18,7 @@ pub(crate) struct ArchiveFilter { url_filter: UrlFilter, archive: Archive, interactive_input: bool, + skip_special: bool, season_episodes: HashMap<String, Vec<Episode>>, season_subtitles_missing: Vec<u32>, season_sorting: Vec<String>, @@ -25,11 +26,17 @@ pub(crate) struct ArchiveFilter { } impl ArchiveFilter { - pub(crate) fn new(url_filter: UrlFilter, archive: Archive, interactive_input: bool) -> Self { + pub(crate) fn new( + url_filter: UrlFilter, + archive: Archive, + interactive_input: bool, + skip_special: bool, + ) -> Self { Self { url_filter, archive, interactive_input, + skip_special, season_episodes: HashMap::new(), season_subtitles_missing: vec![], season_sorting: vec![], @@ -246,6 +253,13 @@ impl Filter for ArchiveFilter { return Ok(None); } + // skip the episode if it's a special + if self.skip_special + && (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0) + { + return Ok(None); + } + let mut episodes = vec![]; if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) { if self.archive.audio.contains(&episode.audio_locale) { diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 376cc2d..da885c3 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -50,6 +50,11 @@ pub struct Download { {episode_id} โ†’ ID of the episode")] #[arg(short, long, default_value = "{title}.mp4")] pub(crate) output: String, + #[arg(help = "Name of the output file if the episode is a special")] + #[arg(long_help = "Name of the output file if the episode is a special. \ + If not set, the '-o'/'--output' flag will be used as name template")] + #[arg(long)] + pub(crate) special_output: Option<String>, #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution.\ @@ -73,6 +78,9 @@ pub struct Download { #[arg(help = "Skip files which are already existing")] #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, + #[arg(help = "Skip special episodes")] + #[arg(long, default_value_t = false)] + pub(crate) skip_specials: bool, #[arg(help = "Skip any interactive input")] #[arg(short, long, default_value_t = false)] @@ -116,6 +124,25 @@ impl Execute for Download { } } + if let Some(special_output) = &self.special_output { + if Path::new(special_output) + .extension() + .unwrap_or_default() + .is_empty() + && !is_special_file(special_output) + && special_output != "-" + { + bail!("No file extension found. Please specify a file extension (via `--special-output`) for the output file") + } + if let Some(ext) = Path::new(special_output).extension() { + if self.force_hardsub { + warn!("Hardsubs are forced for special episodes. Adding subtitles may take a while") + } else if !["mkv", "mov", "mp4"].contains(&ext.to_string_lossy().as_ref()) { + warn!("Detected a container which does not support softsubs. Adding subtitles for special episodes may take a while") + } + } + } + Ok(()) } @@ -135,9 +162,10 @@ impl Execute for Download { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = DownloadFilter::new(url_filter, self.clone(), !self.yes) - .visit(media_collection) - .await?; + let single_format_collection = + DownloadFilter::new(url_filter, self.clone(), !self.yes, self.skip_specials) + .visit(media_collection) + .await?; if single_format_collection.is_empty() { progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); @@ -167,7 +195,15 @@ impl Execute for Download { let mut downloader = download_builder.clone().build(); downloader.add_format(download_format); - let formatted_path = format.format_path((&self.output).into()); + let formatted_path = if format.is_special() { + format.format_path( + self.special_output + .as_ref() + .map_or((&self.output).into(), |so| so.into()), + ) + } else { + format.format_path((&self.output).into()) + }; let (path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 55b1e8b..626896c 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -12,17 +12,24 @@ pub(crate) struct DownloadFilter { url_filter: UrlFilter, download: Download, interactive_input: bool, + skip_special: bool, season_episodes: HashMap<u32, Vec<Episode>>, season_subtitles_missing: Vec<u32>, season_visited: bool, } impl DownloadFilter { - pub(crate) fn new(url_filter: UrlFilter, download: Download, interactive_input: bool) -> Self { + pub(crate) fn new( + url_filter: UrlFilter, + download: Download, + interactive_input: bool, + skip_special: bool, + ) -> Self { Self { url_filter, download, interactive_input, + skip_special, season_episodes: HashMap::new(), season_subtitles_missing: vec![], season_visited: false, @@ -132,6 +139,13 @@ impl Filter for DownloadFilter { return Ok(None); } + // skip the episode if it's a special + if self.skip_special + && (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0) + { + return Ok(None); + } + // check if the audio locale is correct. // should only be incorrect if the console input was a episode url. otherwise // `DownloadFilter::visit_season` returns the correct episodes with matching audio diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 4679878..4afaa07 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -473,6 +473,10 @@ impl Format { tab_info!("FPS: {:.2}", self.fps) } + pub fn is_special(&self) -> bool { + self.sequence_number == 0.0 || self.sequence_number.fract() != 0.0 + } + pub fn has_relative_fmt<S: AsRef<str>>(s: S) -> bool { return s.as_ref().contains("{relative_episode_number}") || s.as_ref().contains("{relative_sequence_number}"); From e5d9c27af7bf2056dfd96ca87bbe34506674a73f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 21:10:15 +0100 Subject: [PATCH 473/630] Fix ass filter path escape on windows (#262) --- crunchy-cli-core/src/utils/download.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index ff9f240..23d5863 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -378,8 +378,27 @@ impl Downloader { output_presets.extend([ "-vf".to_string(), format!( - "ass={}", - subtitles.get(position).unwrap().path.to_str().unwrap() + "ass='{}'", + // ffmpeg doesn't removes all ':' and '\' from the filename when using + // the ass filter. well, on windows these characters are used in + // absolute paths, so they have to be correctly escaped here + if cfg!(windows) { + subtitles + .get(position) + .unwrap() + .path + .to_str() + .unwrap() + .replace('\\', "\\\\") + .replace(':', "\\:") + } else { + subtitles + .get(position) + .unwrap() + .path + .to_string_lossy() + .to_string() + } ), ]) } From f31437fba2bc675fbfeb431f30b01dac5463708f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 21:20:43 +0100 Subject: [PATCH 474/630] Remove leading and trailing whitespaces from output file --- crunchy-cli-core/src/utils/os.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 1f76b90..0596789 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -96,7 +96,7 @@ lazy_static::lazy_static! { /// is based of the implementation of the /// [`sanitize-filename`](https://crates.io/crates/sanitize-filename) crate. pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String { - let path = Cow::from(path.as_ref()); + let path = Cow::from(path.as_ref().trim()); let path = ILLEGAL_RE.replace_all(&path, ""); let path = CONTROL_RE.replace_all(&path, ""); From cd35dfe2766f6448ea43747dcfe1c24c2f599c51 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 21:48:49 +0100 Subject: [PATCH 475/630] Rename --special-output to --output-specials --- crunchy-cli-core/src/archive/command.rs | 6 +++--- crunchy-cli-core/src/download/command.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index f6da256..0ee723e 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -58,7 +58,7 @@ pub struct Archive { #[arg(long_help = "Name of the output file if the episode is a special. \ If not set, the '-o'/'--output' flag will be used as name template")] #[arg(long)] - pub(crate) special_output: Option<String>, + pub(crate) output_specials: Option<String>, #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution.\ @@ -131,7 +131,7 @@ impl Execute for Archive { && self.output != "-" { bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported") - } else if let Some(special_output) = &self.special_output { + } else if let Some(special_output) = &self.output_specials { if PathBuf::from(special_output) .extension() .unwrap_or_default() @@ -197,7 +197,7 @@ impl Execute for Archive { let formatted_path = if format.is_special() { format.format_path( - self.special_output + self.output_specials .as_ref() .map_or((&self.output).into(), |so| so.into()), ) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index da885c3..18355cd 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -54,7 +54,7 @@ pub struct Download { #[arg(long_help = "Name of the output file if the episode is a special. \ If not set, the '-o'/'--output' flag will be used as name template")] #[arg(long)] - pub(crate) special_output: Option<String>, + pub(crate) output_specials: Option<String>, #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution.\ @@ -124,7 +124,7 @@ impl Execute for Download { } } - if let Some(special_output) = &self.special_output { + if let Some(special_output) = &self.output_specials { if Path::new(special_output) .extension() .unwrap_or_default() @@ -132,7 +132,7 @@ impl Execute for Download { && !is_special_file(special_output) && special_output != "-" { - bail!("No file extension found. Please specify a file extension (via `--special-output`) for the output file") + bail!("No file extension found. Please specify a file extension (via `--output-specials`) for the output file") } if let Some(ext) = Path::new(special_output).extension() { if self.force_hardsub { @@ -197,7 +197,7 @@ impl Execute for Download { let formatted_path = if format.is_special() { format.format_path( - self.special_output + self.output_specials .as_ref() .map_or((&self.output).into(), |so| so.into()), ) From 56411c6547223e59c8cb4e8c61d6a29627889537 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 22:01:44 +0100 Subject: [PATCH 476/630] Add missing whitespaces in command help --- crunchy-cli-core/src/archive/command.rs | 4 ++-- crunchy-cli-core/src/download/command.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 0ee723e..7add620 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -37,7 +37,7 @@ pub struct Archive { pub(crate) subtitle: Vec<Locale>, #[arg(help = "Name of the output file")] - #[arg(long_help = "Name of the output file.\ + #[arg(long_help = "Name of the output file. \ If you use one of the following pattern they will get replaced:\n \ {title} โ†’ Title of the video\n \ {series_name} โ†’ Name of the series\n \ @@ -61,7 +61,7 @@ pub struct Archive { pub(crate) output_specials: Option<String>, #[arg(help = "Video resolution")] - #[arg(long_help = "The video resolution.\ + #[arg(long_help = "The video resolution. \ Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ Specifying the exact pixels is not recommended, use one of the other options instead. \ Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 18355cd..4f189c5 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -33,7 +33,7 @@ pub struct Download { pub(crate) subtitle: Option<Locale>, #[arg(help = "Name of the output file")] - #[arg(long_help = "Name of the output file.\ + #[arg(long_help = "Name of the output file. \ If you use one of the following pattern they will get replaced:\n \ {title} โ†’ Title of the video\n \ {series_name} โ†’ Name of the series\n \ From fc6511a361b7ee98830385bf273cd2ce464b1058 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 22:09:21 +0100 Subject: [PATCH 477/630] Format code --- crunchy-cli-core/src/archive/command.rs | 4 ++-- crunchy-cli-core/src/archive/filter.rs | 16 +++++++--------- crunchy-cli-core/src/download/command.rs | 2 +- crunchy-cli-core/src/download/filter.rs | 19 +++++++++---------- crunchy-cli-core/src/lib.rs | 4 ++-- crunchy-cli-core/src/search/filter.rs | 4 ++-- crunchy-cli-core/src/search/format.rs | 1 + crunchy-cli-core/src/utils/download.rs | 13 ++++++------- crunchy-cli-core/src/utils/ffmpeg.rs | 6 +++--- crunchy-cli-core/src/utils/format.rs | 15 +++++++-------- crunchy-cli-core/src/utils/locale.rs | 3 +-- crunchy-cli-core/src/utils/log.rs | 1 - crunchy-cli-core/src/utils/os.rs | 2 +- 13 files changed, 42 insertions(+), 48 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7add620..3585e9c 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -306,8 +306,8 @@ async fn get_format( } MergeBehavior::Audio => download_formats.push(DownloadFormat { video: ( - (*format_pairs.first().unwrap()).1.clone(), - (*format_pairs.first().unwrap()).0.audio.clone(), + format_pairs.first().unwrap().1.clone(), + format_pairs.first().unwrap().0.audio.clone(), ), audios: format_pairs .iter() diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index a4e3188..01612b9 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -103,7 +103,7 @@ impl Filter for ArchiveFilter { seasons.retain(|s| !remove_ids.contains(&s.id)); let duplicated_seasons = get_duplicated_seasons(&seasons); - if duplicated_seasons.len() > 0 { + if !duplicated_seasons.is_empty() { if self.interactive_input { check_for_duplicated_seasons(&mut seasons); } else { @@ -139,8 +139,7 @@ impl Filter for ArchiveFilter { if !matches!(self.visited, Visited::Series) { let mut audio_locales: Vec<Locale> = seasons .iter() - .map(|s| s.audio_locales.clone()) - .flatten() + .flat_map(|s| s.audio_locales.clone()) .collect(); real_dedup_vec(&mut audio_locales); let missing_audio = missing_locales(&audio_locales, &self.archive.audio); @@ -158,8 +157,7 @@ impl Filter for ArchiveFilter { let subtitle_locales: Vec<Locale> = seasons .iter() - .map(|s| s.subtitle_locales.clone()) - .flatten() + .flat_map(|s| s.subtitle_locales.clone()) .collect(); let missing_subtitle = missing_locales(&subtitle_locales, &self.archive.subtitle); if !missing_subtitle.is_empty() { @@ -211,7 +209,7 @@ impl Filter for ArchiveFilter { } } if eps.len() < before_len { - if eps.len() == 0 { + if eps.is_empty() { if matches!(self.visited, Visited::Series) { warn!( "Season {} is not available with {} audio", @@ -237,7 +235,7 @@ impl Filter for ArchiveFilter { for episode in episodes.iter() { self.season_episodes .entry(episode.season_id.clone()) - .or_insert(vec![]) + .or_default() .push(episode.clone()) } } @@ -290,7 +288,7 @@ impl Filter for ArchiveFilter { } let mut subtitle_locales: Vec<Locale> = - episodes.iter().map(|(_, s)| s.clone()).flatten().collect(); + episodes.iter().flat_map(|(_, s)| s.clone()).collect(); real_dedup_vec(&mut subtitle_locales); let missing_subtitles = missing_locales(&subtitle_locales, &self.archive.subtitle); if !missing_subtitles.is_empty() @@ -435,6 +433,6 @@ impl Filter for ArchiveFilter { } } -fn missing_locales<'a>(available: &Vec<Locale>, searched: &'a Vec<Locale>) -> Vec<&'a Locale> { +fn missing_locales<'a>(available: &[Locale], searched: &'a [Locale]) -> Vec<&'a Locale> { searched.iter().filter(|p| !available.contains(p)).collect() } diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 4f189c5..82cb8f8 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -251,7 +251,7 @@ async fn get_format( }; let subtitle = if let Some(subtitle_locale) = &download.subtitle { - stream.subtitles.get(subtitle_locale).map(|s| s.clone()) + stream.subtitles.get(subtitle_locale).cloned() } else { None }; diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 626896c..fb2e563 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -45,14 +45,13 @@ impl Filter for DownloadFilter { async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the // audio is matching only if the field is populated - if !series.audio_locales.is_empty() { - if !series.audio_locales.contains(&self.download.audio) { - error!( - "Series {} is not available with {} audio", - series.title, self.download.audio - ); - return Ok(vec![]); - } + if !series.audio_locales.is_empty() && !series.audio_locales.contains(&self.download.audio) + { + error!( + "Series {} is not available with {} audio", + series.title, self.download.audio + ); + return Ok(vec![]); } let mut seasons = vec![]; @@ -91,7 +90,7 @@ impl Filter for DownloadFilter { } let duplicated_seasons = get_duplicated_seasons(&seasons); - if duplicated_seasons.len() > 0 { + if !duplicated_seasons.is_empty() { if self.interactive_input { check_for_duplicated_seasons(&mut seasons); } else { @@ -118,7 +117,7 @@ impl Filter for DownloadFilter { for episode in episodes.iter() { self.season_episodes .entry(episode.season_number) - .or_insert(vec![]) + .or_default() .push(episode.clone()) } } diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 02bef6f..d6c2220 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -77,7 +77,7 @@ fn version() -> String { let build_date = env!("BUILD_DATE"); if git_commit_hash.is_empty() { - format!("{}", package_version) + package_version.to_string() } else { format!("{} ({} {})", package_version, git_commit_hash, build_date) } @@ -250,7 +250,7 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { "Via `--lang` specified language is not supported. Supported languages: {}", supported_langs .iter() - .map(|l| format!("`{}` ({})", l.to_string(), l.to_human_readable())) + .map(|l| format!("`{}` ({})", l, l.to_human_readable())) .collect::<Vec<String>>() .join(", ") ) diff --git a/crunchy-cli-core/src/search/filter.rs b/crunchy-cli-core/src/search/filter.rs index 264b31d..3bb6d9f 100644 --- a/crunchy-cli-core/src/search/filter.rs +++ b/crunchy-cli-core/src/search/filter.rs @@ -21,7 +21,7 @@ impl FilterOptions { pub fn filter_episodes(&self, mut episodes: Vec<Episode>) -> Vec<Episode> { episodes.retain(|e| { - self.check_audio_language(&vec![e.audio_locale.clone()]) + self.check_audio_language(&[e.audio_locale.clone()]) && self .url_filter .is_episode_valid(e.sequence_number, e.season_number) @@ -38,7 +38,7 @@ impl FilterOptions { ) } - fn check_audio_language(&self, audio: &Vec<Locale>) -> bool { + fn check_audio_language(&self, audio: &[Locale]) -> bool { if !self.audio.is_empty() { return self.audio.iter().any(|a| audio.contains(a)); } diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 55cba7c..f9746b1 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -372,6 +372,7 @@ impl Format { let stream_empty = self.check_pattern_count_empty(Scope::Stream) && self.check_pattern_count_empty(Scope::Subtitle); + #[allow(clippy::type_complexity)] let mut tree: Vec<(Season, Vec<(Episode, Vec<Stream>)>)> = vec![]; let series = if !series_empty { diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 23d5863..945cc14 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -296,7 +296,7 @@ impl Downloader { ]); // the empty language metadata is created to avoid that metadata from the original track // is copied - metadata.extend([format!("-metadata:s:v:{}", i), format!("language=")]) + metadata.extend([format!("-metadata:s:v:{}", i), "language=".to_string()]) } for (i, meta) in audios.iter().enumerate() { input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); @@ -675,7 +675,7 @@ impl Downloader { let result = download().await; if result.is_err() { - after_download_sender.send((-1 as i32, vec![]))?; + after_download_sender.send((-1, vec![]))?; } result @@ -747,7 +747,7 @@ impl Downloader { } } -fn estimate_variant_file_size(variant_data: &VariantData, segments: &Vec<VariantSegment>) -> u64 { +fn estimate_variant_file_size(variant_data: &VariantData, segments: &[VariantSegment]) -> u64 { (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() } @@ -788,9 +788,8 @@ pub fn get_video_length(path: &Path) -> Result<NaiveTime> { /// [crunchy-labs/crunchy-cli#208](https://github.com/crunchy-labs/crunchy-cli/issues/208) for more /// information. fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { - let re = - Regex::new(r#"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),"#) - .unwrap(); + let re = Regex::new(r"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),") + .unwrap(); // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 // digits so them have to be reduced manually to avoid the panic @@ -832,7 +831,7 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { line, format!( "Dialogue: {},{},", - format_naive_time(start.clone()), + format_naive_time(start), &length_as_string ), ) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index af29bfd..787c79a 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -134,7 +134,7 @@ impl FFmpegPreset { description_details.push(format!("{} video quality/compression", q.to_string())) } - let description = if description_details.len() == 0 { + let description = if description_details.is_empty() { format!( "{} encoded with default video quality/compression", codec.to_string() @@ -239,7 +239,7 @@ impl FFmpegPreset { hwaccel.clone(), quality.clone(), )) { - return Err(format!("ffmpeg preset is not supported")); + return Err("ffmpeg preset is not supported".to_string()); } Ok(FFmpegPreset::Predefined( c, @@ -247,7 +247,7 @@ impl FFmpegPreset { quality.unwrap_or(FFmpegQuality::Normal), )) } else { - Err(format!("cannot use ffmpeg preset with without a codec")) + Err("cannot use ffmpeg preset with without a codec".to_string()) } } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 4afaa07..4c8d3c8 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -170,10 +170,7 @@ impl SingleFormat { } pub fn is_episode(&self) -> bool { - match self.source { - MediaCollection::Episode(_) => true, - _ => false, - } + matches!(self.source, MediaCollection::Episode(_)) } } @@ -181,7 +178,7 @@ struct SingleFormatCollectionEpisodeKey(f32); impl PartialOrd for SingleFormatCollectionEpisodeKey { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - self.0.partial_cmp(&other.0) + Some(self.cmp(other)) } } impl Ord for SingleFormatCollectionEpisodeKey { @@ -198,6 +195,7 @@ impl Eq for SingleFormatCollectionEpisodeKey {} struct SingleFormatCollectionSeasonKey((u32, String)); +#[allow(clippy::incorrect_partial_ord_impl_on_ord_type)] impl PartialOrd for SingleFormatCollectionSeasonKey { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { let mut cmp = self.0 .0.partial_cmp(&other.0 .0); @@ -250,7 +248,7 @@ impl SingleFormatCollection { format.season_number, format.season_id.clone(), ))) - .or_insert(BTreeMap::new()) + .or_default() .insert( SingleFormatCollectionEpisodeKey(format.sequence_number), single_formats, @@ -340,6 +338,7 @@ pub struct Format { } impl Format { + #[allow(clippy::type_complexity)] pub fn from_single_formats( mut single_formats: Vec<(SingleFormat, VariantData, Vec<(Subtitle, bool)>)>, ) -> Self { @@ -349,7 +348,7 @@ impl Format { ( single_format.audio.clone(), subtitles - .into_iter() + .iter() .map(|(s, _)| s.locale.clone()) .collect::<Vec<Locale>>(), ) @@ -440,7 +439,7 @@ impl Format { info!( "Downloading {} to {}", self.title, - if is_special_file(&dst) || dst.to_str().unwrap() == "-" { + if is_special_file(dst) || dst.to_str().unwrap() == "-" { dst.to_string_lossy().to_string() } else { format!("'{}'", dst.to_str().unwrap()) diff --git a/crunchy-cli-core/src/utils/locale.rs b/crunchy-cli-core/src/utils/locale.rs index d749fcb..8651078 100644 --- a/crunchy-cli-core/src/utils/locale.rs +++ b/crunchy-cli-core/src/utils/locale.rs @@ -19,8 +19,7 @@ pub fn system_locale() -> Locale { pub fn all_locale_in_locales(locales: Vec<Locale>) -> Vec<Locale> { if locales .iter() - .find(|l| l.to_string().to_lowercase().trim() == "all") - .is_some() + .any(|l| l.to_string().to_lowercase().trim() == "all") { Locale::all() } else { diff --git a/crunchy-cli-core/src/utils/log.rs b/crunchy-cli-core/src/utils/log.rs index 6650e58..942c652 100644 --- a/crunchy-cli-core/src/utils/log.rs +++ b/crunchy-cli-core/src/utils/log.rs @@ -57,7 +57,6 @@ macro_rules! tab_info { } pub(crate) use tab_info; -#[allow(clippy::type_complexity)] pub struct CliLogger { level: LevelFilter, progress: Mutex<Option<ProgressBar>>, diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 0596789..977e968 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -24,7 +24,7 @@ pub fn has_ffmpeg() -> bool { /// Get the temp directory either by the specified `CRUNCHY_CLI_TEMP_DIR` env variable or the dir /// provided by the os. pub fn temp_directory() -> PathBuf { - env::var("CRUNCHY_CLI_TEMP_DIR").map_or(env::temp_dir(), |d| PathBuf::from(d)) + env::var("CRUNCHY_CLI_TEMP_DIR").map_or(env::temp_dir(), PathBuf::from) } /// Any tempfile should be created with this function. The prefix and directory of every file From c08931b6105d3a6756862d2548e5a428c3f93272 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 22:47:09 +0100 Subject: [PATCH 478/630] Add new commands and format option to readme --- README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3cbb8c9..2491939 100644 --- a/README.md +++ b/README.md @@ -61,14 +61,6 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t $ yay -S crunchy-cli-bin ``` -- [Nix](https://nixos.org/) - - This requires [nix](https://nixos.org) and you'll probably need `--extra-experimental-features "nix-command flakes"`, depending on your configurations. - - ```shell - $ nix <run|shell|develop> github:crunchy-labs/crunchy-cli - ``` - - [Scoop](https://scoop.sh/) For Windows users, we support the [scoop](https://scoop.sh/#/) command-line installer. @@ -88,6 +80,14 @@ Check out the [releases](https://github.com/crunchy-labs/crunchy-cli/releases) t Supported archs: `x86_64_linux`, `arm64_monterey`, `sonoma`, `ventura` +- [Nix](https://nixos.org/) + + This requires [nix](https://nixos.org) and you'll probably need `--extra-experimental-features "nix-command flakes"`, depending on your configurations. + + ```shell + $ nix <run|shell|develop> github:crunchy-labs/crunchy-cli + ``` + ### ๐Ÿ›  Build it yourself Since we do not support every platform and architecture you may have to build the project yourself. @@ -191,6 +191,17 @@ You can set specific settings which will be Make sure that proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. +- User Agent + + There might be cases where a custom user agent is necessary, e.g. to bypass the cloudflare bot protection (#104). + In such cases, the `--user-agent` flag can be used to set a custom user agent. + + ```shell + $ crunchy-cli --user-agent "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)" <command> + ``` + + Default is the user agent, defined in the underlying [library](https://github.com/crunchy-labs/crunchyroll-rs). + ### Login The `login` command can store your session, so you don't have to authenticate every time you execute a command. @@ -254,6 +265,16 @@ The `download` command lets you download episodes with a specific audio language Default is `{title}.mp4`. See the [Template Options section](#output-template-options) below for more options. +- Output template for special episodes + + Define an output template which only gets used when the episode is a special (episode number is 0 or has non-zero decimal places) by using the `--output-special` flag. + + ```shell + $ crunchy-cli download --output-specials -o "Special EP: {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal + ``` + + Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. + - Resolution The resolution for videos can be set via the `-r` / `--resolution` flag. @@ -282,6 +303,14 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` +- Skip specials + + If you doesn't want to download special episodes, use the `--skip-specials` flag to skip the download of them. + + ```shell + $ crunchy-cli download --skip-specials https://www.crunchyroll.com/series/GYZJ43JMR/that-time-i-got-reincarnated-as-a-slime[S2] + ``` + - Yes Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. @@ -301,6 +330,17 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --force-hardsub -s en-US https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` +- Threads + + To increase the download speed, video segments are downloaded simultaneously by creating multiple threads. + If you want to manually specify how many threads to use when downloading, do this with the `-t` / `--threads` flag. + + ```shell + $ crunchy-cli download -t 1 https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + + The default thread count is the count of cpu threads your pc has. + ### Archive The `archive` command lets you download episodes with multiple audios and subtitles and merges it into a `.mkv` file. @@ -341,7 +381,7 @@ The `archive` command lets you download episodes with multiple audios and subtit - Output template Define an output template by using the `-o` / `--output` flag. - crunchy-cli uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of it's ability to store multiple audio, video and subtitle tracks at once. + _crunchy-cli_ exclusively uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of its ability to store multiple audio, video and subtitle tracks at once. ```shell $ crunchy-cli archive -o "{title}.mkv" https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx @@ -349,6 +389,17 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `{title}.mkv`. See the [Template Options section](#output-template-options) below for more options. +- Output template for special episodes + + Define an output template which only gets used when the episode is a special (episode number is 0 or has non-zero decimal places) by using the `--output-special` flag. + _crunchy-cli_ exclusively uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of its ability to store multiple audio, video and subtitle tracks at once. + + ```shell + $ crunchy-cli archive --output-specials -o "Special EP: {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal + ``` + + Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. + - Resolution The resolution for videos can be set via the `-r` / `--resolution` flag. @@ -402,6 +453,14 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` +- Skip specials + + If you doesn't want to download special episodes, use the `--skip-specials` flag to skip the download of them. + + ```shell + $ crunchy-cli archive --skip-specials https://www.crunchyroll.com/series/GYZJ43JMR/that-time-i-got-reincarnated-as-a-slime[S2] + ``` + - Yes Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. @@ -413,6 +472,17 @@ The `archive` command lets you download episodes with multiple audios and subtit If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. +- Threads + + To increase the download speed, video segments are downloaded simultaneously by creating multiple threads. + If you want to manually specify how many threads to use when downloading, do this with the `-t` / `--threads` flag. + + ```shell + $ crunchy-cli archive -t 1 https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + + The default thread count is the count of cpu threads your pc has. + ### Search **Supported urls/input** @@ -479,17 +549,19 @@ The `archive` command lets you download episodes with multiple audios and subtit You can use various template options to change how the filename is processed. The following tags are available: -- `{title}` โ†’ Title of the video -- `{series_name}` โ†’ Name of the series -- `{season_name}` โ†’ Name of the season -- `{audio}` โ†’ Audio language of the video -- `{resolution}` โ†’ Resolution of the video -- `{season_number}` โ†’ Number of the season -- `{episode_number}` โ†’ Number of the episode -- `{relative_episode_number}` โ†’ Number of the episode relative to its season -- `{series_id}` โ†’ ID of the series -- `{season_id}` โ†’ ID of the season -- `{episode_id}` โ†’ ID of the episode +- `{title}` โ†’ Title of the video +- `{series_name}` โ†’ Name of the series +- `{season_name}` โ†’ Name of the season +- `{audio}` โ†’ Audio language of the video +- `{resolution}` โ†’ Resolution of the video +- `{season_number}` โ†’ Number of the season +- `{episode_number}` โ†’ Number of the episode +- `{relative_episode_number}` โ†’ Number of the episode relative to its season +- `{sequence_number}` โ†’ Like `{episode_number}` but without possible non-number characters +- `{relative_sequence_number}` โ†’ Like `{relative_episode_number}` but with support for episode 0's and .5's +- `{series_id}` โ†’ ID of the series +- `{season_id}` โ†’ ID of the season +- `{episode_id}` โ†’ ID of the episode Example: From d52fe7fb923b7b60c0b54b3cda2fe3bf1087bb09 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 Nov 2023 22:56:51 +0100 Subject: [PATCH 479/630] Update dependencies and version --- Cargo.lock | 436 ++++++++++++++++++------------------ Cargo.toml | 4 +- crunchy-cli-core/Cargo.toml | 8 +- 3 files changed, 228 insertions(+), 220 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 409a005..8608f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -68,15 +68,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -108,9 +108,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64-serde" @@ -162,9 +162,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-padding" @@ -238,9 +238,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.4" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.4" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ "anstream", "anstyle", @@ -260,18 +260,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.1" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4110a1e6af615a9e6d0a36f805d5c99099f8bab9b8042f5bc1fa220a4a89e36f" +checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", @@ -281,15 +281,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clap_mangen" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44f35c514163027542f7147797ff930523eea288e03642727348ef1a9666f6b" +checksum = "d3be86020147691e1d2ef58f75346a3d4d94807bfc473e377d52f09f0f7d77f7" dependencies = [ "clap", "roff", @@ -360,16 +360,16 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crunchy-cli" -version = "3.0.3" +version = "3.1.0" dependencies = [ "chrono", "clap", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.0.3" +version = "3.1.0" dependencies = [ "anyhow", "async-trait", @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771cd92c5a4cc050f301674d77bca6c23f8f260ef346bd06c11b4b05ab9e0166" +checksum = "19bdd19b3f1b26e1a45ed32f7b2db68981111a9f5e9551b2d225bf470dca1e11" dependencies = [ "aes", "async-trait", @@ -439,9 +439,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260191f1125c7ba31e35071524938de5c6b0c2d248e5a35c62c4605e2da427e" +checksum = "f0a12fb14fd65dede6da7dad3e4c434f6aee9c18cf2bae0d2cc5021cd5a29fec" dependencies = [ "darling", "quote", @@ -505,9 +505,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.13.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b18de4d072c5be455129422f6a69eb17c032467750d857820cb0c9a92e86a4ed" +checksum = "ad33027a1ac2f37def4c33a5987a8e047fd34f77ff7cabc14bd437aa6d8d4dd2" dependencies = [ "base64", "base64-serde", @@ -522,15 +522,17 @@ dependencies = [ "serde_with", "thiserror", "tokio", + "url", "xattr", ] [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -607,30 +609,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fnv" @@ -680,50 +671,38 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", - "futures-macro", "futures-task", "pin-project-lite", "pin-utils", @@ -784,9 +763,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "heck" @@ -857,7 +836,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -866,9 +845,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -893,16 +872,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -964,12 +943,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.2", "serde", ] @@ -1007,9 +986,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iso8601" @@ -1028,9 +1007,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -1043,15 +1022,26 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "log" @@ -1077,9 +1067,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -1104,9 +1094,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", @@ -1135,7 +1125,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "libc", ] @@ -1152,9 +1142,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1192,11 +1182,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -1224,18 +1214,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.5+3.1.3" +version = "300.1.6+3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" +checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ "cc", "libc", @@ -1276,15 +1266,21 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "1.4.3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1307,9 +1303,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", "serde", @@ -1326,38 +1322,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -1367,9 +1354,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1378,15 +1365,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64", "bytes", @@ -1414,6 +1401,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1429,17 +1417,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ "cc", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1456,11 +1443,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -1469,9 +1456,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", "ring", @@ -1502,9 +1489,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", @@ -1527,9 +1514,9 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", @@ -1560,18 +1547,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "a834c4821019838224821468552240d4d95d14e751986442c816572d39a080c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "46fa52d5646bce91b680189fe5b1c049d2ea38dabb4e2e7c8d00ca12cfbfbcfd" dependencies = [ "proc-macro2", "quote", @@ -1580,9 +1567,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1612,15 +1599,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.0", + "indexmap 2.1.0", "serde", "serde_json", "serde_with_macros", @@ -1629,9 +1616,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ "darling", "proc-macro2", @@ -1673,9 +1660,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1683,9 +1670,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -1693,9 +1680,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" @@ -1705,9 +1692,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.37" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1724,32 +1711,53 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.8.0" +name = "system-configuration" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall", "rustix", "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -1758,12 +1766,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -1771,15 +1780,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1801,9 +1810,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -1811,7 +1820,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", ] @@ -1861,9 +1870,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1881,20 +1890,19 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -1940,9 +1948,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -1990,9 +1998,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2000,9 +2008,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", @@ -2015,9 +2023,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -2027,9 +2035,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2037,9 +2045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", @@ -2050,15 +2058,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -2093,10 +2101,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets 0.48.5", ] diff --git a/Cargo.toml b/Cargo.toml index e5882d6..a0c1871 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.0.3" +version = "3.1.0" edition = "2021" license = "MIT" @@ -18,7 +18,7 @@ openssl = ["openssl-tls"] openssl-static = ["openssl-tls-static"] [dependencies] -tokio = { version = "1.32", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.33", features = ["macros", "rt-multi-thread", "time"], default-features = false } native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e38cb1a..9103831 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.0.3" +version = "3.1.0" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.6.2", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.6.3", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" @@ -26,7 +26,7 @@ indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.16" -regex = "1.9" +regex = "1.10" reqwest = { version = "0.11", default-features = false, features = ["socks"] } serde = "1.0" serde_json = "1.0" @@ -34,7 +34,7 @@ serde_plain = "1.0" shlex = "1.2" sys-locale = "0.3" tempfile = "3.8" -tokio = { version = "1.32", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.33", features = ["macros", "rt-multi-thread", "time"] } rustls-native-certs = { version = "0.6", optional = true } [build-dependencies] From 14e71c05b84ff7458478443b2de4ee71273c61a3 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 16 Nov 2023 13:51:30 +0100 Subject: [PATCH 480/630] Fix aur binary checksums (#266) --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f777f9e..43ea795 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,8 +43,8 @@ jobs: curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-completions.zip curl -LO https://github.com/crunchy-labs/crunchy-cli/releases/download/${{ github.ref_name }}/crunchy-cli-${{ github.ref_name }}-manpages.zip curl -LO https://raw.githubusercontent.com/crunchy-labs/crunchy-cli/${{ github.ref_name }}/LICENSE - echo "CRUNCHY_CLI_BIN_x86_64_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-x86_64-linux | cut -f 1 -d ' ')" >> $GITHUB_ENV - echo "CRUNCHY_CLI_BIN_aarch64_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-aarch64-linux | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_x86_64_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-linux-x86_64 | cut -f 1 -d ' ')" >> $GITHUB_ENV + echo "CRUNCHY_CLI_BIN_aarch64_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-linux-aarch64 | cut -f 1 -d ' ')" >> $GITHUB_ENV echo "CRUNCHY_CLI_BIN_COMPLETIONS_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-completions.zip | cut -f 1 -d ' ')" >> $GITHUB_ENV echo "CRUNCHY_CLI_BIN_MANPAGES_SHA256=$(sha256sum crunchy-cli-${{ github.ref_name }}-manpages.zip | cut -f 1 -d ' ')" >> $GITHUB_ENV echo "CRUNCHY_CLI_BIN_LICENSE_SHA256=$(sha256sum LICENSE | cut -f 1 -d ' ')" >> $GITHUB_ENV From 2c370939595149c20ed6ba2fbde3274134be0aad Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 19 Nov 2023 19:24:15 +0100 Subject: [PATCH 481/630] Manually burn-in subtitles only if no pre-burned video is available (#268) --- crunchy-cli-core/src/archive/command.rs | 3 +- crunchy-cli-core/src/download/command.rs | 56 +++++++++++++++++++++--- crunchy-cli-core/src/utils/ffmpeg.rs | 2 + crunchy-cli-core/src/utils/format.rs | 4 ++ crunchy-cli-core/src/utils/video.rs | 37 ++++++++++++---- 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 3585e9c..006bbfa 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -248,7 +248,8 @@ async fn get_format( for single_format in single_formats { let stream = single_format.stream().await?; - let Some((video, audio)) = variant_data_from_stream(&stream, &archive.resolution).await? + let Some((video, audio, _)) = + variant_data_from_stream(&stream, &archive.resolution, None).await? else { if single_format.is_episode() { bail!( diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 82cb8f8..cf1a049 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -1,7 +1,7 @@ use crate::download::filter::DownloadFilter; use crate::utils::context::Context; use crate::utils::download::{DownloadBuilder, DownloadFormat}; -use crate::utils::ffmpeg::FFmpegPreset; +use crate::utils::ffmpeg::{FFmpegPreset, SOFTSUB_CONTAINERS}; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat}; use crate::utils::log::progress; @@ -149,6 +149,25 @@ impl Execute for Download { async fn execute(self, ctx: Context) -> Result<()> { let mut parsed_urls = vec![]; + let output_supports_softsubs = SOFTSUB_CONTAINERS.contains( + &Path::new(&self.output) + .extension() + .unwrap_or_default() + .to_string_lossy() + .as_ref(), + ); + let special_output_supports_softsubs = if let Some(so) = &self.output_specials { + SOFTSUB_CONTAINERS.contains( + &Path::new(so) + .extension() + .unwrap_or_default() + .to_string_lossy() + .as_ref(), + ) + } else { + output_supports_softsubs + }; + for (i, url) in self.urls.clone().into_iter().enumerate() { let progress_handler = progress!("Parsing url {}", i + 1); match parse_url(&ctx.crunchy, url.clone(), true).await { @@ -190,7 +209,18 @@ impl Execute for Download { // the vec contains always only one item let single_format = single_formats.remove(0); - let (download_format, format) = get_format(&self, &single_format).await?; + let (download_format, format) = get_format( + &self, + &single_format, + if self.force_hardsub { + true + } else if single_format.is_special() { + !special_output_supports_softsubs + } else { + !output_supports_softsubs + }, + ) + .await?; let mut downloader = download_builder.clone().build(); downloader.add_format(download_format); @@ -227,9 +257,19 @@ impl Execute for Download { async fn get_format( download: &Download, single_format: &SingleFormat, + try_peer_hardsubs: bool, ) -> Result<(DownloadFormat, Format)> { let stream = single_format.stream().await?; - let Some((video, audio)) = variant_data_from_stream(&stream, &download.resolution).await? + let Some((video, audio, contains_hardsub)) = variant_data_from_stream( + &stream, + &download.resolution, + if try_peer_hardsubs { + download.subtitle.clone() + } else { + None + }, + ) + .await? else { if single_format.is_episode() { bail!( @@ -250,7 +290,9 @@ async fn get_format( } }; - let subtitle = if let Some(subtitle_locale) = &download.subtitle { + let subtitle = if contains_hardsub { + None + } else if let Some(subtitle_locale) = &download.subtitle { stream.subtitles.get(subtitle_locale).cloned() } else { None @@ -266,7 +308,7 @@ async fn get_format( )] }), }; - let format = Format::from_single_formats(vec![( + let mut format = Format::from_single_formats(vec![( single_format.clone(), video, subtitle.map_or(vec![], |s| { @@ -276,6 +318,10 @@ async fn get_format( )] }), )]); + if contains_hardsub { + let (_, subs) = format.locales.get_mut(0).unwrap(); + subs.push(download.subtitle.clone().unwrap()) + } Ok((download_format, format)) } diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 787c79a..7d77833 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -2,6 +2,8 @@ use lazy_static::lazy_static; use regex::Regex; use std::str::FromStr; +pub const SOFTSUB_CONTAINERS: [&str; 3] = ["mkv", "mov", "mp4"]; + #[derive(Clone, Debug, Eq, PartialEq)] pub enum FFmpegPreset { Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality), diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 4c8d3c8..05ac232 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -172,6 +172,10 @@ impl SingleFormat { pub fn is_episode(&self) -> bool { matches!(self.source, MediaCollection::Episode(_)) } + + pub fn is_special(&self) -> bool { + self.sequence_number == 0.0 || self.sequence_number.fract() != 0.0 + } } struct SingleFormatCollectionEpisodeKey(f32); diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index 5b3eaeb..0ae4ba4 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -1,32 +1,47 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, Stream, VariantData}; use crunchyroll_rs::Locale; pub async fn variant_data_from_stream( stream: &Stream, resolution: &Resolution, -) -> Result<Option<(VariantData, VariantData)>> { + subtitle: Option<Locale>, +) -> Result<Option<(VariantData, VariantData, bool)>> { // sometimes Crunchyroll marks episodes without real subtitles that they have subtitles and // reports that only hardsub episode are existing. the following lines are trying to prevent // potential errors which might get caused by this incorrect reporting // (https://github.com/crunchy-labs/crunchy-cli/issues/231) let mut hardsub_locales = stream.streaming_hardsub_locales(); - let hardsub_locale = if !hardsub_locales.contains(&Locale::Custom("".to_string())) + let (hardsub_locale, mut contains_hardsub) = if !hardsub_locales + .contains(&Locale::Custom("".to_string())) && !hardsub_locales.contains(&Locale::Custom(":".to_string())) { // if only one hardsub locale exists, assume that this stream doesn't really contains hardsubs if hardsub_locales.len() == 1 { - Some(hardsub_locales.remove(0)) + (Some(hardsub_locales.remove(0)), false) } else { // fallback to `None`. this should trigger an error message in `stream.dash_streaming_data` // that the requested stream is not available - None + (None, false) } } else { - None + let hardsubs_requested = subtitle.is_some(); + (subtitle, hardsubs_requested) }; - let mut streaming_data = stream.dash_streaming_data(hardsub_locale).await?; + let mut streaming_data = match stream.dash_streaming_data(hardsub_locale).await { + Ok(data) => data, + Err(e) => { + // the error variant is only `crunchyroll_rs::error::Error::Input` when the requested + // hardsub is not available + if let crunchyroll_rs::error::Error::Input { .. } = e { + contains_hardsub = false; + stream.dash_streaming_data(None).await? + } else { + bail!(e) + } + } + }; streaming_data .0 .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); @@ -42,5 +57,11 @@ pub async fn variant_data_from_stream( .into_iter() .find(|v| resolution.height == v.resolution.height), }; - Ok(video_variant.map(|v| (v, streaming_data.1.first().unwrap().clone()))) + Ok(video_variant.map(|v| { + ( + v, + streaming_data.1.first().unwrap().clone(), + contains_hardsub, + ) + })) } From 440ccd99b5482af43b0cec222d49a45ddd5b43e1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 20 Nov 2023 22:05:06 +0100 Subject: [PATCH 482/630] Update dependencies and version --- Cargo.lock | 82 +++++++++++++------------ Cargo.toml | 4 +- crunchy-cli-core/Cargo.toml | 6 +- crunchy-cli-core/src/archive/filter.rs | 8 +-- crunchy-cli-core/src/download/filter.rs | 4 +- crunchy-cli-core/src/search/format.rs | 2 +- 6 files changed, 54 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8608f0e..897731f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,9 +238,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.1.0" +version = "3.1.1" dependencies = [ "chrono", "clap", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.1.0" +version = "3.1.1" dependencies = [ "anyhow", "async-trait", @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bdd19b3f1b26e1a45ed32f7b2db68981111a9f5e9551b2d225bf470dca1e11" +checksum = "cc4ce434784eee7892ad8c3d1ecaea0c0858db51bbb295b474db38c256e8d2fb" dependencies = [ "aes", "async-trait", @@ -423,7 +423,6 @@ dependencies = [ "crunchyroll-rs-internal", "dash-mpd", "futures-util", - "http", "lazy_static", "m3u8-rs", "regex", @@ -439,9 +438,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a12fb14fd65dede6da7dad3e4c434f6aee9c18cf2bae0d2cc5021cd5a29fec" +checksum = "be840f8cf2ce6afc9a9eae268d41423093141ec88f664a515d5ed2a85a66fb60" dependencies = [ "darling", "quote", @@ -505,9 +504,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad33027a1ac2f37def4c33a5987a8e047fd34f77ff7cabc14bd437aa6d8d4dd2" +checksum = "5471fc46c0b229c8f2308d83be857c745c9f06cc83a433d7047909722e0453b4" dependencies = [ "base64", "base64-serde", @@ -609,9 +608,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys 0.48.0", @@ -655,9 +654,12 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] [[package]] name = "fs2" @@ -721,9 +723,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -738,9 +740,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -748,7 +750,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -787,9 +789,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1039,9 +1041,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "log" @@ -1443,9 +1445,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -1456,9 +1458,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", "ring", @@ -1480,9 +1482,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64", ] @@ -1547,18 +1549,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.191" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a834c4821019838224821468552240d4d95d14e751986442c816572d39a080c9" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.191" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fa52d5646bce91b680189fe5b1c049d2ea38dabb4e2e7c8d00ca12cfbfbcfd" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -1810,9 +1812,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -1827,9 +1829,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a0c1871..098c048 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.1.0" +version = "3.1.1" edition = "2021" license = "MIT" @@ -18,7 +18,7 @@ openssl = ["openssl-tls"] openssl-static = ["openssl-tls-static"] [dependencies] -tokio = { version = "1.33", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.34", features = ["macros", "rt-multi-thread", "time"], default-features = false } native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 9103831..5771888 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.1.0" +version = "3.1.1" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-trait = "0.1" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.6.3", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.7.0", features = ["dash-stream"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" @@ -34,7 +34,7 @@ serde_plain = "1.0" shlex = "1.2" sys-locale = "0.3" tempfile = "3.8" -tokio = { version = "1.33", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.34", features = ["macros", "rt-multi-thread", "time"] } rustls-native-certs = { version = "0.6", optional = true } [build-dependencies] diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 01612b9..1777072 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -223,7 +223,7 @@ impl Filter for ArchiveFilter { "Season {} is only available with {} audio until episode {} ({})", season.season_number, season_locale.unwrap_or(Locale::ja_JP), - last_episode.episode_number, + last_episode.sequence_number, last_episode.title ) } @@ -278,7 +278,7 @@ impl Filter for ArchiveFilter { if !missing_audio.is_empty() { warn!( "Episode {} is not available with {} audio", - episode.episode_number, + episode.sequence_number, missing_audio .into_iter() .map(|l| l.to_string()) @@ -298,7 +298,7 @@ impl Filter for ArchiveFilter { { warn!( "Episode {} is not available with {} subtitles", - episode.episode_number, + episode.sequence_number, missing_subtitles .into_iter() .map(|l| l.to_string()) @@ -343,7 +343,7 @@ impl Filter for ArchiveFilter { if relative_episode_number.is_none() || relative_sequence_number.is_none() { warn!( "Failed to get relative episode number for episode {} ({}) of {} season {}", - episode.episode_number, + episode.sequence_number, episode.title, episode.series_title, episode.season_number, diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index fb2e563..51f1ad8 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -158,7 +158,7 @@ impl Filter for DownloadFilter { { let error_message = format!( "Episode {} ({}) of {} season {} is not available with {} audio", - episode.episode_number, + episode.sequence_number, episode.title, episode.series_title, episode.season_number, @@ -234,7 +234,7 @@ impl Filter for DownloadFilter { if relative_episode_number.is_none() || relative_sequence_number.is_none() { warn!( "Failed to get relative episode number for episode {} ({}) of {} season {}", - episode.episode_number, + episode.sequence_number, episode.title, episode.series_title, episode.season_number, diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index f9746b1..156bd95 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -70,7 +70,7 @@ impl From<&Episode> for FormatEpisode { title: value.title.clone(), description: value.description.clone(), locale: value.audio_locale.clone(), - number: value.episode_number, + number: value.episode_number.unwrap_or_default(), sequence_number: value.sequence_number, duration: value.duration.num_milliseconds(), air_date: value.episode_air_date.timestamp(), From d5df3df95f5972495c8c317f0fb243c32ede0b37 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 1 Dec 2023 01:02:53 +0100 Subject: [PATCH 483/630] Fix fixed subtitle formatting and sorting (#272) --- crunchy-cli-core/src/utils/download.rs | 33 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 945cc14..c65b4d2 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -17,6 +17,7 @@ use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tempfile::TempPath; @@ -788,8 +789,10 @@ pub fn get_video_length(path: &Path) -> Result<NaiveTime> { /// [crunchy-labs/crunchy-cli#208](https://github.com/crunchy-labs/crunchy-cli/issues/208) for more /// information. fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { - let re = Regex::new(r"^Dialogue:\s\d+,(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),") - .unwrap(); + let re = Regex::new( + r"^Dialogue:\s(?P<layer>\d+),(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),", + ) + .unwrap(); // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 // digits so them have to be reduced manually to avoid the panic @@ -804,9 +807,9 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { formatted_time.split_at(2).0.parse().unwrap() } ) + .split_off(1) // <- in the ASS spec, the hour has only one digit } - let length_as_string = format_naive_time(max_length); let mut entries = (vec![], vec![]); let mut as_lines: Vec<String> = String::from_utf8_lossy(raw.as_slice()) @@ -818,21 +821,33 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { if line.trim() == "[Script Info]" { line.push_str("\nScaledBorderAndShadow: yes") } else if let Some(capture) = re.captures(line) { - let start = capture.name("start").map_or(NaiveTime::default(), |s| { + let mut start = capture.name("start").map_or(NaiveTime::default(), |s| { NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() }); - let end = capture.name("end").map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + let mut end = capture.name("end").map_or(NaiveTime::default(), |e| { + NaiveTime::parse_from_str(e.as_str(), "%H:%M:%S.%f").unwrap() }); - if end > max_length { + if start > max_length || end > max_length { + let layer = capture + .name("layer") + .map_or(0, |l| i32::from_str(l.as_str()).unwrap()); + + if start > max_length { + start = max_length; + } + if start > max_length || end > max_length { + end = max_length; + } + *line = re .replace( line, format!( - "Dialogue: {},{},", + "Dialogue: {},{},{},", + layer, format_naive_time(start), - &length_as_string + format_naive_time(end) ), ) .to_string() From 8f77028fcb933137d66c4f4fb2e0d3b7f4454843 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 1 Dec 2023 01:17:49 +0100 Subject: [PATCH 484/630] Show error message instead of panicking when capturing video length of invalid file (#258) --- crunchy-cli-core/src/utils/download.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index c65b4d2..de1aed0 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -768,7 +768,13 @@ pub fn get_video_length(path: &Path) -> Result<NaiveTime> { .args(["-i", path.to_str().unwrap()]) .output()?; let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; - let caps = video_length.captures(ffmpeg_output.as_str()).unwrap(); + let caps = video_length.captures(ffmpeg_output.as_str()).map_or( + Err(anyhow::anyhow!( + "failed to get video length: {}", + ffmpeg_output + )), + Ok, + )?; Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) } From 9ca3b79291da1d4d4f3f6abb4c0728f641f75ae7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 3 Dec 2023 00:15:57 +0100 Subject: [PATCH 485/630] Fix spelling --- crunchy-cli-core/src/utils/ffmpeg.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 7d77833..39678dc 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -229,7 +229,7 @@ impl FFmpegPreset { quality = Some(q) } else { return Err(format!( - "'{}' is not a valid ffmpeg preset (unknown token '{}'", + "'{}' is not a valid ffmpeg preset (unknown token '{}')", s, token )); } @@ -286,12 +286,10 @@ impl FFmpegPreset { output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } FFmpegHwAccel::Apple => { - // Apple's Video Toolbox encoders ignore `-crf`, - // use `-q:v` instead. It's on a scale of 1-100, - // 100 being lossless. Just did some math - // ((-a/51+1)*99+1 where `a` is the old crf value) - // so these settings very likely need some more - // tweeking. + // Apple's Video Toolbox encoders ignore `-crf`, use `-q:v` + // instead. It's on a scale of 1-100, 100 being lossless. Just + // did some math ((-a/51+1)*99+1 where `a` is the old crf value) + // so these settings very likely need some more tweaking match quality { FFmpegQuality::Lossless => output.extend(["-q:v", "65"]), FFmpegQuality::Normal => (), From 9487dd3dbffd4646713ce4e0ec1d6210fc1a1f14 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 8 Dec 2023 22:27:12 +0100 Subject: [PATCH 486/630] Show ffmpeg progress (#270) --- Cargo.lock | 1 + crunchy-cli-core/Cargo.toml | 5 +- crunchy-cli-core/src/utils/download.rs | 142 +++++++++++++++++++------ crunchy-cli-core/src/utils/os.rs | 60 +++++++++++ 4 files changed, 176 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 897731f..fa5723f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,7 @@ dependencies = [ "indicatif", "lazy_static", "log", + "nix", "num_cpus", "regex", "reqwest", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5771888..4f0912f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -34,8 +34,11 @@ serde_plain = "1.0" shlex = "1.2" sys-locale = "0.3" tempfile = "3.8" -tokio = { version = "1.34", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } rustls-native-certs = { version = "0.6", optional = true } +[target.'cfg(not(target_os = "windows"))'.dependencies] +nix = { version = "0.27", features = ["fs"] } + [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index de1aed0..c154103 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,16 +1,14 @@ use crate::utils::context::Context; use crate::utils::ffmpeg::FFmpegPreset; -use crate::utils::log::progress; -use crate::utils::os::{is_special_file, temp_directory, tempfile}; +use crate::utils::os::{is_special_file, temp_directory, temp_named_pipe, tempfile}; use anyhow::{bail, Result}; use chrono::NaiveTime; use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; use crunchyroll_rs::Locale; -use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; +use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; -use std::borrow::Borrow; -use std::borrow::BorrowMut; +use std::borrow::{Borrow, BorrowMut}; use std::cmp::Ordering; use std::collections::BTreeMap; use std::env; @@ -21,6 +19,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tempfile::TempPath; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::Mutex; use tokio::task::JoinSet; @@ -177,15 +176,19 @@ impl Downloader { let mut videos = vec![]; let mut audios = vec![]; let mut subtitles = vec![]; + let mut max_frames = 0f64; + let fmt_space = self + .formats + .iter() + .flat_map(|f| { + f.audios + .iter() + .map(|(_, locale)| format!("Downloading {} audio", locale).len()) + }) + .max() + .unwrap(); for (i, format) in self.formats.iter().enumerate() { - let fmt_space = format - .audios - .iter() - .map(|(_, locale)| format!("Downloading {} audio", locale).len()) - .max() - .unwrap(); - let video_path = self .download_video( ctx, @@ -232,7 +235,11 @@ impl Downloader { None }; - let len = get_video_length(&video_path)?; + let (len, fps) = get_video_stats(&video_path)?; + let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps; + if frames > max_frames { + max_frames = frames; + } for (subtitle, not_cc) in format.subtitles.iter() { if let Some(pb) = &progress_spinner { let mut progress_message = pb.message(); @@ -337,8 +344,14 @@ impl Downloader { } let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args(); + let fifo = temp_named_pipe()?; - let mut command_args = vec!["-y".to_string(), "-hide_banner".to_string()]; + let mut command_args = vec![ + "-y".to_string(), + "-hide_banner".to_string(), + "-vstats_file".to_string(), + fifo.name(), + ]; command_args.extend(input_presets); command_args.extend(input); command_args.extend(maps); @@ -433,8 +446,6 @@ impl Downloader { } } - let progress_handler = progress!("Generating output file"); - let ffmpeg = Command::new("ffmpeg") // pass ffmpeg stdout to real stdout only if output file is stdout .stdout(if dst.to_str().unwrap() == "-" { @@ -444,14 +455,26 @@ impl Downloader { }) .stderr(Stdio::piped()) .args(command_args) - .output()?; - if !ffmpeg.status.success() { - bail!("{}", String::from_utf8_lossy(ffmpeg.stderr.as_slice())) + .spawn()?; + let ffmpeg_progress = tokio::spawn(async move { + ffmpeg_progress( + max_frames as u64, + fifo, + format!("{:<1$}", "Generating output file", fmt_space + 1), + ) + .await + }); + + let result = ffmpeg.wait_with_output()?; + if !result.status.success() { + bail!("{}", String::from_utf8_lossy(result.stderr.as_slice())) + } + ffmpeg_progress.abort(); + match ffmpeg_progress.await { + Ok(r) => Ok(r?), + Err(e) if e.is_cancelled() => Ok(()), + Err(e) => Err(anyhow::Error::from(e)), } - - progress_handler.stop("Output file generated"); - - Ok(()) } async fn check_free_space( @@ -752,13 +775,10 @@ fn estimate_variant_file_size(variant_data: &VariantData, segments: &[VariantSeg (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() } -/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry -/// long after the actual video ends with artificially extends the video length on some video players. -/// To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more -/// information. -pub fn get_video_length(path: &Path) -> Result<NaiveTime> { +/// Get the length and fps of a video. +pub fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; + let video_fps = Regex::new(r"(?P<fps>[\d/.]+)\sfps")?; let ffmpeg = Command::new("ffmpeg") .stdout(Stdio::null()) @@ -768,15 +788,26 @@ pub fn get_video_length(path: &Path) -> Result<NaiveTime> { .args(["-i", path.to_str().unwrap()]) .output()?; let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; - let caps = video_length.captures(ffmpeg_output.as_str()).map_or( + let length_caps = video_length.captures(ffmpeg_output.as_str()).map_or( Err(anyhow::anyhow!( "failed to get video length: {}", ffmpeg_output )), Ok, )?; + let fps_caps = video_fps.captures(ffmpeg_output.as_str()).map_or( + Err(anyhow::anyhow!( + "failed to get video fps: {}", + ffmpeg_output + )), + Ok, + )?; - Ok(NaiveTime::parse_from_str(caps.name("time").unwrap().as_str(), "%H:%M:%S%.f").unwrap()) + Ok(( + NaiveTime::parse_from_str(length_caps.name("time").unwrap().as_str(), "%H:%M:%S%.f") + .unwrap(), + fps_caps.name("fps").unwrap().as_str().parse().unwrap(), + )) } /// Fix the subtitles in multiple ways as Crunchyroll sometimes delivers them malformed. @@ -875,3 +906,52 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { *raw = as_lines.join("\n").into_bytes() } + +async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( + total_frames: u64, + stats: R, + message: String, +) -> Result<()> { + let current_frame = Regex::new(r"frame=\s+(?P<frame>\d+)")?; + + let progress = if log::max_level() == LevelFilter::Info { + let progress = ProgressBar::new(total_frames) + .with_style( + ProgressStyle::with_template(":: {msg} [{wide_bar}] {percent:>3}%") + .unwrap() + .progress_chars("##-"), + ) + .with_message(message) + .with_finish(ProgressFinish::Abandon); + progress.set_draw_target(ProgressDrawTarget::stdout()); + progress.enable_steady_tick(Duration::from_millis(200)); + Some(progress) + } else { + None + }; + + let reader = BufReader::new(stats); + let mut lines = reader.lines(); + while let Some(line) = lines.next_line().await? { + let frame: u64 = current_frame + .captures(line.as_str()) + .unwrap() + .name("frame") + .unwrap() + .as_str() + .parse()?; + + if let Some(p) = &progress { + p.set_position(frame) + } + + debug!( + "Processed frame [{}/{} {:.2}%]", + frame, + total_frames, + (frame as f64 / total_frames as f64) * 100f64 + ) + } + + Ok(()) +} diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 977e968..7055265 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -3,9 +3,12 @@ use regex::{Regex, RegexBuilder}; use std::borrow::Cow; use std::io::ErrorKind; use std::path::{Path, PathBuf}; +use std::pin::Pin; use std::process::{Command, Stdio}; +use std::task::{Context, Poll}; use std::{env, io}; use tempfile::{Builder, NamedTempFile}; +use tokio::io::{AsyncRead, ReadBuf}; pub fn has_ffmpeg() -> bool { if let Err(e) = Command::new("ffmpeg").stderr(Stdio::null()).spawn() { @@ -43,6 +46,63 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { Ok(tempfile) } +pub struct TempNamedPipe { + name: String, + + #[cfg(not(target_os = "windows"))] + reader: tokio::net::unix::pipe::Receiver, + #[cfg(target_os = "windows")] + reader: tokio::net::windows::named_pipe::NamedPipeServer, +} + +impl TempNamedPipe { + pub fn name(&self) -> String { + self.name.clone() + } +} + +impl AsyncRead for TempNamedPipe { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll<io::Result<()>> { + Pin::new(&mut self.reader).poll_read(cx, buf) + } +} + +impl Drop for TempNamedPipe { + fn drop(&mut self) { + #[cfg(not(target_os = "windows"))] + let _ = nix::unistd::unlink(self.name.as_str()); + } +} + +pub fn temp_named_pipe() -> io::Result<TempNamedPipe> { + let (_, path) = tempfile("")?.keep()?; + let path = path.to_string_lossy().to_string(); + let _ = std::fs::remove_file(path.clone()); + + #[cfg(not(target_os = "windows"))] + { + nix::unistd::mkfifo(path.as_str(), nix::sys::stat::Mode::S_IRWXU)?; + + Ok(TempNamedPipe { + reader: tokio::net::unix::pipe::OpenOptions::new().open_receiver(&path)?, + name: path, + }) + } + #[cfg(target_os = "windows")] + { + let path = format!(r"\\.\pipe\{}", &path); + + Ok(TempNamedPipe { + reader: tokio::net::windows::named_pipe::ServerOptions::new().create(&path)?, + name: path, + }) + } +} + /// Check if the given path exists and rename it until the new (renamed) file does not exist. pub fn free_file(mut path: PathBuf) -> (PathBuf, bool) { // do not rename it if it exists but is a special file From 6c7ab04b99a05d50dd2e865785ea05d760b8e25d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 8 Dec 2023 23:04:04 +0100 Subject: [PATCH 487/630] Lint --- crunchy-cli-core/src/lib.rs | 2 +- crunchy-cli-core/src/utils/format.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index d6c2220..779e31d 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -162,7 +162,7 @@ pub async fn cli_entrypoint() { ctrlc::set_handler(move || { debug!("Ctrl-c detected"); - if let Ok(dir) = fs::read_dir(&env::temp_dir()) { + if let Ok(dir) = fs::read_dir(env::temp_dir()) { for file in dir.flatten() { if file .path() diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 05ac232..08b1b23 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -199,7 +199,7 @@ impl Eq for SingleFormatCollectionEpisodeKey {} struct SingleFormatCollectionSeasonKey((u32, String)); -#[allow(clippy::incorrect_partial_ord_impl_on_ord_type)] +#[allow(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for SingleFormatCollectionSeasonKey { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { let mut cmp = self.0 .0.partial_cmp(&other.0 .0); From b4057599a1f6579f758946520655aca29d4c885a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 9 Dec 2023 01:33:54 +0100 Subject: [PATCH 488/630] Add --ffmpeg-threads flag to control the ffmpeg thread number --- crunchy-cli-core/src/archive/command.rs | 11 +++++++++++ crunchy-cli-core/src/download/command.rs | 13 ++++++++++++- crunchy-cli-core/src/utils/download.rs | 18 +++++++++++++++--- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 006bbfa..1c0723a 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -90,6 +90,16 @@ pub struct Archive { #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] pub(crate) ffmpeg_preset: Option<FFmpegPreset>, + #[arg( + help = "The number of threads used by ffmpeg to generate the output file. Does not work with every codec/preset" + )] + #[arg( + long_help = "The number of threads used by ffmpeg to generate the output file. \ + Does not work with every codec/preset and is skipped entirely when specifying custom ffmpeg output arguments instead of a preset for `--ffmpeg-preset`. \ + By default, ffmpeg chooses the thread count which works best for the output codec" + )] + #[arg(long)] + pub(crate) ffmpeg_threads: Option<usize>, #[arg( help = "Set which subtitle language should be set as default / auto shown when starting a video" @@ -182,6 +192,7 @@ impl Execute for Archive { let download_builder = DownloadBuilder::new() .default_subtitle(self.default_subtitle.clone()) .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) + .ffmpeg_threads(self.ffmpeg_threads) .output_format(Some("matroska".to_string())) .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index cf1a049..a45354b 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -57,7 +57,7 @@ pub struct Download { pub(crate) output_specials: Option<String>, #[arg(help = "Video resolution")] - #[arg(long_help = "The video resolution.\ + #[arg(long_help = "The video resolution. \ Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ Specifying the exact pixels is not recommended, use one of the other options instead. \ Crunchyroll let you choose the quality with pixel abbreviation on their clients, so you might be already familiar with the available options. \ @@ -74,6 +74,16 @@ pub struct Download { #[arg(long)] #[arg(value_parser = FFmpegPreset::parse)] pub(crate) ffmpeg_preset: Option<FFmpegPreset>, + #[arg( + help = "The number of threads used by ffmpeg to generate the output file. Does not work with every codec/preset" + )] + #[arg( + long_help = "The number of threads used by ffmpeg to generate the output file. \ + Does not work with every codec/preset and is skipped entirely when specifying custom ffmpeg output arguments instead of a preset for `--ffmpeg-preset`. \ + By default, ffmpeg chooses the thread count which works best for the output codec" + )] + #[arg(long)] + pub(crate) ffmpeg_threads: Option<usize>, #[arg(help = "Skip files which are already existing")] #[arg(long, default_value_t = false)] @@ -203,6 +213,7 @@ impl Execute for Download { None }) .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) + .ffmpeg_threads(self.ffmpeg_threads) .threads(self.threads); for mut single_formats in single_format_collection.into_iter() { diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index c154103..c715362 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -51,6 +51,7 @@ pub struct DownloadBuilder { subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, threads: usize, + ffmpeg_threads: Option<usize>, } impl DownloadBuilder { @@ -63,6 +64,7 @@ impl DownloadBuilder { subtitle_sort: None, force_hardsub: false, threads: num_cpus::get(), + ffmpeg_threads: None, } } @@ -75,7 +77,9 @@ impl DownloadBuilder { subtitle_sort: self.subtitle_sort, force_hardsub: self.force_hardsub, - threads: self.threads, + + download_threads: self.threads, + ffmpeg_threads: self.ffmpeg_threads, formats: vec![], } @@ -102,7 +106,9 @@ pub struct Downloader { subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, - threads: usize, + + download_threads: usize, + ffmpeg_threads: Option<usize>, formats: Vec<DownloadFormat>, } @@ -343,6 +349,7 @@ impl Downloader { } } + let preset_custom = matches!(self.ffmpeg_preset, FFmpegPreset::Custom(_)); let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args(); let fifo = temp_named_pipe()?; @@ -356,6 +363,11 @@ impl Downloader { command_args.extend(input); command_args.extend(maps); command_args.extend(metadata); + if !preset_custom { + if let Some(ffmpeg_threads) = self.ffmpeg_threads { + command_args.extend(vec!["-threads".to_string(), ffmpeg_threads.to_string()]) + } + } // set default subtitle if let Some(default_subtitle) = self.default_subtitle { @@ -618,7 +630,7 @@ impl Downloader { None }; - let cpus = self.threads; + let cpus = self.download_threads; let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); for _ in 0..cpus { segs.push(vec![]) From 77609be598b639949e17d893ba135289394e692c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 9 Dec 2023 01:41:33 +0100 Subject: [PATCH 489/630] Replace all login username references with email --- README.md | 8 ++++---- crunchy-cli-core/src/lib.rs | 6 +++--- crunchy-cli-core/src/login/command.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2491939..77877b6 100644 --- a/README.md +++ b/README.md @@ -110,12 +110,12 @@ $ cargo install --force --path . crunchy-cli requires you to log in. Though you can use a non-premium account, you will not have access to premium content without a subscription. -You can authenticate with your credentials (user:password) or by using a refresh token. +You can authenticate with your credentials (email:password) or by using a refresh token. - Credentials ```shell - $ crunchy-cli --credentials "user:password" <command> + $ crunchy-cli --credentials "email:password" <command> ``` - Refresh Token @@ -208,8 +208,8 @@ The `login` command can store your session, so you don't have to authenticate ev ```shell # save the refresh token which gets generated when login with credentials. -# your username/email and password won't be stored at any time on disk -$ crunchy-cli login --credentials "user:password" +# your email and password won't be stored at any time on disk +$ crunchy-cli login --credentials "email:password" # save etp-rt cookie $ crunchy-cli login --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" ``` diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 779e31d..8150b50 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -342,10 +342,10 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { }; let crunchy = if let Some(credentials) = &login_method.credentials { - if let Some((user, password)) = credentials.split_once(':') { - builder.login_with_credentials(user, password).await? + if let Some((email, password)) = credentials.split_once(':') { + builder.login_with_credentials(email, password).await? } else { - bail!("Invalid credentials format. Please provide your credentials as user:password") + bail!("Invalid credentials format. Please provide your credentials as email:password") } } else if let Some(etp_rt) = &login_method.etp_rt { builder.login_with_etp_rt(etp_rt).await? diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index f5099d7..3722438 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -46,7 +46,7 @@ impl Execute for Login { #[derive(Clone, Debug, Parser)] pub struct LoginMethod { #[arg( - help = "Login with credentials (username or email and password). Must be provided as user:password" + help = "Login with credentials (email and password). Must be provided as email:password" )] #[arg(long)] pub credentials: Option<String>, From f9e431e181d72a52b73e0ede41c382cbd83fe341 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 9 Dec 2023 17:22:04 +0100 Subject: [PATCH 490/630] Add ability to use root flags after subcommands --- crunchy-cli-core/src/lib.rs | 13 ++++++------- crunchy-cli-core/src/login/command.rs | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 8150b50..366f153 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -41,7 +41,7 @@ pub struct Cli { #[arg( help = "Overwrite the language in which results are returned. Default is your system language" )] - #[arg(long)] + #[arg(global = true, long)] lang: Option<Locale>, #[arg(help = "Enable experimental fixes which may resolve some unexpected errors")] @@ -50,7 +50,7 @@ pub struct Cli { If everything works as intended this option isn't needed, but sometimes Crunchyroll mislabels \ the audio of a series/season or episode or returns a wrong season number. This is when using this option might help to solve the issue" )] - #[arg(long, default_value_t = false)] + #[arg(global = true, long, default_value_t = false)] experimental_fixes: bool, #[clap(flatten)] @@ -59,12 +59,11 @@ pub struct Cli { #[arg(help = "Use a proxy to route all traffic through")] #[arg(long_help = "Use a proxy to route all traffic through. \ Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself")] - #[clap(long)] - #[arg(value_parser = crate::utils::clap::clap_parse_proxy)] + #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_proxy)] proxy: Option<Proxy>, #[arg(help = "Use custom user agent")] - #[clap(long)] + #[arg(global = true, long)] user_agent: Option<String>, #[clap(subcommand)] @@ -94,14 +93,14 @@ enum Command { #[derive(Debug, Parser)] struct Verbosity { #[arg(help = "Verbose output")] - #[arg(short, long)] + #[arg(global = true, short, long)] verbose: bool, #[arg(help = "Quiet output. Does not print anything unless it's a error")] #[arg( long_help = "Quiet output. Does not print anything unless it's a error. Can be helpful if you pipe the output to stdout" )] - #[arg(short, long)] + #[arg(global = true, short, long)] quiet: bool, } diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index 3722438..0b4bb4b 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -48,13 +48,13 @@ pub struct LoginMethod { #[arg( help = "Login with credentials (email and password). Must be provided as email:password" )] - #[arg(long)] + #[arg(global = true, long)] pub credentials: Option<String>, #[arg(help = "Login with the etp-rt cookie")] #[arg( long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there" )] - #[arg(long)] + #[arg(global = true, long)] pub etp_rt: Option<String>, #[arg(help = "Login anonymously / without an account")] #[arg(long, default_value_t = false)] From be3248a4f9a1d8480558eaf78c8365f7695d5c1b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Dec 2023 02:52:42 +0100 Subject: [PATCH 491/630] Add download/request speed limiter (#250) --- Cargo.lock | 90 +++++++++++++++++++++--- crunchy-cli-core/Cargo.toml | 8 ++- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/download/command.rs | 2 +- crunchy-cli-core/src/lib.rs | 67 +++++++++++------- crunchy-cli-core/src/utils/clap.rs | 17 +++++ crunchy-cli-core/src/utils/download.rs | 47 +++---------- crunchy-cli-core/src/utils/mod.rs | 1 + crunchy-cli-core/src/utils/rate_limit.rs | 72 +++++++++++++++++++ 9 files changed, 230 insertions(+), 76 deletions(-) create mode 100644 crunchy-cli-core/src/utils/rate_limit.rs diff --git a/Cargo.lock b/Cargo.lock index fa5723f..037a4ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,18 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "async-speed-limit" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d287ccbfb44ae20287d2f9c72ad9e560d50810883870697db5b320c541f183" +dependencies = [ + "futures-core", + "futures-io", + "futures-timer", + "pin-project-lite", +] + [[package]] name = "async-trait" version = "0.1.74" @@ -385,6 +397,7 @@ name = "crunchy-cli-core" version = "3.1.1" dependencies = [ "anyhow", + "async-speed-limit", "async-trait", "chrono", "clap", @@ -394,6 +407,8 @@ dependencies = [ "dialoguer", "dirs", "fs2", + "futures-util", + "http", "indicatif", "lazy_static", "log", @@ -409,13 +424,14 @@ dependencies = [ "sys-locale", "tempfile", "tokio", + "tower-service", ] [[package]] name = "crunchyroll-rs" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4ce434784eee7892ad8c3d1ecaea0c0858db51bbb295b474db38c256e8d2fb" +checksum = "56d0d1d6a75ed27b2dc93b84fa0667cffb92a513d206b8ccd1b895fab5ad2e9c" dependencies = [ "aes", "async-trait", @@ -434,14 +450,15 @@ dependencies = [ "serde_urlencoded", "smart-default", "tokio", - "webpki-roots", + "tower-service", + "webpki-roots 0.26.0", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be840f8cf2ce6afc9a9eae268d41423093141ec88f664a515d5ed2a85a66fb60" +checksum = "bdd0a20e750354408294b674a50b6d5dacec315ff9ead7c3a7c093f1e3594335" dependencies = [ "darling", "quote", @@ -687,6 +704,23 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.29" @@ -699,6 +733,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.29" @@ -706,7 +746,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1409,12 +1453,14 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tokio-socks", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.25.2", "winreg", ] @@ -1490,6 +1536,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pki-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7673e0aa20ee4937c6aacfc12bb8341cfbf054cdd21df6bec5fd0629fe9339b" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1813,9 +1865,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", @@ -2065,6 +2117,19 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.65" @@ -2081,6 +2146,15 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "webpki-roots" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2cfda980f21be5a7ed2eadb3e6fe074d56022bea2cdeb1a62eb220fc04188" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 4f0912f..173c3ac 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -13,21 +13,24 @@ openssl-tls-static = ["reqwest/native-tls", "reqwest/native-tls-alpn", "reqwest/ [dependencies] anyhow = "1.0" +async-speed-limit = "0.4" async-trait = "0.1" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.7.0", features = ["dash-stream"] } +crunchyroll-rs = { version = "0.8.0", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" derive_setters = "0.1" +futures-util = { version = "0.3", features = ["io"] } fs2 = "0.4" +http = "0.2" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" -reqwest = { version = "0.11", default-features = false, features = ["socks"] } +reqwest = { version = "0.11", default-features = false, features = ["socks", "stream"] } serde = "1.0" serde_json = "1.0" serde_plain = "1.0" @@ -35,6 +38,7 @@ shlex = "1.2" sys-locale = "0.3" tempfile = "3.8" tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } +tower-service = "0.3" rustls-native-certs = { version = "0.6", optional = true } [target.'cfg(not(target_os = "windows"))'.dependencies] diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 1c0723a..ea81d15 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -242,7 +242,7 @@ impl Execute for Archive { format.visual_output(&path); - downloader.download(&ctx, &path).await? + downloader.download(&path).await? } } diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index a45354b..ade6adf 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -257,7 +257,7 @@ impl Execute for Download { format.visual_output(&path); - downloader.download(&ctx, &path).await? + downloader.download(&path).await? } } diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 366f153..3299b31 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -17,6 +17,7 @@ mod login; mod search; mod utils; +use crate::utils::rate_limit::RateLimiterService; pub use archive::Archive; use dialoguer::console::Term; pub use download::Download; @@ -66,6 +67,15 @@ pub struct Cli { #[arg(global = true, long)] user_agent: Option<String>, + #[arg( + help = "Maximal speed to download/request (may be a bit off here and there). Must be in format of <number>[B|KB|MB]" + )] + #[arg( + long_help = "Maximal speed to download/request (may be a bit off here and there). Must be in format of <number>[B|KB|MB] (e.g. 500KB or 10MB)" + )] + #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_speed_limit)] + speed_limit: Option<u32>, + #[clap(subcommand)] command: Command, } @@ -264,39 +274,44 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { lang }; + let client = { + let mut builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(p) = &cli.proxy { + builder = builder.proxy(p.clone()) + } + if let Some(ua) = &cli.user_agent { + builder = builder.user_agent(ua) + } + + #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] + let client = { + let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); + + for certificate in rustls_native_certs::load_native_certs().unwrap() { + builder = builder.add_root_certificate( + reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), + ) + } + + builder.build().unwrap() + }; + #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] + let client = builder.build().unwrap(); + + client + }; + let mut builder = Crunchyroll::builder() .locale(locale) - .client({ - let mut builder = CrunchyrollBuilder::predefined_client_builder(); - if let Some(p) = &cli.proxy { - builder = builder.proxy(p.clone()) - } - if let Some(ua) = &cli.user_agent { - builder = builder.user_agent(ua) - } - - #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] - let client = { - let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); - - for certificate in rustls_native_certs::load_native_certs().unwrap() { - builder = builder.add_root_certificate( - reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), - ) - } - - builder.build().unwrap() - }; - #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] - let client = builder.build().unwrap(); - - client - }) + .client(client.clone()) .stabilization_locales(cli.experimental_fixes) .stabilization_season_number(cli.experimental_fixes); if let Command::Download(download) = &cli.command { builder = builder.preferred_audio_locale(download.audio.clone()) } + if let Some(speed_limit) = cli.speed_limit { + builder = builder.middleware(RateLimiterService::new(speed_limit, client)); + } let root_login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 diff --git a/crunchy-cli-core/src/utils/clap.rs b/crunchy-cli-core/src/utils/clap.rs index c3088d8..37a34d3 100644 --- a/crunchy-cli-core/src/utils/clap.rs +++ b/crunchy-cli-core/src/utils/clap.rs @@ -9,3 +9,20 @@ pub fn clap_parse_resolution(s: &str) -> Result<Resolution, String> { pub fn clap_parse_proxy(s: &str) -> Result<Proxy, String> { Proxy::all(s).map_err(|e| e.to_string()) } + +pub fn clap_parse_speed_limit(s: &str) -> Result<u32, String> { + let quota = s.to_lowercase(); + + let bytes = if let Ok(b) = quota.parse() { + b + } else if let Ok(b) = quota.trim_end_matches('b').parse::<u32>() { + b + } else if let Ok(kb) = quota.trim_end_matches("kb").parse::<u32>() { + kb * 1024 + } else if let Ok(mb) = quota.trim_end_matches("mb").parse::<u32>() { + mb * 1024 * 1024 + } else { + return Err("Invalid speed limit".to_string()); + }; + Ok(bytes) +} diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index c715362..bdd76f2 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,4 +1,3 @@ -use crate::utils::context::Context; use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::os::{is_special_file, temp_directory, temp_named_pipe, tempfile}; use anyhow::{bail, Result}; @@ -8,7 +7,7 @@ use crunchyroll_rs::Locale; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; -use std::borrow::{Borrow, BorrowMut}; +use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::BTreeMap; use std::env; @@ -118,7 +117,7 @@ impl Downloader { self.formats.push(format); } - pub async fn download(mut self, ctx: &Context, dst: &Path) -> Result<()> { + pub async fn download(mut self, dst: &Path) -> Result<()> { // `.unwrap_or_default()` here unless https://doc.rust-lang.org/stable/std/path/fn.absolute.html // gets stabilized as the function might throw error on weird file paths let required = self.check_free_space(dst).await.unwrap_or_default(); @@ -197,7 +196,6 @@ impl Downloader { for (i, format) in self.formats.iter().enumerate() { let video_path = self .download_video( - ctx, &format.video.0, format!("{:<1$}", format!("Downloading video #{}", i + 1), fmt_space), ) @@ -205,7 +203,6 @@ impl Downloader { for (variant_data, locale) in format.audios.iter() { let audio_path = self .download_audio( - ctx, variant_data, format!("{:<1$}", format!("Downloading {} audio", locale), fmt_space), ) @@ -554,14 +551,13 @@ impl Downloader { async fn download_video( &self, - ctx: &Context, variant_data: &VariantData, message: String, ) -> Result<TempPath> { let tempfile = tempfile(".mp4")?; let (mut file, path) = tempfile.into_parts(); - self.download_segments(ctx, &mut file, message, variant_data) + self.download_segments(&mut file, message, variant_data) .await?; Ok(path) @@ -569,14 +565,13 @@ impl Downloader { async fn download_audio( &self, - ctx: &Context, variant_data: &VariantData, message: String, ) -> Result<TempPath> { let tempfile = tempfile(".m4a")?; let (mut file, path) = tempfile.into_parts(); - self.download_segments(ctx, &mut file, message, variant_data) + self.download_segments(&mut file, message, variant_data) .await?; Ok(path) @@ -601,7 +596,6 @@ impl Downloader { async fn download_segments( &self, - ctx: &Context, writer: &mut impl Write, message: String, variant_data: &VariantData, @@ -609,7 +603,6 @@ impl Downloader { let segments = variant_data.segments().await?; let total_segments = segments.len(); - let client = Arc::new(ctx.crunchy.client()); let count = Arc::new(Mutex::new(0)); let progress = if log::max_level() == LevelFilter::Info { @@ -643,7 +636,6 @@ impl Downloader { let mut join_set: JoinSet<Result<()>> = JoinSet::new(); for num in 0..cpus { - let thread_client = client.clone(); let thread_sender = sender.clone(); let thread_segments = segs.remove(0); let thread_count = count.clone(); @@ -656,42 +648,21 @@ impl Downloader { let download = || async move { for (i, segment) in thread_segments.into_iter().enumerate() { let mut retry_count = 0; - let mut buf = loop { - let request = thread_client - .get(&segment.url) - .timeout(Duration::from_secs(60)) - .send(); - - let response = match request.await { - Ok(r) => r, + let buf = loop { + let mut buf = vec![]; + match segment.write_to(&mut buf).await { + Ok(_) => break buf, Err(e) => { if retry_count == 5 { bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count); - continue - } - }; - - match response.bytes().await { - Ok(b) => break b.to_vec(), - Err(e) => { - if e.is_body() { - if retry_count == 5 { - bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) - } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) - } else { - bail!("{}", e) - } + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) } } retry_count += 1; }; - buf = VariantSegment::decrypt(buf.borrow_mut(), segment.key)?.to_vec(); - let mut c = thread_count.lock().await; debug!( "Downloaded and decrypted segment [{}/{} {:.2}%] {}", diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index d46cc33..e5c4894 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -9,4 +9,5 @@ pub mod locale; pub mod log; pub mod os; pub mod parse; +pub mod rate_limit; pub mod video; diff --git a/crunchy-cli-core/src/utils/rate_limit.rs b/crunchy-cli-core/src/utils/rate_limit.rs new file mode 100644 index 0000000..16b22b3 --- /dev/null +++ b/crunchy-cli-core/src/utils/rate_limit.rs @@ -0,0 +1,72 @@ +use async_speed_limit::Limiter; +use crunchyroll_rs::error::Error; +use futures_util::TryStreamExt; +use reqwest::{Client, Request, Response, ResponseBuilderExt}; +use std::future::Future; +use std::io; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tower_service::Service; + +pub struct RateLimiterService { + client: Arc<Client>, + rate_limiter: Limiter, +} + +impl RateLimiterService { + pub fn new(bytes: u32, client: Client) -> Self { + Self { + client: Arc::new(client), + rate_limiter: Limiter::new(bytes as f64), + } + } +} + +impl Service<Request> for RateLimiterService { + type Response = Response; + type Error = Error; + type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Request) -> Self::Future { + let client = self.client.clone(); + let rate_limiter = self.rate_limiter.clone(); + + Box::pin(async move { + let mut body = vec![]; + let res = client.execute(req).await?; + let _url = res.url().clone().to_string(); + let url = _url.as_str(); + + let mut http_res = http::Response::builder() + .url(res.url().clone()) + .status(res.status()) + .version(res.version()); + *http_res.headers_mut().unwrap() = res.headers().clone(); + http_res + .extensions_ref() + .unwrap() + .clone_from(&res.extensions()); + + let limiter = rate_limiter.limit( + res.bytes_stream() + .map_err(io::Error::other) + .into_async_read(), + ); + + futures_util::io::copy(limiter, &mut body) + .await + .map_err(|e| Error::Request { + url: url.to_string(), + status: None, + message: e.to_string(), + })?; + + Ok(Response::from(http_res.body(body).unwrap())) + }) + } +} From b97c2a922ebb3e36e5de137fdf04c35e7ab2dfa3 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Dec 2023 03:36:30 +0100 Subject: [PATCH 492/630] Fix windows ci --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7493041..bb7d196 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,7 @@ jobs: - name: Install system dependencies uses: msys2/setup-msys2@v2 with: + update: true install: mingw-w64-x86_64-rust base-devel - name: Build From 8613ea80cc9bde3dccfcc1429823aea3cdcec484 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Dec 2023 13:52:33 +0100 Subject: [PATCH 493/630] Add forced flag to all CC subtitles (#274) --- crunchy-cli-core/src/utils/download.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index bdd76f2..5e9bbce 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -434,12 +434,23 @@ impl Downloader { { command_args.extend([ format!("-disposition:s:s:{}", position), - "forced".to_string(), + "default".to_string(), ]) } } } + // set the 'forced' flag to CC subtitles + for (i, subtitle) in subtitles.iter().enumerate() { + // well, checking if the title contains '(CC)' might not be the best solutions from a + // performance perspective but easier than adjusting the `FFmpegMeta` struct + if !subtitle.title.contains("(CC)") { + continue; + } + + command_args.extend([format!("-disposition:s:s:{}", i), "forced".to_string()]) + } + command_args.extend(output_presets); if let Some(output_format) = self.output_format { command_args.extend(["-f".to_string(), output_format]); From 0a26083232e1e835f333d0b4039653f27560adfc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Dec 2023 14:27:05 +0100 Subject: [PATCH 494/630] Fix ffmpeg progress not working with fast encoder --- Cargo.lock | 1 + crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/utils/download.rs | 70 +++++++++++++++++--------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 037a4ff..919ba0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,6 +424,7 @@ dependencies = [ "sys-locale", "tempfile", "tokio", + "tokio-util", "tower-service", ] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 173c3ac..245c6ea 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -38,6 +38,7 @@ shlex = "1.2" sys-locale = "0.3" tempfile = "3.8" tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } +tokio-util = "0.7" tower-service = "0.3" rustls-native-certs = { version = "0.6", optional = true } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 5e9bbce..706b539 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -19,9 +19,11 @@ use std::sync::Arc; use std::time::Duration; use tempfile::TempPath; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; +use tokio::select; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::Mutex; use tokio::task::JoinSet; +use tokio_util::sync::CancellationToken; #[derive(Clone, Debug)] pub enum MergeBehavior { @@ -476,25 +478,25 @@ impl Downloader { .stderr(Stdio::piped()) .args(command_args) .spawn()?; + let ffmpeg_progress_cancel = CancellationToken::new(); + let ffmpeg_progress_cancellation_token = ffmpeg_progress_cancel.clone(); let ffmpeg_progress = tokio::spawn(async move { ffmpeg_progress( max_frames as u64, fifo, format!("{:<1$}", "Generating output file", fmt_space + 1), + ffmpeg_progress_cancellation_token, ) .await }); let result = ffmpeg.wait_with_output()?; if !result.status.success() { + ffmpeg_progress.abort(); bail!("{}", String::from_utf8_lossy(result.stderr.as_slice())) } - ffmpeg_progress.abort(); - match ffmpeg_progress.await { - Ok(r) => Ok(r?), - Err(e) if e.is_cancelled() => Ok(()), - Err(e) => Err(anyhow::Error::from(e)), - } + ffmpeg_progress_cancel.cancel(); + Ok(ffmpeg_progress.await??) } async fn check_free_space( @@ -905,6 +907,7 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( total_frames: u64, stats: R, message: String, + cancellation_token: CancellationToken, ) -> Result<()> { let current_frame = Regex::new(r"frame=\s+(?P<frame>\d+)")?; @@ -926,25 +929,46 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( let reader = BufReader::new(stats); let mut lines = reader.lines(); - while let Some(line) = lines.next_line().await? { - let frame: u64 = current_frame - .captures(line.as_str()) - .unwrap() - .name("frame") - .unwrap() - .as_str() - .parse()?; + loop { + select! { + // when gracefully canceling this future, set the progress to 100% (finished). sometimes + // ffmpeg is too fast or already finished when the reading process of 'stats' starts + // which causes the progress to be stuck at 0% + _ = cancellation_token.cancelled() => { + if let Some(p) = &progress { + p.set_position(total_frames) + } + debug!( + "Processed frame [{}/{} 100%]", + total_frames, + total_frames + ); + return Ok(()) + } + line = lines.next_line() => { + let Some(line) = line? else { + break + }; + let frame: u64 = current_frame + .captures(line.as_str()) + .unwrap() + .name("frame") + .unwrap() + .as_str() + .parse()?; - if let Some(p) = &progress { - p.set_position(frame) + if let Some(p) = &progress { + p.set_position(frame) + } + + debug!( + "Processed frame [{}/{} {:.2}%]", + frame, + total_frames, + (frame as f64 / total_frames as f64) * 100f64 + ) + } } - - debug!( - "Processed frame [{}/{} {:.2}%]", - frame, - total_frames, - (frame as f64 / total_frames as f64) * 100f64 - ) } Ok(()) From 0da81a481484112a588b33dc20a2e20355462f16 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 19 Dec 2023 22:37:16 +0100 Subject: [PATCH 495/630] Add `--include-fonts` flag for `archive` (#277) --- crunchy-cli-core/src/archive/command.rs | 6 +- crunchy-cli-core/src/download/command.rs | 2 +- crunchy-cli-core/src/utils/download.rs | 219 ++++++++++++++++++++++- crunchy-cli-core/src/utils/os.rs | 8 +- 4 files changed, 228 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index ea81d15..9ccc0ff 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -106,6 +106,9 @@ pub struct Archive { )] #[arg(long)] pub(crate) default_subtitle: Option<Locale>, + #[arg(help = "Include fonts in the downloaded file")] + #[arg(long)] + pub(crate) include_fonts: bool, #[arg(help = "Skip files which are already existing")] #[arg(long, default_value_t = false)] @@ -189,8 +192,9 @@ impl Execute for Archive { single_format_collection.full_visual_output(); - let download_builder = DownloadBuilder::new() + let download_builder = DownloadBuilder::new(ctx.crunchy.client()) .default_subtitle(self.default_subtitle.clone()) + .download_fonts(self.include_fonts) .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) .ffmpeg_threads(self.ffmpeg_threads) .output_format(Some("matroska".to_string())) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index ade6adf..faa265c 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -204,7 +204,7 @@ impl Execute for Download { single_format_collection.full_visual_output(); - let download_builder = DownloadBuilder::new() + let download_builder = DownloadBuilder::new(ctx.crunchy.client()) .default_subtitle(self.subtitle.clone()) .force_hardsub(self.force_hardsub) .output_format(if is_special_file(&self.output) || self.output == "-" { diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 706b539..64a6d1a 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,5 +1,6 @@ use crate::utils::ffmpeg::FFmpegPreset; -use crate::utils::os::{is_special_file, temp_directory, temp_named_pipe, tempfile}; +use crate::utils::filter::real_dedup_vec; +use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pipe, tempfile}; use anyhow::{bail, Result}; use chrono::NaiveTime; use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; @@ -7,16 +8,17 @@ use crunchyroll_rs::Locale; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; +use reqwest::Client; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::BTreeMap; -use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; +use std::{env, fs}; use tempfile::TempPath; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use tokio::select; @@ -45,25 +47,29 @@ impl MergeBehavior { #[derive(Clone, derive_setters::Setters)] pub struct DownloadBuilder { + client: Client, ffmpeg_preset: FFmpegPreset, default_subtitle: Option<Locale>, output_format: Option<String>, audio_sort: Option<Vec<Locale>>, subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, + download_fonts: bool, threads: usize, ffmpeg_threads: Option<usize>, } impl DownloadBuilder { - pub fn new() -> DownloadBuilder { + pub fn new(client: Client) -> DownloadBuilder { Self { + client, ffmpeg_preset: FFmpegPreset::default(), default_subtitle: None, output_format: None, audio_sort: None, subtitle_sort: None, force_hardsub: false, + download_fonts: false, threads: num_cpus::get(), ffmpeg_threads: None, } @@ -71,6 +77,7 @@ impl DownloadBuilder { pub fn build(self) -> Downloader { Downloader { + client: self.client, ffmpeg_preset: self.ffmpeg_preset, default_subtitle: self.default_subtitle, output_format: self.output_format, @@ -78,6 +85,7 @@ impl DownloadBuilder { subtitle_sort: self.subtitle_sort, force_hardsub: self.force_hardsub, + download_fonts: self.download_fonts, download_threads: self.threads, ffmpeg_threads: self.ffmpeg_threads, @@ -100,6 +108,8 @@ pub struct DownloadFormat { } pub struct Downloader { + client: Client, + ffmpeg_preset: FFmpegPreset, default_subtitle: Option<Locale>, output_format: Option<String>, @@ -107,6 +117,7 @@ pub struct Downloader { subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, + download_fonts: bool, download_threads: usize, ffmpeg_threads: Option<usize>, @@ -183,6 +194,7 @@ impl Downloader { let mut videos = vec![]; let mut audios = vec![]; let mut subtitles = vec![]; + let mut fonts = vec![]; let mut max_frames = 0f64; let fmt_space = self .formats @@ -296,8 +308,64 @@ impl Downloader { }); } + if self.download_fonts + && !self.force_hardsub + && dst.extension().unwrap_or_default().to_str().unwrap() == "mkv" + { + let mut font_names = vec![]; + for subtitle in subtitles.iter() { + font_names.extend(get_subtitle_stats(&subtitle.path)?) + } + real_dedup_vec(&mut font_names); + + let progress_spinner = if log::max_level() == LevelFilter::Info { + let progress_spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template( + format!( + ":: {:<1$} {{msg}} {{spinner}}", + "Downloading fonts", fmt_space + ) + .as_str(), + ) + .unwrap() + .tick_strings(&["โ€”", "\\", "|", "/", ""]), + ) + .with_finish(ProgressFinish::Abandon); + progress_spinner.enable_steady_tick(Duration::from_millis(100)); + Some(progress_spinner) + } else { + None + }; + for font_name in font_names { + if let Some(pb) = &progress_spinner { + let mut progress_message = pb.message(); + if !progress_message.is_empty() { + progress_message += ", " + } + progress_message += &font_name; + pb.set_message(progress_message) + } + if let Some((font, cached)) = self.download_font(&font_name).await? { + if cached { + if let Some(pb) = &progress_spinner { + let mut progress_message = pb.message(); + progress_message += " (cached)"; + pb.set_message(progress_message) + } + debug!("Downloaded font {} (cached)", font_name); + } else { + debug!("Downloaded font {}", font_name); + } + + fonts.push(font) + } + } + } + let mut input = vec![]; let mut maps = vec![]; + let mut attachments = vec![]; let mut metadata = vec![]; for (i, meta) in videos.iter().enumerate() { @@ -324,6 +392,14 @@ impl Downloader { ]); } + for (i, font) in fonts.iter().enumerate() { + attachments.extend(["-attach".to_string(), font.to_string_lossy().to_string()]); + metadata.extend([ + format!("-metadata:s:t:{}", i), + "mimetype=font/woff2".to_string(), + ]) + } + // this formats are supporting embedding subtitles into the video container instead of // burning it into the video stream directly let container_supports_softsubs = !self.force_hardsub @@ -361,6 +437,7 @@ impl Downloader { command_args.extend(input_presets); command_args.extend(input); command_args.extend(maps); + command_args.extend(attachments); command_args.extend(metadata); if !preset_custom { if let Some(ffmpeg_threads) = self.ffmpeg_threads { @@ -607,6 +684,33 @@ impl Downloader { Ok(path) } + async fn download_font(&self, name: &str) -> Result<Option<(PathBuf, bool)>> { + let Some((_, font_file)) = FONTS.iter().find(|(f, _)| f == &name) else { + return Ok(None); + }; + + let cache_dir = cache_dir("fonts")?; + let file = cache_dir.join(font_file); + if file.exists() { + return Ok(Some((file, true))); + } + + // the speed limiter does not apply to this + let font = self + .client + .get(format!( + "https://static.crunchyroll.com/vilos-v2/web/vilos/assets/libass-fonts/{}", + font_file + )) + .send() + .await? + .bytes() + .await?; + fs::write(&file, font.to_vec())?; + + Ok(Some((file, false))) + } + async fn download_segments( &self, writer: &mut impl Write, @@ -772,7 +876,7 @@ fn estimate_variant_file_size(variant_data: &VariantData, segments: &[VariantSeg } /// Get the length and fps of a video. -pub fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { +fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; let video_fps = Regex::new(r"(?P<fps>[\d/.]+)\sfps")?; @@ -806,6 +910,113 @@ pub fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { )) } +// all subtitle fonts (extracted from javascript) +const FONTS: [(&str, &str); 66] = [ + ("Adobe Arabic", "AdobeArabic-Bold.woff2"), + ("Andale Mono", "andalemo.woff2"), + ("Arial", "arial.woff2"), + ("Arial Black", "ariblk.woff2"), + ("Arial Bold", "arialbd.woff2"), + ("Arial Bold Italic", "arialbi.woff2"), + ("Arial Italic", "ariali.woff2"), + ("Arial Unicode MS", "arialuni.woff2"), + ("Comic Sans MS", "comic.woff2"), + ("Comic Sans MS Bold", "comicbd.woff2"), + ("Courier New", "cour.woff2"), + ("Courier New Bold", "courbd.woff2"), + ("Courier New Bold Italic", "courbi.woff2"), + ("Courier New Italic", "couri.woff2"), + ("DejaVu LGC Sans Mono", "DejaVuLGCSansMono.woff2"), + ("DejaVu LGC Sans Mono Bold", "DejaVuLGCSansMono-Bold.woff2"), + ( + "DejaVu LGC Sans Mono Bold Oblique", + "DejaVuLGCSansMono-BoldOblique.woff2", + ), + ( + "DejaVu LGC Sans Mono Oblique", + "DejaVuLGCSansMono-Oblique.woff2", + ), + ("DejaVu Sans", "DejaVuSans.woff2"), + ("DejaVu Sans Bold", "DejaVuSans-Bold.woff2"), + ("DejaVu Sans Bold Oblique", "DejaVuSans-BoldOblique.woff2"), + ("DejaVu Sans Condensed", "DejaVuSansCondensed.woff2"), + ( + "DejaVu Sans Condensed Bold", + "DejaVuSansCondensed-Bold.woff2", + ), + ( + "DejaVu Sans Condensed Bold Oblique", + "DejaVuSansCondensed-BoldOblique.woff2", + ), + ( + "DejaVu Sans Condensed Oblique", + "DejaVuSansCondensed-Oblique.woff2", + ), + ("DejaVu Sans ExtraLight", "DejaVuSans-ExtraLight.woff2"), + ("DejaVu Sans Mono", "DejaVuSansMono.woff2"), + ("DejaVu Sans Mono Bold", "DejaVuSansMono-Bold.woff2"), + ( + "DejaVu Sans Mono Bold Oblique", + "DejaVuSansMono-BoldOblique.woff2", + ), + ("DejaVu Sans Mono Oblique", "DejaVuSansMono-Oblique.woff2"), + ("DejaVu Sans Oblique", "DejaVuSans-Oblique.woff2"), + ("Gautami", "gautami.woff2"), + ("Georgia", "georgia.woff2"), + ("Georgia Bold", "georgiab.woff2"), + ("Georgia Bold Italic", "georgiaz.woff2"), + ("Georgia Italic", "georgiai.woff2"), + ("Impact", "impact.woff2"), + ("Mangal", "MANGAL.woff2"), + ("Meera Inimai", "MeeraInimai-Regular.woff2"), + ("Noto Sans Thai", "NotoSansThai.woff2"), + ("Rubik", "Rubik-Regular.woff2"), + ("Rubik Black", "Rubik-Black.woff2"), + ("Rubik Black Italic", "Rubik-BlackItalic.woff2"), + ("Rubik Bold", "Rubik-Bold.woff2"), + ("Rubik Bold Italic", "Rubik-BoldItalic.woff2"), + ("Rubik Italic", "Rubik-Italic.woff2"), + ("Rubik Light", "Rubik-Light.woff2"), + ("Rubik Light Italic", "Rubik-LightItalic.woff2"), + ("Rubik Medium", "Rubik-Medium.woff2"), + ("Rubik Medium Italic", "Rubik-MediumItalic.woff2"), + ("Tahoma", "tahoma.woff2"), + ("Times New Roman", "times.woff2"), + ("Times New Roman Bold", "timesbd.woff2"), + ("Times New Roman Bold Italic", "timesbi.woff2"), + ("Times New Roman Italic", "timesi.woff2"), + ("Trebuchet MS", "trebuc.woff2"), + ("Trebuchet MS Bold", "trebucbd.woff2"), + ("Trebuchet MS Bold Italic", "trebucbi.woff2"), + ("Trebuchet MS Italic", "trebucit.woff2"), + ("Verdana", "verdana.woff2"), + ("Verdana Bold", "verdanab.woff2"), + ("Verdana Bold Italic", "verdanaz.woff2"), + ("Verdana Italic", "verdanai.woff2"), + ("Vrinda", "vrinda.woff2"), + ("Vrinda Bold", "vrindab.woff2"), + ("Webdings", "webdings.woff2"), +]; +lazy_static::lazy_static! { + static ref FONT_REGEX: Regex = Regex::new(r"(?m)^Style:\s.+?,(?P<font>.+?),").unwrap(); +} + +/// Get the fonts used in the subtitle. +fn get_subtitle_stats(path: &Path) -> Result<Vec<String>> { + let mut fonts = vec![]; + + for capture in FONT_REGEX.captures_iter(&(fs::read_to_string(path)?)) { + if let Some(font) = capture.name("font") { + let font_string = font.as_str().to_string(); + if !fonts.contains(&font_string) { + fonts.push(font_string) + } + } + } + + Ok(fonts) +} + /// Fix the subtitles in multiple ways as Crunchyroll sometimes delivers them malformed. /// /// Look and feel fix: Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 7055265..48ed229 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use std::pin::Pin; use std::process::{Command, Stdio}; use std::task::{Context, Poll}; -use std::{env, io}; +use std::{env, fs, io}; use tempfile::{Builder, NamedTempFile}; use tokio::io::{AsyncRead, ReadBuf}; @@ -46,6 +46,12 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { Ok(tempfile) } +pub fn cache_dir<S: AsRef<str>>(name: S) -> io::Result<PathBuf> { + let cache_dir = temp_directory().join(format!(".crunchy-cli_{}_cache", name.as_ref())); + fs::create_dir_all(&cache_dir)?; + Ok(cache_dir) +} + pub struct TempNamedPipe { name: String, From 19935df545bd78d63f6f93b31b8442a84217a9a4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 23 Dec 2023 15:28:10 +0100 Subject: [PATCH 496/630] Add more output format options (#284) --- crunchy-cli-core/src/archive/command.rs | 17 +++++++- crunchy-cli-core/src/download/command.rs | 15 ++++++- crunchy-cli-core/src/utils/format.rs | 52 +++++++++++++++++++++++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 9ccc0ff..68de727 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -15,7 +15,7 @@ use anyhow::Result; use chrono::Duration; use crunchyroll_rs::media::{Resolution, Subtitle}; use crunchyroll_rs::Locale; -use log::debug; +use log::{debug, warn}; use std::collections::HashMap; use std::path::PathBuf; @@ -43,12 +43,16 @@ pub struct Archive { {series_name} โ†’ Name of the series\n \ {season_name} โ†’ Name of the season\n \ {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ + {width} โ†’ Width of the video\n \ + {height} โ†’ Height of the video\n \ {season_number} โ†’ Number of the season\n \ {episode_number} โ†’ Number of the episode\n \ {relative_episode_number} โ†’ Number of the episode relative to its season\n \ {sequence_number} โ†’ Like '{episode_number}' but without possible non-number characters\n \ {relative_sequence_number} โ†’ Like '{relative_episode_number}' but with support for episode 0's and .5's\n \ + {release_year} โ†’ Release year of the video\n \ + {release_month} โ†’ Release month of the video\n \ + {release_day} โ†’ Release day of the video\n \ {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] @@ -157,6 +161,15 @@ impl Execute for Archive { } } + if self.output.contains("{resolution}") + || self + .output_specials + .as_ref() + .map_or(false, |os| os.contains("{resolution}")) + { + warn!("The '{{resolution}}' format option is deprecated and will be removed in a future version. Please use '{{width}}' and '{{height}}' instead") + } + self.audio = all_locale_in_locales(self.audio.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index faa265c..210b861 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -39,12 +39,16 @@ pub struct Download { {series_name} โ†’ Name of the series\n \ {season_name} โ†’ Name of the season\n \ {audio} โ†’ Audio language of the video\n \ - {resolution} โ†’ Resolution of the video\n \ + {width} โ†’ Width of the video\n \ + {height} โ†’ Height of the video\n \ {season_number} โ†’ Number of the season\n \ {episode_number} โ†’ Number of the episode\n \ {relative_episode_number} โ†’ Number of the episode relative to its season\n \ {sequence_number} โ†’ Like '{episode_number}' but without possible non-number characters\n \ {relative_sequence_number} โ†’ Like '{relative_episode_number}' but with support for episode 0's and .5's\n \ + {release_year} โ†’ Release year of the video\n \ + {release_month} โ†’ Release month of the video\n \ + {release_day} โ†’ Release day of the video\n \ {series_id} โ†’ ID of the series\n \ {season_id} โ†’ ID of the season\n \ {episode_id} โ†’ ID of the episode")] @@ -153,6 +157,15 @@ impl Execute for Download { } } + if self.output.contains("{resolution}") + || self + .output_specials + .as_ref() + .map_or(false, |os| os.contains("{resolution}")) + { + warn!("The '{{resolution}}' format option is deprecated and will be removed in a future version. Please use '{{width}}' and '{{height}}' instead") + } + Ok(()) } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 08b1b23..adbfb6d 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -2,7 +2,7 @@ use crate::utils::filter::real_dedup_vec; use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; use anyhow::Result; -use chrono::Duration; +use chrono::{Datelike, Duration}; use crunchyroll_rs::media::{Resolution, Stream, Subtitle, VariantData}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; use log::{debug, info}; @@ -17,6 +17,10 @@ pub struct SingleFormat { pub title: String, pub description: String, + pub release_year: u64, + pub release_month: u64, + pub release_day: u64, + pub audio: Locale, pub subtitles: Vec<Locale>, @@ -60,6 +64,9 @@ impl SingleFormat { }, title: episode.title.clone(), description: episode.description.clone(), + release_year: episode.episode_air_date.year() as u64, + release_month: episode.episode_air_date.month() as u64, + release_day: episode.episode_air_date.day() as u64, audio: episode.audio_locale.clone(), subtitles, series_id: episode.series_id.clone(), @@ -86,6 +93,9 @@ impl SingleFormat { identifier: movie.id.clone(), title: movie.title.clone(), description: movie.description.clone(), + release_year: movie.free_available_date.year() as u64, + release_month: movie.free_available_date.month() as u64, + release_day: movie.free_available_date.day() as u64, audio: Locale::ja_JP, subtitles, series_id: movie.movie_listing_id.clone(), @@ -108,6 +118,9 @@ impl SingleFormat { identifier: music_video.id.clone(), title: music_video.title.clone(), description: music_video.description.clone(), + release_year: music_video.original_release.year() as u64, + release_month: music_video.original_release.month() as u64, + release_day: music_video.original_release.day() as u64, audio: Locale::ja_JP, subtitles: vec![], series_id: music_video.id.clone(), @@ -130,6 +143,9 @@ impl SingleFormat { identifier: concert.id.clone(), title: concert.title.clone(), description: concert.description.clone(), + release_year: concert.original_release.year() as u64, + release_month: concert.original_release.month() as u64, + release_day: concert.original_release.day() as u64, audio: Locale::ja_JP, subtitles: vec![], series_id: concert.id.clone(), @@ -324,9 +340,16 @@ pub struct Format { pub locales: Vec<(Locale, Vec<Locale>)>, + // deprecated pub resolution: Resolution, + pub width: u64, + pub height: u64, pub fps: f64, + pub release_year: u64, + pub release_month: u64, + pub release_day: u64, + pub series_id: String, pub series_name: String, @@ -364,8 +387,13 @@ impl Format { title: first_format.title, description: first_format.description, locales, - resolution: first_stream.resolution, + resolution: first_stream.resolution.clone(), + width: first_stream.resolution.width, + height: first_stream.resolution.height, fps: first_stream.fps, + release_year: first_format.release_year, + release_month: first_format.release_month, + release_day: first_format.release_day, series_id: first_format.series_id, series_name: first_format.series_name, season_id: first_format.season_id, @@ -396,6 +424,14 @@ impl Format { ), ) .replace("{resolution}", &sanitize(self.resolution.to_string(), true)) + .replace( + "{width}", + &sanitize(self.resolution.width.to_string(), true), + ) + .replace( + "{height}", + &sanitize(self.resolution.height.to_string(), true), + ) .replace("{series_id}", &sanitize(&self.series_id, true)) .replace("{series_name}", &sanitize(&self.series_name, true)) .replace("{season_id}", &sanitize(&self.season_id, true)) @@ -434,6 +470,18 @@ impl Format { true, ) ), + ) + .replace( + "{release_year}", + &sanitize(self.release_year.to_string(), true), + ) + .replace( + "{release_month}", + &format!("{:0>2}", sanitize(self.release_month.to_string(), true)), + ) + .replace( + "{release_day}", + &format!("{:0>2}", sanitize(self.release_day.to_string(), true)), ); PathBuf::from(path) From d503d459cd37675285405bba13a7ce78ec733555 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 2 Jan 2024 22:26:16 +0100 Subject: [PATCH 497/630] Differ between illegal Windows and non Windows file characters --- crunchy-cli-core/src/utils/os.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index 48ed229..d02c325 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -148,24 +148,23 @@ pub fn is_special_file<P: AsRef<Path>>(path: P) -> bool { } lazy_static::lazy_static! { - static ref ILLEGAL_RE: Regex = Regex::new(r#"[\?<>:\*\|":]"#).unwrap(); - static ref CONTROL_RE: Regex = Regex::new(r"[\x00-\x1f\x80-\x9f]").unwrap(); - static ref RESERVED_RE: Regex = Regex::new(r"^\.+$").unwrap(); + static ref WINDOWS_NON_PRINTABLE_RE: Regex = Regex::new(r"[\x00-\x1f\x80-\x9f]").unwrap(); + static ref WINDOWS_ILLEGAL_RE: Regex = Regex::new(r#"[<>:"|?*]"#).unwrap(); static ref WINDOWS_RESERVED_RE: Regex = RegexBuilder::new(r"(?i)^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$") .case_insensitive(true) .build() .unwrap(); static ref WINDOWS_TRAILING_RE: Regex = Regex::new(r"[\. ]+$").unwrap(); + + static ref LINUX_NON_PRINTABLE: Regex = Regex::new(r"[\x00]").unwrap(); + + static ref RESERVED_RE: Regex = Regex::new(r"^\.+$").unwrap(); } -/// Sanitizes a filename with the option to include/exclude the path separator from sanitizing. This -/// is based of the implementation of the -/// [`sanitize-filename`](https://crates.io/crates/sanitize-filename) crate. +/// Sanitizes a filename with the option to include/exclude the path separator from sanitizing. pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String { let path = Cow::from(path.as_ref().trim()); - let path = ILLEGAL_RE.replace_all(&path, ""); - let path = CONTROL_RE.replace_all(&path, ""); let path = RESERVED_RE.replace(&path, ""); let collect = |name: String| { @@ -177,7 +176,9 @@ pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String }; if cfg!(windows) { - let path = WINDOWS_RESERVED_RE.replace(&path, ""); + let path = WINDOWS_NON_PRINTABLE_RE.replace_all(&path, ""); + let path = WINDOWS_ILLEGAL_RE.replace_all(&path, ""); + let path = WINDOWS_RESERVED_RE.replace_all(&path, ""); let path = WINDOWS_TRAILING_RE.replace(&path, ""); let mut path = path.to_string(); if include_path_separator { @@ -185,6 +186,7 @@ pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String } collect(path) } else { + let path = LINUX_NON_PRINTABLE.replace_all(&path, ""); let mut path = path.to_string(); if include_path_separator { path = path.replace('/', ""); From 2e6246c439c4e0656fcfb1ad9abf69212c06f4df Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 2 Jan 2024 22:26:52 +0100 Subject: [PATCH 498/630] Do not sanitize user path input --- crunchy-cli-core/src/utils/format.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index adbfb6d..716f124 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -409,8 +409,9 @@ impl Format { /// Formats the given string if it has specific pattern in it. It also sanitizes the filename. pub fn format_path(&self, path: PathBuf) -> PathBuf { - let mut path = sanitize(path.to_string_lossy(), false); - path = path + let path = path + .to_string_lossy() + .to_string() .replace("{title}", &sanitize(&self.title, true)) .replace( "{audio}", From 172e3612d0c8b81fb9f85d082280f59fcb710d4e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 2 Jan 2024 22:47:48 +0100 Subject: [PATCH 499/630] Fix open-ended episode filter (#293) --- crunchy-cli-core/src/utils/parse.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index 3fc7e7c..42d8f93 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -46,10 +46,13 @@ impl UrlFilter { let from_season = f.from_season.unwrap_or(u32::MIN); let to_season = f.to_season.unwrap_or(u32::MAX); - episode >= from_episode - && episode <= to_episode - && season >= from_season - && season <= to_season + if season < from_season || season > to_season { + false + } else if season == from_season { + episode >= from_episode && episode <= to_episode + } else { + true + } }) } } From 283a3802b22d556eade5b8fca6114025efc6f879 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 2 Jan 2024 23:59:44 +0100 Subject: [PATCH 500/630] Update dependencies and version --- Cargo.lock | 451 +++++++++++++---------- Cargo.toml | 4 +- crunchy-cli-core/Cargo.toml | 9 +- crunchy-cli-core/src/archive/command.rs | 1 - crunchy-cli-core/src/archive/filter.rs | 1 - crunchy-cli-core/src/download/command.rs | 1 - crunchy-cli-core/src/download/filter.rs | 1 - crunchy-cli-core/src/lib.rs | 3 +- crunchy-cli-core/src/login/command.rs | 1 - crunchy-cli-core/src/search/command.rs | 1 - crunchy-cli-core/src/utils/filter.rs | 3 - 11 files changed, 273 insertions(+), 203 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 919ba0a..be9c26c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -74,37 +74,37 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "async-speed-limit" @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.8" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" dependencies = [ "clap_builder", "clap_derive", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.4" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +checksum = "97aeaa95557bd02f23fbb662f981670c3d20c5a26e69f7354b28f57092437fcd" dependencies = [ "clap", ] @@ -299,9 +299,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clap_mangen" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3be86020147691e1d2ef58f75346a3d4d94807bfc473e377d52f09f0f7d77f7" +checksum = "10b5db60b3310cdb376fbeb8826e875a38080d0c61bdec0a91a3da8338948736" dependencies = [ "clap", "roff", @@ -356,9 +356,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -366,9 +366,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.1.1" +version = "3.2.0" dependencies = [ "chrono", "clap", @@ -394,11 +394,10 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.1.1" +version = "3.2.0" dependencies = [ "anyhow", "async-speed-limit", - "async-trait", "chrono", "clap", "crunchyroll-rs", @@ -430,9 +429,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d0d1d6a75ed27b2dc93b84fa0667cffb92a513d206b8ccd1b895fab5ad2e9c" +checksum = "e1f57b3ceca94beb53d65b710853e851e72a2ebb20366b982a7db39d5a42ca50" dependencies = [ "aes", "async-trait", @@ -457,9 +456,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd0a20e750354408294b674a50b6d5dacec315ff9ead7c3a7c093f1e3594335" +checksum = "51bae6adce1bb6fbb91847f5f2f18a6b433708bb149566eaea836b1b78df93b4" dependencies = [ "darling", "quote", @@ -478,12 +477,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" dependencies = [ "nix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -523,32 +522,34 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.14.4" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5471fc46c0b229c8f2308d83be857c745c9f06cc83a433d7047909722e0453b4" +checksum = "7cf94350e05e27c941b8cfc06bffeec3afcac11f42df289378ddf43e192d2e15" dependencies = [ "base64", "base64-serde", "chrono", "fs-err", "iso8601", - "log", + "lazy_static", "num-traits", "quick-xml", "regex", "serde", + "serde_path_to_error", "serde_with", "thiserror", "tokio", + "tracing", "url", "xattr", ] [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -627,12 +628,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -664,9 +665,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -692,30 +693,30 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -724,15 +725,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -742,9 +743,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", @@ -780,9 +781,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" @@ -811,9 +812,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -846,9 +847,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -869,9 +870,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -884,7 +885,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -920,9 +921,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -970,9 +971,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -996,7 +997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "serde", ] @@ -1049,15 +1050,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1070,9 +1071,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libredox" @@ -1087,9 +1088,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" @@ -1099,9 +1100,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "m3u8-rs" -version = "5.0.4" +version = "5.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39af8845edca961e3286dcbafeb9e6407d3df6a616ef086847162d46f438d75" +checksum = "0c1d7ba86f7ea62f17f4310c55e93244619ddc7dadfc7e565de1967e4e41e6e7" dependencies = [ "chrono", "nom", @@ -1115,9 +1116,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" @@ -1142,9 +1143,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -1215,24 +1216,24 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -1262,18 +1263,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.6+3.1.4" +version = "300.2.1+3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" +checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -1290,9 +1291,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -1308,15 +1309,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "portable-atomic" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "powerfmt" @@ -1326,9 +1327,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" dependencies = [ "unicode-ident", ] @@ -1361,9 +1362,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1419,9 +1420,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64", "bytes", @@ -1461,15 +1462,15 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", "winreg", ] [[package]] name = "ring" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", "getrandom", @@ -1493,22 +1494,22 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", @@ -1539,9 +1540,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7673e0aa20ee4937c6aacfc12bb8341cfbf054cdd21df6bec5fd0629fe9339b" +checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" [[package]] name = "rustls-webpki" @@ -1555,17 +1556,17 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1603,18 +1604,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", @@ -1623,15 +1624,25 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_plain" version = "1.0.2" @@ -1714,16 +1725,6 @@ dependencies = [ "syn", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -1748,9 +1749,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.39" +version = "2.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" dependencies = [ "proc-macro2", "quote", @@ -1789,31 +1790,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", @@ -1822,9 +1823,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", @@ -1842,9 +1843,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -1866,9 +1867,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -1876,7 +1877,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -1951,9 +1952,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -1965,9 +1978,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -1977,9 +1990,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -2010,12 +2023,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", ] @@ -2054,9 +2067,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2064,9 +2077,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -2079,9 +2092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -2091,9 +2104,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2101,9 +2114,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -2114,9 +2127,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-streams" @@ -2133,9 +2146,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -2143,9 +2156,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webpki-roots" @@ -2180,11 +2193,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -2205,6 +2218,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -2235,6 +2257,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2247,6 +2284,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2259,6 +2302,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2271,6 +2320,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2283,6 +2338,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2295,6 +2356,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2307,6 +2374,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -2319,6 +2392,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.50.0" @@ -2331,9 +2410,11 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" dependencies = [ "libc", + "linux-raw-sys", + "rustix", ] diff --git a/Cargo.toml b/Cargo.toml index 098c048..9843e97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.1.1" +version = "3.2.0" edition = "2021" license = "MIT" @@ -18,7 +18,7 @@ openssl = ["openssl-tls"] openssl-static = ["openssl-tls-static"] [dependencies] -tokio = { version = "1.34", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time"], default-features = false } native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 245c6ea..0e2b03f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.1.1" +version = "3.2.0" edition = "2021" license = "MIT" @@ -14,10 +14,9 @@ openssl-tls-static = ["reqwest/native-tls", "reqwest/native-tls-alpn", "reqwest/ [dependencies] anyhow = "1.0" async-speed-limit = "0.4" -async-trait = "0.1" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.8.0", features = ["dash-stream", "experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.8.1", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" @@ -36,8 +35,8 @@ serde_json = "1.0" serde_plain = "1.0" shlex = "1.2" sys-locale = "0.3" -tempfile = "3.8" -tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } +tempfile = "3.9" +tokio = { version = "1.35", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } tokio-util = "0.7" tower-service = "0.3" rustls-native-certs = { version = "0.6", optional = true } diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 68de727..17f21a1 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -134,7 +134,6 @@ pub struct Archive { pub(crate) urls: Vec<String>, } -#[async_trait::async_trait(?Send)] impl Execute for Archive { fn pre_check(&mut self) -> Result<()> { if !has_ffmpeg() { diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 1777072..2a47738 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -45,7 +45,6 @@ impl ArchiveFilter { } } -#[async_trait::async_trait] impl Filter for ArchiveFilter { type T = Vec<SingleFormat>; type Output = SingleFormatCollection; diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 210b861..30fea44 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -113,7 +113,6 @@ pub struct Download { pub(crate) urls: Vec<String>, } -#[async_trait::async_trait(?Send)] impl Execute for Download { fn pre_check(&mut self) -> Result<()> { if !has_ffmpeg() { diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 51f1ad8..5076a33 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -37,7 +37,6 @@ impl DownloadFilter { } } -#[async_trait::async_trait] impl Filter for DownloadFilter { type T = SingleFormat; type Output = SingleFormatCollection; diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 3299b31..25e30d6 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -24,12 +24,11 @@ pub use download::Download; pub use login::Login; pub use search::Search; -#[async_trait::async_trait(?Send)] trait Execute { fn pre_check(&mut self) -> Result<()> { Ok(()) } - async fn execute(mut self, ctx: Context) -> Result<()>; + async fn execute(self, ctx: Context) -> Result<()>; } #[derive(Debug, Parser)] diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index 0b4bb4b..bdc30c3 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -18,7 +18,6 @@ pub struct Login { pub remove: bool, } -#[async_trait::async_trait(?Send)] impl Execute for Login { async fn execute(self, ctx: Context) -> Result<()> { if let Some(login_file_path) = session_file_path() { diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index 1ec4cb9..c357ab4 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -100,7 +100,6 @@ pub struct Search { input: String, } -#[async_trait::async_trait(?Send)] impl Execute for Search { async fn execute(self, ctx: Context) -> Result<()> { let input = if crunchyroll_rs::parse::parse_url(&self.input).is_some() { diff --git a/crunchy-cli-core/src/utils/filter.rs b/crunchy-cli-core/src/utils/filter.rs index bb9957d..63fac9d 100644 --- a/crunchy-cli-core/src/utils/filter.rs +++ b/crunchy-cli-core/src/utils/filter.rs @@ -3,9 +3,6 @@ use crunchyroll_rs::{ Concert, Episode, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series, }; -// Check when https://github.com/dtolnay/async-trait/issues/224 is resolved and update async-trait -// to the new fixed version (as this causes some issues) -#[async_trait::async_trait] pub trait Filter { type T: Send + Sized; type Output: Send + Sized; From fc6da9a76dad9d3791811822844d48382bbb759e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Jan 2024 00:00:00 +0100 Subject: [PATCH 501/630] Use latest Rust version in Linux and Mac toolchain --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb7d196..54da377 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,11 @@ jobs: target/ key: ${{ matrix.toolchain }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Install cross run: cargo install --force cross @@ -76,6 +81,11 @@ jobs: target/ key: x86_64-apple-darwin-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Build run: cargo build --release --target x86_64-apple-darwin From d3837f2495341a20a6911053adf44194500120a5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Jan 2024 00:34:16 +0100 Subject: [PATCH 502/630] Add new flags and format options to README --- README.md | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 77877b6..be97bec 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,14 @@ You can set specific settings which will be Default is the user agent, defined in the underlying [library](https://github.com/crunchy-labs/crunchyroll-rs). +- Speed limit + + If you want to limit how fast requests/downloads should be, you can use the `--speed-limit` flag. Allowed units are `B` (bytes), `KB` (kilobytes) and `MB` (megabytes). + + ```shell + $ crunchy-cli --speed-limit 10MB + ``` + ### Login The `login` command can store your session, so you don't have to authenticate every time you execute a command. @@ -270,7 +278,7 @@ The `download` command lets you download episodes with a specific audio language Define an output template which only gets used when the episode is a special (episode number is 0 or has non-zero decimal places) by using the `--output-special` flag. ```shell - $ crunchy-cli download --output-specials -o "Special EP: {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal + $ crunchy-cli download --output-specials -o "Special EP - {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal ``` Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. @@ -295,6 +303,14 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli downlaod --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` +- FFmpeg threads + + If you want to manually set how many threads FFmpeg should use, you can use the `--ffmpeg-threads` flag. This does not work with every codec/preset and is skipped entirely when specifying custom ffmpeg output arguments instead of a preset for `--ffmpeg-preset`. + + ```shell + $ crunchy-cli download --ffmpeg-threads 4 https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + - Skip existing If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. @@ -395,7 +411,7 @@ The `archive` command lets you download episodes with multiple audios and subtit _crunchy-cli_ exclusively uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of its ability to store multiple audio, video and subtitle tracks at once. ```shell - $ crunchy-cli archive --output-specials -o "Special EP: {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal + $ crunchy-cli archive --output-specials -o "Special EP - {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal ``` Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. @@ -435,6 +451,14 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` +- FFmpeg threads + + If you want to manually set how many threads FFmpeg should use, you can use the `--ffmpeg-threads` flag. This does not work with every codec/preset and is skipped entirely when specifying custom ffmpeg output arguments instead of a preset for `--ffmpeg-preset`. + + ```shell + $ crunchy-cli archive --ffmpeg-threads 4 https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + - Default subtitle `--default-subtitle` Set which subtitle language is to be flagged as **default** and **forced**. @@ -445,6 +469,14 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is none. +- Include fonts + + You can include the fonts required by subtitles directly into the output file with the `--include-fonts` flag. This will use the embedded font for subtitles instead of the system font when playing the video in a video player which supports it. + + ```shell + $ crunchy-cli archive --include-fonts https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + - Skip existing If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. @@ -553,12 +585,16 @@ You can use various template options to change how the filename is processed. Th - `{series_name}` โ†’ Name of the series - `{season_name}` โ†’ Name of the season - `{audio}` โ†’ Audio language of the video -- `{resolution}` โ†’ Resolution of the video +- `{width}` โ†’ Width of the video +- `{height}` โ†’ Height of the video - `{season_number}` โ†’ Number of the season - `{episode_number}` โ†’ Number of the episode - `{relative_episode_number}` โ†’ Number of the episode relative to its season - `{sequence_number}` โ†’ Like `{episode_number}` but without possible non-number characters - `{relative_sequence_number}` โ†’ Like `{relative_episode_number}` but with support for episode 0's and .5's +- `{release_year}` โ†’ Release year of the video +- `{release_month}` โ†’ Release month of the video +- `{release_day} ` โ†’ Release day of the video - `{series_id}` โ†’ ID of the series - `{season_id}` โ†’ ID of the season - `{episode_id}` โ†’ ID of the episode From 99f96e3e35af1ee63b61827a64ca164e107f61e4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Jan 2024 01:07:12 +0100 Subject: [PATCH 503/630] Fix login command not working --- crunchy-cli-core/src/lib.rs | 26 +++++--------------------- crunchy-cli-core/src/login/command.rs | 4 +--- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 25e30d6..f1d659a 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -315,15 +315,9 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { let root_login_methods_count = cli.login_method.credentials.is_some() as u8 + cli.login_method.etp_rt.is_some() as u8 + cli.login_method.anonymous as u8; - let mut login_login_methods_count = 0; - if let Command::Login(login) = &cli.command { - login_login_methods_count += login.login_method.credentials.is_some() as u8 - + login.login_method.etp_rt.is_some() as u8 - + login.login_method.anonymous as u8 - } let progress_handler = progress!("Logging in"); - if root_login_methods_count + login_login_methods_count == 0 { + if root_login_methods_count == 0 { if let Some(login_file_path) = login::session_file_path() { if login_file_path.exists() { let session = fs::read_to_string(login_file_path)?; @@ -340,29 +334,19 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { } } bail!("Please use a login method ('--credentials', '--etp-rt' or '--anonymous')") - } else if root_login_methods_count + login_login_methods_count > 1 { + } else if root_login_methods_count > 1 { bail!("Please use only one login method ('--credentials', '--etp-rt' or '--anonymous')") } - let login_method = if login_login_methods_count > 0 { - if let Command::Login(login) = &cli.command { - login.login_method.clone() - } else { - unreachable!() - } - } else { - cli.login_method.clone() - }; - - let crunchy = if let Some(credentials) = &login_method.credentials { + let crunchy = if let Some(credentials) = &cli.login_method.credentials { if let Some((email, password)) = credentials.split_once(':') { builder.login_with_credentials(email, password).await? } else { bail!("Invalid credentials format. Please provide your credentials as email:password") } - } else if let Some(etp_rt) = &login_method.etp_rt { + } else if let Some(etp_rt) = &cli.login_method.etp_rt { builder.login_with_etp_rt(etp_rt).await? - } else if login_method.anonymous { + } else if cli.login_method.anonymous { builder.login_anonymously().await? } else { bail!("should never happen") diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index bdc30c3..5642948 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -11,8 +11,6 @@ use std::path::PathBuf; #[derive(Debug, clap::Parser)] #[clap(about = "Save your login credentials persistent on disk")] pub struct Login { - #[clap(flatten)] - pub login_method: LoginMethod, #[arg(help = "Remove your stored credentials (instead of saving them)")] #[arg(long)] pub remove: bool, @@ -56,7 +54,7 @@ pub struct LoginMethod { #[arg(global = true, long)] pub etp_rt: Option<String>, #[arg(help = "Login anonymously / without an account")] - #[arg(long, default_value_t = false)] + #[arg(global = true, long, default_value_t = false)] pub anonymous: bool, } From d90f45fa319ab66c13e5020fd5986256019b245f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Jan 2024 01:08:40 +0100 Subject: [PATCH 504/630] Update checkout action version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54da377..aa70330 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: toolchain: aarch64-unknown-linux-musl steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cargo cache uses: actions/cache@v3 @@ -68,7 +68,7 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cargo cache uses: actions/cache@v3 @@ -100,7 +100,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cargo cache uses: actions/cache@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 43ea795..4a477b3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get version run: echo "RELEASE_VERSION=$(echo ${{ github.ref_name }} | cut -c 2-)" >> $GITHUB_ENV From c37d55aade245a9e0dfb07ceac95cac5bc6e2922 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Jan 2024 01:20:34 +0100 Subject: [PATCH 505/630] Update version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be9c26c..f6d6401 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.2.0" +version = "3.2.1" dependencies = [ "chrono", "clap", @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.2.0" +version = "3.2.1" dependencies = [ "anyhow", "async-speed-limit", diff --git a/Cargo.toml b/Cargo.toml index 9843e97..15eafe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.2.0" +version = "3.2.1" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 0e2b03f..1658506 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.2.0" +version = "3.2.1" edition = "2021" license = "MIT" From 650338d3e6eb50293eee3f440cb76e8960e8c72a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Jan 2024 10:57:30 +0100 Subject: [PATCH 506/630] Prepend `./` to the output path on linux if the input path is only a filename (#303) --- crunchy-cli-core/src/utils/download.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 64a6d1a..1b99c30 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -534,7 +534,18 @@ impl Downloader { if let Some(output_format) = self.output_format { command_args.extend(["-f".to_string(), output_format]); } - command_args.push(dst.to_str().unwrap().to_string()); + + // prepend './' to the path on linux since ffmpeg may interpret the path incorrectly if it's just the filename. + // see https://github.com/crunchy-labs/crunchy-cli/issues/303 for example + if !cfg!(windows) + && dst + .parent() + .map_or(true, |p| p.to_string_lossy().is_empty()) + { + command_args.push(Path::new("./").join(dst).to_string_lossy().to_string()); + } else { + command_args.push(dst.to_string_lossy().to_string()) + } debug!("ffmpeg {}", command_args.join(" ")); From ef2898f0e1e44277abd648d2c6f39fde11e0071f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Jan 2024 15:24:30 +0100 Subject: [PATCH 507/630] Update dependencies and version --- Cargo.lock | 56 ++++++++++++++++++------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6d6401..24f8472 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" [[package]] name = "base64-serde" @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.12" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ "clap_builder", "clap_derive", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ "anstream", "anstyle", @@ -372,16 +372,16 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crunchy-cli" -version = "3.2.1" +version = "3.2.2" dependencies = [ "chrono", "clap", @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.2.1" +version = "3.2.2" dependencies = [ "anyhow", "async-speed-limit", @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f57b3ceca94beb53d65b710853e851e72a2ebb20366b982a7db39d5a42ca50" +checksum = "828ff3c0f11de8f8afda7dc3bd24e206e1b13cee6abfd87856123305864681d2" dependencies = [ "aes", "async-trait", @@ -456,9 +456,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae6adce1bb6fbb91847f5f2f18a6b433708bb149566eaea836b1b78df93b4" +checksum = "c7051a39e25a19ef0aa753e7da179787a3db0fb8a01977a7e22cd288f7ff0e27" dependencies = [ "darling", "quote", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -1071,9 +1071,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libredox" @@ -1327,9 +1327,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -1604,18 +1604,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", @@ -1624,9 +1624,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -1749,9 +1749,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.46" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 15eafe2..ee56886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.2.1" +version = "3.2.2" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 1658506..510e000 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.2.1" +version = "3.2.2" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.8.1", features = ["dash-stream", "experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.8.2", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 7c42f29596372cba0c3cff844965ac792afc2c55 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 10 Jan 2024 13:13:55 +0100 Subject: [PATCH 508/630] Only use tempfile name as windows named pipe name (#305) --- crunchy-cli-core/src/utils/os.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index d02c325..da91f97 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -86,25 +86,27 @@ impl Drop for TempNamedPipe { pub fn temp_named_pipe() -> io::Result<TempNamedPipe> { let (_, path) = tempfile("")?.keep()?; - let path = path.to_string_lossy().to_string(); - let _ = std::fs::remove_file(path.clone()); + let _ = fs::remove_file(path.clone()); #[cfg(not(target_os = "windows"))] { - nix::unistd::mkfifo(path.as_str(), nix::sys::stat::Mode::S_IRWXU)?; + let filename = path.to_string_lossy().to_string(); + + nix::unistd::mkfifo(filename.as_str(), nix::sys::stat::Mode::S_IRWXU)?; Ok(TempNamedPipe { reader: tokio::net::unix::pipe::OpenOptions::new().open_receiver(&path)?, - name: path, + name: filename, }) } #[cfg(target_os = "windows")] { - let path = format!(r"\\.\pipe\{}", &path); + let pipe_name = path.file_name().unwrap().to_string_lossy().to_string(); + let pipe_path = format!(r"\\.\pipe\{}", pipe_name); Ok(TempNamedPipe { - reader: tokio::net::windows::named_pipe::ServerOptions::new().create(&path)?, - name: path, + reader: tokio::net::windows::named_pipe::ServerOptions::new().create(pipe_path)?, + name: pipe_name, }) } } From 333d574e567f259d1c1603d5692695d4d97fe6ef Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 10 Jan 2024 13:37:16 +0100 Subject: [PATCH 509/630] Update dependencies and version --- Cargo.lock | 76 +++---------------------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24f8472..2edb15a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,15 +315,15 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.2.2" +version = "3.2.3" dependencies = [ "chrono", "clap", @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.2.2" +version = "3.2.3" dependencies = [ "anyhow", "async-speed-limit", @@ -2200,15 +2200,6 @@ dependencies = [ "windows-targets 0.52.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2227,21 +2218,6 @@ dependencies = [ "windows-targets 0.52.0", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -2272,12 +2248,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2290,12 +2260,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2308,12 +2272,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2326,12 +2284,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2344,12 +2296,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2362,12 +2308,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2380,12 +2320,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index ee56886..43d64dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.2.2" +version = "3.2.3" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 510e000..56b5a04 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.2.2" +version = "3.2.3" edition = "2021" license = "MIT" From 35447c5cb0f4ea329dee95e09e3c9806a9ca01cf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 10 Jan 2024 21:02:03 +0100 Subject: [PATCH 510/630] Fix Windows output progress bar (#305) --- crunchy-cli-core/src/utils/download.rs | 4 +- crunchy-cli-core/src/utils/os.rs | 59 +++++++++++++++++++------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 1b99c30..bcbb49f 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -432,7 +432,7 @@ impl Downloader { "-y".to_string(), "-hide_banner".to_string(), "-vstats_file".to_string(), - fifo.name(), + fifo.path().to_string_lossy().to_string(), ]; command_args.extend(input_presets); command_args.extend(input); @@ -584,7 +584,7 @@ impl Downloader { bail!("{}", String::from_utf8_lossy(result.stderr.as_slice())) } ffmpeg_progress_cancel.cancel(); - Ok(ffmpeg_progress.await??) + ffmpeg_progress.await? } async fn check_free_space( diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index da91f97..a9d3ede 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -7,7 +7,7 @@ use std::pin::Pin; use std::process::{Command, Stdio}; use std::task::{Context, Poll}; use std::{env, fs, io}; -use tempfile::{Builder, NamedTempFile}; +use tempfile::{Builder, NamedTempFile, TempPath}; use tokio::io::{AsyncRead, ReadBuf}; pub fn has_ffmpeg() -> bool { @@ -53,17 +53,17 @@ pub fn cache_dir<S: AsRef<str>>(name: S) -> io::Result<PathBuf> { } pub struct TempNamedPipe { - name: String, + path: TempPath, #[cfg(not(target_os = "windows"))] reader: tokio::net::unix::pipe::Receiver, #[cfg(target_os = "windows")] - reader: tokio::net::windows::named_pipe::NamedPipeServer, + file: tokio::fs::File, } impl TempNamedPipe { - pub fn name(&self) -> String { - self.name.clone() + pub fn path(&self) -> &Path { + &self.path } } @@ -73,40 +73,67 @@ impl AsyncRead for TempNamedPipe { cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll<io::Result<()>> { - Pin::new(&mut self.reader).poll_read(cx, buf) + #[cfg(not(target_os = "windows"))] + return Pin::new(&mut self.reader).poll_read(cx, buf); + // very very dirty implementation of a 'tail' like behavior + #[cfg(target_os = "windows")] + { + let mut tmp_bytes = vec![0; buf.remaining()]; + let mut tmp_buf = ReadBuf::new(tmp_bytes.as_mut_slice()); + + loop { + return match Pin::new(&mut self.file).poll_read(cx, &mut tmp_buf) { + Poll::Ready(r) => { + if r.is_ok() { + if !tmp_buf.filled().is_empty() { + buf.put_slice(tmp_buf.filled()) + } else { + // sleep to not loop insanely fast and consume unnecessary system resources + std::thread::sleep(std::time::Duration::from_millis(50)); + continue; + } + } + Poll::Ready(r) + } + Poll::Pending => Poll::Pending, + }; + } + } } } impl Drop for TempNamedPipe { fn drop(&mut self) { #[cfg(not(target_os = "windows"))] - let _ = nix::unistd::unlink(self.name.as_str()); + let _ = nix::unistd::unlink(self.path.to_string_lossy().to_string().as_str()); } } pub fn temp_named_pipe() -> io::Result<TempNamedPipe> { - let (_, path) = tempfile("")?.keep()?; - let _ = fs::remove_file(path.clone()); + let tmp = tempfile("")?; #[cfg(not(target_os = "windows"))] { - let filename = path.to_string_lossy().to_string(); + let path = tmp.into_temp_path(); + let _ = fs::remove_file(&path); - nix::unistd::mkfifo(filename.as_str(), nix::sys::stat::Mode::S_IRWXU)?; + nix::unistd::mkfifo( + path.to_string_lossy().to_string().as_str(), + nix::sys::stat::Mode::S_IRWXU, + )?; Ok(TempNamedPipe { reader: tokio::net::unix::pipe::OpenOptions::new().open_receiver(&path)?, - name: filename, + path, }) } #[cfg(target_os = "windows")] { - let pipe_name = path.file_name().unwrap().to_string_lossy().to_string(); - let pipe_path = format!(r"\\.\pipe\{}", pipe_name); + let (file, path) = tmp.into_parts(); Ok(TempNamedPipe { - reader: tokio::net::windows::named_pipe::ServerOptions::new().create(pipe_path)?, - name: pipe_name, + file: tokio::fs::File::from_std(file), + path, }) } } From 3f401ccbd7b26e4da20035eaaebffb626ce1790e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 10 Jan 2024 23:17:20 +0100 Subject: [PATCH 511/630] Fix output progressbar always on 100% when using `download` --- crunchy-cli-core/src/utils/download.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index bcbb49f..e5b0444 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -231,6 +231,13 @@ impl Downloader { }, }) } + + let (len, fps) = get_video_stats(&video_path)?; + let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps; + if frames > max_frames { + max_frames = frames; + } + if !format.subtitles.is_empty() { let progress_spinner = if log::max_level() == LevelFilter::Info { let progress_spinner = ProgressBar::new_spinner() @@ -252,11 +259,6 @@ impl Downloader { None }; - let (len, fps) = get_video_stats(&video_path)?; - let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps; - if frames > max_frames { - max_frames = frames; - } for (subtitle, not_cc) in format.subtitles.iter() { if let Some(pb) = &progress_spinner { let mut progress_message = pb.message(); From f3faa5bf9458b656ef068cd3aebff5343cd924c6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 11 Jan 2024 13:47:31 +0100 Subject: [PATCH 512/630] Update dependencies and version --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2edb15a..3cec7e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.2.3" +version = "3.2.4" dependencies = [ "chrono", "clap", @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.2.3" +version = "3.2.4" dependencies = [ "anyhow", "async-speed-limit", @@ -787,9 +787,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" dependencies = [ "bytes", "fnv", diff --git a/Cargo.toml b/Cargo.toml index 43d64dd..ae62c61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.2.3" +version = "3.2.4" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 56b5a04..3df8090 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.2.3" +version = "3.2.4" edition = "2021" license = "MIT" From 20f796f603e6c151154e9fed8d49caf42a433b40 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Jan 2024 20:36:00 +0100 Subject: [PATCH 513/630] Re-add download timeout --- crunchy-cli-core/src/archive/command.rs | 19 +++--- crunchy-cli-core/src/download/command.rs | 23 +++---- crunchy-cli-core/src/lib.rs | 79 ++++++++++++++---------- crunchy-cli-core/src/utils/context.rs | 4 ++ crunchy-cli-core/src/utils/download.rs | 41 +++++++++--- crunchy-cli-core/src/utils/rate_limit.rs | 1 + 6 files changed, 104 insertions(+), 63 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 17f21a1..331374a 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -204,15 +204,16 @@ impl Execute for Archive { single_format_collection.full_visual_output(); - let download_builder = DownloadBuilder::new(ctx.crunchy.client()) - .default_subtitle(self.default_subtitle.clone()) - .download_fonts(self.include_fonts) - .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) - .ffmpeg_threads(self.ffmpeg_threads) - .output_format(Some("matroska".to_string())) - .audio_sort(Some(self.audio.clone())) - .subtitle_sort(Some(self.subtitle.clone())) - .threads(self.threads); + let download_builder = + DownloadBuilder::new(ctx.client.clone(), ctx.rate_limiter.clone()) + .default_subtitle(self.default_subtitle.clone()) + .download_fonts(self.include_fonts) + .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) + .ffmpeg_threads(self.ffmpeg_threads) + .output_format(Some("matroska".to_string())) + .audio_sort(Some(self.audio.clone())) + .subtitle_sort(Some(self.subtitle.clone())) + .threads(self.threads); for single_formats in single_format_collection.into_iter() { let (download_formats, mut format) = get_format(&self, &single_formats).await?; diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 30fea44..f72923f 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -216,17 +216,18 @@ impl Execute for Download { single_format_collection.full_visual_output(); - let download_builder = DownloadBuilder::new(ctx.crunchy.client()) - .default_subtitle(self.subtitle.clone()) - .force_hardsub(self.force_hardsub) - .output_format(if is_special_file(&self.output) || self.output == "-" { - Some("mpegts".to_string()) - } else { - None - }) - .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) - .ffmpeg_threads(self.ffmpeg_threads) - .threads(self.threads); + let download_builder = + DownloadBuilder::new(ctx.client.clone(), ctx.rate_limiter.clone()) + .default_subtitle(self.subtitle.clone()) + .force_hardsub(self.force_hardsub) + .output_format(if is_special_file(&self.output) || self.output == "-" { + Some("mpegts".to_string()) + } else { + None + }) + .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) + .ffmpeg_threads(self.ffmpeg_threads) + .threads(self.threads); for mut single_formats in single_format_collection.into_iter() { // the vec contains always only one item diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index f1d659a..2d37aaa 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -8,7 +8,7 @@ use crunchyroll_rs::crunchyroll::CrunchyrollBuilder; use crunchyroll_rs::error::Error; use crunchyroll_rs::{Crunchyroll, Locale}; use log::{debug, error, warn, LevelFilter}; -use reqwest::Proxy; +use reqwest::{Client, Proxy}; use std::{env, fs}; mod archive; @@ -235,11 +235,51 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { } async fn create_ctx(cli: &mut Cli) -> Result<Context> { - let crunchy = crunchyroll_session(cli).await?; - Ok(Context { crunchy }) + let client = { + let mut builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(p) = &cli.proxy { + builder = builder.proxy(p.clone()) + } + if let Some(ua) = &cli.user_agent { + builder = builder.user_agent(ua) + } + + #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] + let client = { + let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); + + for certificate in rustls_native_certs::load_native_certs().unwrap() { + builder = builder.add_root_certificate( + reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), + ) + } + + builder.build().unwrap() + }; + #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] + let client = builder.build().unwrap(); + + client + }; + + let rate_limiter = cli + .speed_limit + .map(|l| RateLimiterService::new(l, client.clone())); + + let crunchy = crunchyroll_session(cli, client.clone(), rate_limiter.clone()).await?; + + Ok(Context { + crunchy, + client, + rate_limiter, + }) } -async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { +async fn crunchyroll_session( + cli: &mut Cli, + client: Client, + rate_limiter: Option<RateLimiterService>, +) -> Result<Crunchyroll> { let supported_langs = vec![ Locale::ar_ME, Locale::de_DE, @@ -273,33 +313,6 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { lang }; - let client = { - let mut builder = CrunchyrollBuilder::predefined_client_builder(); - if let Some(p) = &cli.proxy { - builder = builder.proxy(p.clone()) - } - if let Some(ua) = &cli.user_agent { - builder = builder.user_agent(ua) - } - - #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] - let client = { - let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); - - for certificate in rustls_native_certs::load_native_certs().unwrap() { - builder = builder.add_root_certificate( - reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), - ) - } - - builder.build().unwrap() - }; - #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] - let client = builder.build().unwrap(); - - client - }; - let mut builder = Crunchyroll::builder() .locale(locale) .client(client.clone()) @@ -308,8 +321,8 @@ async fn crunchyroll_session(cli: &mut Cli) -> Result<Crunchyroll> { if let Command::Download(download) = &cli.command { builder = builder.preferred_audio_locale(download.audio.clone()) } - if let Some(speed_limit) = cli.speed_limit { - builder = builder.middleware(RateLimiterService::new(speed_limit, client)); + if let Some(rate_limiter) = rate_limiter { + builder = builder.middleware(rate_limiter) } let root_login_methods_count = cli.login_method.credentials.is_some() as u8 diff --git a/crunchy-cli-core/src/utils/context.rs b/crunchy-cli-core/src/utils/context.rs index f8df024..693174d 100644 --- a/crunchy-cli-core/src/utils/context.rs +++ b/crunchy-cli-core/src/utils/context.rs @@ -1,5 +1,9 @@ +use crate::utils::rate_limit::RateLimiterService; use crunchyroll_rs::Crunchyroll; +use reqwest::Client; pub struct Context { pub crunchy: Crunchyroll, + pub client: Client, + pub rate_limiter: Option<RateLimiterService>, } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index e5b0444..df32ad7 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,6 +1,7 @@ use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::real_dedup_vec; use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pipe, tempfile}; +use crate::utils::rate_limit::RateLimiterService; use anyhow::{bail, Result}; use chrono::NaiveTime; use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; @@ -26,6 +27,7 @@ use tokio::sync::mpsc::unbounded_channel; use tokio::sync::Mutex; use tokio::task::JoinSet; use tokio_util::sync::CancellationToken; +use tower_service::Service; #[derive(Clone, Debug)] pub enum MergeBehavior { @@ -48,6 +50,7 @@ impl MergeBehavior { #[derive(Clone, derive_setters::Setters)] pub struct DownloadBuilder { client: Client, + rate_limiter: Option<RateLimiterService>, ffmpeg_preset: FFmpegPreset, default_subtitle: Option<Locale>, output_format: Option<String>, @@ -60,9 +63,10 @@ pub struct DownloadBuilder { } impl DownloadBuilder { - pub fn new(client: Client) -> DownloadBuilder { + pub fn new(client: Client, rate_limiter: Option<RateLimiterService>) -> DownloadBuilder { Self { client, + rate_limiter, ffmpeg_preset: FFmpegPreset::default(), default_subtitle: None, output_format: None, @@ -78,6 +82,7 @@ impl DownloadBuilder { pub fn build(self) -> Downloader { Downloader { client: self.client, + rate_limiter: self.rate_limiter, ffmpeg_preset: self.ffmpeg_preset, default_subtitle: self.default_subtitle, output_format: self.output_format, @@ -109,6 +114,7 @@ pub struct DownloadFormat { pub struct Downloader { client: Client, + rate_limiter: Option<RateLimiterService>, ffmpeg_preset: FFmpegPreset, default_subtitle: Option<Locale>, @@ -768,6 +774,8 @@ impl Downloader { for num in 0..cpus { let thread_sender = sender.clone(); let thread_segments = segs.remove(0); + let thread_client = self.client.clone(); + let mut thread_rate_limiter = self.rate_limiter.clone(); let thread_count = count.clone(); join_set.spawn(async move { let after_download_sender = thread_sender.clone(); @@ -778,21 +786,34 @@ impl Downloader { let download = || async move { for (i, segment) in thread_segments.into_iter().enumerate() { let mut retry_count = 0; - let buf = loop { - let mut buf = vec![]; - match segment.write_to(&mut buf).await { - Ok(_) => break buf, - Err(e) => { - if retry_count == 5 { - bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), e) - } - debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), e, 5 - retry_count) + let mut buf = loop { + let request = thread_client + .get(&segment.url) + .timeout(Duration::from_secs(60)); + let response = if let Some(rate_limiter) = &mut thread_rate_limiter { + rate_limiter.call(request.build()?).await.map_err(anyhow::Error::new) + } else { + request.send().await.map_err(anyhow::Error::new) + }; + + let err = match response { + Ok(r) => match r.bytes().await { + Ok(b) => break b.to_vec(), + Err(e) => anyhow::Error::new(e) } + Err(e) => e, + }; + + if retry_count == 5 { + bail!("Max retry count reached ({}), multiple errors occurred while receiving segment {}: {}", retry_count, num + (i * cpus), err) } + debug!("Failed to download segment {} ({}). Retrying, {} out of 5 retries left", num + (i * cpus), err, 5 - retry_count); retry_count += 1; }; + buf = VariantSegment::decrypt(&mut buf, segment.key)?.to_vec(); + let mut c = thread_count.lock().await; debug!( "Downloaded and decrypted segment [{}/{} {:.2}%] {}", diff --git a/crunchy-cli-core/src/utils/rate_limit.rs b/crunchy-cli-core/src/utils/rate_limit.rs index 16b22b3..bca0aaa 100644 --- a/crunchy-cli-core/src/utils/rate_limit.rs +++ b/crunchy-cli-core/src/utils/rate_limit.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use std::task::{Context, Poll}; use tower_service::Service; +#[derive(Clone)] pub struct RateLimiterService { client: Arc<Client>, rate_limiter: Limiter, From 5490243df8f9622dbb75ed8916f24cec12b6acb9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Jan 2024 21:02:33 +0100 Subject: [PATCH 514/630] Fix episode filtering not working if specifying no season --- crunchy-cli-core/src/utils/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index 42d8f93..d82e3d3 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -48,7 +48,7 @@ impl UrlFilter { if season < from_season || season > to_season { false - } else if season == from_season { + } else if season == from_season || (f.from_season.is_none() && f.to_season.is_none()) { episode >= from_episode && episode <= to_episode } else { true From fbe182239a219b5bd13e05e8f254346773edf4f5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Jan 2024 22:15:08 +0100 Subject: [PATCH 515/630] Update dependencies and version --- Cargo.lock | 60 ++++++++++++++++++------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cec7e0..58e768c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" dependencies = [ "anstyle", "anstyle-parse", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.6" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64-serde" @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.14" +version = "4.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" +checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" dependencies = [ "clap_builder", "clap_derive", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.14" +version = "4.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" +checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" dependencies = [ "anstream", "anstyle", @@ -299,9 +299,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clap_mangen" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b5db60b3310cdb376fbeb8826e875a38080d0c61bdec0a91a3da8338948736" +checksum = "4a7c2b01e5e779c19f46a94bbd398f33ae63b0f78c07108351fb4536845bb7fd" dependencies = [ "clap", "roff", @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.2.4" +version = "3.2.5" dependencies = [ "chrono", "clap", @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.2.4" +version = "3.2.5" dependencies = [ "anyhow", "async-speed-limit", @@ -1056,9 +1056,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -1494,9 +1494,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.1", "errno", @@ -2067,9 +2067,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2077,9 +2077,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", @@ -2092,9 +2092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -2104,9 +2104,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2114,9 +2114,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", @@ -2127,9 +2127,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-streams" @@ -2146,9 +2146,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index ae62c61..425e078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.2.4" +version = "3.2.5" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 3df8090..a937e01 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.2.4" +version = "3.2.5" edition = "2021" license = "MIT" From 937e9a2fdcfa1aafd12d54b6a7522d9285d42fc6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Jan 2024 22:33:32 +0100 Subject: [PATCH 516/630] Fix verbosity not applied if flag is used globally --- crunchy-cli-core/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 2d37aaa..bc04ae3 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -36,7 +36,7 @@ trait Execute { #[clap(name = "crunchy-cli")] pub struct Cli { #[clap(flatten)] - verbosity: Option<Verbosity>, + verbosity: Verbosity, #[arg( help = "Overwrite the language in which results are returned. Default is your system language" @@ -116,13 +116,13 @@ struct Verbosity { pub async fn cli_entrypoint() { let mut cli: Cli = Cli::parse(); - if let Some(verbosity) = &cli.verbosity { - if verbosity.verbose as u8 + verbosity.quiet as u8 > 1 { + if cli.verbosity.verbose || cli.verbosity.quiet { + if cli.verbosity.verbose && cli.verbosity.quiet { eprintln!("Output cannot be verbose ('-v') and quiet ('-q') at the same time"); std::process::exit(1) - } else if verbosity.verbose { + } else if cli.verbosity.verbose { CliLogger::init(LevelFilter::Debug).unwrap() - } else if verbosity.quiet { + } else if cli.verbosity.quiet { CliLogger::init(LevelFilter::Error).unwrap() } } else { @@ -134,14 +134,14 @@ pub async fn cli_entrypoint() { match &mut cli.command { Command::Archive(archive) => { // prevent interactive select to be shown when output should be quiet - if cli.verbosity.is_some() && cli.verbosity.as_ref().unwrap().quiet { + if cli.verbosity.quiet { archive.yes = true; } pre_check_executor(archive).await } Command::Download(download) => { // prevent interactive select to be shown when output should be quiet - if cli.verbosity.is_some() && cli.verbosity.as_ref().unwrap().quiet { + if cli.verbosity.quiet { download.yes = true; } pre_check_executor(download).await From 6e01e9e8a77d435d2835b1cf0d644d82ca7dbdb6 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Jan 2024 22:39:05 +0100 Subject: [PATCH 517/630] Fix comment misspelling --- crunchy-cli-core/src/utils/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index d82e3d3..70cbfbb 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -138,7 +138,7 @@ pub async fn parse_url( let old_url_regex = Regex::new(r"https?://(www\.)?crunchyroll\.com/.+").unwrap(); if old_url_regex.is_match(&url) { debug!("Detected maybe old url"); - // replace the 'http' prefix with 'https' as https is not supported by the reqwest client + // replace the 'http' prefix with 'https' as http is not supported by the reqwest client if url.starts_with("http://") { url.replace_range(0..4, "https") } From 658bb86800cd4447a28859fbc9a59e1c673ac3c4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 26 Jan 2024 00:07:15 +0100 Subject: [PATCH 518/630] Run ci on every branch --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa70330..3d3c984 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,6 @@ name: ci on: push: - branches: - - master pull_request: workflow_dispatch: From 444dc65a298dd34d5beb33c9e3bef11b6c7c1fd2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 28 Jan 2024 00:45:37 +0100 Subject: [PATCH 519/630] Clarify risks of using the `--experimental-fixes` flag --- crunchy-cli-core/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index bc04ae3..d362a2c 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -44,9 +44,12 @@ pub struct Cli { #[arg(global = true, long)] lang: Option<Locale>, - #[arg(help = "Enable experimental fixes which may resolve some unexpected errors")] + #[arg( + help = "Enable experimental fixes which may resolve some unexpected errors. Generally not recommended as this flag may crash the program completely" + )] #[arg( long_help = "Enable experimental fixes which may resolve some unexpected errors. \ + It is not recommended to use this this flag regularly, it might cause unexpected errors which may crash the program completely. \ If everything works as intended this option isn't needed, but sometimes Crunchyroll mislabels \ the audio of a series/season or episode or returns a wrong season number. This is when using this option might help to solve the issue" )] From 3b9fc5289066b696565c52e1f14209c583ea8664 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 28 Jan 2024 01:03:39 +0100 Subject: [PATCH 520/630] Add notice & warning that an anonymous or non-premium account may result to incomplete results with `search` (#288) --- README.md | 4 ++++ crunchy-cli-core/src/search/command.rs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index be97bec..f088fd7 100644 --- a/README.md +++ b/README.md @@ -517,6 +517,10 @@ The `archive` command lets you download episodes with multiple audios and subtit ### Search +The `search` command is a powerful tool to query the Crunchyroll library. +It behaves like the regular search on the website but is able to further process the results and return everything it can find, from the series title down to the raw stream url. +_Using this command with the `--anonymous` flag or a non-premium account may return incomplete results._ + **Supported urls/input** - Single episode (with [episode filtering](#episode-filtering)) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index c357ab4..3d03d28 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -7,6 +7,7 @@ use anyhow::{bail, Result}; use crunchyroll_rs::common::StreamExt; use crunchyroll_rs::search::QueryResults; use crunchyroll_rs::{Episode, Locale, MediaCollection, MovieListing, MusicVideo, Series}; +use log::warn; #[derive(Debug, clap::Parser)] #[clap(about = "Search in videos")] @@ -102,6 +103,10 @@ pub struct Search { impl Execute for Search { async fn execute(self, ctx: Context) -> Result<()> { + if !ctx.crunchy.premium() { + warn!("Using `search` anonymously or with a non-premium account may return incomplete results") + } + let input = if crunchyroll_rs::parse::parse_url(&self.input).is_some() { match parse_url(&ctx.crunchy, self.input.clone(), true).await { Ok(ok) => vec![ok], From 7cf7a8e71c476c56806da00a8f41a92db0e44350 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 28 Jan 2024 02:04:42 +0100 Subject: [PATCH 521/630] Take closed_captions api field for subtitles into account (#297) --- crunchy-cli-core/src/archive/command.rs | 12 +++++++++--- crunchy-cli-core/src/download/command.rs | 7 ++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 331374a..065b3da 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -301,8 +301,8 @@ async fn get_format( let subtitles: Vec<(Subtitle, bool)> = archive .subtitle .iter() - .filter_map(|s| { - stream + .flat_map(|s| { + let subtitles = stream .subtitles .get(s) .cloned() @@ -313,7 +313,13 @@ async fn get_format( l, single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, ) - }) + }); + let cc = stream.closed_captions.get(s).cloned().map(|l| (l, false)); + + subtitles + .into_iter() + .chain(cc.into_iter()) + .collect::<Vec<(Subtitle, bool)>>() }) .collect(); diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index f72923f..d1565c7 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -317,7 +317,12 @@ async fn get_format( let subtitle = if contains_hardsub { None } else if let Some(subtitle_locale) = &download.subtitle { - stream.subtitles.get(subtitle_locale).cloned() + stream + .subtitles + .get(subtitle_locale) + .cloned() + // use closed captions as fallback if no actual subtitles are found + .or_else(|| stream.closed_captions.get(subtitle_locale).cloned()) } else { None }; From a4abb14ae357657132cf8e4e1d0edb7e92d5379b Mon Sep 17 00:00:00 2001 From: kralverde <80051564+kralverde@users.noreply.github.com> Date: Mon, 29 Jan 2024 02:18:42 -0500 Subject: [PATCH 522/630] use a 'close enough' method to audio auto merge (#286) (#320) * use a 'close enough' method to audio merge * change default, rename flag, and use more gooder words --- crunchy-cli-core/src/archive/command.rs | 62 ++++++++++++++++++------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 065b3da..b045cce 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -16,7 +16,7 @@ use chrono::Duration; use crunchyroll_rs::media::{Resolution, Subtitle}; use crunchyroll_rs::Locale; use log::{debug, warn}; -use std::collections::HashMap; +use std::ops::Sub; use std::path::PathBuf; #[derive(Clone, Debug, clap::Parser)] @@ -86,6 +86,12 @@ pub struct Archive { #[arg(value_parser = MergeBehavior::parse)] pub(crate) merge: MergeBehavior, + #[arg( + help = "If the merge behavior is 'auto', only download multiple video tracks if their length difference is higher than the given milliseconds" + )] + #[arg(long, default_value_t = 200)] + pub(crate) merge_auto_tolerance: u32, + #[arg(help = format!("Presets for converting the video to a specific coding format. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long_help = format!("Presets for converting the video to a specific coding format. \ @@ -356,26 +362,46 @@ async fn get_format( .collect(), }), MergeBehavior::Auto => { - let mut d_formats: HashMap<Duration, DownloadFormat> = HashMap::new(); - + let mut d_formats: Vec<(Duration, DownloadFormat)> = vec![]; + for (single_format, video, audio, subtitles) in format_pairs { - if let Some(d_format) = d_formats.get_mut(&single_format.duration) { - d_format.audios.push((audio, single_format.audio.clone())); - d_format.subtitles.extend(subtitles) - } else { - d_formats.insert( - single_format.duration, - DownloadFormat { - video: (video, single_format.audio.clone()), - audios: vec![(audio, single_format.audio.clone())], - subtitles, - }, - ); - } + let closest_format = d_formats.iter_mut().min_by(|(x, _), (y, _)| { + x.sub(single_format.duration) + .abs() + .cmp(&y.sub(single_format.duration).abs()) + }); + + match closest_format { + Some(closest_format) + if closest_format + .0 + .sub(single_format.duration) + .abs() + .num_milliseconds() + < archive.merge_auto_tolerance.into() => + { + // If less than `audio_error` apart, use same audio. + closest_format + .1 + .audios + .push((audio, single_format.audio.clone())); + closest_format.1.subtitles.extend(subtitles); + } + _ => { + d_formats.push(( + single_format.duration, + DownloadFormat { + video: (video, single_format.audio.clone()), + audios: vec![(audio, single_format.audio.clone())], + subtitles, + }, + )); + } + }; } - for d_format in d_formats.into_values() { - download_formats.push(d_format) + for (_, d_format) in d_formats.into_iter() { + download_formats.push(d_format); } } } From 982e521e0b9cbd98d0443005a68b5a72a4bdcfa6 Mon Sep 17 00:00:00 2001 From: kralverde <80051564+kralverde@users.noreply.github.com> Date: Mon, 29 Jan 2024 02:24:56 -0500 Subject: [PATCH 523/630] add universal output flag (#319) * add universal filenames setting * rename flag and help --- crunchy-cli-core/src/archive/command.rs | 8 ++++- crunchy-cli-core/src/download/command.rs | 8 ++++- crunchy-cli-core/src/utils/format.rs | 38 ++++++++++++------------ crunchy-cli-core/src/utils/os.rs | 4 +-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index b045cce..7ed45d7 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -64,6 +64,11 @@ pub struct Archive { #[arg(long)] pub(crate) output_specials: Option<String>, + #[arg(help = "Sanitize the output file for use with all operating systems. \ + This option only affects template options and not static characters.")] + #[arg(long, default_value_t = false)] + pub(crate) universal_output: bool, + #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution. \ Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ @@ -234,9 +239,10 @@ impl Execute for Archive { self.output_specials .as_ref() .map_or((&self.output).into(), |so| so.into()), + self.universal_output, ) } else { - format.format_path((&self.output).into()) + format.format_path((&self.output).into(), self.universal_output) }; let (path, changed) = free_file(formatted_path.clone()); diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index d1565c7..7f3f305 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -60,6 +60,11 @@ pub struct Download { #[arg(long)] pub(crate) output_specials: Option<String>, + #[arg(help = "Sanitize the output file for use with all operating systems. \ + This option only affects template options and not static characters.")] + #[arg(long, default_value_t = false)] + pub(crate) universal_output: bool, + #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution. \ Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ @@ -254,9 +259,10 @@ impl Execute for Download { self.output_specials .as_ref() .map_or((&self.output).into(), |so| so.into()), + self.universal_output, ) } else { - format.format_path((&self.output).into()) + format.format_path((&self.output).into(), self.universal_output) }; let (path, changed) = free_file(formatted_path.clone()); diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 716f124..b3ec050 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -408,11 +408,11 @@ impl Format { } /// Formats the given string if it has specific pattern in it. It also sanitizes the filename. - pub fn format_path(&self, path: PathBuf) -> PathBuf { + pub fn format_path(&self, path: PathBuf, universal: bool) -> PathBuf { let path = path .to_string_lossy() .to_string() - .replace("{title}", &sanitize(&self.title, true)) + .replace("{title}", &sanitize(&self.title, true, universal)) .replace( "{audio}", &sanitize( @@ -421,30 +421,30 @@ impl Format { .map(|(a, _)| a.to_string()) .collect::<Vec<String>>() .join("|"), - true, + true, universal, ), ) - .replace("{resolution}", &sanitize(self.resolution.to_string(), true)) + .replace("{resolution}", &sanitize(self.resolution.to_string(), true, universal)) .replace( "{width}", - &sanitize(self.resolution.width.to_string(), true), + &sanitize(self.resolution.width.to_string(), true, universal), ) .replace( "{height}", - &sanitize(self.resolution.height.to_string(), true), + &sanitize(self.resolution.height.to_string(), true, universal), ) - .replace("{series_id}", &sanitize(&self.series_id, true)) - .replace("{series_name}", &sanitize(&self.series_name, true)) - .replace("{season_id}", &sanitize(&self.season_id, true)) - .replace("{season_name}", &sanitize(&self.season_title, true)) + .replace("{series_id}", &sanitize(&self.series_id, true, universal)) + .replace("{series_name}", &sanitize(&self.series_name, true, universal)) + .replace("{season_id}", &sanitize(&self.season_id, true, universal)) + .replace("{season_name}", &sanitize(&self.season_title, true, universal)) .replace( "{season_number}", - &format!("{:0>2}", sanitize(self.season_number.to_string(), true)), + &format!("{:0>2}", sanitize(self.season_number.to_string(), true, universal)), ) - .replace("{episode_id}", &sanitize(&self.episode_id, true)) + .replace("{episode_id}", &sanitize(&self.episode_id, true, universal)) .replace( "{episode_number}", - &format!("{:0>2}", sanitize(&self.episode_number, true)), + &format!("{:0>2}", sanitize(&self.episode_number, true, universal)), ) .replace( "{relative_episode_number}", @@ -452,13 +452,13 @@ impl Format { "{:0>2}", sanitize( self.relative_episode_number.unwrap_or_default().to_string(), - true, + true, universal, ) ), ) .replace( "{sequence_number}", - &format!("{:0>2}", sanitize(self.sequence_number.to_string(), true)), + &format!("{:0>2}", sanitize(self.sequence_number.to_string(), true, universal)), ) .replace( "{relative_sequence_number}", @@ -468,21 +468,21 @@ impl Format { self.relative_sequence_number .unwrap_or_default() .to_string(), - true, + true, universal, ) ), ) .replace( "{release_year}", - &sanitize(self.release_year.to_string(), true), + &sanitize(self.release_year.to_string(), true, universal), ) .replace( "{release_month}", - &format!("{:0>2}", sanitize(self.release_month.to_string(), true)), + &format!("{:0>2}", sanitize(self.release_month.to_string(), true, universal)), ) .replace( "{release_day}", - &format!("{:0>2}", sanitize(self.release_day.to_string(), true)), + &format!("{:0>2}", sanitize(self.release_day.to_string(), true, universal)), ); PathBuf::from(path) diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index a9d3ede..e57c4d0 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -191,7 +191,7 @@ lazy_static::lazy_static! { } /// Sanitizes a filename with the option to include/exclude the path separator from sanitizing. -pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String { +pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool, universal: bool) -> String { let path = Cow::from(path.as_ref().trim()); let path = RESERVED_RE.replace(&path, ""); @@ -204,7 +204,7 @@ pub fn sanitize<S: AsRef<str>>(path: S, include_path_separator: bool) -> String } }; - if cfg!(windows) { + if universal || cfg!(windows) { let path = WINDOWS_NON_PRINTABLE_RE.replace_all(&path, ""); let path = WINDOWS_ILLEGAL_RE.replace_all(&path, ""); let path = WINDOWS_RESERVED_RE.replace_all(&path, ""); From f8309f2e803fed7f450e219779253643e72f60b3 Mon Sep 17 00:00:00 2001 From: kralverde <80051564+kralverde@users.noreply.github.com> Date: Mon, 29 Jan 2024 02:26:40 -0500 Subject: [PATCH 524/630] add archive no-closed-captions flag (#323) --- crunchy-cli-core/src/archive/command.rs | 5 +++++ crunchy-cli-core/src/utils/download.rs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7ed45d7..c860b0a 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -125,6 +125,10 @@ pub struct Archive { #[arg(long)] pub(crate) include_fonts: bool, + #[arg(help = "Omit closed caption subtitles in the downloaded file")] + #[arg(long, default_value_t = false)] + pub(crate) no_closed_caption: bool, + #[arg(help = "Skip files which are already existing")] #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, @@ -224,6 +228,7 @@ impl Execute for Archive { .output_format(Some("matroska".to_string())) .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())) + .no_closed_caption(self.no_closed_caption) .threads(self.threads); for single_formats in single_format_collection.into_iter() { diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index df32ad7..317a709 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -58,6 +58,7 @@ pub struct DownloadBuilder { subtitle_sort: Option<Vec<Locale>>, force_hardsub: bool, download_fonts: bool, + no_closed_caption: bool, threads: usize, ffmpeg_threads: Option<usize>, } @@ -74,6 +75,7 @@ impl DownloadBuilder { subtitle_sort: None, force_hardsub: false, download_fonts: false, + no_closed_caption: false, threads: num_cpus::get(), ffmpeg_threads: None, } @@ -91,6 +93,7 @@ impl DownloadBuilder { force_hardsub: self.force_hardsub, download_fonts: self.download_fonts, + no_closed_caption: self.no_closed_caption, download_threads: self.threads, ffmpeg_threads: self.ffmpeg_threads, @@ -124,6 +127,7 @@ pub struct Downloader { force_hardsub: bool, download_fonts: bool, + no_closed_caption: bool, download_threads: usize, ffmpeg_threads: Option<usize>, @@ -266,6 +270,10 @@ impl Downloader { }; for (subtitle, not_cc) in format.subtitles.iter() { + if !not_cc && self.no_closed_caption { + continue; + } + if let Some(pb) = &progress_spinner { let mut progress_message = pb.message(); if !progress_message.is_empty() { From 0f06c7ac71e6eeff81e468b1f3a9b95829378c96 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 29 Jan 2024 11:52:24 +0100 Subject: [PATCH 525/630] Change delimiter of audio template option to `_` and make it configurable via the `CRUNCHY_CLI_FORMAT_DELIMITER` env variable (#311) --- crunchy-cli-core/src/utils/format.rs | 50 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index b3ec050..e60ef54 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -8,6 +8,7 @@ use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVide use log::{debug, info}; use std::cmp::Ordering; use std::collections::BTreeMap; +use std::env; use std::path::{Path, PathBuf}; #[derive(Clone)] @@ -420,11 +421,18 @@ impl Format { .iter() .map(|(a, _)| a.to_string()) .collect::<Vec<String>>() - .join("|"), - true, universal, + .join( + &env::var("CRUNCHY_CLI_FORMAT_DELIMITER") + .map_or("_".to_string(), |e| e), + ), + true, + universal, ), ) - .replace("{resolution}", &sanitize(self.resolution.to_string(), true, universal)) + .replace( + "{resolution}", + &sanitize(self.resolution.to_string(), true, universal), + ) .replace( "{width}", &sanitize(self.resolution.width.to_string(), true, universal), @@ -434,12 +442,21 @@ impl Format { &sanitize(self.resolution.height.to_string(), true, universal), ) .replace("{series_id}", &sanitize(&self.series_id, true, universal)) - .replace("{series_name}", &sanitize(&self.series_name, true, universal)) + .replace( + "{series_name}", + &sanitize(&self.series_name, true, universal), + ) .replace("{season_id}", &sanitize(&self.season_id, true, universal)) - .replace("{season_name}", &sanitize(&self.season_title, true, universal)) + .replace( + "{season_name}", + &sanitize(&self.season_title, true, universal), + ) .replace( "{season_number}", - &format!("{:0>2}", sanitize(self.season_number.to_string(), true, universal)), + &format!( + "{:0>2}", + sanitize(self.season_number.to_string(), true, universal) + ), ) .replace("{episode_id}", &sanitize(&self.episode_id, true, universal)) .replace( @@ -452,13 +469,17 @@ impl Format { "{:0>2}", sanitize( self.relative_episode_number.unwrap_or_default().to_string(), - true, universal, + true, + universal, ) ), ) .replace( "{sequence_number}", - &format!("{:0>2}", sanitize(self.sequence_number.to_string(), true, universal)), + &format!( + "{:0>2}", + sanitize(self.sequence_number.to_string(), true, universal) + ), ) .replace( "{relative_sequence_number}", @@ -468,7 +489,8 @@ impl Format { self.relative_sequence_number .unwrap_or_default() .to_string(), - true, universal, + true, + universal, ) ), ) @@ -478,11 +500,17 @@ impl Format { ) .replace( "{release_month}", - &format!("{:0>2}", sanitize(self.release_month.to_string(), true, universal)), + &format!( + "{:0>2}", + sanitize(self.release_month.to_string(), true, universal) + ), ) .replace( "{release_day}", - &format!("{:0>2}", sanitize(self.release_day.to_string(), true, universal)), + &format!( + "{:0>2}", + sanitize(self.release_day.to_string(), true, universal) + ), ); PathBuf::from(path) From a2464bad4ed7528e67ac6d0579cfb16b30982919 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 30 Jan 2024 23:49:20 +0100 Subject: [PATCH 526/630] Add M1 runner to mac build ci --- .github/workflows/ci.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d3c984..ef9e921 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,18 @@ jobs: if-no-files-found: error build-mac: - runs-on: macos-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 uses x86_64, macos-14 aarch64 + # see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + include: + - os: macos-13 + arch: x86_64 + toolchain: x86_64-apple-darwin + - os: macos-14 + arch: aarch64 + toolchain: aarch64-apple-darwin steps: - name: Checkout uses: actions/checkout@v4 @@ -85,13 +96,13 @@ jobs: toolchain: stable - name: Build - run: cargo build --release --target x86_64-apple-darwin + run: cargo build --release --target ${{ matrix.toolchain }} - name: Upload binary artifact uses: actions/upload-artifact@v3 with: - name: crunchy-cli-darwin-x86_64 - path: ./target/x86_64-apple-darwin/release/crunchy-cli + name: crunchy-cli-darwin-${{ matrix.arch }} + path: ./target/${{ matrix.toolchain }}/release/crunchy-cli if-no-files-found: error build-windows: From 5d68f0334abf602ad5ea6f14c83af14912fae783 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 30 Jan 2024 23:55:52 +0100 Subject: [PATCH 527/630] Update actions used in ci --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef9e921..5727dbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v4 - name: Cargo cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/bin/ @@ -42,21 +42,21 @@ jobs: run: cross build --release --no-default-features --features openssl-tls-static --target ${{ matrix.toolchain }} - name: Upload binary artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crunchy-cli-linux-${{ matrix.arch }} path: ./target/${{ matrix.toolchain }}/release/crunchy-cli if-no-files-found: error - name: Upload manpages artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: manpages path: ./target/${{ matrix.toolchain }}/release/manpages if-no-files-found: error - name: Upload completions artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: completions path: ./target/${{ matrix.toolchain }}/release/completions @@ -80,7 +80,7 @@ jobs: uses: actions/checkout@v4 - name: Cargo cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/bin/ @@ -99,7 +99,7 @@ jobs: run: cargo build --release --target ${{ matrix.toolchain }} - name: Upload binary artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crunchy-cli-darwin-${{ matrix.arch }} path: ./target/${{ matrix.toolchain }}/release/crunchy-cli @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v4 - name: Cargo cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/bin/ @@ -133,7 +133,7 @@ jobs: run: cargo build --release --target x86_64-pc-windows-gnu - name: Upload binary artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crunchy-cli-windows-x86_64 path: ./target/x86_64-pc-windows-gnu/release/crunchy-cli.exe From 8187269128a88047a6c601ab763e22f3dd9f2490 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 1 Feb 2024 14:45:12 +0100 Subject: [PATCH 528/630] Upload manpages and completions only once in ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5727dbd..ae289c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,7 @@ jobs: if-no-files-found: error - name: Upload manpages artifact + if: ${{ matrix.arch == 'x86_64' }} # only upload the manpages once uses: actions/upload-artifact@v4 with: name: manpages @@ -56,6 +57,7 @@ jobs: if-no-files-found: error - name: Upload completions artifact + if: ${{ matrix.arch == 'x86_64' }} # only upload the completions once uses: actions/upload-artifact@v4 with: name: completions From c31b1f4db94295e8b903799280189a3c52e0b5e8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 14 Feb 2024 20:27:00 +0100 Subject: [PATCH 529/630] Update nix flake.lock (#333) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index b9c63db..e92c958 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1692128808, - "narHash": "sha256-Di1Zm/P042NuwThMiZNrtmaAjd4Tm2qBOKHX7xUOfMk=", + "lastModified": 1707877513, + "narHash": "sha256-sp0w2apswd3wv0sAEF7StOGHkns3XUQaO5erhWFZWXk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4ed9856be002a730234a1a1ed9dcd9dd10cbdb40", + "rev": "89653a03e0915e4a872788d10680e7eec92f8600", "type": "github" }, "original": { @@ -41,11 +41,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { From d3ab2245a84d549f1e31ece5c6f208fd484ec139 Mon Sep 17 00:00:00 2001 From: Kevin <16837578+KevinStaude@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:52:47 +0100 Subject: [PATCH 530/630] Update README.md minor fix --output-specials -o "something" isn't working --output-specials "something" is correct --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f088fd7..a4adc17 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ The `download` command lets you download episodes with a specific audio language Define an output template which only gets used when the episode is a special (episode number is 0 or has non-zero decimal places) by using the `--output-special` flag. ```shell - $ crunchy-cli download --output-specials -o "Special EP - {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal + $ crunchy-cli download --output-specials "Special EP - {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal ``` Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. @@ -411,7 +411,7 @@ The `archive` command lets you download episodes with multiple audios and subtit _crunchy-cli_ exclusively uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of its ability to store multiple audio, video and subtitle tracks at once. ```shell - $ crunchy-cli archive --output-specials -o "Special EP - {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal + $ crunchy-cli archive --output-specials "Special EP - {title}" https://www.crunchyroll.com/watch/GY8D975JY/veldoras-journal ``` Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. From 2084328069dcd253dd8c60e1151d63c176660d38 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 23 Feb 2024 17:35:12 +0100 Subject: [PATCH 531/630] Fix ffmpeg progress panic (#337) --- crunchy-cli-core/src/utils/download.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 317a709..9a67f7f 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1202,13 +1202,16 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( let Some(line) = line? else { break }; - let frame: u64 = current_frame - .captures(line.as_str()) - .unwrap() - .name("frame") - .unwrap() - .as_str() - .parse()?; + + // we're manually unpack the regex here as `.unwrap()` may fail in some cases, e.g. + // https://github.com/crunchy-labs/crunchy-cli/issues/337 + let Some(frame_cap) = current_frame.captures(line.as_str()) else { + break + }; + let Some(frame_str) = frame_cap.name("frame") else { + break + }; + let frame: u64 = frame_str.as_str().parse()?; if let Some(p) = &progress { p.set_position(frame) From 6a7aa25e1a6f58392daed84400656b8d7744bf9c Mon Sep 17 00:00:00 2001 From: bytedream <63594396+bytedream@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:46:48 +0100 Subject: [PATCH 532/630] Add ffmpeg amd hardware acceleration presets (#324) --- crunchy-cli-core/src/utils/ffmpeg.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index 39678dc..a61ed93 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -69,6 +69,7 @@ ffmpeg_enum! { ffmpeg_enum! { enum FFmpegHwAccel { Nvidia, + Amd, Apple } } @@ -101,7 +102,11 @@ impl FFmpegPreset { FFmpegHwAccel::all(), FFmpegQuality::all(), ), - (FFmpegCodec::Av1, vec![], FFmpegQuality::all()), + ( + FFmpegCodec::Av1, + vec![FFmpegHwAccel::Amd], + FFmpegQuality::all(), + ), ]; let mut return_values = vec![]; @@ -285,6 +290,10 @@ impl FFmpegPreset { crf_quality(); output.extend(["-c:v", "h264_nvenc", "-c:a", "copy"]) } + FFmpegHwAccel::Amd => { + crf_quality(); + output.extend(["-c:v", "h264_amf", "-c:a", "copy"]) + } FFmpegHwAccel::Apple => { // Apple's Video Toolbox encoders ignore `-crf`, use `-q:v` // instead. It's on a scale of 1-100, 100 being lossless. Just @@ -332,8 +341,12 @@ impl FFmpegPreset { "hvc1", ]) } + FFmpegHwAccel::Amd => { + crf_quality(); + output.extend(["-c:v", "hevc_amf", "-c:a", "copy"]) + } FFmpegHwAccel::Apple => { - // See the comment that starts on line 287. + // See the comment for apple h264 hwaccel match quality { FFmpegQuality::Lossless => output.extend(["-q:v", "61"]), FFmpegQuality::Normal => (), @@ -356,12 +369,17 @@ impl FFmpegPreset { } } FFmpegCodec::Av1 => { - output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); - - match quality { + let mut crf_quality = || match quality { FFmpegQuality::Lossless => output.extend(["-crf", "22"]), FFmpegQuality::Normal => (), FFmpegQuality::Low => output.extend(["-crf", "35"]), + }; + + crf_quality(); + if let Some(FFmpegHwAccel::Amd) = hwaccel_opt { + output.extend(["-c:v", "av1_amf", "-c:a", "copy"]); + } else { + output.extend(["-c:v", "libsvtav1", "-c:a", "copy"]); } } } From 5634ce3277f6e570a1ea9b5ca20ed0391a9f9d68 Mon Sep 17 00:00:00 2001 From: bytedream <63594396+bytedream@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:48:18 +0100 Subject: [PATCH 533/630] Add archive `--skip-existing-method` flag (#292) (#325) * Add archive `--skip-existing-method` flag (#292) * Fix re-download only issued when local file has more audios/subtitles & respect `--no-closed-captions` flag --- crunchy-cli-core/src/archive/command.rs | 148 +++++++++++++++++++++-- crunchy-cli-core/src/download/command.rs | 4 +- 2 files changed, 139 insertions(+), 13 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index c860b0a..741dd5b 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -16,8 +16,11 @@ use chrono::Duration; use crunchyroll_rs::media::{Resolution, Subtitle}; use crunchyroll_rs::Locale; use log::{debug, warn}; +use regex::Regex; +use std::fmt::{Display, Formatter}; use std::ops::Sub; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; #[derive(Clone, Debug, clap::Parser)] #[clap(about = "Archive a video")] @@ -68,7 +71,7 @@ pub struct Archive { This option only affects template options and not static characters.")] #[arg(long, default_value_t = false)] pub(crate) universal_output: bool, - + #[arg(help = "Video resolution")] #[arg(long_help = "The video resolution. \ Can either be specified via the pixels (e.g. 1920x1080), the abbreviation for pixels (e.g. 1080p) or 'common-use' words (e.g. best). \ @@ -129,9 +132,19 @@ pub struct Archive { #[arg(long, default_value_t = false)] pub(crate) no_closed_caption: bool, - #[arg(help = "Skip files which are already existing")] + #[arg(help = "Skip files which are already existing by their name")] #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, + #[arg( + help = "Only works in combination with `--skip-existing`. Sets the method how already existing files should be skipped. Valid methods are 'audio' and 'subtitle'" + )] + #[arg(long_help = "Only works in combination with `--skip-existing`. \ + By default, already existing files are determined by their name and the download of the corresponding episode is skipped. \ + With this flag you can modify this behavior. \ + Valid options are 'audio' and 'subtitle' (if the file already exists but the audio/subtitle are less from what should be downloaded, the episode gets downloaded and the file overwritten).")] + #[arg(long, default_values_t = SkipExistingMethod::default())] + #[arg(value_parser = SkipExistingMethod::parse)] + pub(crate) skip_existing_method: Vec<SkipExistingMethod>, #[arg(help = "Skip special episodes")] #[arg(long, default_value_t = false)] pub(crate) skip_specials: bool, @@ -244,19 +257,69 @@ impl Execute for Archive { self.output_specials .as_ref() .map_or((&self.output).into(), |so| so.into()), - self.universal_output, + self.universal_output, ) } else { format.format_path((&self.output).into(), self.universal_output) }; - let (path, changed) = free_file(formatted_path.clone()); + let (mut path, changed) = free_file(formatted_path.clone()); if changed && self.skip_existing { - debug!( - "Skipping already existing file '{}'", - formatted_path.to_string_lossy() - ); - continue; + let mut skip = true; + + if !self.skip_existing_method.is_empty() { + if let Some((mut audio_locales, mut subtitle_locales)) = + get_video_streams(&formatted_path)? + { + let method_audio = self + .skip_existing_method + .contains(&SkipExistingMethod::Audio); + let method_subtitle = self + .skip_existing_method + .contains(&SkipExistingMethod::Subtitle); + + let audio_differ = if method_audio { + format + .locales + .iter() + .any(|(a, _)| !audio_locales.contains(a)) + } else { + false + }; + let subtitle_differ = if method_subtitle { + format + .locales + .clone() + .into_iter() + .flat_map(|(a, mut s)| { + // remove the closed caption if the flag is given to omit + // closed captions + if self.no_closed_caption && a != Locale::ja_JP { + s.retain(|l| l != &a) + } + s + }) + .any(|l| !subtitle_locales.contains(&l)) + } else { + false + }; + + if (method_audio && audio_differ) + || (method_subtitle && subtitle_differ) + { + skip = false; + path = formatted_path.clone() + } + } + } + + if skip { + debug!( + "Skipping already existing file '{}'", + formatted_path.to_string_lossy() + ); + continue; + } } format.locales.sort_by(|(a, _), (b, _)| { @@ -284,6 +347,36 @@ impl Execute for Archive { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum SkipExistingMethod { + Audio, + Subtitle, +} + +impl Display for SkipExistingMethod { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let value = match self { + SkipExistingMethod::Audio => "audio", + SkipExistingMethod::Subtitle => "subtitle", + }; + write!(f, "{}", value) + } +} + +impl SkipExistingMethod { + fn parse(s: &str) -> Result<Self, String> { + match s.to_lowercase().as_str() { + "audio" => Ok(Self::Audio), + "subtitle" => Ok(Self::Subtitle), + _ => Err(format!("invalid skip existing method '{}'", s)), + } + } + + fn default<'a>() -> &'a [Self] { + &[] + } +} + async fn get_format( archive: &Archive, single_formats: &Vec<SingleFormat>, @@ -374,7 +467,7 @@ async fn get_format( }), MergeBehavior::Auto => { let mut d_formats: Vec<(Duration, DownloadFormat)> = vec![]; - + for (single_format, video, audio, subtitles) in format_pairs { let closest_format = d_formats.iter_mut().min_by(|(x, _), (y, _)| { x.sub(single_format.duration) @@ -422,3 +515,36 @@ async fn get_format( Format::from_single_formats(single_format_to_format_pairs), )) } + +fn get_video_streams(path: &Path) -> Result<Option<(Vec<Locale>, Vec<Locale>)>> { + let video_streams = + Regex::new(r"(?m)Stream\s#\d+:\d+\((?P<language>.+)\):\s(?P<type>(Audio|Subtitle))") + .unwrap(); + + let ffmpeg = Command::new("ffmpeg") + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .arg("-hide_banner") + .args(["-i", &path.to_string_lossy().to_string()]) + .output()?; + let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; + + let mut audio = vec![]; + let mut subtitle = vec![]; + for cap in video_streams.captures_iter(&ffmpeg_output) { + let locale = cap.name("language").unwrap().as_str(); + let type_ = cap.name("type").unwrap().as_str(); + + match type_ { + "Audio" => audio.push(Locale::from(locale.to_string())), + "Subtitle" => subtitle.push(Locale::from(locale.to_string())), + _ => unreachable!(), + } + } + + if audio.is_empty() && subtitle.is_empty() { + Ok(None) + } else { + Ok(Some((audio, subtitle))) + } +} diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 7f3f305..1f8cd8e 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -94,7 +94,7 @@ pub struct Download { #[arg(long)] pub(crate) ffmpeg_threads: Option<usize>, - #[arg(help = "Skip files which are already existing")] + #[arg(help = "Skip files which are already existing by their name")] #[arg(long, default_value_t = false)] pub(crate) skip_existing: bool, #[arg(help = "Skip special episodes")] @@ -259,7 +259,7 @@ impl Execute for Download { self.output_specials .as_ref() .map_or((&self.output).into(), |so| so.into()), - self.universal_output, + self.universal_output, ) } else { format.format_path((&self.output).into(), self.universal_output) From 52da6eacc9165a2681667c68b0878823834b02b4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 23 Feb 2024 21:41:47 +0100 Subject: [PATCH 534/630] Fix search command always showing non-premium account warning message --- Cargo.lock | 72 +++++++++++++++++++++++--- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/search/command.rs | 2 +- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58e768c..1ca5441 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "828ff3c0f11de8f8afda7dc3bd24e206e1b13cee6abfd87856123305864681d2" +checksum = "0fcce6c297f8f78d4a56511a2c4321d4eb72f09132469e07955116f8e23582a6" dependencies = [ "aes", "async-trait", @@ -440,6 +440,7 @@ dependencies = [ "crunchyroll-rs-internal", "dash-mpd", "futures-util", + "jsonwebtoken", "lazy_static", "m3u8-rs", "regex", @@ -456,9 +457,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7051a39e25a19ef0aa753e7da179787a3db0fb8a01977a7e22cd288f7ff0e27" +checksum = "d1f3c9595b79a54c90aa96ba5e15ebbcc944690a9e0cb24989dc677872370b39" dependencies = [ "darling", "quote", @@ -775,8 +776,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1063,6 +1066,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1100,9 +1118,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "m3u8-rs" -version = "5.0.5" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1d7ba86f7ea62f17f4310c55e93244619ddc7dadfc7e565de1967e4e41e6e7" +checksum = "f03cd3335fb5f2447755d45cda9c70f76013626a9db44374973791b0926a86c3" dependencies = [ "chrono", "nom", @@ -1189,6 +1207,26 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -1289,6 +1327,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1705,6 +1753,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.9" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index a937e01..a0d4fb3 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.8.2", features = ["dash-stream", "experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.8.3", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index 3d03d28..226e242 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -103,7 +103,7 @@ pub struct Search { impl Execute for Search { async fn execute(self, ctx: Context) -> Result<()> { - if !ctx.crunchy.premium() { + if !ctx.crunchy.premium().await { warn!("Using `search` anonymously or with a non-premium account may return incomplete results") } From d2589a3a6f8579c4f16ed70ba66a4e11bc25be85 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 25 Feb 2024 19:01:27 +0100 Subject: [PATCH 535/630] Use macos 12 instead of 13 for ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae289c1..82b604d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,10 +68,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # macos-13 uses x86_64, macos-14 aarch64 + # macos-12 uses x86_64, macos-14 aarch64 # see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources include: - - os: macos-13 + - os: macos-12 arch: x86_64 toolchain: x86_64-apple-darwin - os: macos-14 From 9a6959970ab9b03da91029096fdf387a7b5f44a8 Mon Sep 17 00:00:00 2001 From: Hannes Braun <hannes@hannesbraun.net> Date: Mon, 26 Feb 2024 20:09:54 +0100 Subject: [PATCH 536/630] Remove superfluous mut keywords (#341) --- crunchy-cli-core/src/archive/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 741dd5b..8e70c2e 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -268,7 +268,7 @@ impl Execute for Archive { let mut skip = true; if !self.skip_existing_method.is_empty() { - if let Some((mut audio_locales, mut subtitle_locales)) = + if let Some((audio_locales, subtitle_locales)) = get_video_streams(&formatted_path)? { let method_audio = self From 3099aac0e7260c32d65cff5528dd0e942b838fdf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 26 Feb 2024 20:42:33 +0100 Subject: [PATCH 537/630] Revert macos action downgrade and disable caching instead --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82b604d..6991cad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,10 +68,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # macos-12 uses x86_64, macos-14 aarch64 + # macos-13 uses x86_64, macos-14 aarch64 # see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources include: - - os: macos-12 + - os: macos-13 arch: x86_64 toolchain: x86_64-apple-darwin - os: macos-14 @@ -82,6 +82,7 @@ jobs: uses: actions/checkout@v4 - name: Cargo cache + if: ${{ matrix.os != 'macos-13' }} # when using cache, the 'Setup Rust' step fails for macos 13 uses: actions/cache@v4 with: path: | From 9c44fa7dae8ca9f2edb01cf08675dc2d62d46ea7 Mon Sep 17 00:00:00 2001 From: Username404-59 <53659497+Username404-59@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:40:41 +0100 Subject: [PATCH 538/630] README.md: Fix a typo (#344) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4adc17..57a8b7c 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ The `download` command lets you download episodes with a specific audio language If you need more specific ffmpeg customizations you could either convert the output file manually or use ffmpeg output arguments as value for this flag. ```shell - $ crunchy-cli downlaod --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + $ crunchy-cli download --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` - FFmpeg threads From 56f0ed1795d971fb4342b9f0a66216e8af18fd6f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 01:58:35 +0100 Subject: [PATCH 539/630] Add `--include-chapters` flag to `archive` and `download` (#301) --- Cargo.lock | 320 ++++++++++------------- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/archive/command.rs | 24 +- crunchy-cli-core/src/download/command.rs | 17 +- crunchy-cli-core/src/utils/download.rs | 90 ++++++- crunchy-cli-core/src/utils/format.rs | 10 +- 6 files changed, 272 insertions(+), 191 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ca5441..7c2a6f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.7" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -174,9 +174,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-padding" @@ -210,12 +210,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -225,9 +222,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -235,7 +232,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] @@ -250,9 +247,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.16" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -260,30 +257,30 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.16" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.0", ] [[package]] name = "clap_complete" -version = "4.4.6" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97aeaa95557bd02f23fbb662f981670c3d20c5a26e69f7354b28f57092437fcd" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", @@ -293,9 +290,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clap_mangen" @@ -328,9 +325,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", "time", @@ -339,12 +336,12 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" +checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" dependencies = [ "cookie", - "idna 0.2.3", + "idna 0.3.0", "log", "publicsuffix", "serde", @@ -429,9 +426,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcce6c297f8f78d4a56511a2c4321d4eb72f09132469e07955116f8e23582a6" +checksum = "467fc4159e38121aa5efb3807de957eefff02d14ba3439494f89f351e3539b73" dependencies = [ "aes", "async-trait", @@ -457,9 +454,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f3c9595b79a54c90aa96ba5e15ebbcc944690a9e0cb24989dc677872370b39" +checksum = "62db42661f84dc2e2f7c5fef1e8906fff29ff316624da54039aec748a49e7a3b" dependencies = [ "darling", "quote", @@ -506,7 +503,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn", ] @@ -523,9 +520,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.14.7" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cf94350e05e27c941b8cfc06bffeec3afcac11f42df289378ddf43e192d2e15" +checksum = "18c18f28b58beade78e0f61a846a63a122cb92c5f5ed6bad29d7ad13287c7526" dependencies = [ "base64", "base64-serde", @@ -540,7 +537,6 @@ dependencies = [ "serde_path_to_error", "serde_with", "thiserror", - "tokio", "tracing", "url", "xattr", @@ -827,9 +823,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -951,17 +947,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.3.0" @@ -1074,11 +1059,9 @@ checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" dependencies = [ "base64", "js-sys", - "pem", "ring", "serde", "serde_json", - "simple_asn1", ] [[package]] @@ -1089,9 +1072,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" @@ -1099,16 +1082,16 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", "redox_syscall", ] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" @@ -1126,12 +1109,6 @@ dependencies = [ "nom", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "memchr" version = "2.7.1" @@ -1152,18 +1129,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1192,7 +1169,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "libc", ] @@ -1207,31 +1184,11 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -1273,7 +1230,7 @@ version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -1327,16 +1284,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "pem" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" -dependencies = [ - "base64", - "serde", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -1375,9 +1322,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -1439,9 +1386,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -1451,9 +1398,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -1468,9 +1415,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946" dependencies = [ "base64", "bytes", @@ -1498,6 +1445,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -1542,11 +1490,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", @@ -1588,9 +1536,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" +checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" [[package]] name = "rustls-webpki" @@ -1652,18 +1600,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", @@ -1714,9 +1662,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.4.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" dependencies = [ "base64", "chrono", @@ -1724,6 +1672,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.1.0", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -1731,9 +1680,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.4.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" dependencies = [ "darling", "proc-macro2", @@ -1749,21 +1698,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" @@ -1787,12 +1724,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1808,16 +1745,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "syn" -version = "2.0.48" +name = "strsim" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sys-locale" version = "0.3.1" @@ -1829,20 +1778,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -1850,31 +1799,30 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", @@ -1927,9 +1875,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -2193,9 +2141,9 @@ checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-streams" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", @@ -2257,7 +2205,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -2275,7 +2223,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -2295,17 +2243,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -2316,9 +2264,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -2328,9 +2276,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -2340,9 +2288,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -2352,9 +2300,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -2364,9 +2312,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -2376,9 +2324,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -2388,9 +2336,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" @@ -2404,9 +2352,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index a0d4fb3..2d3ac61 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.8.3", features = ["dash-stream", "experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.8.5", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 8e70c2e..bf61fdf 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -1,6 +1,8 @@ use crate::archive::filter::ArchiveFilter; use crate::utils::context::Context; -use crate::utils::download::{DownloadBuilder, DownloadFormat, MergeBehavior}; +use crate::utils::download::{ + DownloadBuilder, DownloadFormat, DownloadFormatMetadata, MergeBehavior, +}; use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat}; @@ -127,6 +129,17 @@ pub struct Archive { #[arg(help = "Include fonts in the downloaded file")] #[arg(long)] pub(crate) include_fonts: bool, + #[arg( + help = "Includes chapters (e.g. intro, credits, ...). Only works if `--merge` is set to 'audio'" + )] + #[arg( + long_help = "Includes chapters (e.g. intro, credits, ...). . Only works if `--merge` is set to 'audio'. \ + Because chapters are essentially only special timeframes in episodes like the intro, most of the video timeline isn't covered by a chapter. + These \"gaps\" are filled with an 'Episode' chapter because many video players are ignore those gaps and just assume that a chapter ends when the next chapter start is reached, even if a specific end-time is set. + Also chapters aren't always available, so in this case, just a big 'Episode' chapter from start to end will be created" + )] + #[arg(long, default_value_t = false)] + pub(crate) include_chapters: bool, #[arg(help = "Omit closed caption subtitles in the downloaded file")] #[arg(long, default_value_t = false)] @@ -188,6 +201,10 @@ impl Execute for Archive { } } + if self.include_chapters && !matches!(self.merge, MergeBehavior::Audio) { + bail!("`--include-chapters` can only be used if `--merge` is set to 'audio'") + } + if self.output.contains("{resolution}") || self .output_specials @@ -446,6 +463,7 @@ async fn get_format( video: (video, single_format.audio.clone()), audios: vec![(audio, single_format.audio.clone())], subtitles, + metadata: DownloadFormatMetadata { skip_events: None }, }) } } @@ -464,6 +482,9 @@ async fn get_format( .iter() .flat_map(|(_, _, _, subtitles)| subtitles.clone()) .collect(), + metadata: DownloadFormatMetadata { + skip_events: format_pairs.first().unwrap().0.skip_events().await?, + }, }), MergeBehavior::Auto => { let mut d_formats: Vec<(Duration, DownloadFormat)> = vec![]; @@ -498,6 +519,7 @@ async fn get_format( video: (video, single_format.audio.clone()), audios: vec![(audio, single_format.audio.clone())], subtitles, + metadata: DownloadFormatMetadata { skip_events: None }, }, )); } diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 1f8cd8e..08756e0 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -1,6 +1,6 @@ use crate::download::filter::DownloadFilter; use crate::utils::context::Context; -use crate::utils::download::{DownloadBuilder, DownloadFormat}; +use crate::utils::download::{DownloadBuilder, DownloadFormat, DownloadFormatMetadata}; use crate::utils::ffmpeg::{FFmpegPreset, SOFTSUB_CONTAINERS}; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat}; @@ -101,6 +101,14 @@ pub struct Download { #[arg(long, default_value_t = false)] pub(crate) skip_specials: bool, + #[arg(help = "Includes chapters (e.g. intro, credits, ...)")] + #[arg(long_help = "Includes chapters (e.g. intro, credits, ...). \ + Because chapters are essentially only special timeframes in episodes like the intro, most of the video timeline isn't covered by a chapter. + These \"gaps\" are filled with an 'Episode' chapter because many video players are ignore those gaps and just assume that a chapter ends when the next chapter start is reached, even if a specific end-time is set. + Also chapters aren't always available, so in this case, just a big 'Episode' chapter from start to end will be created")] + #[arg(long, default_value_t = false)] + pub(crate) include_chapters: bool, + #[arg(help = "Skip any interactive input")] #[arg(short, long, default_value_t = false)] pub(crate) yes: bool, @@ -342,6 +350,13 @@ async fn get_format( single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, )] }), + metadata: DownloadFormatMetadata { + skip_events: if download.include_chapters { + single_format.skip_events().await? + } else { + None + }, + }, }; let mut format = Format::from_single_formats(vec![( single_format.clone(), diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 9a67f7f..c768195 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -4,7 +4,7 @@ use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pi use crate::utils::rate_limit::RateLimiterService; use anyhow::{bail, Result}; use chrono::NaiveTime; -use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; +use crunchyroll_rs::media::{SkipEvents, SkipEventsEvent, Subtitle, VariantData, VariantSegment}; use crunchyroll_rs::Locale; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; @@ -113,6 +113,11 @@ pub struct DownloadFormat { pub video: (VariantData, Locale), pub audios: Vec<(VariantData, Locale)>, pub subtitles: Vec<(Subtitle, bool)>, + pub metadata: DownloadFormatMetadata, +} + +pub struct DownloadFormatMetadata { + pub skip_events: Option<SkipEvents>, } pub struct Downloader { @@ -205,6 +210,8 @@ impl Downloader { let mut audios = vec![]; let mut subtitles = vec![]; let mut fonts = vec![]; + let mut chapters = None; + let mut max_len = NaiveTime::MIN; let mut max_frames = 0f64; let fmt_space = self .formats @@ -243,6 +250,9 @@ impl Downloader { } let (len, fps) = get_video_stats(&video_path)?; + if max_len < len { + max_len = len + } let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps; if frames > max_frames { max_frames = frames; @@ -322,6 +332,22 @@ impl Downloader { format!("#{}", i + 1) }, }); + + if let Some(skip_events) = &format.metadata.skip_events { + let (file, path) = tempfile(".chapter")?.into_parts(); + chapters = Some(( + (file, path), + [ + skip_events.recap.as_ref().map(|e| ("Recap", e)), + skip_events.intro.as_ref().map(|e| ("Intro", e)), + skip_events.credits.as_ref().map(|e| ("Credits", e)), + skip_events.preview.as_ref().map(|e| ("Preview", e)), + ] + .into_iter() + .flatten() + .collect::<Vec<(&str, &SkipEventsEvent)>>(), + )); + } } if self.download_fonts @@ -440,6 +466,20 @@ impl Downloader { } } + if let Some(((file, path), chapters)) = chapters.as_mut() { + write_ffmpeg_chapters(file, max_len, chapters)?; + input.extend(["-i".to_string(), path.to_string_lossy().to_string()]); + maps.extend([ + "-map_metadata".to_string(), + (videos.len() + + audios.len() + + container_supports_softsubs + .then_some(subtitles.len()) + .unwrap_or_default()) + .to_string(), + ]) + } + let preset_custom = matches!(self.ffmpeg_preset, FFmpegPreset::Custom(_)); let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args(); let fifo = temp_named_pipe()?; @@ -1156,6 +1196,54 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { *raw = as_lines.join("\n").into_bytes() } +fn write_ffmpeg_chapters( + file: &mut fs::File, + video_len: NaiveTime, + events: &mut Vec<(&str, &SkipEventsEvent)>, +) -> Result<()> { + let video_len = video_len + .signed_duration_since(NaiveTime::MIN) + .num_seconds() as u32; + events.sort_by(|(_, event_a), (_, event_b)| event_a.start.cmp(&event_b.start)); + + writeln!(file, ";FFMETADATA1")?; + + let mut last_end_time = 0; + for (name, event) in events { + // include an extra 'Episode' chapter if the start of the current chapter is more than 10 + // seconds later than the end of the last chapter. + // this is done before writing the actual chapter of this loop to keep the chapter + // chronologically in order + if event.start as i32 - last_end_time as i32 > 10 { + writeln!(file, "[CHAPTER]")?; + writeln!(file, "TIMEBASE=1/1")?; + writeln!(file, "START={}", last_end_time)?; + writeln!(file, "END={}", event.start)?; + writeln!(file, "title=Episode")?; + } + + writeln!(file, "[CHAPTER]")?; + writeln!(file, "TIMEBASE=1/1")?; + writeln!(file, "START={}", event.start)?; + writeln!(file, "END={}", event.end)?; + writeln!(file, "title={}", name)?; + + last_end_time = event.end; + } + + // only add a traling chapter if the gab between the end of the last chapter and the total video + // length is greater than 10 seconds + if video_len as i32 - last_end_time as i32 > 10 { + writeln!(file, "[CHAPTER]")?; + writeln!(file, "TIMEBASE=1/1")?; + writeln!(file, "START={}", last_end_time)?; + writeln!(file, "END={}", video_len)?; + writeln!(file, "title=Episode")?; + } + + Ok(()) +} + async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( total_frames: u64, stats: R, diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index e60ef54..8956a04 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -3,7 +3,7 @@ use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; use anyhow::Result; use chrono::{Datelike, Duration}; -use crunchyroll_rs::media::{Resolution, Stream, Subtitle, VariantData}; +use crunchyroll_rs::media::{Resolution, SkipEvents, Stream, Subtitle, VariantData}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; use log::{debug, info}; use std::cmp::Ordering; @@ -175,6 +175,14 @@ impl SingleFormat { Ok(stream) } + pub async fn skip_events(&self) -> Result<Option<SkipEvents>> { + match &self.source { + MediaCollection::Episode(e) => Ok(Some(e.skip_events().await?)), + MediaCollection::Movie(m) => Ok(Some(m.skip_events().await?)), + _ => Ok(None), + } + } + pub fn source_type(&self) -> String { match &self.source { MediaCollection::Episode(_) => "episode", From 3f33db6728daab51b48ed6a150068071aeeff030 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 02:07:05 +0100 Subject: [PATCH 540/630] Remove deprecated `openssl` and `openssl-static` features --- Cargo.toml | 4 ---- build.rs | 7 ------- 2 files changed, 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 425e078..01c5be1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,6 @@ native-tls = ["crunchy-cli-core/native-tls"] openssl-tls = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls"] openssl-tls-static = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls-static"] -# deprecated -openssl = ["openssl-tls"] -openssl-static = ["openssl-tls-static"] - [dependencies] tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time"], default-features = false } diff --git a/build.rs b/build.rs index 5b464c4..313cb6d 100644 --- a/build.rs +++ b/build.rs @@ -19,13 +19,6 @@ fn main() -> std::io::Result<()> { println!("cargo:warning=Multiple tls backends are activated (through the '*-tls' features). Consider to activate only one as it is not possible to change the backend during runtime. The active backend for this build will be '{}'.", active_tls_backend) } - if cfg!(feature = "openssl") { - println!("cargo:warning=The 'openssl' feature is deprecated and will be removed in a future version. Use the 'openssl-tls' feature instead.") - } - if cfg!(feature = "openssl-static") { - println!("cargo:warning=The 'openssl-static' feature is deprecated and will be removed in a future version. Use the 'openssl-tls-static' feature instead.") - } - // note that we're using an anti-pattern here / violate the rust conventions. build script are // not supposed to write outside of 'OUT_DIR'. to have the generated files in the build "root" // (the same directory where the output binary lives) is much simpler than in 'OUT_DIR' since From f1d266c940f508a00b6a4408b70c73b263f785b7 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 04:04:58 +0100 Subject: [PATCH 541/630] Add options to specify audio & subtitle locales as IETF language tag and add `--language_tagging` flag for `archive` and `download` to modify the output file language tagging (#330) --- crunchy-cli-core/src/archive/command.rs | 58 +++++++++-- crunchy-cli-core/src/download/command.rs | 61 +++++++++++- crunchy-cli-core/src/utils/download.rs | 26 ++++- crunchy-cli-core/src/utils/format.rs | 10 +- crunchy-cli-core/src/utils/locale.rs | 120 +++++++++++++++++++++++ 5 files changed, 260 insertions(+), 15 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index bf61fdf..600065a 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -6,7 +6,7 @@ use crate::utils::download::{ use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat}; -use crate::utils::locale::all_locale_in_locales; +use crate::utils::locale::{all_locale_in_locales, resolve_locales, LanguageTagging}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::parse_url; @@ -20,6 +20,7 @@ use crunchyroll_rs::Locale; use log::{debug, warn}; use regex::Regex; use std::fmt::{Display, Formatter}; +use std::iter::zip; use std::ops::Sub; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -31,15 +32,19 @@ pub struct Archive { #[arg(help = format!("Audio languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Audio languages. Can be used multiple times. \ - Available languages are:\n {}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + Available languages are:\n {}\nIETF tagged language codes for the shown available locales can be used too", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_values_t = vec![Locale::ja_JP, crate::utils::locale::system_locale()])] pub(crate) audio: Vec<Locale>, + #[arg(skip)] + output_audio_locales: Vec<String>, #[arg(help = format!("Subtitle languages. Can be used multiple times. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Subtitle languages. Can be used multiple times. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + Available languages are: {}\nIETF tagged language codes for the shown available locales can be used too", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(short, long, default_values_t = Locale::all())] pub(crate) subtitle: Vec<Locale>, + #[arg(skip)] + output_subtitle_locales: Vec<String>, #[arg(help = "Name of the output file")] #[arg(long_help = "Name of the output file. \ @@ -95,12 +100,22 @@ pub struct Archive { #[arg(short, long, default_value = "auto")] #[arg(value_parser = MergeBehavior::parse)] pub(crate) merge: MergeBehavior, - #[arg( help = "If the merge behavior is 'auto', only download multiple video tracks if their length difference is higher than the given milliseconds" )] #[arg(long, default_value_t = 200)] pub(crate) merge_auto_tolerance: u32, + #[arg( + long, + help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ + Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard)" + )] + #[arg( + long_help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ + Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard; you might run in issues as there are multiple locales which resolve to the same IETF language code, e.g. 'es-LA' and 'es-ES' are both resolving to 'es')" + )] + #[arg(value_parser = LanguageTagging::parse)] + pub(crate) language_tagging: Option<LanguageTagging>, #[arg(help = format!("Presets for converting the video to a specific coding format. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] @@ -217,6 +232,26 @@ impl Execute for Archive { self.audio = all_locale_in_locales(self.audio.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); + if let Some(language_tagging) = &self.language_tagging { + self.audio = resolve_locales(&self.audio); + self.subtitle = resolve_locales(&self.subtitle); + self.output_audio_locales = language_tagging.convert_locales(&self.audio); + self.output_subtitle_locales = language_tagging.convert_locales(&self.subtitle); + } else { + self.output_audio_locales = self + .audio + .clone() + .into_iter() + .map(|l| l.to_string()) + .collect(); + self.output_subtitle_locales = self + .subtitle + .clone() + .into_iter() + .map(|l| l.to_string()) + .collect(); + } + Ok(()) } @@ -259,7 +294,13 @@ impl Execute for Archive { .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())) .no_closed_caption(self.no_closed_caption) - .threads(self.threads); + .threads(self.threads) + .audio_locale_output_map( + zip(self.audio.clone(), self.output_audio_locales.clone()).collect(), + ) + .subtitle_locale_output_map( + zip(self.subtitle.clone(), self.output_subtitle_locales.clone()).collect(), + ); for single_formats in single_format_collection.into_iter() { let (download_formats, mut format) = get_format(&self, &single_formats).await?; @@ -275,9 +316,14 @@ impl Execute for Archive { .as_ref() .map_or((&self.output).into(), |so| so.into()), self.universal_output, + self.language_tagging.as_ref(), ) } else { - format.format_path((&self.output).into(), self.universal_output) + format.format_path( + (&self.output).into(), + self.universal_output, + self.language_tagging.as_ref(), + ) }; let (mut path, changed) = free_file(formatted_path.clone()); diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 08756e0..843f5cd 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -4,6 +4,7 @@ use crate::utils::download::{DownloadBuilder, DownloadFormat, DownloadFormatMeta use crate::utils::ffmpeg::{FFmpegPreset, SOFTSUB_CONTAINERS}; use crate::utils::filter::Filter; use crate::utils::format::{Format, SingleFormat}; +use crate::utils::locale::{resolve_locales, LanguageTagging}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::parse_url; @@ -14,6 +15,7 @@ use anyhow::Result; use crunchyroll_rs::media::Resolution; use crunchyroll_rs::Locale; use log::{debug, warn}; +use std::collections::HashMap; use std::path::Path; #[derive(Clone, Debug, clap::Parser)] @@ -23,14 +25,18 @@ pub struct Download { #[arg(help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Audio language. Can only be used if the provided url(s) point to a series. \ - Available languages are:\n {}", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] + Available languages are:\n {}\nIETF tagged language codes for the shown available locales can be used too", Locale::all().into_iter().map(|l| format!("{:<6} โ†’ {}", l.to_string(), l.to_human_readable())).collect::<Vec<String>>().join("\n ")))] #[arg(short, long, default_value_t = crate::utils::locale::system_locale())] pub(crate) audio: Locale, + #[arg(skip)] + output_audio_locale: String, #[arg(help = format!("Subtitle language. Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(long_help = format!("Subtitle language. If set, the subtitle will be burned into the video and cannot be disabled. \ - Available languages are: {}", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] + Available languages are: {}\nIETF tagged language codes for the shown available locales can be used too", Locale::all().into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", ")))] #[arg(short, long)] pub(crate) subtitle: Option<Locale>, + #[arg(skip)] + output_subtitle_locale: String, #[arg(help = "Name of the output file")] #[arg(long_help = "Name of the output file. \ @@ -75,6 +81,18 @@ pub struct Download { #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] pub(crate) resolution: Resolution, + #[arg( + long, + help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ + Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard)" + )] + #[arg( + long_help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ + Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard; you might run in issues as there are multiple locales which resolve to the same IETF language code, e.g. 'es-LA' and 'es-ES' are both resolving to 'es')" + )] + #[arg(value_parser = LanguageTagging::parse)] + pub(crate) language_tagging: Option<LanguageTagging>, + #[arg(help = format!("Presets for converting the video to a specific coding format. \ Available presets: \n {}", FFmpegPreset::available_matches_human_readable().join("\n ")))] #[arg(long_help = format!("Presets for converting the video to a specific coding format. \ @@ -178,6 +196,27 @@ impl Execute for Download { warn!("The '{{resolution}}' format option is deprecated and will be removed in a future version. Please use '{{width}}' and '{{height}}' instead") } + if let Some(language_tagging) = &self.language_tagging { + self.audio = resolve_locales(&[self.audio.clone()]).remove(0); + self.subtitle = self + .subtitle + .as_ref() + .map(|s| resolve_locales(&[s.clone()]).remove(0)); + self.output_audio_locale = language_tagging.for_locale(&self.audio); + self.output_subtitle_locale = self + .subtitle + .as_ref() + .map(|s| language_tagging.for_locale(s)) + .unwrap_or_default() + } else { + self.output_audio_locale = self.audio.to_string(); + self.output_subtitle_locale = self + .subtitle + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_default(); + } + Ok(()) } @@ -240,7 +279,16 @@ impl Execute for Download { }) .ffmpeg_preset(self.ffmpeg_preset.clone().unwrap_or_default()) .ffmpeg_threads(self.ffmpeg_threads) - .threads(self.threads); + .threads(self.threads) + .audio_locale_output_map(HashMap::from([( + self.audio.clone(), + self.output_audio_locale.clone(), + )])) + .subtitle_locale_output_map( + self.subtitle.as_ref().map_or(HashMap::new(), |s| { + HashMap::from([(s.clone(), self.output_subtitle_locale.clone())]) + }), + ); for mut single_formats in single_format_collection.into_iter() { // the vec contains always only one item @@ -268,9 +316,14 @@ impl Execute for Download { .as_ref() .map_or((&self.output).into(), |so| so.into()), self.universal_output, + self.language_tagging.as_ref(), ) } else { - format.format_path((&self.output).into(), self.universal_output) + format.format_path( + (&self.output).into(), + self.universal_output, + self.language_tagging.as_ref(), + ) }; let (path, changed) = free_file(formatted_path.clone()); diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index c768195..f3dc631 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -12,7 +12,7 @@ use regex::Regex; use reqwest::Client; use std::borrow::Borrow; use std::cmp::Ordering; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -61,6 +61,8 @@ pub struct DownloadBuilder { no_closed_caption: bool, threads: usize, ffmpeg_threads: Option<usize>, + audio_locale_output_map: HashMap<Locale, String>, + subtitle_locale_output_map: HashMap<Locale, String>, } impl DownloadBuilder { @@ -78,6 +80,8 @@ impl DownloadBuilder { no_closed_caption: false, threads: num_cpus::get(), ffmpeg_threads: None, + audio_locale_output_map: HashMap::new(), + subtitle_locale_output_map: HashMap::new(), } } @@ -99,6 +103,9 @@ impl DownloadBuilder { ffmpeg_threads: self.ffmpeg_threads, formats: vec![], + + audio_locale_output_map: self.audio_locale_output_map, + subtitle_locale_output_map: self.subtitle_locale_output_map, } } } @@ -138,6 +145,9 @@ pub struct Downloader { ffmpeg_threads: Option<usize>, formats: Vec<DownloadFormat>, + + audio_locale_output_map: HashMap<Locale, String>, + subtitle_locale_output_map: HashMap<Locale, String>, } impl Downloader { @@ -426,7 +436,12 @@ impl Downloader { maps.extend(["-map".to_string(), (i + videos.len()).to_string()]); metadata.extend([ format!("-metadata:s:a:{}", i), - format!("language={}", meta.language), + format!( + "language={}", + self.audio_locale_output_map + .get(&meta.language) + .unwrap_or(&meta.language.to_string()) + ), ]); metadata.extend([ format!("-metadata:s:a:{}", i), @@ -457,7 +472,12 @@ impl Downloader { ]); metadata.extend([ format!("-metadata:s:s:{}", i), - format!("language={}", meta.language), + format!( + "language={}", + self.subtitle_locale_output_map + .get(&meta.language) + .unwrap_or(&meta.language.to_string()) + ), ]); metadata.extend([ format!("-metadata:s:s:{}", i), diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 8956a04..7146a55 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -1,4 +1,5 @@ use crate::utils::filter::real_dedup_vec; +use crate::utils::locale::LanguageTagging; use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; use anyhow::Result; @@ -417,7 +418,12 @@ impl Format { } /// Formats the given string if it has specific pattern in it. It also sanitizes the filename. - pub fn format_path(&self, path: PathBuf, universal: bool) -> PathBuf { + pub fn format_path( + &self, + path: PathBuf, + universal: bool, + language_tagging: Option<&LanguageTagging>, + ) -> PathBuf { let path = path .to_string_lossy() .to_string() @@ -427,7 +433,7 @@ impl Format { &sanitize( self.locales .iter() - .map(|(a, _)| a.to_string()) + .map(|(a, _)| language_tagging.map_or(a.to_string(), |t| t.for_locale(a))) .collect::<Vec<String>>() .join( &env::var("CRUNCHY_CLI_FORMAT_DELIMITER") diff --git a/crunchy-cli-core/src/utils/locale.rs b/crunchy-cli-core/src/utils/locale.rs index 8651078..0827299 100644 --- a/crunchy-cli-core/src/utils/locale.rs +++ b/crunchy-cli-core/src/utils/locale.rs @@ -1,4 +1,124 @@ use crunchyroll_rs::Locale; +use log::warn; + +#[derive(Clone, Debug)] +#[allow(clippy::upper_case_acronyms)] +pub enum LanguageTagging { + Default, + IETF, +} + +impl LanguageTagging { + pub fn parse(s: &str) -> Result<Self, String> { + Ok(match s.to_lowercase().as_str() { + "default" => Self::Default, + "ietf" => Self::IETF, + _ => return Err(format!("'{}' is not a valid language tagging", s)), + }) + } + + pub fn convert_locales(&self, locales: &[Locale]) -> Vec<String> { + let ietf_language_codes = ietf_language_codes(); + let mut converted = vec![]; + + match &self { + LanguageTagging::Default => { + for locale in locales { + let Some((_, available)) = + ietf_language_codes.iter().find(|(_, l)| l.contains(locale)) + else { + // if no matching IETF language code was found, just pass it as it is + converted.push(locale.to_string()); + continue; + }; + converted.push(available.first().unwrap().to_string()) + } + } + LanguageTagging::IETF => { + for locale in locales { + let Some((tag, _)) = + ietf_language_codes.iter().find(|(_, l)| l.contains(locale)) + else { + // if no matching IETF language code was found, just pass it as it is + converted.push(locale.to_string()); + continue; + }; + converted.push(tag.to_string()) + } + } + } + + converted + } + + pub fn for_locale(&self, locale: &Locale) -> String { + match &self { + LanguageTagging::Default => ietf_language_codes() + .iter() + .find(|(_, l)| l.contains(locale)) + .map_or(locale.to_string(), |(_, l)| l[0].to_string()), + LanguageTagging::IETF => ietf_language_codes() + .iter() + .find(|(_, l)| l.contains(locale)) + .map_or(locale.to_string(), |(tag, _)| tag.to_string()), + } + } +} + +pub fn resolve_locales(locales: &[Locale]) -> Vec<Locale> { + let ietf_language_codes = ietf_language_codes(); + let all_locales = Locale::all(); + + let mut resolved = vec![]; + for locale in locales { + if all_locales.contains(locale) { + resolved.push(locale.clone()) + } else if let Some((_, resolved_locales)) = ietf_language_codes + .iter() + .find(|(tag, _)| tag == &locale.to_string().as_str()) + { + let (first, alternatives) = resolved_locales.split_first().unwrap(); + + resolved.push(first.clone()); + // ignoring `Locale::en_IN` because I think the majority of users which want english + // audio / subs want the "actual" english version and not the hindi accent dub + if !alternatives.is_empty() && resolved_locales.first().unwrap() != &Locale::en_IN { + warn!("Resolving locale '{}' to '{}', but there are some alternatives: {}. If you an alternative instead, please write it completely out instead of '{}'", locale, first, alternatives.iter().map(|l| format!("'{l}'")).collect::<Vec<String>>().join(", "), locale) + } + } else { + resolved.push(locale.clone()); + warn!("Unknown locale '{}'", locale) + } + } + + resolved +} + +fn ietf_language_codes<'a>() -> Vec<(&'a str, Vec<Locale>)> { + vec![ + ("ar", vec![Locale::ar_ME, Locale::ar_SA]), + ("ca", vec![Locale::ca_ES]), + ("de", vec![Locale::de_DE]), + ("en", vec![Locale::en_US, Locale::hi_IN]), + ("es", vec![Locale::es_ES, Locale::es_419, Locale::es_LA]), + ("fr", vec![Locale::fr_FR]), + ("hi", vec![Locale::hi_IN]), + ("id", vec![Locale::id_ID]), + ("it", vec![Locale::it_IT]), + ("ja", vec![Locale::ja_JP]), + ("ko", vec![Locale::ko_KR]), + ("ms", vec![Locale::ms_MY]), + ("pl", vec![Locale::pl_PL]), + ("pt", vec![Locale::pt_PT, Locale::pt_BR]), + ("ru", vec![Locale::ru_RU]), + ("ta", vec![Locale::ta_IN]), + ("te", vec![Locale::te_IN]), + ("th", vec![Locale::th_TH]), + ("tr", vec![Locale::tr_TR]), + ("vi", vec![Locale::vi_VN]), + ("zh", vec![Locale::zh_CN, Locale::zh_HK, Locale::zh_TW]), + ] +} /// Return the locale of the system. pub fn system_locale() -> Locale { From e3a7fd92468a9cc1b61556f4eefff4d9c08e95b8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 12:49:47 +0100 Subject: [PATCH 542/630] Add option so specify different proxies for api and download requests (#282) --- crunchy-cli-core/src/lib.rs | 86 +++++++++++++++++------------- crunchy-cli-core/src/utils/clap.rs | 37 ++++++++++++- 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index d362a2c..9dd3360 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -61,9 +61,10 @@ pub struct Cli { #[arg(help = "Use a proxy to route all traffic through")] #[arg(long_help = "Use a proxy to route all traffic through. \ - Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself")] - #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_proxy)] - proxy: Option<Proxy>, + Make sure that the proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. \ + Besides specifying a simple url, you also can partially control where a proxy should be used: '<url>:' only proxies api requests, ':<url>' only proxies download traffic, '<url>:<url>' proxies api requests through the first url and download traffic through the second url")] + #[arg(global = true, long, value_parser = crate::utils::clap::clap_parse_proxies)] + proxy: Option<(Option<Proxy>, Option<Proxy>)>, #[arg(help = "Use custom user agent")] #[arg(global = true, long)] @@ -238,43 +239,29 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { } async fn create_ctx(cli: &mut Cli) -> Result<Context> { - let client = { - let mut builder = CrunchyrollBuilder::predefined_client_builder(); - if let Some(p) = &cli.proxy { - builder = builder.proxy(p.clone()) - } - if let Some(ua) = &cli.user_agent { - builder = builder.user_agent(ua) - } + let crunchy_client = reqwest_client( + cli.proxy.as_ref().and_then(|p| p.0.clone()), + cli.user_agent.clone(), + ); + let internal_client = reqwest_client( + cli.proxy.as_ref().and_then(|p| p.1.clone()), + cli.user_agent.clone(), + ); - #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] - let client = { - let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); - - for certificate in rustls_native_certs::load_native_certs().unwrap() { - builder = builder.add_root_certificate( - reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), - ) - } - - builder.build().unwrap() - }; - #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] - let client = builder.build().unwrap(); - - client - }; - - let rate_limiter = cli - .speed_limit - .map(|l| RateLimiterService::new(l, client.clone())); - - let crunchy = crunchyroll_session(cli, client.clone(), rate_limiter.clone()).await?; + let crunchy = crunchyroll_session( + cli, + crunchy_client.clone(), + cli.speed_limit + .map(|l| RateLimiterService::new(l, crunchy_client)), + ) + .await?; Ok(Context { crunchy, - client, - rate_limiter, + client: internal_client.clone(), + rate_limiter: cli + .speed_limit + .map(|l| RateLimiterService::new(l, internal_client)), }) } @@ -372,3 +359,30 @@ async fn crunchyroll_session( Ok(crunchy) } + +fn reqwest_client(proxy: Option<Proxy>, user_agent: Option<String>) -> Client { + let mut builder = CrunchyrollBuilder::predefined_client_builder(); + if let Some(p) = proxy { + builder = builder.proxy(p) + } + if let Some(ua) = user_agent { + builder = builder.user_agent(ua) + } + + #[cfg(any(feature = "openssl-tls", feature = "openssl-tls-static"))] + let client = { + let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); + + for certificate in rustls_native_certs::load_native_certs().unwrap() { + builder = builder.add_root_certificate( + reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), + ) + } + + builder.build().unwrap() + }; + #[cfg(not(any(feature = "openssl-tls", feature = "openssl-tls-static")))] + let client = builder.build().unwrap(); + + client +} diff --git a/crunchy-cli-core/src/utils/clap.rs b/crunchy-cli-core/src/utils/clap.rs index 37a34d3..35de71f 100644 --- a/crunchy-cli-core/src/utils/clap.rs +++ b/crunchy-cli-core/src/utils/clap.rs @@ -1,13 +1,46 @@ use crate::utils::parse::parse_resolution; use crunchyroll_rs::media::Resolution; +use regex::Regex; use reqwest::Proxy; pub fn clap_parse_resolution(s: &str) -> Result<Resolution, String> { parse_resolution(s.to_string()).map_err(|e| e.to_string()) } -pub fn clap_parse_proxy(s: &str) -> Result<Proxy, String> { - Proxy::all(s).map_err(|e| e.to_string()) +pub fn clap_parse_proxies(s: &str) -> Result<(Option<Proxy>, Option<Proxy>), String> { + let double_proxy_regex = + Regex::new(r"^(?P<first>(https?|socks5h?)://.+):(?P<second>(https?|socks5h?)://.+)$") + .unwrap(); + + if let Some(capture) = double_proxy_regex.captures(s) { + // checks if the input is formatted like 'https://example.com:socks5://examples.com' and + // splits the string into 2 separate proxies at the middle colon + + let first = capture.name("first").unwrap().as_str(); + let second = capture.name("second").unwrap().as_str(); + Ok(( + Some(Proxy::all(first).map_err(|e| format!("first proxy: {e}"))?), + Some(Proxy::all(second).map_err(|e| format!("second proxy: {e}"))?), + )) + } else if s.starts_with(':') { + // checks if the input is formatted like ':https://example.com' and returns a proxy on the + // second tuple position + Ok(( + None, + Some(Proxy::all(s.trim_start_matches(':')).map_err(|e| e.to_string())?), + )) + } else if s.ends_with(':') { + // checks if the input is formatted like 'https://example.com:' and returns a proxy on the + // first tuple position + Ok(( + Some(Proxy::all(s.trim_end_matches(':')).map_err(|e| e.to_string())?), + None, + )) + } else { + // returns the same proxy for both tuple positions + let proxy = Proxy::all(s).map_err(|e| e.to_string())?; + Ok((Some(proxy.clone()), Some(proxy))) + } } pub fn clap_parse_speed_limit(s: &str) -> Result<u32, String> { From 3bf24587745b99d963826cb293cadf9603c186f2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 13:28:18 +0100 Subject: [PATCH 543/630] Pass command args manually to cli entrypoint instead of parsing from environment --- crunchy-cli-core/src/lib.rs | 4 ++-- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 9dd3360..38f22f4 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -117,8 +117,8 @@ struct Verbosity { quiet: bool, } -pub async fn cli_entrypoint() { - let mut cli: Cli = Cli::parse(); +pub async fn main(args: &[String]) { + let mut cli: Cli = Cli::parse_from(args); if cli.verbosity.verbose || cli.verbosity.quiet { if cli.verbosity.verbose && cli.verbosity.quiet { diff --git a/src/main.rs b/src/main.rs index da3c699..9d44bef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,5 +8,5 @@ compile_error!("At least one tls feature must be activated"); #[tokio::main] async fn main() { - crunchy_cli_core::cli_entrypoint().await + crunchy_cli_core::main(&std::env::args().collect::<Vec<String>>()).await } From 013273b832387aa9e3818b3abca97956342e73a9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 13:40:16 +0100 Subject: [PATCH 544/630] Format code --- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/archive/filter.rs | 2 +- crunchy-cli-core/src/utils/download.rs | 20 +++++++++----------- crunchy-cli-core/src/utils/parse.rs | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 600065a..8258b36 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -593,7 +593,7 @@ fn get_video_streams(path: &Path) -> Result<Option<(Vec<Locale>, Vec<Locale>)>> .stdout(Stdio::null()) .stderr(Stdio::piped()) .arg("-hide_banner") - .args(["-i", &path.to_string_lossy().to_string()]) + .args(["-i", &path.to_string_lossy()]) .output()?; let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 2a47738..90ab373 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -180,7 +180,7 @@ impl Filter for ArchiveFilter { Some( season .audio_locales - .get(0) + .first() .cloned() .unwrap_or(Locale::ja_JP), ) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index f3dc631..7f81bc9 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -793,7 +793,7 @@ impl Downloader { .await? .bytes() .await?; - fs::write(&file, font.to_vec())?; + fs::write(&file, font)?; Ok(Some((file, false))) } @@ -990,20 +990,18 @@ fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { .args(["-i", path.to_str().unwrap()]) .output()?; let ffmpeg_output = String::from_utf8(ffmpeg.stderr)?; - let length_caps = video_length.captures(ffmpeg_output.as_str()).map_or( - Err(anyhow::anyhow!( + let length_caps = video_length + .captures(ffmpeg_output.as_str()) + .ok_or(anyhow::anyhow!( "failed to get video length: {}", ffmpeg_output - )), - Ok, - )?; - let fps_caps = video_fps.captures(ffmpeg_output.as_str()).map_or( - Err(anyhow::anyhow!( + ))?; + let fps_caps = video_fps + .captures(ffmpeg_output.as_str()) + .ok_or(anyhow::anyhow!( "failed to get video fps: {}", ffmpeg_output - )), - Ok, - )?; + ))?; Ok(( NaiveTime::parse_from_str(length_caps.name("time").unwrap().as_str(), "%H:%M:%S%.f") diff --git a/crunchy-cli-core/src/utils/parse.rs b/crunchy-cli-core/src/utils/parse.rs index 70cbfbb..2c4a53b 100644 --- a/crunchy-cli-core/src/utils/parse.rs +++ b/crunchy-cli-core/src/utils/parse.rs @@ -147,7 +147,7 @@ pub async fn parse_url( url = crunchy.client().get(&url).send().await?.url().to_string() } - let parsed_url = crunchyroll_rs::parse_url(url).map_or(Err(anyhow!("Invalid url")), Ok)?; + let parsed_url = crunchyroll_rs::parse_url(url).ok_or(anyhow!("Invalid url"))?; debug!("Url type: {:?}", parsed_url); let media_collection = match parsed_url { UrlType::Series(id) From a0fa2bfd8a131a213457a977394ad4d078ac4317 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 13:40:26 +0100 Subject: [PATCH 545/630] Update dependencies and version --- Cargo.lock | 45 ++++++++++++++++++---- Cargo.toml | 8 ++-- README.md | 76 +++++++++++++++++++++++++++++++++++++ crunchy-cli-core/Cargo.toml | 14 +++---- crunchy-cli-core/src/lib.rs | 5 +-- 5 files changed, 126 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c2a6f4..99b223b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.35" @@ -378,7 +384,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.2.5" +version = "3.3.0" dependencies = [ "chrono", "clap", @@ -391,7 +397,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.2.5" +version = "3.3.0" dependencies = [ "anyhow", "async-speed-limit", @@ -408,7 +414,7 @@ dependencies = [ "indicatif", "lazy_static", "log", - "nix", + "nix 0.28.0", "num_cpus", "regex", "reqwest", @@ -479,7 +485,7 @@ version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" dependencies = [ - "nix", + "nix 0.27.1", "windows-sys 0.52.0", ] @@ -1174,6 +1180,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1441,7 +1459,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -1515,12 +1533,13 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.1", + "rustls-pki-types", "schannel", "security-framework", ] @@ -1534,6 +1553,16 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +dependencies = [ + "base64", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index 01c5be1..cf4f676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.2.5" +version = "3.3.0" edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ openssl-tls = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli- openssl-tls-static = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls-static"] [dependencies] -tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.36", features = ["macros", "rt-multi-thread", "time"], default-features = false } native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } @@ -22,8 +22,8 @@ crunchy-cli-core = { path = "./crunchy-cli-core" } [build-dependencies] chrono = "0.4" -clap = { version = "4.4", features = ["string"] } -clap_complete = "4.4" +clap = { version = "4.5", features = ["string"] } +clap_complete = "4.5" clap_mangen = "0.2" crunchy-cli-core = { path = "./crunchy-cli-core" } diff --git a/README.md b/README.md index 57a8b7c..46bee72 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,8 @@ You can set specific settings which will be The `--proxy` flag supports https and socks5 proxies to route all your traffic through. This may be helpful to bypass the geo-restrictions Crunchyroll has on certain series. + You are also able to set in which part of the cli a proxy should be used. + Instead of a normal url you can also use: `<url>:` (only proxies api requests), `:<url>` (only proxies download traffic), `<url>:<url>` (proxies api requests through the first url and download traffic through the second url). ```shell $ crunchy-cli --proxy socks5://127.0.0.1:8080 <command> @@ -283,6 +285,14 @@ The `download` command lets you download episodes with a specific audio language Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. +- Universal output + + The output template options can be forced to get sanitized via the `--universal-output` flag to be valid across all supported operating systems (Windows has a lot of characters which aren't allowed in filenames...). + + ```shell + $ crunchy-cli download --universal-output -o https://www.crunchyroll.com/watch/G7PU4XD48/tales-veldoras-journal-2 + ``` + - Resolution The resolution for videos can be set via the `-r` / `--resolution` flag. @@ -293,6 +303,15 @@ The `download` command lets you download episodes with a specific audio language Default is `best`. +- Language tagging + + You can force the usage of a specific language tagging in the output file with the `--language-tagging` flag. + This might be useful as some video players doesn't recognize the language tagging Crunchyroll uses internally. + + ```shell + $ crunchy-cli download --language-tagging ietf https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + - FFmpeg Preset You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. @@ -327,6 +346,15 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --skip-specials https://www.crunchyroll.com/series/GYZJ43JMR/that-time-i-got-reincarnated-as-a-slime[S2] ``` +- Include chapters + + Crunchyroll sometimes provide information about skippable events like the intro or credits. + These information can be stored as chapters in the resulting video file via the `--include-chapters` flag. + + ```shell + $ crunchy-cli download --include-chapters https://www.crunchyroll.com/watch/G0DUND0K2/the-journeys-end + ``` + - Yes Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. @@ -416,6 +444,14 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. +- Universal output + + The output template options can be forced to get sanitized via the `--universal-output` flag to be valid across all supported operating systems (Windows has a lot of characters which aren't allowed in filenames...). + + ```shell + $ crunchy-cli archive --universal-output -o https://www.crunchyroll.com/watch/G7PU4XD48/tales-veldoras-journal-2 + ``` + - Resolution The resolution for videos can be set via the `-r` / `--resolution` flag. @@ -441,6 +477,26 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `auto`. +- Merge auto tolerance + + Sometimes two video tracks are downloaded with `--merge` set to `auto` even if they only differ some milliseconds in length which shouldn't be noticeable to the viewer. + To prevent this, you can specify a range in milliseconds with the `--merge-auto-tolerance` flag that only downloads one video if the length difference is in the given range. + + ```shell + $ crunchy-cli archive -m auto --merge-auto-tolerance 100 https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + + Default are `200` milliseconds. + +- Language tagging + + You can force the usage of a specific language tagging in the output file with the `--language-tagging` flag. + This might be useful as some video players doesn't recognize the language tagging Crunchyroll uses internally. + + ```shell + $ crunchy-cli archive --language-tagging ietf https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + - FFmpeg Preset You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. @@ -477,6 +533,16 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --include-fonts https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` +- Include chapters + + Crunchyroll sometimes provide information about skippable events like the intro or credits. + These information can be stored as chapters in the resulting video file via the `--include-chapters` flag. + This flag only works if `--merge` is set to `audio` because chapters cannot be mapped to a specific video steam. + + ```shell + $ crunchy-cli archive --include-chapters https://www.crunchyroll.com/watch/G0DUND0K2/the-journeys-end + ``` + - Skip existing If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. @@ -485,6 +551,16 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` +- Skip existing method + + By default, already existing files are determined by their name and the download of the corresponding episode is skipped. + But sometimes Crunchyroll adds dubs or subs to an already existing episode and these changes aren't recognized and `--skip-existing` just skips it. + This behavior can be changed by the `--skip-existing-method` flag. Valid options are `audio` and `subtitle` (if the file already exists but the audio/subtitle are less from what should be downloaded, the episode gets downloaded and the file overwritten). + + ```shell + $ crunchy-cli archive --skip-existing-method audio --skip-existing-method video https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + ``` + - Skip specials If you doesn't want to download special episodes, use the `--skip-specials` flag to skip the download of them. diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 2d3ac61..fcb178a 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.2.5" +version = "3.3.0" edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ openssl-tls-static = ["reqwest/native-tls", "reqwest/native-tls-alpn", "reqwest/ [dependencies] anyhow = "1.0" async-speed-limit = "0.4" -clap = { version = "4.4", features = ["derive", "string"] } +clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" crunchyroll-rs = { version = "0.8.5", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" @@ -33,16 +33,16 @@ reqwest = { version = "0.11", default-features = false, features = ["socks", "st serde = "1.0" serde_json = "1.0" serde_plain = "1.0" -shlex = "1.2" +shlex = "1.3" sys-locale = "0.3" -tempfile = "3.9" -tokio = { version = "1.35", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } +tempfile = "3.10" +tokio = { version = "1.36", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } tokio-util = "0.7" tower-service = "0.3" -rustls-native-certs = { version = "0.6", optional = true } +rustls-native-certs = { version = "0.7", optional = true } [target.'cfg(not(target_os = "windows"))'.dependencies] -nix = { version = "0.27", features = ["fs"] } +nix = { version = "0.28", features = ["fs"] } [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 38f22f4..8067c42 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -374,9 +374,8 @@ fn reqwest_client(proxy: Option<Proxy>, user_agent: Option<String>) -> Client { let mut builder = builder.use_native_tls().tls_built_in_root_certs(false); for certificate in rustls_native_certs::load_native_certs().unwrap() { - builder = builder.add_root_certificate( - reqwest::Certificate::from_der(certificate.0.as_slice()).unwrap(), - ) + builder = + builder.add_root_certificate(reqwest::Certificate::from_der(&certificate).unwrap()) } builder.build().unwrap() From 88a28e843f53be5e3f8919e64e8fa11eaf97d8bf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 19:40:36 +0100 Subject: [PATCH 546/630] Manually specify ffmpeg output color format --- crunchy-cli-core/src/utils/download.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 7f81bc9..7f87583 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -606,6 +606,10 @@ impl Downloader { command_args.extend([format!("-disposition:s:s:{}", i), "forced".to_string()]) } + // manually specifying the color model for the output file. this must be done manually + // because some Crunchyroll episodes are encoded in a way that ffmpeg cannot re-encode + command_args.extend(["-pix_fmt".to_string(), "yuv420p".to_string()]); + command_args.extend(output_presets); if let Some(output_format) = self.output_format { command_args.extend(["-f".to_string(), output_format]); From d3696c783cef5e8f368978e4e014d07b8a83b913 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 21:57:20 +0100 Subject: [PATCH 547/630] Include archive chapters only if flag is set --- crunchy-cli-core/src/archive/command.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 8258b36..f7bea0e 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -529,7 +529,11 @@ async fn get_format( .flat_map(|(_, _, _, subtitles)| subtitles.clone()) .collect(), metadata: DownloadFormatMetadata { - skip_events: format_pairs.first().unwrap().0.skip_events().await?, + skip_events: if archive.include_chapters { + format_pairs.first().unwrap().0.skip_events().await? + } else { + None + }, }, }), MergeBehavior::Auto => { From 26a858c1a13d66f303d7256f4c6d168d04a25a69 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 10 Mar 2024 22:04:58 +0100 Subject: [PATCH 548/630] Update dependencies and version --- Cargo.lock | 198 ++++++++++++++++++------------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 +- 3 files changed, 101 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99b223b..113b430 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "async-speed-limit" @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytes" @@ -302,9 +302,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clap_mangen" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a7c2b01e5e779c19f46a94bbd398f33ae63b0f78c07108351fb4536845bb7fd" +checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" dependencies = [ "clap", "roff", @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "crunchy-cli" -version = "3.3.0" +version = "3.3.1" dependencies = [ "chrono", "clap", @@ -397,7 +397,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.3.0" +version = "3.3.1" dependencies = [ "anyhow", "async-speed-limit", @@ -414,7 +414,7 @@ dependencies = [ "indicatif", "lazy_static", "log", - "nix 0.28.0", + "nix", "num_cpus", "regex", "reqwest", @@ -432,9 +432,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467fc4159e38121aa5efb3807de957eefff02d14ba3439494f89f351e3539b73" +checksum = "0f99fcd7627d214fd57cd1d030e8c859a773e19aa29fb0d15017aa84efaba353" dependencies = [ "aes", "async-trait", @@ -455,14 +455,14 @@ dependencies = [ "smart-default", "tokio", "tower-service", - "webpki-roots 0.26.0", + "webpki-roots 0.26.1", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62db42661f84dc2e2f7c5fef1e8906fff29ff316624da54039aec748a49e7a3b" +checksum = "d2dd269b2df82ebbec9e8164e9950c6ad14a01cfcbb85eceeb3f3ef26c7da90c" dependencies = [ "darling", "quote", @@ -481,19 +481,19 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix 0.27.1", + "nix", "windows-sys 0.52.0", ] [[package]] name = "darling" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -501,9 +501,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", @@ -604,9 +604,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encode_unicode" @@ -740,9 +740,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" @@ -792,9 +792,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -802,7 +802,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.1.0", + "indexmap 2.2.5", "slab", "tokio", "tokio-util", @@ -841,9 +841,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -926,9 +926,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -986,9 +986,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -997,9 +997,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -1050,9 +1050,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1101,9 +1101,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "m3u8-rs" @@ -1169,17 +1169,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.28.0" @@ -1202,6 +1191,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.18" @@ -1244,9 +1239,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", "cfg-if", @@ -1276,18 +1271,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.2.1+3.2.0" +version = "300.2.3+3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -1322,9 +1317,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" @@ -1476,22 +1471,23 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.3", + "webpki-roots 0.25.4", "winreg", ] [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1581,9 +1577,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "schannel" @@ -1649,9 +1645,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -1660,9 +1656,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -1699,7 +1695,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.1.0", + "indexmap 2.2.5", "serde", "serde_derive", "serde_json", @@ -1860,12 +1856,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -1880,10 +1877,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -2027,9 +2025,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2039,9 +2037,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -2104,9 +2102,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2114,9 +2112,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -2129,9 +2127,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -2141,9 +2139,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2151,9 +2149,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -2164,9 +2162,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-streams" @@ -2183,9 +2181,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -2193,15 +2191,15 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de2cfda980f21be5a7ed2eadb3e6fe074d56022bea2cdeb1a62eb220fc04188" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index cf4f676..681fe73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.3.0" +version = "3.3.1" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index fcb178a..0242606 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.3.0" +version = "3.3.1" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.8.5", features = ["dash-stream", "experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.8.6", features = ["dash-stream", "experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 89be8ac4296a72df4f877348892e7a6b27f1850f Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 25 Mar 2024 13:30:40 +0100 Subject: [PATCH 549/630] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 46bee72..7636dc1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# This project has been sunset as Crunchyroll moved to a DRM-only system. See [#362](https://github.com/crunchy-labs/crunchy-cli/issues/362). + # crunchy-cli ๐Ÿ‘‡ A Command-line downloader for [Crunchyroll](https://www.crunchyroll.com). From ba8028737dbd5e99f7cc7d40d432e0f34505d9fa Mon Sep 17 00:00:00 2001 From: Amelia <afrosty.skye@gmail.com> Date: Wed, 3 Apr 2024 06:49:51 -0700 Subject: [PATCH 550/630] Update missing fonts (#360) * Update missing fonts * Compile fix --- crunchy-cli-core/src/utils/download.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 7f87583..736f9e6 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1015,7 +1015,7 @@ fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { } // all subtitle fonts (extracted from javascript) -const FONTS: [(&str, &str); 66] = [ +const FONTS: [(&str, &str); 68] = [ ("Adobe Arabic", "AdobeArabic-Bold.woff2"), ("Andale Mono", "andalemo.woff2"), ("Arial", "arial.woff2"), @@ -1073,6 +1073,8 @@ const FONTS: [(&str, &str); 66] = [ ("Impact", "impact.woff2"), ("Mangal", "MANGAL.woff2"), ("Meera Inimai", "MeeraInimai-Regular.woff2"), + ("Noto Sans Tamil", "NotoSansTamil.woff2"), + ("Noto Sans Telugu", "NotoSansTelegu.woff2"), ("Noto Sans Thai", "NotoSansThai.woff2"), ("Rubik", "Rubik-Regular.woff2"), ("Rubik Black", "Rubik-Black.woff2"), From e694046b07fbcabf40714e62d1ac7a98f511fcdd Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Apr 2024 15:48:15 +0200 Subject: [PATCH 551/630] Move to new, DRM-free, endpoint --- Cargo.lock | 404 ++++++++++------------- crunchy-cli-core/Cargo.toml | 6 +- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/download/command.rs | 2 +- crunchy-cli-core/src/search/format.rs | 36 +- crunchy-cli-core/src/utils/download.rs | 57 ++-- crunchy-cli-core/src/utils/format.rs | 31 +- crunchy-cli-core/src/utils/video.rs | 38 +-- 8 files changed, 245 insertions(+), 331 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 113b430..b7617b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,17 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - [[package]] name = "aho-corasick" version = "1.1.2" @@ -156,13 +145,19 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64-serde" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" dependencies = [ - "base64", + "base64 0.21.7", "serde", ] @@ -178,15 +173,6 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.15.4" @@ -199,15 +185,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" version = "1.0.90" @@ -228,9 +205,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -241,16 +218,6 @@ dependencies = [ "windows-targets 0.52.4", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clap" version = "4.5.2" @@ -373,15 +340,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - [[package]] name = "crunchy-cli" version = "3.3.1" @@ -432,20 +390,17 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.8.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f99fcd7627d214fd57cd1d030e8c859a773e19aa29fb0d15017aa84efaba353" +checksum = "05fcd99a09d001333ab482412473aaa03c84f980d451845b4c43d58986b6eb64" dependencies = [ - "aes", "async-trait", - "cbc", "chrono", "crunchyroll-rs-internal", "dash-mpd", "futures-util", "jsonwebtoken", "lazy_static", - "m3u8-rs", "regex", "reqwest", "rustls", @@ -455,30 +410,20 @@ dependencies = [ "smart-default", "tokio", "tower-service", - "webpki-roots 0.26.1", + "webpki-roots", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.8.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dd269b2df82ebbec9e8164e9950c6ad14a01cfcbb85eceeb3f3ef26c7da90c" +checksum = "d406a27eca9ceab379b601101cd96b0f7bfff34e93487ca58d54118a8b4fbf91" dependencies = [ "darling", "quote", "syn", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "ctrlc" version = "3.4.4" @@ -526,11 +471,11 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c18f28b58beade78e0f61a846a63a122cb92c5f5ed6bad29d7ad13287c7526" +checksum = "6cafa2c33eff2857e1a14c38aa9a432aa565a01e77804a541fce7aec3affb8f8" dependencies = [ - "base64", + "base64 0.22.0", "base64-serde", "chrono", "fs-err", @@ -614,15 +559,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -761,16 +697,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.12" @@ -790,25 +716,6 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "h2" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.2.5", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -841,9 +748,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -852,12 +759,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", "pin-project-lite", ] @@ -867,61 +786,76 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" -version = "0.14.28" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http", "hyper", + "hyper-util", "rustls", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1008,16 +942,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "block-padding", - "generic-array", -] - [[package]] name = "instant" version = "0.1.12" @@ -1059,11 +983,11 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.2.0" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ - "base64", + "base64 0.21.7", "js-sys", "ring", "serde", @@ -1105,16 +1029,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "m3u8-rs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03cd3335fb5f2447755d45cda9c70f76013626a9db44374973791b0926a86c3" -dependencies = [ - "chrono", - "nom", -] - [[package]] name = "memchr" version = "2.7.1" @@ -1127,6 +1041,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1303,6 +1227,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1399,9 +1343,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1428,38 +1372,39 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.25" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946" +checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "cookie", "cookie_store", - "encoding_rs", "futures-core", "futures-util", - "h2", "http", "http-body", + "http-body-util", "hyper", "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls", "rustls-pemfile 1.0.4", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1471,7 +1416,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.4", + "webpki-roots", "winreg", ] @@ -1517,14 +1462,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] @@ -1546,7 +1493,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] @@ -1555,7 +1502,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" dependencies = [ - "base64", + "base64 0.21.7", "rustls-pki-types", ] @@ -1567,11 +1514,12 @@ checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] @@ -1590,16 +1538,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.9.2" @@ -1687,11 +1625,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" dependencies = [ - "base64", + "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", @@ -1705,9 +1643,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" dependencies = [ "darling", "proc-macro2", @@ -1736,6 +1674,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "smart-default" version = "0.7.1" @@ -1775,6 +1719,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.52" @@ -1801,27 +1751,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" -dependencies = [ - "bitflags 2.4.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.10.1" @@ -1836,18 +1765,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", @@ -1902,9 +1831,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1940,11 +1869,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] @@ -1974,6 +1904,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -1986,6 +1938,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2018,10 +1971,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "typenum" -version = "1.17.0" +name = "unicase" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] [[package]] name = "unicode-bidi" @@ -2189,12 +2145,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.1" @@ -2387,3 +2337,9 @@ dependencies = [ "linux-raw-sys", "rustix", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 0242606..7617c58 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -16,20 +16,20 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.8.6", features = ["dash-stream", "experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.0", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" derive_setters = "0.1" futures-util = { version = "0.3", features = ["io"] } fs2 = "0.4" -http = "0.2" +http = "1.1" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" -reqwest = { version = "0.11", default-features = false, features = ["socks", "stream"] } +reqwest = { version = "0.12", default-features = false, features = ["socks", "stream"] } serde = "1.0" serde_json = "1.0" serde_plain = "1.0" diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index f7bea0e..0dc0b86 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -487,7 +487,7 @@ async fn get_format( single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, ) }); - let cc = stream.closed_captions.get(s).cloned().map(|l| (l, false)); + let cc = stream.captions.get(s).cloned().map(|l| (l, false)); subtitles .into_iter() diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 843f5cd..47b29c9 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -389,7 +389,7 @@ async fn get_format( .get(subtitle_locale) .cloned() // use closed captions as fallback if no actual subtitles are found - .or_else(|| stream.closed_captions.get(subtitle_locale).cloned()) + .or_else(|| stream.captions.get(subtitle_locale).cloned()) } else { None }; diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 156bd95..10eefd8 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -163,37 +163,15 @@ impl From<&Concert> for FormatConcert { struct FormatStream { pub locale: Locale, pub dash_url: String, - pub drm_dash_url: String, - pub hls_url: String, - pub drm_hls_url: String, + pub is_drm: bool, } impl From<&Stream> for FormatStream { fn from(value: &Stream) -> Self { - let (dash_url, drm_dash_url, hls_url, drm_hls_url) = - value.variants.get(&Locale::Custom("".to_string())).map_or( - ( - "".to_string(), - "".to_string(), - "".to_string(), - "".to_string(), - ), - |v| { - ( - v.adaptive_dash.clone().unwrap_or_default().url, - v.drm_adaptive_dash.clone().unwrap_or_default().url, - v.adaptive_hls.clone().unwrap_or_default().url, - v.drm_adaptive_hls.clone().unwrap_or_default().url, - ) - }, - ); - Self { locale: value.audio_locale.clone(), - dash_url, - drm_dash_url, - hls_url, - drm_hls_url, + dash_url: value.url.clone(), + is_drm: value.session.uses_stream_limits, } } } @@ -441,7 +419,7 @@ impl Format { if !stream_empty { for (_, episodes) in tree.iter_mut() { for (episode, streams) in episodes { - streams.push(episode.stream().await?) + streams.push(episode.stream_maybe_without_drm().await?) } } } else { @@ -510,7 +488,7 @@ impl Format { } if !stream_empty { for (movie, streams) in tree.iter_mut() { - streams.push(movie.stream().await?) + streams.push(movie.stream_maybe_without_drm().await?) } } else { for (_, streams) in tree.iter_mut() { @@ -548,7 +526,7 @@ impl Format { let stream_empty = self.check_pattern_count_empty(Scope::Stream); let music_video = must_match_if_true!(!music_video_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.clone()).unwrap_or_default(); - let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.stream().await?).unwrap_or_default(); + let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::MusicVideo(music_video) => music_video.stream_maybe_without_drm().await?).unwrap_or_default(); let music_video_map = self.serializable_to_json_map(FormatMusicVideo::from(&music_video)); let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); @@ -570,7 +548,7 @@ impl Format { let stream_empty = self.check_pattern_count_empty(Scope::Stream); let concert = must_match_if_true!(!concert_empty => media_collection|MediaCollection::Concert(concert) => concert.clone()).unwrap_or_default(); - let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::Concert(concert) => concert.stream().await?).unwrap_or_default(); + let stream = must_match_if_true!(!stream_empty => media_collection|MediaCollection::Concert(concert) => concert.stream_maybe_without_drm().await?).unwrap_or_default(); let concert_map = self.serializable_to_json_map(FormatConcert::from(&concert)); let stream_map = self.serializable_to_json_map(FormatStream::from(&stream)); diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 736f9e6..8a1fe66 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -4,7 +4,7 @@ use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pi use crate::utils::rate_limit::RateLimiterService; use anyhow::{bail, Result}; use chrono::NaiveTime; -use crunchyroll_rs::media::{SkipEvents, SkipEventsEvent, Subtitle, VariantData, VariantSegment}; +use crunchyroll_rs::media::{SkipEvents, SkipEventsEvent, StreamData, StreamSegment, Subtitle}; use crunchyroll_rs::Locale; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; @@ -117,8 +117,8 @@ struct FFmpegMeta { } pub struct DownloadFormat { - pub video: (VariantData, Locale), - pub audios: Vec<(VariantData, Locale)>, + pub video: (StreamData, Locale), + pub audios: Vec<(StreamData, Locale)>, pub subtitles: Vec<(Subtitle, bool)>, pub metadata: DownloadFormatMetadata, } @@ -671,20 +671,17 @@ impl Downloader { &self, dst: &Path, ) -> Result<(Option<(PathBuf, u64)>, Option<(PathBuf, u64)>)> { - let mut all_variant_data = vec![]; + let mut all_stream_data = vec![]; for format in &self.formats { - all_variant_data.push(&format.video.0); - all_variant_data.extend(format.audios.iter().map(|(a, _)| a)) + all_stream_data.push(&format.video.0); + all_stream_data.extend(format.audios.iter().map(|(a, _)| a)) } let mut estimated_required_space: u64 = 0; - for variant_data in all_variant_data { - // nearly no overhead should be generated with this call(s) as we're using dash as - // stream provider and generating the dash segments does not need any fetching of - // additional (http) resources as hls segments would - let segments = variant_data.segments().await?; + for stream_data in all_stream_data { + let segments = stream_data.segments(); // sum the length of all streams up - estimated_required_space += estimate_variant_file_size(variant_data, &segments); + estimated_required_space += estimate_variant_file_size(stream_data, &segments); } let tmp_stat = fs2::statvfs(temp_directory()).unwrap(); @@ -730,29 +727,21 @@ impl Downloader { Ok((tmp_required, dst_required)) } - async fn download_video( - &self, - variant_data: &VariantData, - message: String, - ) -> Result<TempPath> { + async fn download_video(&self, stream_data: &StreamData, message: String) -> Result<TempPath> { let tempfile = tempfile(".mp4")?; let (mut file, path) = tempfile.into_parts(); - self.download_segments(&mut file, message, variant_data) + self.download_segments(&mut file, message, stream_data) .await?; Ok(path) } - async fn download_audio( - &self, - variant_data: &VariantData, - message: String, - ) -> Result<TempPath> { + async fn download_audio(&self, stream_data: &StreamData, message: String) -> Result<TempPath> { let tempfile = tempfile(".m4a")?; let (mut file, path) = tempfile.into_parts(); - self.download_segments(&mut file, message, variant_data) + self.download_segments(&mut file, message, stream_data) .await?; Ok(path) @@ -806,15 +795,15 @@ impl Downloader { &self, writer: &mut impl Write, message: String, - variant_data: &VariantData, + stream_data: &StreamData, ) -> Result<()> { - let segments = variant_data.segments().await?; + let segments = stream_data.segments(); let total_segments = segments.len(); let count = Arc::new(Mutex::new(0)); let progress = if log::max_level() == LevelFilter::Info { - let estimated_file_size = estimate_variant_file_size(variant_data, &segments); + let estimated_file_size = estimate_variant_file_size(stream_data, &segments); let progress = ProgressBar::new(estimated_file_size) .with_style( @@ -832,7 +821,7 @@ impl Downloader { }; let cpus = self.download_threads; - let mut segs: Vec<Vec<VariantSegment>> = Vec::with_capacity(cpus); + let mut segs: Vec<Vec<StreamSegment>> = Vec::with_capacity(cpus); for _ in 0..cpus { segs.push(vec![]) } @@ -858,7 +847,7 @@ impl Downloader { let download = || async move { for (i, segment) in thread_segments.into_iter().enumerate() { let mut retry_count = 0; - let mut buf = loop { + let buf = loop { let request = thread_client .get(&segment.url) .timeout(Duration::from_secs(60)); @@ -884,11 +873,9 @@ impl Downloader { retry_count += 1; }; - buf = VariantSegment::decrypt(&mut buf, segment.key)?.to_vec(); - let mut c = thread_count.lock().await; debug!( - "Downloaded and decrypted segment [{}/{} {:.2}%] {}", + "Downloaded segment [{}/{} {:.2}%] {}", num + (i * cpus) + 1, total_segments, ((*c + 1) as f64 / total_segments as f64) * 100f64, @@ -928,7 +915,7 @@ impl Downloader { if let Some(p) = &progress { let progress_len = p.length().unwrap(); - let estimated_segment_len = (variant_data.bandwidth / 8) + let estimated_segment_len = (stream_data.bandwidth / 8) * segments.get(pos as usize).unwrap().length.as_secs(); let bytes_len = bytes.len() as u64; @@ -977,8 +964,8 @@ impl Downloader { } } -fn estimate_variant_file_size(variant_data: &VariantData, segments: &[VariantSegment]) -> u64 { - (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() +fn estimate_variant_file_size(stream_data: &StreamData, segments: &[StreamSegment]) -> u64 { + (stream_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() } /// Get the length and fps of a video. diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 7146a55..df79d64 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -2,9 +2,9 @@ use crate::utils::filter::real_dedup_vec; use crate::utils::locale::LanguageTagging; use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; -use anyhow::Result; +use anyhow::{bail, Result}; use chrono::{Datelike, Duration}; -use crunchyroll_rs::media::{Resolution, SkipEvents, Stream, Subtitle, VariantData}; +use crunchyroll_rs::media::{Resolution, SkipEvents, Stream, StreamData, Subtitle}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; use log::{debug, info}; use std::cmp::Ordering; @@ -167,12 +167,17 @@ impl SingleFormat { pub async fn stream(&self) -> Result<Stream> { let stream = match &self.source { - MediaCollection::Episode(e) => e.stream().await?, - MediaCollection::Movie(m) => m.stream().await?, - MediaCollection::MusicVideo(mv) => mv.stream().await?, - MediaCollection::Concert(c) => c.stream().await?, + MediaCollection::Episode(e) => e.stream_maybe_without_drm().await?, + MediaCollection::Movie(m) => m.stream_maybe_without_drm().await?, + MediaCollection::MusicVideo(mv) => mv.stream_maybe_without_drm().await?, + MediaCollection::Concert(c) => c.stream_maybe_without_drm().await?, _ => unreachable!(), }; + + if stream.session.uses_stream_limits { + bail!("Found a stream which probably uses DRM. DRM downloads aren't supported") + } + Ok(stream) } @@ -331,9 +336,7 @@ impl Iterator for SingleFormatCollectionIterator { type Item = Vec<SingleFormat>; fn next(&mut self) -> Option<Self::Item> { - let Some((_, episodes)) = self.0 .0.iter_mut().next() else { - return None; - }; + let (_, episodes) = self.0 .0.iter_mut().next()?; let value = episodes.pop_first().unwrap().1; if episodes.is_empty() { @@ -377,7 +380,7 @@ pub struct Format { impl Format { #[allow(clippy::type_complexity)] pub fn from_single_formats( - mut single_formats: Vec<(SingleFormat, VariantData, Vec<(Subtitle, bool)>)>, + mut single_formats: Vec<(SingleFormat, StreamData, Vec<(Subtitle, bool)>)>, ) -> Self { let locales: Vec<(Locale, Vec<Locale>)> = single_formats .iter() @@ -397,10 +400,10 @@ impl Format { title: first_format.title, description: first_format.description, locales, - resolution: first_stream.resolution.clone(), - width: first_stream.resolution.width, - height: first_stream.resolution.height, - fps: first_stream.fps, + resolution: first_stream.resolution().unwrap(), + width: first_stream.resolution().unwrap().width, + height: first_stream.resolution().unwrap().height, + fps: first_stream.fps().unwrap(), release_year: first_format.release_year, release_month: first_format.release_month, release_day: first_format.release_day, diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index 0ae4ba4..7f7d73e 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -1,17 +1,17 @@ use anyhow::{bail, Result}; -use crunchyroll_rs::media::{Resolution, Stream, VariantData}; +use crunchyroll_rs::media::{Resolution, Stream, StreamData}; use crunchyroll_rs::Locale; pub async fn variant_data_from_stream( stream: &Stream, resolution: &Resolution, subtitle: Option<Locale>, -) -> Result<Option<(VariantData, VariantData, bool)>> { +) -> Result<Option<(StreamData, StreamData, bool)>> { // sometimes Crunchyroll marks episodes without real subtitles that they have subtitles and // reports that only hardsub episode are existing. the following lines are trying to prevent // potential errors which might get caused by this incorrect reporting // (https://github.com/crunchy-labs/crunchy-cli/issues/231) - let mut hardsub_locales = stream.streaming_hardsub_locales(); + let mut hardsub_locales: Vec<Locale> = stream.hard_subs.keys().cloned().collect(); let (hardsub_locale, mut contains_hardsub) = if !hardsub_locales .contains(&Locale::Custom("".to_string())) && !hardsub_locales.contains(&Locale::Custom(":".to_string())) @@ -29,39 +29,29 @@ pub async fn variant_data_from_stream( (subtitle, hardsubs_requested) }; - let mut streaming_data = match stream.dash_streaming_data(hardsub_locale).await { + let (mut videos, mut audios) = match stream.stream_data(hardsub_locale).await { Ok(data) => data, Err(e) => { // the error variant is only `crunchyroll_rs::error::Error::Input` when the requested // hardsub is not available if let crunchyroll_rs::error::Error::Input { .. } = e { contains_hardsub = false; - stream.dash_streaming_data(None).await? + stream.stream_data(None).await? } else { bail!(e) } } - }; - streaming_data - .0 - .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); - streaming_data - .1 - .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); + } + .unwrap(); + videos.sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); + audios.sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); let video_variant = match resolution.height { - u64::MAX => Some(streaming_data.0.into_iter().next().unwrap()), - u64::MIN => Some(streaming_data.0.into_iter().last().unwrap()), - _ => streaming_data - .0 + u64::MAX => Some(videos.into_iter().next().unwrap()), + u64::MIN => Some(videos.into_iter().last().unwrap()), + _ => videos .into_iter() - .find(|v| resolution.height == v.resolution.height), + .find(|v| resolution.height == v.resolution().unwrap().height), }; - Ok(video_variant.map(|v| { - ( - v, - streaming_data.1.first().unwrap().clone(), - contains_hardsub, - ) - })) + Ok(video_variant.map(|v| (v, audios.first().unwrap().clone(), contains_hardsub))) } From f16cd25ea4a40f48b1744c67de09549756ec8505 Mon Sep 17 00:00:00 2001 From: Amelia Frost <afrosty.skye@gmail.com> Date: Wed, 3 Apr 2024 16:09:33 +0200 Subject: [PATCH 552/630] Fix for some chapters being sent by CR as floats (#351) * Fix for some chapters being sent by CR as floats. See: https://github.com/crunchy-labs/crunchyroll-rs/commit/3f3a80f7f7205e8ecb67e15fcf82b988eb88d9b2 * Compile fix for error[E0277]: cannot multiply `f32` by `u32` * Format Co-authored-by: bytedream <bytedream@protonmail.com> --- crunchy-cli-core/src/utils/download.rs | 42 ++++++++++++++------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 8a1fe66..d7904f3 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1214,41 +1214,45 @@ fn write_ffmpeg_chapters( ) -> Result<()> { let video_len = video_len .signed_duration_since(NaiveTime::MIN) - .num_seconds() as u32; - events.sort_by(|(_, event_a), (_, event_b)| event_a.start.cmp(&event_b.start)); + .num_milliseconds() as f32 + / 1000.0; + events.sort_by(|(_, event_a), (_, event_b)| event_a.start.total_cmp(&event_b.start)); writeln!(file, ";FFMETADATA1")?; - let mut last_end_time = 0; + let mut last_end_time = 0.0; for (name, event) in events { - // include an extra 'Episode' chapter if the start of the current chapter is more than 10 - // seconds later than the end of the last chapter. - // this is done before writing the actual chapter of this loop to keep the chapter - // chronologically in order - if event.start as i32 - last_end_time as i32 > 10 { + /* + - Convert from seconds to milliseconds for the correct timescale + - Include an extra 'Episode' chapter if the start of the current chapter is more than 10 + seconds later than the end of the last chapter. + This is done before writing the actual chapter of this loop to keep the chapter + chronologically in order + */ + if event.start - last_end_time > 10.0 { writeln!(file, "[CHAPTER]")?; - writeln!(file, "TIMEBASE=1/1")?; - writeln!(file, "START={}", last_end_time)?; - writeln!(file, "END={}", event.start)?; + writeln!(file, "TIMEBASE=1/1000")?; + writeln!(file, "START={}", (last_end_time * 1000.0) as u32)?; + writeln!(file, "END={}", (event.start * 1000.0) as u32)?; writeln!(file, "title=Episode")?; } writeln!(file, "[CHAPTER]")?; - writeln!(file, "TIMEBASE=1/1")?; - writeln!(file, "START={}", event.start)?; - writeln!(file, "END={}", event.end)?; + writeln!(file, "TIMEBASE=1/1000")?; + writeln!(file, "START={}", (event.start * 1000.0) as u32)?; + writeln!(file, "END={}", (event.end * 1000.0) as u32)?; writeln!(file, "title={}", name)?; last_end_time = event.end; } - // only add a traling chapter if the gab between the end of the last chapter and the total video + // only add a trailing chapter if the gap between the end of the last chapter and the total video // length is greater than 10 seconds - if video_len as i32 - last_end_time as i32 > 10 { + if video_len - last_end_time > 10.0 { writeln!(file, "[CHAPTER]")?; - writeln!(file, "TIMEBASE=1/1")?; - writeln!(file, "START={}", last_end_time)?; - writeln!(file, "END={}", video_len)?; + writeln!(file, "TIMEBASE=1/1000")?; + writeln!(file, "START={}", (last_end_time * 1000.0) as u32)?; + writeln!(file, "END={}", (video_len * 1000.0) as u32)?; writeln!(file, "title=Episode")?; } From 111e461b302544f027f31d8b2453a268a7ab0013 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Apr 2024 16:20:24 +0200 Subject: [PATCH 553/630] Update dependencies and version --- Cargo.lock | 134 +++++++++++++++++------------------- Cargo.toml | 4 +- crunchy-cli-core/Cargo.toml | 4 +- 3 files changed, 66 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7617b1..52bba41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "async-speed-limit" @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", @@ -120,15 +120,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -169,9 +169,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bumpalo" @@ -181,9 +181,9 @@ checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" @@ -220,9 +220,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -237,7 +237,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", ] [[package]] @@ -251,9 +251,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", @@ -342,7 +342,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.3.1" +version = "3.3.2" dependencies = [ "chrono", "clap", @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.3.1" +version = "3.3.2" dependencies = [ "anyhow", "async-speed-limit", @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fnv" @@ -730,9 +730,9 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -920,9 +920,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -968,9 +968,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" @@ -1008,13 +1008,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] @@ -1031,9 +1030,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" @@ -1099,7 +1098,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "cfg_aliases", "libc", @@ -1167,7 +1166,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -1204,9 +1203,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1249,9 +1248,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1279,9 +1278,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -1321,20 +1320,11 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1366,9 +1356,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" @@ -1449,11 +1439,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1508,9 +1498,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -1540,9 +1530,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1553,9 +1543,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -1583,9 +1573,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1633,7 +1623,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", @@ -1715,9 +1705,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -1727,9 +1717,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.52" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 681fe73..159b3cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.3.1" +version = "3.3.2" edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ openssl-tls = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli- openssl-tls-static = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls-static"] [dependencies] -tokio = { version = "1.36", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.37", features = ["macros", "rt-multi-thread", "time"], default-features = false } native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 7617c58..f3e8b5f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.3.1" +version = "3.3.2" edition = "2021" license = "MIT" @@ -36,7 +36,7 @@ serde_plain = "1.0" shlex = "1.3" sys-locale = "0.3" tempfile = "3.10" -tokio = { version = "1.36", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } +tokio = { version = "1.37", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } tokio-util = "0.7" tower-service = "0.3" rustls-native-certs = { version = "0.7", optional = true } From c0f334684679665a06cb6dc247017f63d52e2090 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Apr 2024 16:46:49 +0200 Subject: [PATCH 554/630] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7636dc1..5463e30 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# This project has been sunset as Crunchyroll moved to a DRM-only system. See [#362](https://github.com/crunchy-labs/crunchy-cli/issues/362). +> ~~This project has been sunset as Crunchyroll moved to a DRM-only system. See [#362](https://github.com/crunchy-labs/crunchy-cli/issues/362).~~ +> +> Well there is one endpoint which still has DRM-free streams, I guess I still have a bit time until (finally) everything is DRM-only. # crunchy-cli From af8ab248261c7d3b56930363fa6956ed01a8be9d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Apr 2024 17:13:44 +0200 Subject: [PATCH 555/630] Update search command url help --- crunchy-cli-core/src/search/command.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index 226e242..f683e24 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -88,9 +88,7 @@ pub struct Search { /// /// stream.locale โ†’ Stream locale/language /// stream.dash_url โ†’ Stream url in DASH format - /// stream.drm_dash_url โ†’ Stream url in DRM protected DASH format - /// stream.hls_url โ†’ Stream url in HLS format - /// stream.drm_hls_url โ†’ Stream url in DRM protected HLS format + /// stream.is_drm โ†’ If `stream.is_drm` is DRM encrypted /// /// subtitle.locale โ†’ Subtitle locale/language /// subtitle.url โ†’ Url to the subtitle From 8c1868f2fd9fb6348a09313f8cdf6f41239d58b3 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 3 Apr 2024 17:14:07 +0200 Subject: [PATCH 556/630] Update dependencies and version --- Cargo.lock | 65 +++++++++++++++++++++++++++++++++---- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 6 ++-- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52bba41..54d364c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,7 +342,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.3.2" +version = "3.3.3" dependencies = [ "chrono", "clap", @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.3.2" +version = "3.3.3" dependencies = [ "anyhow", "async-speed-limit", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05fcd99a09d001333ab482412473aaa03c84f980d451845b4c43d58986b6eb64" +checksum = "b9dba87354272cbe34eea8c27cab75b5104d7334aa6374db4807bd145f77a5ac" dependencies = [ "async-trait", "chrono", @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d406a27eca9ceab379b601101cd96b0f7bfff34e93487ca58d54118a8b4fbf91" +checksum = "7f7727afdfa43dcc8981a83c299a2e416262053402dc0586b15bfe0488f05e23" dependencies = [ "darling", "quote", @@ -559,6 +559,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -716,6 +725,25 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -795,6 +823,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1370,8 +1399,10 @@ dependencies = [ "bytes", "cookie", "cookie_store", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -1395,6 +1426,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1741,6 +1773,27 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.10.1" diff --git a/Cargo.toml b/Cargo.toml index 159b3cd..3a86a3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.3.2" +version = "3.3.3" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f3e8b5f..9d175d8 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.3.2" +version = "3.3.3" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.0", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.1", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" @@ -29,7 +29,7 @@ lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" -reqwest = { version = "0.12", default-features = false, features = ["socks", "stream"] } +reqwest = { version = "0.12", features = ["socks", "stream"] } serde = "1.0" serde_json = "1.0" serde_plain = "1.0" From 6b6d24a575ab4a4e873b93d11127d201f0efebb8 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 4 Apr 2024 21:01:34 +0200 Subject: [PATCH 557/630] Update dependencies and version --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54d364c..f584cee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,7 +342,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.3.3" +version = "3.3.4" dependencies = [ "chrono", "clap", @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.3.3" +version = "3.3.4" dependencies = [ "anyhow", "async-speed-limit", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9dba87354272cbe34eea8c27cab75b5104d7334aa6374db4807bd145f77a5ac" +checksum = "0010e5dded0388e3a1e69105c2e65637d092eff049ba10f132f216c8e26a2473" dependencies = [ "async-trait", "chrono", @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7727afdfa43dcc8981a83c299a2e416262053402dc0586b15bfe0488f05e23" +checksum = "7e5226275711b3d1dc6afdc5e2241a45bb5d4dc1a813902265d628ccfe1ab67d" dependencies = [ "darling", "quote", @@ -727,9 +727,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", diff --git a/Cargo.toml b/Cargo.toml index 3a86a3d..221db3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.3.3" +version = "3.3.4" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 9d175d8..0bbf493 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.3.3" +version = "3.3.4" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.1", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.2", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From c40ea8b1320eda6b2243147ec345161d7b81b53a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 5 Apr 2024 22:31:39 +0200 Subject: [PATCH 558/630] Update dependencies and version --- Cargo.lock | 35 +++++++++++++---------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f584cee..7d0b7e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,7 +342,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.3.4" +version = "3.3.5" dependencies = [ "chrono", "clap", @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.3.4" +version = "3.3.5" dependencies = [ "anyhow", "async-speed-limit", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0010e5dded0388e3a1e69105c2e65637d092eff049ba10f132f216c8e26a2473" +checksum = "2eae6c95ec38118d02ef2ca738e245d8afc404f05e502051013dc37e0295bb32" dependencies = [ "async-trait", "chrono", @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5226275711b3d1dc6afdc5e2241a45bb5d4dc1a813902265d628ccfe1ab67d" +checksum = "2f589713700c948db9a976d3f83816ab12efebdf759044a7bb963dad62000c12" dependencies = [ "darling", "quote", @@ -1391,11 +1391,11 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" +checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "bytes", "cookie", "cookie_store", @@ -1420,7 +1420,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -1503,21 +1503,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.1", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.1" @@ -2362,9 +2353,9 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", diff --git a/Cargo.toml b/Cargo.toml index 221db3f..c49244b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.3.4" +version = "3.3.5" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 0bbf493..07973e1 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.3.4" +version = "3.3.5" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.2", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.3", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 4b74299733732ee07f94dcc503d1c64920c888f2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 5 Apr 2024 22:53:53 +0200 Subject: [PATCH 559/630] Only run ci action on branch push --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6991cad..70f4400 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,8 @@ name: ci on: push: + branches: + - '*' pull_request: workflow_dispatch: From 25cde6163c8c0a5b13459274f4239b1b7f99181a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 6 Apr 2024 21:23:21 +0200 Subject: [PATCH 560/630] Add account scope for search command --- crunchy-cli-core/src/lib.rs | 2 - crunchy-cli-core/src/search/command.rs | 13 ++++-- crunchy-cli-core/src/search/format.rs | 59 ++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 8067c42..483cb63 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -225,8 +225,6 @@ async fn execute_executor(executor: impl Execute, ctx: Context) { if let Some(crunchy_error) = err.downcast_mut::<Error>() { if let Error::Block { message, .. } = crunchy_error { *message = "Triggered Cloudflare bot protection. Try again later or use a VPN or proxy to spoof your location".to_string() - } else if let Error::Request { message, .. } = crunchy_error { - *message = "You've probably hit a rate limit. Try again later, generally after 10-20 minutes the rate limit is over and you can continue to use the cli".to_string() } error!("An error occurred: {}", crunchy_error) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index f683e24..c29ce34 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -8,6 +8,7 @@ use crunchyroll_rs::common::StreamExt; use crunchyroll_rs::search::QueryResults; use crunchyroll_rs::{Episode, Locale, MediaCollection, MovieListing, MusicVideo, Series}; use log::warn; +use std::sync::Arc; #[derive(Debug, clap::Parser)] #[clap(about = "Search in videos")] @@ -87,11 +88,16 @@ pub struct Search { /// concert.premium_only โ†’ If the concert is only available with Crunchyroll premium /// /// stream.locale โ†’ Stream locale/language - /// stream.dash_url โ†’ Stream url in DASH format - /// stream.is_drm โ†’ If `stream.is_drm` is DRM encrypted + /// stream.dash_url โ†’ Stream url in DASH format. You need to set the `Authorization` header to `Bearer <account.token>` when requesting this url + /// stream.is_drm โ†’ If `stream.dash_url` is DRM encrypted /// /// subtitle.locale โ†’ Subtitle locale/language /// subtitle.url โ†’ Url to the subtitle + /// + /// account.token โ†’ Access token to make request to restricted endpoints. This token is only valid for a max. of 5 minutes + /// account.id โ†’ Internal ID of the user account + /// account.profile_name โ†’ Profile name of the account + /// account.email โ†’ Email address of the account #[arg(short, long, verbatim_doc_comment)] #[arg(default_value = "S{{season.number}}E{{episode.number}} - {{episode.title}}")] output: String, @@ -143,13 +149,14 @@ impl Execute for Search { output }; + let crunchy_arc = Arc::new(ctx.crunchy); for (media_collection, url_filter) in input { let filter_options = FilterOptions { audio: self.audio.clone(), url_filter, }; - let format = Format::new(self.output.clone(), filter_options)?; + let format = Format::new(self.output.clone(), filter_options, crunchy_arc.clone())?; println!("{}", format.parse(media_collection).await?); } diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 10eefd8..7ea84d8 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -2,13 +2,15 @@ use crate::search::filter::FilterOptions; use anyhow::{bail, Result}; use crunchyroll_rs::media::{Stream, Subtitle}; use crunchyroll_rs::{ - Concert, Episode, Locale, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series, + Concert, Crunchyroll, Episode, Locale, MediaCollection, Movie, MovieListing, MusicVideo, + Season, Series, }; use regex::Regex; use serde::Serialize; use serde_json::{Map, Value}; use std::collections::HashMap; use std::ops::Range; +use std::sync::Arc; #[derive(Default, Serialize)] struct FormatSeries { @@ -191,6 +193,27 @@ impl From<&Subtitle> for FormatSubtitle { } } +#[derive(Default, Serialize)] +struct FormatAccount { + pub token: String, + pub id: String, + pub profile_name: String, + pub email: String, +} + +impl FormatAccount { + pub async fn async_from(value: &Crunchyroll) -> Result<Self> { + let account = value.account().await?; + + Ok(Self { + token: value.access_token().await, + id: account.account_id, + profile_name: account.profile_name, + email: account.email, + }) + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash)] enum Scope { Series, @@ -202,6 +225,7 @@ enum Scope { Concert, Stream, Subtitle, + Account, } macro_rules! must_match_if_true { @@ -230,10 +254,15 @@ pub struct Format { pattern_count: HashMap<Scope, u32>, input: String, filter_options: FilterOptions, + crunchyroll: Arc<Crunchyroll>, } impl Format { - pub fn new(input: String, filter_options: FilterOptions) -> Result<Self> { + pub fn new( + input: String, + filter_options: FilterOptions, + crunchyroll: Arc<Crunchyroll>, + ) -> Result<Self> { let scope_regex = Regex::new(r"(?m)\{\{\s*(?P<scope>\w+)\.(?P<field>\w+)\s*}}").unwrap(); let mut pattern = vec![]; let mut pattern_count = HashMap::new(); @@ -260,6 +289,7 @@ impl Format { Scope::Concert => FormatConcert Scope::Stream => FormatStream Scope::Subtitle => FormatSubtitle + Scope::Account => FormatAccount ); for capture in scope_regex.captures_iter(&input) { @@ -277,6 +307,7 @@ impl Format { "concert" => Scope::Concert, "stream" => Scope::Stream, "subtitle" => Scope::Subtitle, + "account" => Scope::Account, _ => bail!("'{}.{}' is not a valid keyword", scope, field), }; @@ -302,6 +333,7 @@ impl Format { pattern_count, input, filter_options, + crunchyroll, }) } @@ -316,6 +348,7 @@ impl Format { Scope::Episode, Scope::Stream, Scope::Subtitle, + Scope::Account, ])?; self.parse_series(media_collection).await @@ -326,17 +359,28 @@ impl Format { Scope::Movie, Scope::Stream, Scope::Subtitle, + Scope::Account, ])?; self.parse_movie_listing(media_collection).await } MediaCollection::MusicVideo(_) => { - self.check_scopes(vec![Scope::MusicVideo, Scope::Stream, Scope::Subtitle])?; + self.check_scopes(vec![ + Scope::MusicVideo, + Scope::Stream, + Scope::Subtitle, + Scope::Account, + ])?; self.parse_music_video(media_collection).await } MediaCollection::Concert(_) => { - self.check_scopes(vec![Scope::Concert, Scope::Stream, Scope::Subtitle])?; + self.check_scopes(vec![ + Scope::Concert, + Scope::Stream, + Scope::Subtitle, + Scope::Account, + ])?; self.parse_concert(media_collection).await } @@ -349,6 +393,7 @@ impl Format { let episode_empty = self.check_pattern_count_empty(Scope::Episode); let stream_empty = self.check_pattern_count_empty(Scope::Stream) && self.check_pattern_count_empty(Scope::Subtitle); + let account_empty = self.check_pattern_count_empty(Scope::Account); #[allow(clippy::type_complexity)] let mut tree: Vec<(Season, Vec<(Episode, Vec<Stream>)>)> = vec![]; @@ -431,6 +476,11 @@ impl Format { } let mut output = vec![]; + let account_map = if !account_empty { + self.serializable_to_json_map(FormatAccount::async_from(&self.crunchyroll).await?) + } else { + Map::default() + }; let series_map = self.serializable_to_json_map(FormatSeries::from(&series)); for (season, episodes) in tree { let season_map = self.serializable_to_json_map(FormatSeason::from(&season)); @@ -442,6 +492,7 @@ impl Format { output.push( self.replace_all( HashMap::from([ + (Scope::Account, &account_map), (Scope::Series, &series_map), (Scope::Season, &season_map), (Scope::Episode, &episode_map), From fe49161e933e739784393a5ba92e29291025b803 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Apr 2024 00:37:19 +0200 Subject: [PATCH 561/630] End ffmpeg progress always with at least 100% --- crunchy-cli-core/src/utils/download.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index d7904f3..cee89e2 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1285,21 +1285,11 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( let reader = BufReader::new(stats); let mut lines = reader.lines(); + let mut frame = 0; loop { select! { - // when gracefully canceling this future, set the progress to 100% (finished). sometimes - // ffmpeg is too fast or already finished when the reading process of 'stats' starts - // which causes the progress to be stuck at 0% _ = cancellation_token.cancelled() => { - if let Some(p) = &progress { - p.set_position(total_frames) - } - debug!( - "Processed frame [{}/{} 100%]", - total_frames, - total_frames - ); - return Ok(()) + break } line = lines.next_line() => { let Some(line) = line? else { @@ -1314,7 +1304,7 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( let Some(frame_str) = frame_cap.name("frame") else { break }; - let frame: u64 = frame_str.as_str().parse()?; + frame = frame_str.as_str().parse()?; if let Some(p) = &progress { p.set_position(frame) @@ -1330,5 +1320,15 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( } } + // when this future is gracefully cancelled or if ffmpeg is too fast or already finished when + // reading process of 'stats' starts (which causes the progress to be stuck at 0%), the progress + // is manually set to 100% here + if frame < total_frames { + if let Some(p) = &progress { + p.set_position(frame) + } + debug!("Processed frame [{}/{} 100%]", total_frames, total_frames); + } + Ok(()) } From 1a511e12f95e7e4e76d97a4624306387909c6590 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Apr 2024 13:57:06 +0200 Subject: [PATCH 562/630] Add archive start sync flag --- Cargo.lock | 122 +++++ crunchy-cli-core/Cargo.toml | 2 + crunchy-cli-core/src/archive/command.rs | 35 +- crunchy-cli-core/src/download/command.rs | 4 +- crunchy-cli-core/src/lib.rs | 33 +- crunchy-cli-core/src/utils/download.rs | 653 +++++++++++++++++------ crunchy-cli-core/src/utils/os.rs | 20 +- crunchy-cli-core/src/utils/video.rs | 2 +- 8 files changed, 692 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d0b7e1..c1f24f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,18 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "bytemuck" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -369,6 +381,8 @@ dependencies = [ "fs2", "futures-util", "http", + "image", + "image_hasher", "indicatif", "lazy_static", "log", @@ -936,6 +950,32 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +dependencies = [ + "bytemuck", + "byteorder", + "num-traits", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image_hasher" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481465fe767d92494987319b0b447a5829edf57f09c52bf8639396abaaeaf78" +dependencies = [ + "base64 0.22.0", + "image", + "rustdct", + "serde", + "transpose", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1143,12 +1183,30 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1305,6 +1363,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "primal-check" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -1469,6 +1536,30 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustdct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b61555105d6a9bf98797c063c362a1d24ed8ab0431655e38f1cf51e52089551" +dependencies = [ + "rustfft", +] + +[[package]] +name = "rustfft" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + [[package]] name = "rustix" version = "0.38.32" @@ -1720,6 +1811,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strsim" version = "0.10.0" @@ -1998,6 +2095,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -2377,3 +2484,18 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 07973e1..bd47aba 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -24,6 +24,8 @@ derive_setters = "0.1" futures-util = { version = "0.3", features = ["io"] } fs2 = "0.4" http = "1.1" +image = { version = "0.25", features = ["jpeg"], default-features = false } +image_hasher = "2.0" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 0dc0b86..64ad66a 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -10,7 +10,7 @@ use crate::utils::locale::{all_locale_in_locales, resolve_locales, LanguageTaggi use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::parse_url; -use crate::utils::video::variant_data_from_stream; +use crate::utils::video::stream_data_from_stream; use crate::Execute; use anyhow::bail; use anyhow::Result; @@ -89,6 +89,17 @@ pub struct Archive { #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] pub(crate) resolution: Resolution, + #[arg(help = "Tries to sync the timing of all downloaded audios to match one video")] + #[arg( + long_help = "Tries to sync the timing of all downloaded audios to match one video. \ + This is done by downloading the first few segments/frames of all video tracks that differ in length and comparing them frame by frame. \ + The value of this flag determines how accurate the syncing is, generally speaking everything over 15 begins to be more inaccurate and everything below 6 is too accurate (and won't succeed). \ + If you want to provide a custom value to this flag, you have to set it with an equals (e.g. `--sync-start=10` instead of `--sync-start 10`). \ + When the syncing fails, the command is continued as if `--sync-start` wasn't provided for this episode + " + )] + #[arg(long, require_equals = true, num_args = 0..=1, default_missing_value = "7.5")] + pub(crate) sync_start: Option<f64>, #[arg( help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio' and 'video'" )] @@ -216,8 +227,19 @@ impl Execute for Archive { } } - if self.include_chapters && !matches!(self.merge, MergeBehavior::Audio) { - bail!("`--include-chapters` can only be used if `--merge` is set to 'audio'") + if self.include_chapters + && !matches!(self.merge, MergeBehavior::Audio) + && self.sync_start.is_none() + { + bail!("`--include-chapters` can only be used if `--merge` is set to 'audio' or `--sync-start` is set") + } + + if !matches!(self.merge, MergeBehavior::Auto) && self.sync_start.is_some() { + bail!("`--sync-start` can only be used if `--merge` is set to `auto`") + } + + if self.sync_start.is_some() && self.ffmpeg_preset.is_none() { + warn!("Using `--sync-start` without `--ffmpeg-preset` might produce worse sync results than with `--ffmpeg-preset` set") } if self.output.contains("{resolution}") @@ -294,6 +316,7 @@ impl Execute for Archive { .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())) .no_closed_caption(self.no_closed_caption) + .sync_start_value(self.sync_start) .threads(self.threads) .audio_locale_output_map( zip(self.audio.clone(), self.output_audio_locales.clone()).collect(), @@ -450,7 +473,7 @@ async fn get_format( for single_format in single_formats { let stream = single_format.stream().await?; let Some((video, audio, _)) = - variant_data_from_stream(&stream, &archive.resolution, None).await? + stream_data_from_stream(&stream, &archive.resolution, None).await? else { if single_format.is_episode() { bail!( @@ -569,7 +592,9 @@ async fn get_format( video: (video, single_format.audio.clone()), audios: vec![(audio, single_format.audio.clone())], subtitles, - metadata: DownloadFormatMetadata { skip_events: None }, + metadata: DownloadFormatMetadata { + skip_events: single_format.skip_events().await?, + }, }, )); } diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 47b29c9..fd29030 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -8,7 +8,7 @@ use crate::utils::locale::{resolve_locales, LanguageTagging}; use crate::utils::log::progress; use crate::utils::os::{free_file, has_ffmpeg, is_special_file}; use crate::utils::parse::parse_url; -use crate::utils::video::variant_data_from_stream; +use crate::utils::video::stream_data_from_stream; use crate::Execute; use anyhow::bail; use anyhow::Result; @@ -351,7 +351,7 @@ async fn get_format( try_peer_hardsubs: bool, ) -> Result<(DownloadFormat, Format)> { let stream = single_format.stream().await?; - let Some((video, audio, contains_hardsub)) = variant_data_from_stream( + let Some((video, audio, contains_hardsub)) = stream_data_from_stream( &stream, &download.resolution, if try_peer_hardsubs { diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 483cb63..d8d5ec5 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -184,16 +184,29 @@ pub async fn main(args: &[String]) { .unwrap_or_default() .starts_with(".crunchy-cli_") { - let result = fs::remove_file(file.path()); - debug!( - "Ctrl-c removed temporary file {} {}", - file.path().to_string_lossy(), - if result.is_ok() { - "successfully" - } else { - "not successfully" - } - ) + if file.file_type().map_or(true, |ft| ft.is_file()) { + let result = fs::remove_file(file.path()); + debug!( + "Ctrl-c removed temporary file {} {}", + file.path().to_string_lossy(), + if result.is_ok() { + "successfully" + } else { + "not successfully" + } + ) + } else { + let result = fs::remove_dir_all(file.path()); + debug!( + "Ctrl-c removed temporary directory {} {}", + file.path().to_string_lossy(), + if result.is_ok() { + "successfully" + } else { + "not successfully" + } + ) + } } } } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index cee89e2..fe0e84a 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,11 +1,15 @@ use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::real_dedup_vec; -use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pipe, tempfile}; +use crate::utils::log::progress; +use crate::utils::os::{ + cache_dir, is_special_file, temp_directory, temp_named_pipe, tempdir, tempfile, +}; use crate::utils::rate_limit::RateLimiterService; use anyhow::{bail, Result}; -use chrono::NaiveTime; +use chrono::{NaiveTime, TimeDelta}; use crunchyroll_rs::media::{SkipEvents, SkipEventsEvent, StreamData, StreamSegment, Subtitle}; use crunchyroll_rs::Locale; +use image_hasher::{Hasher, HasherConfig, ImageHash}; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; @@ -59,6 +63,7 @@ pub struct DownloadBuilder { force_hardsub: bool, download_fonts: bool, no_closed_caption: bool, + sync_start_value: Option<f64>, threads: usize, ffmpeg_threads: Option<usize>, audio_locale_output_map: HashMap<Locale, String>, @@ -78,6 +83,7 @@ impl DownloadBuilder { force_hardsub: false, download_fonts: false, no_closed_caption: false, + sync_start_value: None, threads: num_cpus::get(), ffmpeg_threads: None, audio_locale_output_map: HashMap::new(), @@ -99,6 +105,8 @@ impl DownloadBuilder { download_fonts: self.download_fonts, no_closed_caption: self.no_closed_caption, + sync_start_value: self.sync_start_value, + download_threads: self.threads, ffmpeg_threads: self.ffmpeg_threads, @@ -110,10 +118,23 @@ impl DownloadBuilder { } } -struct FFmpegMeta { +struct FFmpegVideoMeta { path: TempPath, - language: Locale, - title: String, + length: TimeDelta, + start_time: Option<TimeDelta>, +} + +struct FFmpegAudioMeta { + path: TempPath, + locale: Locale, + start_time: Option<TimeDelta>, +} + +struct FFmpegSubtitleMeta { + path: TempPath, + locale: Locale, + cc: bool, + start_time: Option<TimeDelta>, } pub struct DownloadFormat { @@ -141,6 +162,8 @@ pub struct Downloader { download_fonts: bool, no_closed_caption: bool, + sync_start_value: Option<f64>, + download_threads: usize, ffmpeg_threads: Option<usize>, @@ -216,13 +239,16 @@ impl Downloader { } } + let mut video_offset = None; + let mut audio_offsets = HashMap::new(); + let mut subtitle_offsets = HashMap::new(); let mut videos = vec![]; let mut audios = vec![]; let mut subtitles = vec![]; let mut fonts = vec![]; let mut chapters = None; - let mut max_len = NaiveTime::MIN; - let mut max_frames = 0f64; + let mut max_len = TimeDelta::min_value(); + let mut max_frames = 0; let fmt_space = self .formats .iter() @@ -234,115 +260,252 @@ impl Downloader { .max() .unwrap(); - for (i, format) in self.formats.iter().enumerate() { - let video_path = self - .download_video( - &format.video.0, - format!("{:<1$}", format!("Downloading video #{}", i + 1), fmt_space), - ) - .await?; - for (variant_data, locale) in format.audios.iter() { - let audio_path = self - .download_audio( - variant_data, - format!("{:<1$}", format!("Downloading {} audio", locale), fmt_space), + if self.formats.len() > 1 && self.sync_start_value.is_some() { + let all_segments_count: Vec<usize> = self + .formats + .iter() + .map(|f| f.video.0.segments().len()) + .collect(); + let sync_segments = 11.max( + all_segments_count.iter().max().unwrap() - all_segments_count.iter().min().unwrap(), + ); + let mut sync_vids = vec![]; + for (i, format) in self.formats.iter().enumerate() { + let path = self + .download_video( + &format.video.0, + format!("Downloading video #{} sync segments", i + 1), + Some(sync_segments), ) .await?; - audios.push(FFmpegMeta { - path: audio_path, - language: locale.clone(), - title: if i == 0 { - locale.to_human_readable() - } else { - format!("{} [Video: #{}]", locale.to_human_readable(), i + 1) - }, + sync_vids.push(SyncVideo { + path, + length: len_from_segments(&format.video.0.segments()), + available_frames: (len_from_segments( + &format.video.0.segments()[0..sync_segments], + ) + .num_milliseconds() as f64 + * format.video.0.fps().unwrap() + / 1000.0) as u64, + idx: i, }) } - let (len, fps) = get_video_stats(&video_path)?; + let _progress_handler = + progress!("Syncing video start times (this might take some time)"); + let mut offsets = sync_videos(sync_vids, self.sync_start_value.unwrap())?; + drop(_progress_handler); + + let mut offset_pre_checked = false; + if let Some(tmp_offsets) = &offsets { + let formats_with_offset: Vec<TimeDelta> = self + .formats + .iter() + .enumerate() + .map(|(i, f)| { + len_from_segments(&f.video.0.segments()) + - TimeDelta::milliseconds( + tmp_offsets + .get(&i) + .map(|o| (*o as f64 / f.video.0.fps().unwrap() * 1000.0) as i64) + .unwrap_or_default(), + ) + }) + .collect(); + let min = formats_with_offset.iter().min().unwrap(); + let max = formats_with_offset.iter().max().unwrap(); + + if max.num_seconds() - min.num_seconds() > 15 { + warn!("Found difference of >15 seconds after sync, skipping applying it"); + offsets = None; + offset_pre_checked = true + } + } + + if let Some(offsets) = offsets { + let mut root_format_idx = 0; + let mut root_format_length = 0; + let mut audio_count: usize = 0; + let mut subtitle_count: usize = 0; + for (i, format) in self.formats.iter().enumerate() { + let format_fps = format.video.0.fps().unwrap(); + let format_len = format + .video + .0 + .segments() + .iter() + .map(|s| s.length.as_millis()) + .sum::<u128>() as u64 + - offsets.get(&i).map_or(0, |o| *o); + if format_len > root_format_length { + root_format_idx = i; + root_format_length = format_len; + } + + for _ in &format.audios { + if let Some(offset) = &offsets.get(&i) { + audio_offsets.insert( + audio_count, + TimeDelta::milliseconds( + (**offset as f64 / format_fps * 1000.0) as i64, + ), + ); + } + audio_count += 1 + } + for _ in &format.subtitles { + if let Some(offset) = &offsets.get(&i) { + subtitle_offsets.insert( + subtitle_count, + TimeDelta::milliseconds( + (**offset as f64 / format_fps * 1000.0) as i64, + ), + ); + } + subtitle_count += 1 + } + } + + let mut root_format = self.formats.remove(root_format_idx); + + let mut audio_prepend = vec![]; + let mut subtitle_prepend = vec![]; + let mut audio_append = vec![]; + let mut subtitle_append = vec![]; + for (i, format) in self.formats.into_iter().enumerate() { + if i < root_format_idx { + audio_prepend.extend(format.audios); + subtitle_prepend.extend(format.subtitles); + } else { + audio_append.extend(format.audios); + subtitle_append.extend(format.subtitles); + } + } + root_format.audios.splice(0..0, audio_prepend); + root_format.subtitles.splice(0..0, subtitle_prepend); + root_format.audios.extend(audio_append); + root_format.subtitles.extend(subtitle_append); + + self.formats = vec![root_format]; + video_offset = offsets.get(&root_format_idx).map(|o| { + TimeDelta::milliseconds( + (*o as f64 / self.formats[0].video.0.fps().unwrap() * 1000.0) as i64, + ) + }) + } else if !offset_pre_checked { + warn!("Couldn't find reliable sync positions") + } + } + + // downloads all videos + for (i, format) in self.formats.iter().enumerate() { + let path = self + .download_video( + &format.video.0, + format!("{:<1$}", format!("Downloading video #{}", i + 1), fmt_space), + None, + ) + .await?; + + let (len, fps) = get_video_stats(&path)?; if max_len < len { max_len = len } - let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps; - if frames > max_frames { - max_frames = frames; + let frames = ((len.num_milliseconds() as f64 + - video_offset.unwrap_or_default().num_milliseconds() as f64) + / 1000.0 + * fps) as u64; + if max_frames < frames { + max_frames = frames } - if !format.subtitles.is_empty() { - let progress_spinner = if log::max_level() == LevelFilter::Info { - let progress_spinner = ProgressBar::new_spinner() - .with_style( - ProgressStyle::with_template( - format!( - ":: {:<1$} {{msg}} {{spinner}}", - "Downloading subtitles", fmt_space - ) - .as_str(), + videos.push(FFmpegVideoMeta { + path, + length: len, + start_time: video_offset, + }) + } + + // downloads all audios + for format in &self.formats { + for (j, (stream_data, locale)) in format.audios.iter().enumerate() { + let path = self + .download_audio( + stream_data, + format!("{:<1$}", format!("Downloading {} audio", locale), fmt_space), + ) + .await?; + audios.push(FFmpegAudioMeta { + path, + locale: locale.clone(), + start_time: audio_offsets.get(&j).cloned(), + }) + } + } + + for (i, format) in self.formats.iter().enumerate() { + if format.subtitles.is_empty() { + continue; + } + + let progress_spinner = if log::max_level() == LevelFilter::Info { + let progress_spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template( + format!( + ":: {:<1$} {{msg}} {{spinner}}", + "Downloading subtitles", fmt_space ) - .unwrap() - .tick_strings(&["โ€”", "\\", "|", "/", ""]), + .as_str(), ) - .with_finish(ProgressFinish::Abandon); - progress_spinner.enable_steady_tick(Duration::from_millis(100)); - Some(progress_spinner) - } else { - None - }; + .unwrap() + .tick_strings(&["โ€”", "\\", "|", "/", ""]), + ) + .with_finish(ProgressFinish::Abandon); + progress_spinner.enable_steady_tick(Duration::from_millis(100)); + Some(progress_spinner) + } else { + None + }; - for (subtitle, not_cc) in format.subtitles.iter() { - if !not_cc && self.no_closed_caption { - continue; - } - - if let Some(pb) = &progress_spinner { - let mut progress_message = pb.message(); - if !progress_message.is_empty() { - progress_message += ", " - } - progress_message += &subtitle.locale.to_string(); - if !not_cc { - progress_message += " (CC)"; - } - if i != 0 { - progress_message += &format!(" [Video: #{}]", i + 1); - } - pb.set_message(progress_message) - } - - let mut subtitle_title = subtitle.locale.to_human_readable(); - if !not_cc { - subtitle_title += " (CC)" - } - if i != 0 { - subtitle_title += &format!(" [Video: #{}]", i + 1) - } - - let subtitle_path = self.download_subtitle(subtitle.clone(), len).await?; - debug!( - "Downloaded {} subtitles{}{}", - subtitle.locale, - (!not_cc).then_some(" (cc)").unwrap_or_default(), - (i != 0) - .then_some(format!(" for video {}", i)) - .unwrap_or_default() - ); - subtitles.push(FFmpegMeta { - path: subtitle_path, - language: subtitle.locale.clone(), - title: subtitle_title, - }) + for (j, (subtitle, not_cc)) in format.subtitles.iter().enumerate() { + if !not_cc && self.no_closed_caption { + continue; } - } - videos.push(FFmpegMeta { - path: video_path, - language: format.video.1.clone(), - title: if self.formats.len() == 1 { - "Default".to_string() - } else { - format!("#{}", i + 1) - }, - }); + if let Some(pb) = &progress_spinner { + let mut progress_message = pb.message(); + if !progress_message.is_empty() { + progress_message += ", " + } + progress_message += &subtitle.locale.to_string(); + if !not_cc { + progress_message += " (CC)"; + } + if i.min(videos.len() - 1) != 0 { + progress_message += &format!(" [Video: #{}]", i + 1); + } + pb.set_message(progress_message) + } + + let path = self + .download_subtitle(subtitle.clone(), videos[i.min(videos.len() - 1)].length) + .await?; + debug!( + "Downloaded {} subtitles{}", + subtitle.locale, + (!not_cc).then_some(" (cc)").unwrap_or_default(), + ); + subtitles.push(FFmpegSubtitleMeta { + path, + locale: subtitle.locale.clone(), + cc: !not_cc, + start_time: subtitle_offsets.get(&j).cloned(), + }) + } + } + + for format in self.formats.iter() { if let Some(skip_events) = &format.metadata.skip_events { let (file, path) = tempfile(".chapter")?.into_parts(); chapters = Some(( @@ -421,17 +584,30 @@ impl Downloader { let mut metadata = vec![]; for (i, meta) in videos.iter().enumerate() { + if let Some(start_time) = meta.start_time { + input.extend(["-ss".to_string(), format_time_delta(start_time)]) + } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), i.to_string()]); metadata.extend([ format!("-metadata:s:v:{}", i), - format!("title={}", meta.title), + format!( + "title={}", + if videos.len() == 1 { + "Default".to_string() + } else { + format!("#{}", i + 1) + } + ), ]); // the empty language metadata is created to avoid that metadata from the original track // is copied metadata.extend([format!("-metadata:s:v:{}", i), "language=".to_string()]) } for (i, meta) in audios.iter().enumerate() { + if let Some(start_time) = meta.start_time { + input.extend(["-ss".to_string(), format_time_delta(start_time)]) + } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), (i + videos.len()).to_string()]); metadata.extend([ @@ -439,13 +615,20 @@ impl Downloader { format!( "language={}", self.audio_locale_output_map - .get(&meta.language) - .unwrap_or(&meta.language.to_string()) + .get(&meta.locale) + .unwrap_or(&meta.locale.to_string()) ), ]); metadata.extend([ format!("-metadata:s:a:{}", i), - format!("title={}", meta.title), + format!( + "title={}", + if videos.len() == 1 { + meta.locale.to_human_readable() + } else { + format!("{} [Video: #{}]", meta.locale.to_human_readable(), i + 1,) + } + ), ]); } @@ -465,6 +648,9 @@ impl Downloader { if container_supports_softsubs { for (i, meta) in subtitles.iter().enumerate() { + if let Some(start_time) = meta.start_time { + input.extend(["-ss".to_string(), format_time_delta(start_time)]) + } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend([ "-map".to_string(), @@ -475,13 +661,22 @@ impl Downloader { format!( "language={}", self.subtitle_locale_output_map - .get(&meta.language) - .unwrap_or(&meta.language.to_string()) + .get(&meta.locale) + .unwrap_or(&meta.locale.to_string()) ), ]); metadata.extend([ format!("-metadata:s:s:{}", i), - format!("title={}", meta.title), + format!("title={}", { + let mut title = meta.locale.to_string(); + if meta.cc { + title += " (CC)" + } + if videos.len() > 1 { + title += &format!(" [Video: #{}]", i + 1) + } + title + }), ]); } } @@ -523,10 +718,7 @@ impl Downloader { // set default subtitle if let Some(default_subtitle) = self.default_subtitle { - if let Some(position) = subtitles - .iter() - .position(|m| m.language == default_subtitle) - { + if let Some(position) = subtitles.iter().position(|m| m.locale == default_subtitle) { if container_supports_softsubs { match dst.extension().unwrap_or_default().to_str().unwrap() { "mov" | "mp4" => output_presets.extend([ @@ -585,7 +777,7 @@ impl Downloader { if container_supports_softsubs { if let Some(position) = subtitles .iter() - .position(|meta| meta.language == default_subtitle) + .position(|meta| meta.locale == default_subtitle) { command_args.extend([ format!("-disposition:s:s:{}", position), @@ -597,9 +789,7 @@ impl Downloader { // set the 'forced' flag to CC subtitles for (i, subtitle) in subtitles.iter().enumerate() { - // well, checking if the title contains '(CC)' might not be the best solutions from a - // performance perspective but easier than adjusting the `FFmpegMeta` struct - if !subtitle.title.contains("(CC)") { + if !subtitle.cc { continue; } @@ -632,7 +822,7 @@ impl Downloader { // create parent directory if it does not exist if let Some(parent) = dst.parent() { if !parent.exists() { - std::fs::create_dir_all(parent)? + fs::create_dir_all(parent)? } } @@ -650,7 +840,7 @@ impl Downloader { let ffmpeg_progress_cancellation_token = ffmpeg_progress_cancel.clone(); let ffmpeg_progress = tokio::spawn(async move { ffmpeg_progress( - max_frames as u64, + max_frames, fifo, format!("{:<1$}", "Generating output file", fmt_space + 1), ffmpeg_progress_cancellation_token, @@ -681,7 +871,7 @@ impl Downloader { let segments = stream_data.segments(); // sum the length of all streams up - estimated_required_space += estimate_variant_file_size(stream_data, &segments); + estimated_required_space += estimate_stream_data_file_size(stream_data, &segments); } let tmp_stat = fs2::statvfs(temp_directory()).unwrap(); @@ -727,11 +917,16 @@ impl Downloader { Ok((tmp_required, dst_required)) } - async fn download_video(&self, stream_data: &StreamData, message: String) -> Result<TempPath> { + async fn download_video( + &self, + stream_data: &StreamData, + message: String, + max_segments: Option<usize>, + ) -> Result<TempPath> { let tempfile = tempfile(".mp4")?; let (mut file, path) = tempfile.into_parts(); - self.download_segments(&mut file, message, stream_data) + self.download_segments(&mut file, message, stream_data, max_segments) .await?; Ok(path) @@ -741,7 +936,7 @@ impl Downloader { let tempfile = tempfile(".m4a")?; let (mut file, path) = tempfile.into_parts(); - self.download_segments(&mut file, message, stream_data) + self.download_segments(&mut file, message, stream_data, None) .await?; Ok(path) @@ -750,7 +945,7 @@ impl Downloader { async fn download_subtitle( &self, subtitle: Subtitle, - max_length: NaiveTime, + max_length: TimeDelta, ) -> Result<TempPath> { let tempfile = tempfile(".ass")?; let (mut file, path) = tempfile.into_parts(); @@ -796,14 +991,20 @@ impl Downloader { writer: &mut impl Write, message: String, stream_data: &StreamData, + max_segments: Option<usize>, ) -> Result<()> { - let segments = stream_data.segments(); + let mut segments = stream_data.segments(); + if let Some(max_segments) = max_segments { + segments = segments + .drain(0..max_segments.min(segments.len() - 1)) + .collect(); + } let total_segments = segments.len(); let count = Arc::new(Mutex::new(0)); let progress = if log::max_level() == LevelFilter::Info { - let estimated_file_size = estimate_variant_file_size(stream_data, &segments); + let estimated_file_size = estimate_stream_data_file_size(stream_data, &segments); let progress = ProgressBar::new(estimated_file_size) .with_style( @@ -820,7 +1021,7 @@ impl Downloader { None }; - let cpus = self.download_threads; + let cpus = self.download_threads.min(segments.len()); let mut segs: Vec<Vec<StreamSegment>> = Vec::with_capacity(cpus); for _ in 0..cpus { segs.push(vec![]) @@ -964,12 +1165,12 @@ impl Downloader { } } -fn estimate_variant_file_size(stream_data: &StreamData, segments: &[StreamSegment]) -> u64 { +fn estimate_stream_data_file_size(stream_data: &StreamData, segments: &[StreamSegment]) -> u64 { (stream_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>() } /// Get the length and fps of a video. -fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { +fn get_video_stats(path: &Path) -> Result<(TimeDelta, f64)> { let video_length = Regex::new(r"Duration:\s(?P<time>\d+:\d+:\d+\.\d+),")?; let video_fps = Regex::new(r"(?P<fps>[\d/.]+)\sfps")?; @@ -996,7 +1197,8 @@ fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { Ok(( NaiveTime::parse_from_str(length_caps.name("time").unwrap().as_str(), "%H:%M:%S%.f") - .unwrap(), + .unwrap() + .signed_duration_since(NaiveTime::MIN), fps_caps.name("fps").unwrap().as_str().parse().unwrap(), )) } @@ -1125,28 +1327,12 @@ fn get_subtitle_stats(path: &Path) -> Result<Vec<String>> { /// players. To prevent this, the subtitle entries must be manually sorted. See /// [crunchy-labs/crunchy-cli#208](https://github.com/crunchy-labs/crunchy-cli/issues/208) for more /// information. -fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { +fn fix_subtitles(raw: &mut Vec<u8>, max_length: TimeDelta) { let re = Regex::new( r"^Dialogue:\s(?P<layer>\d+),(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),", ) .unwrap(); - // chrono panics if we try to format NaiveTime with `%2f` and the nano seconds has more than 2 - // digits so them have to be reduced manually to avoid the panic - fn format_naive_time(native_time: NaiveTime) -> String { - let formatted_time = native_time.format("%f").to_string(); - format!( - "{}.{}", - native_time.format("%T"), - if formatted_time.len() <= 2 { - native_time.format("%2f").to_string() - } else { - formatted_time.split_at(2).0.parse().unwrap() - } - ) - .split_off(1) // <- in the ASS spec, the hour has only one digit - } - let mut entries = (vec![], vec![]); let mut as_lines: Vec<String> = String::from_utf8_lossy(raw.as_slice()) @@ -1158,12 +1344,18 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { if line.trim() == "[Script Info]" { line.push_str("\nScaledBorderAndShadow: yes") } else if let Some(capture) = re.captures(line) { - let mut start = capture.name("start").map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() - }); - let mut end = capture.name("end").map_or(NaiveTime::default(), |e| { - NaiveTime::parse_from_str(e.as_str(), "%H:%M:%S.%f").unwrap() - }); + let mut start = capture + .name("start") + .map_or(NaiveTime::default(), |s| { + NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() + }) + .signed_duration_since(NaiveTime::MIN); + let mut end = capture + .name("end") + .map_or(NaiveTime::default(), |e| { + NaiveTime::parse_from_str(e.as_str(), "%H:%M:%S.%f").unwrap() + }) + .signed_duration_since(NaiveTime::MIN); if start > max_length || end > max_length { let layer = capture @@ -1183,8 +1375,8 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { format!( "Dialogue: {},{},{},", layer, - format_naive_time(start), - format_naive_time(end) + format_time_delta(start), + format_time_delta(end) ), ) .to_string() @@ -1209,13 +1401,10 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) { fn write_ffmpeg_chapters( file: &mut fs::File, - video_len: NaiveTime, + video_len: TimeDelta, events: &mut Vec<(&str, &SkipEventsEvent)>, ) -> Result<()> { - let video_len = video_len - .signed_duration_since(NaiveTime::MIN) - .num_milliseconds() as f32 - / 1000.0; + let video_len = video_len.num_milliseconds() as f32 / 1000.0; events.sort_by(|(_, event_a), (_, event_b)| event_a.start.total_cmp(&event_b.start)); writeln!(file, ";FFMETADATA1")?; @@ -1332,3 +1521,149 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( Ok(()) } + +struct SyncVideo { + path: TempPath, + length: TimeDelta, + available_frames: u64, + idx: usize, +} + +fn sync_videos(mut sync_videos: Vec<SyncVideo>, value: f64) -> Result<Option<HashMap<usize, u64>>> { + let mut result = HashMap::new(); + let hasher = HasherConfig::new().to_hasher(); + let start_frame = 50; + + sync_videos.sort_by_key(|sv| sv.length); + + let sync_base = sync_videos.remove(0); + let sync_hashes = extract_frame_hashes(&sync_base.path, start_frame, 100, &hasher)?; + + for sync_video in sync_videos { + let mut highest_frame_match = f64::INFINITY; + let mut frame = start_frame; + let mut hashes = vec![]; + + loop { + if frame == sync_video.available_frames { + debug!( + "Failed to sync videos, end of stream {} reached (highest frame match: {})", + sync_video.idx + 1, + highest_frame_match + ); + return Ok(None); + } + + hashes.drain(0..(hashes.len() as i32 - sync_hashes.len() as i32).max(0) as usize); + hashes.extend(extract_frame_hashes( + &sync_video.path, + frame, + 300 - hashes.len() as u64, + &hasher, + )?); + + let check_frame_windows_result = check_frame_windows(&sync_hashes, &hashes); + if let Some(offset) = check_frame_windows_result + .iter() + .enumerate() + .find_map(|(i, cfw)| (*cfw <= value).then_some(i)) + { + result.insert(sync_video.idx, frame + offset as u64 - start_frame); + break; + } else { + let curr_highest_frame_match = *check_frame_windows_result + .iter() + .min_by(|a, b| a.total_cmp(b)) + .unwrap(); + if curr_highest_frame_match < highest_frame_match { + highest_frame_match = curr_highest_frame_match + } + } + + frame = (frame + 300 - sync_hashes.len() as u64).min(sync_video.available_frames) + } + } + + Ok(Some(result)) +} + +fn extract_frame_hashes( + input_file: &Path, + start_frame: u64, + frame_count: u64, + hasher: &Hasher, +) -> Result<Vec<ImageHash>> { + let frame_dir = tempdir(format!( + "{}_sync_frames", + input_file + .file_name() + .unwrap_or_default() + .to_string_lossy() + .trim_end_matches( + &input_file + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .to_string() + ) + ))?; + let extract_output = Command::new("ffmpeg") + .arg("-hide_banner") + .arg("-y") + .args(["-i", input_file.to_string_lossy().to_string().as_str()]) + .args([ + "-vf", + format!( + r#"select=between(n\,{}\,{}),setpts=PTS-STARTPTS"#, + start_frame, + start_frame + frame_count + ) + .as_str(), + ]) + .args(["-vframes", frame_count.to_string().as_str()]) + .arg(format!("{}/%03d.jpg", frame_dir.path().to_string_lossy())) + .output()?; + if !extract_output.status.success() { + bail!( + "{}", + String::from_utf8_lossy(extract_output.stderr.as_slice()) + ) + } + + let mut hashes = vec![]; + for file in frame_dir.path().read_dir()? { + let file = file?; + let img = image::open(file.path())?; + hashes.push(hasher.hash_image(&img)) + } + Ok(hashes) +} + +fn check_frame_windows(base_hashes: &[ImageHash], check_hashes: &[ImageHash]) -> Vec<f64> { + let mut results = vec![]; + + for i in 0..(check_hashes.len() - base_hashes.len()) { + let check_window = &check_hashes[i..(base_hashes.len() + i)]; + let sum = std::iter::zip(base_hashes, check_window) + .map(|(a, b)| a.dist(b)) + .sum::<u32>(); + results.push(sum as f64 / check_window.len() as f64); + } + results +} + +fn format_time_delta(time_delta: TimeDelta) -> String { + let hours = time_delta.num_hours(); + let minutes = time_delta.num_minutes() - time_delta.num_hours() * 60; + let seconds = time_delta.num_seconds() - time_delta.num_minutes() * 60; + let milliseconds = time_delta.num_milliseconds() - time_delta.num_seconds() * 1000; + + format!( + "{}:{:0>2}:{:0>2}.{:0>3}", + hours, minutes, seconds, milliseconds + ) +} + +fn len_from_segments(segments: &[StreamSegment]) -> TimeDelta { + TimeDelta::milliseconds(segments.iter().map(|s| s.length.as_millis()).sum::<u128>() as i64) +} diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index e57c4d0..b65abc2 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -7,7 +7,7 @@ use std::pin::Pin; use std::process::{Command, Stdio}; use std::task::{Context, Poll}; use std::{env, fs, io}; -use tempfile::{Builder, NamedTempFile, TempPath}; +use tempfile::{Builder, NamedTempFile, TempDir, TempPath}; use tokio::io::{AsyncRead, ReadBuf}; pub fn has_ffmpeg() -> bool { @@ -31,7 +31,7 @@ pub fn temp_directory() -> PathBuf { } /// Any tempfile should be created with this function. The prefix and directory of every file -/// created with this method stays the same which is helpful to query all existing tempfiles and +/// created with this function stays the same which is helpful to query all existing tempfiles and /// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like /// setting the wrong prefix if done manually. pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { @@ -46,6 +46,22 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { Ok(tempfile) } +/// Any tempdir should be created with this function. The prefix and directory of every directory +/// created with this function stays the same which is helpful to query all existing tempdirs and +/// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like +/// setting the wrong prefix if done manually. +pub fn tempdir<S: AsRef<str>>(suffix: S) -> io::Result<TempDir> { + let tempdir = Builder::default() + .prefix(".crunchy-cli_") + .suffix(suffix.as_ref()) + .tempdir_in(temp_directory())?; + debug!( + "Created temporary directory: {}", + tempdir.path().to_string_lossy() + ); + Ok(tempdir) +} + pub fn cache_dir<S: AsRef<str>>(name: S) -> io::Result<PathBuf> { let cache_dir = temp_directory().join(format!(".crunchy-cli_{}_cache", name.as_ref())); fs::create_dir_all(&cache_dir)?; diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index 7f7d73e..07f6e76 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use crunchyroll_rs::media::{Resolution, Stream, StreamData}; use crunchyroll_rs::Locale; -pub async fn variant_data_from_stream( +pub async fn stream_data_from_stream( stream: &Stream, resolution: &Resolution, subtitle: Option<Locale>, From 771594a2317df3c0e33da82c711e57bec80be93e Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Apr 2024 14:03:30 +0200 Subject: [PATCH 563/630] Remove hardcoded pixel format (#352) --- crunchy-cli-core/src/utils/download.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index fe0e84a..122d436 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -796,10 +796,6 @@ impl Downloader { command_args.extend([format!("-disposition:s:s:{}", i), "forced".to_string()]) } - // manually specifying the color model for the output file. this must be done manually - // because some Crunchyroll episodes are encoded in a way that ffmpeg cannot re-encode - command_args.extend(["-pix_fmt".to_string(), "yuv420p".to_string()]); - command_args.extend(output_presets); if let Some(output_format) = self.output_format { command_args.extend(["-f".to_string(), output_format]); From 77103ff1f1f904707721930846782785d76492f1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Apr 2024 14:18:10 +0200 Subject: [PATCH 564/630] Update dependencies and version --- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1f24f4..c0d6f44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,9 +199,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" [[package]] name = "cfg-if" @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.3.5" +version = "3.4.0" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.3.5" +version = "3.4.0" dependencies = [ "anyhow", "async-speed-limit", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eae6c95ec38118d02ef2ca738e245d8afc404f05e502051013dc37e0295bb32" +checksum = "2ec02ad9896fe71bfc53c48e8b8949aa81ab7f3cb9ff31fd197565d6cd7922ea" dependencies = [ "async-trait", "chrono", @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f589713700c948db9a976d3f83816ab12efebdf759044a7bb963dad62000c12" +checksum = "24d7cdd1fa6a6303a56f45041060641d6b4446e8065a91f1bcb8aa4a8f1f483c" dependencies = [ "darling", "quote", @@ -722,9 +722,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -1602,11 +1602,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index c49244b..2bdc3bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.3.5" +version = "3.4.0" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index bd47aba..1bda9e1 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.3.5" +version = "3.4.0" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.3", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.4", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 18534b259b835b74631fca5f19ff6da7e2652afe Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 8 Apr 2024 14:34:50 +0200 Subject: [PATCH 565/630] Remove deprecated {resolution} output format option --- crunchy-cli-core/src/archive/command.rs | 9 --------- crunchy-cli-core/src/download/command.rs | 9 --------- crunchy-cli-core/src/utils/format.rs | 4 ---- 3 files changed, 22 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 64ad66a..234bf68 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -242,15 +242,6 @@ impl Execute for Archive { warn!("Using `--sync-start` without `--ffmpeg-preset` might produce worse sync results than with `--ffmpeg-preset` set") } - if self.output.contains("{resolution}") - || self - .output_specials - .as_ref() - .map_or(false, |os| os.contains("{resolution}")) - { - warn!("The '{{resolution}}' format option is deprecated and will be removed in a future version. Please use '{{width}}' and '{{height}}' instead") - } - self.audio = all_locale_in_locales(self.audio.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index fd29030..58055bd 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -187,15 +187,6 @@ impl Execute for Download { } } - if self.output.contains("{resolution}") - || self - .output_specials - .as_ref() - .map_or(false, |os| os.contains("{resolution}")) - { - warn!("The '{{resolution}}' format option is deprecated and will be removed in a future version. Please use '{{width}}' and '{{height}}' instead") - } - if let Some(language_tagging) = &self.language_tagging { self.audio = resolve_locales(&[self.audio.clone()]).remove(0); self.subtitle = self diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index df79d64..17f324f 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -446,10 +446,6 @@ impl Format { universal, ), ) - .replace( - "{resolution}", - &sanitize(self.resolution.to_string(), true, universal), - ) .replace( "{width}", &sanitize(self.resolution.width.to_string(), true, universal), From 0115730d608c68d4b54095166d1fabafbf02c476 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Apr 2024 18:36:18 +0200 Subject: [PATCH 566/630] Add archive `--sync-start` flag documentation to README --- README.md | 12 +++++++++++- crunchy-cli-core/src/archive/command.rs | 23 ++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5463e30..b30852b 100644 --- a/README.md +++ b/README.md @@ -468,7 +468,7 @@ The `archive` command lets you download episodes with multiple audios and subtit - Merge behavior - Due to censorship, some episodes have multiple lengths for different languages. + Due to censorship or additional intros, some episodes have multiple lengths for different languages. In the best case, when multiple audio & subtitle tracks are used, there is only one *video* track and all other languages can be stored as audio-only. But, as said, this is not always the case. With the `-m` / `--merge` flag you can define the behaviour when an episodes' video tracks differ in length. @@ -492,6 +492,16 @@ The `archive` command lets you download episodes with multiple audios and subtit Default are `200` milliseconds. +- Sync start + + If you want that all videos of the same episode should start at the same time and `--merge` doesn't fit your needs (e.g. one video has an intro, all other doesn't), you might consider using the `--sync-start`. + It tries to sync the timing of all downloaded audios to match one video. + This is done by downloading the first few segments/frames of all video tracks that differ in length and comparing them frame by frame. + The flag takes an optional value determines how accurate the syncing is, generally speaking everything over 15 begins to be more inaccurate and everything below 6 is too accurate (and won't succeed). + When the syncing fails, the command is continued as if `--sync-start` wasn't provided for this episode. + + Default is `7.5`. + - Language tagging You can force the usage of a specific language tagging in the output file with the `--language-tagging` flag. diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 234bf68..1c18133 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -89,17 +89,6 @@ pub struct Archive { #[arg(value_parser = crate::utils::clap::clap_parse_resolution)] pub(crate) resolution: Resolution, - #[arg(help = "Tries to sync the timing of all downloaded audios to match one video")] - #[arg( - long_help = "Tries to sync the timing of all downloaded audios to match one video. \ - This is done by downloading the first few segments/frames of all video tracks that differ in length and comparing them frame by frame. \ - The value of this flag determines how accurate the syncing is, generally speaking everything over 15 begins to be more inaccurate and everything below 6 is too accurate (and won't succeed). \ - If you want to provide a custom value to this flag, you have to set it with an equals (e.g. `--sync-start=10` instead of `--sync-start 10`). \ - When the syncing fails, the command is continued as if `--sync-start` wasn't provided for this episode - " - )] - #[arg(long, require_equals = true, num_args = 0..=1, default_missing_value = "7.5")] - pub(crate) sync_start: Option<f64>, #[arg( help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio' and 'video'" )] @@ -121,6 +110,18 @@ pub struct Archive { help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard)" )] + #[arg(help = "Tries to sync the timing of all downloaded audios to match one video")] + #[arg( + long_help = "Tries to sync the timing of all downloaded audios to match one video. \ + This is done by downloading the first few segments/frames of all video tracks that differ in length and comparing them frame by frame. \ + The value of this flag determines how accurate the syncing is, generally speaking everything over 15 begins to be more inaccurate and everything below 6 is too accurate (and won't succeed). \ + If you want to provide a custom value to this flag, you have to set it with an equals (e.g. `--sync-start=10` instead of `--sync-start 10`). \ + When the syncing fails, the command is continued as if `--sync-start` wasn't provided for this episode + " + )] + #[arg(long, require_equals = true, num_args = 0..=1, default_missing_value = "7.5")] + pub(crate) sync_start: Option<f64>, + #[arg( long_help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard; you might run in issues as there are multiple locales which resolve to the same IETF language code, e.g. 'es-LA' and 'es-ES' are both resolving to 'es')" From a73773ce1deb26e8cf08fe6b7b6f358cdca9e673 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Apr 2024 18:55:15 +0200 Subject: [PATCH 567/630] Add id to every flag in README --- README.md | 98 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index b30852b..09db003 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,13 @@ crunchy-cli requires you to log in. Though you can use a non-premium account, you will not have access to premium content without a subscription. You can authenticate with your credentials (email:password) or by using a refresh token. -- Credentials +- <span id="global-credentials">Credentials</span> ```shell $ crunchy-cli --credentials "email:password" <command> ``` -- Refresh Token +- <span id="global-etp-rt">Refresh Token</span> To obtain a refresh token, you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). @@ -132,7 +132,7 @@ You can authenticate with your credentials (email:password) or by using a refres $ crunchy-cli --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" <command> ``` -- Stay Anonymous +- <span id="global-anonymous">Stay Anonymous</span> Login without an account (you won't be able to access premium content): @@ -144,7 +144,7 @@ You can authenticate with your credentials (email:password) or by using a refres You can set specific settings which will be -- Verbose output +- <span id="global-verbose">Verbose output</span> If you want to include debug information in the output, use the `-v` / `--verbose` flag to show it. @@ -152,9 +152,9 @@ You can set specific settings which will be $ crunchy-cli -v <command> ``` - This flag can't be used with `-q` / `--quiet`. + This flag can't be used in combination with `-q` / `--quiet`. -- Quiet output +- <span id="global-quiet">Quiet output</span> If you want to hide all output, use the `-q` / `--quiet` flag to do so. This is especially useful if you want to pipe the output video to an external program (like a video player). @@ -163,7 +163,9 @@ You can set specific settings which will be $ crunchy-cli -q <command> ``` -- Language + This flag can't be used in combination with `-v` / `--verbose`. + +- <span id="global-lang">Language</span> By default, the resulting metadata like title or description are shown in your system language (if Crunchyroll supports it, else in English). If you want to show the results in another language, use the `--lang` flag to set it. @@ -172,7 +174,7 @@ You can set specific settings which will be $ crunchy-cli --lang de-DE <command> ``` -- Experimental fixes +- <span id="global-experimental-fixes">Experimental fixes</span> Crunchyroll constantly changes and breaks its services or just delivers incorrect answers. The `--experimental-fixes` flag tries to fix some of those issues. @@ -184,7 +186,7 @@ You can set specific settings which will be For an overview which parts this flag affects, see the [documentation](https://docs.rs/crunchyroll-rs/latest/crunchyroll_rs/crunchyroll/struct.CrunchyrollBuilder.html) of the underlying Crunchyroll library, all functions beginning with `stabilization_` are applied. -- Proxy +- <span id="global-proxy">Proxy</span> The `--proxy` flag supports https and socks5 proxies to route all your traffic through. This may be helpful to bypass the geo-restrictions Crunchyroll has on certain series. @@ -197,7 +199,7 @@ You can set specific settings which will be Make sure that proxy can either forward TLS requests, which is needed to bypass the (cloudflare) bot protection, or that it is configured so that the proxy can bypass the protection itself. -- User Agent +- <span id="global-user-agent">User Agent</span> There might be cases where a custom user agent is necessary, e.g. to bypass the cloudflare bot protection (#104). In such cases, the `--user-agent` flag can be used to set a custom user agent. @@ -208,7 +210,7 @@ You can set specific settings which will be Default is the user agent, defined in the underlying [library](https://github.com/crunchy-labs/crunchyroll-rs). -- Speed limit +- <span id="global-speed-limit">Speed limit</span> If you want to limit how fast requests/downloads should be, you can use the `--speed-limit` flag. Allowed units are `B` (bytes), `KB` (kilobytes) and `MB` (megabytes). @@ -247,7 +249,7 @@ The `download` command lets you download episodes with a specific audio language **Options** -- Audio language +- <span id="download-audio">Audio language</span> Set the audio language with the `-a` / `--audio` flag. This only works if the url points to a series since episode urls are language specific. @@ -258,7 +260,7 @@ The `download` command lets you download episodes with a specific audio language Default is your system locale. If not supported by Crunchyroll, `en-US` (American English) is the default. -- Subtitle language +- <span id="download-subtitle">Subtitle language</span> Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. In formats that support it (.mp4, .mov and .mkv ), subtitles are stored as soft-subs. All other formats are hardsubbed: the subtitles will be burned into the video track (cf. [hardsub](https://www.urbandictionary.com/define.php?term=hardsub)) and thus can not be turned off. @@ -269,7 +271,7 @@ The `download` command lets you download episodes with a specific audio language Default is none. -- Output template +- <span id="download-output">Output template</span> Define an output template by using the `-o` / `--output` flag. @@ -279,7 +281,7 @@ The `download` command lets you download episodes with a specific audio language Default is `{title}.mp4`. See the [Template Options section](#output-template-options) below for more options. -- Output template for special episodes +- <span id="download-output-specials">Output template for special episodes</span> Define an output template which only gets used when the episode is a special (episode number is 0 or has non-zero decimal places) by using the `--output-special` flag. @@ -289,7 +291,7 @@ The `download` command lets you download episodes with a specific audio language Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. -- Universal output +- <span id="download-universal-output">Universal output</span> The output template options can be forced to get sanitized via the `--universal-output` flag to be valid across all supported operating systems (Windows has a lot of characters which aren't allowed in filenames...). @@ -297,7 +299,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --universal-output -o https://www.crunchyroll.com/watch/G7PU4XD48/tales-veldoras-journal-2 ``` -- Resolution +- <span id="download-resolution">Resolution</span> The resolution for videos can be set via the `-r` / `--resolution` flag. @@ -307,7 +309,7 @@ The `download` command lets you download episodes with a specific audio language Default is `best`. -- Language tagging +- <span id="download-language-tagging">Language tagging</span> You can force the usage of a specific language tagging in the output file with the `--language-tagging` flag. This might be useful as some video players doesn't recognize the language tagging Crunchyroll uses internally. @@ -316,7 +318,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --language-tagging ietf https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- FFmpeg Preset +- <span id="download-ffmpeg-preset">FFmpeg Preset</span> You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. Multiple predefined presets how videos should be encoded (h264, h265, av1, ...) are available, you can see them with `crunchy-cli download --help`. @@ -326,7 +328,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- FFmpeg threads +- <span id="download-ffmpeg-threads">FFmpeg threads</span> If you want to manually set how many threads FFmpeg should use, you can use the `--ffmpeg-threads` flag. This does not work with every codec/preset and is skipped entirely when specifying custom ffmpeg output arguments instead of a preset for `--ffmpeg-preset`. @@ -334,7 +336,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --ffmpeg-threads 4 https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- Skip existing +- <span id="download-skip-existing">Skip existing</span> If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. @@ -342,7 +344,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` -- Skip specials +- <span id="download-skip-specials">Skip specials</span> If you doesn't want to download special episodes, use the `--skip-specials` flag to skip the download of them. @@ -350,7 +352,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --skip-specials https://www.crunchyroll.com/series/GYZJ43JMR/that-time-i-got-reincarnated-as-a-slime[S2] ``` -- Include chapters +- <span id="download-include-chapters">Include chapters</span> Crunchyroll sometimes provide information about skippable events like the intro or credits. These information can be stored as chapters in the resulting video file via the `--include-chapters` flag. @@ -359,7 +361,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --include-chapters https://www.crunchyroll.com/watch/G0DUND0K2/the-journeys-end ``` -- Yes +- <span id="download-yes">Yes</span> Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. The `--yes` flag suppresses this interactive prompt and just downloads all seasons. @@ -370,7 +372,7 @@ The `download` command lets you download episodes with a specific audio language If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. -- Force hardsub +- <span id="download-force-hardsub">Force hardsub</span> If you want to burn-in the subtitles, even if the output format/container supports soft-subs (e.g. `.mp4`), use the `--force-hardsub` flag to do so. @@ -378,7 +380,7 @@ The `download` command lets you download episodes with a specific audio language $ crunchy-cli download --force-hardsub -s en-US https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- Threads +- <span id="download-threads">Threads</span> To increase the download speed, video segments are downloaded simultaneously by creating multiple threads. If you want to manually specify how many threads to use when downloading, do this with the `-t` / `--threads` flag. @@ -406,7 +408,7 @@ The `archive` command lets you download episodes with multiple audios and subtit **Options** -- Audio languages +- <span id="archive-audio">Audio languages</span> Set the audio language with the `-a` / `--audio` flag. Can be used multiple times. @@ -416,7 +418,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is your system locale (if not supported by Crunchyroll, `en-US` (American English) and `ja-JP` (Japanese) are used). -- Subtitle languages +- <span id="archive-subtitle">Subtitle languages</span> Besides the audio, you can specify the subtitle language by using the `-s` / `--subtitle` flag. @@ -426,7 +428,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `all` subtitles. -- Output template +- <span id="archive-output">Output template</span> Define an output template by using the `-o` / `--output` flag. _crunchy-cli_ exclusively uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of its ability to store multiple audio, video and subtitle tracks at once. @@ -437,7 +439,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `{title}.mkv`. See the [Template Options section](#output-template-options) below for more options. -- Output template for special episodes +- <span id="archive-output-specials">Output template for special episodes</span> Define an output template which only gets used when the episode is a special (episode number is 0 or has non-zero decimal places) by using the `--output-special` flag. _crunchy-cli_ exclusively uses the [`.mkv`](https://en.wikipedia.org/wiki/Matroska) container format, because of its ability to store multiple audio, video and subtitle tracks at once. @@ -448,7 +450,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is the template, set by the `-o` / `--output` flag. See the [Template Options section](#output-template-options) below for more options. -- Universal output +- <span id="archive-universal-output">Universal output</span> The output template options can be forced to get sanitized via the `--universal-output` flag to be valid across all supported operating systems (Windows has a lot of characters which aren't allowed in filenames...). @@ -456,7 +458,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --universal-output -o https://www.crunchyroll.com/watch/G7PU4XD48/tales-veldoras-journal-2 ``` -- Resolution +- <span id="archive-resolution">Resolution</span> The resolution for videos can be set via the `-r` / `--resolution` flag. @@ -466,7 +468,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `best`. -- Merge behavior +- <span id="archive-merge">Merge behavior</span> Due to censorship or additional intros, some episodes have multiple lengths for different languages. In the best case, when multiple audio & subtitle tracks are used, there is only one *video* track and all other languages can be stored as audio-only. @@ -481,7 +483,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `auto`. -- Merge auto tolerance +- <span id="archive-merge-auto-tolerance">Merge auto tolerance</span> Sometimes two video tracks are downloaded with `--merge` set to `auto` even if they only differ some milliseconds in length which shouldn't be noticeable to the viewer. To prevent this, you can specify a range in milliseconds with the `--merge-auto-tolerance` flag that only downloads one video if the length difference is in the given range. @@ -492,7 +494,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default are `200` milliseconds. -- Sync start +- <span id="archive-sync-start">Sync start</span> If you want that all videos of the same episode should start at the same time and `--merge` doesn't fit your needs (e.g. one video has an intro, all other doesn't), you might consider using the `--sync-start`. It tries to sync the timing of all downloaded audios to match one video. @@ -502,7 +504,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `7.5`. -- Language tagging +- <span id="archive-language-tagging">Language tagging</span> You can force the usage of a specific language tagging in the output file with the `--language-tagging` flag. This might be useful as some video players doesn't recognize the language tagging Crunchyroll uses internally. @@ -511,7 +513,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --language-tagging ietf https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` -- FFmpeg Preset +- <span id="archive-ffmpeg-preset">FFmpeg Preset</span> You can specify specific built-in presets with the `--ffmpeg-preset` flag to convert videos to a specific coding while downloading. Multiple predefined presets how videos should be encoded (h264, h265, av1, ...) are available, you can see them with `crunchy-cli archive --help`. @@ -521,7 +523,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --ffmpeg-preset av1-lossless https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- FFmpeg threads +- <span id="archive-ffmpeg-threads">FFmpeg threads</span> If you want to manually set how many threads FFmpeg should use, you can use the `--ffmpeg-threads` flag. This does not work with every codec/preset and is skipped entirely when specifying custom ffmpeg output arguments instead of a preset for `--ffmpeg-preset`. @@ -529,7 +531,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --ffmpeg-threads 4 https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome ``` -- Default subtitle +- <span id="archive-default-subtitle">Default subtitle</span> `--default-subtitle` Set which subtitle language is to be flagged as **default** and **forced**. @@ -539,7 +541,7 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is none. -- Include fonts +- <span id="archive-include-fonts">Include fonts</span> You can include the fonts required by subtitles directly into the output file with the `--include-fonts` flag. This will use the embedded font for subtitles instead of the system font when playing the video in a video player which supports it. @@ -547,7 +549,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --include-fonts https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` -- Include chapters +- <span id="archive-include-chapters">Include chapters</span> Crunchyroll sometimes provide information about skippable events like the intro or credits. These information can be stored as chapters in the resulting video file via the `--include-chapters` flag. @@ -557,7 +559,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --include-chapters https://www.crunchyroll.com/watch/G0DUND0K2/the-journeys-end ``` -- Skip existing +- <span id="archive-skip-existing">Skip existing</span> If you re-download a series but want to skip episodes you've already downloaded, the `--skip-existing` flag skips the already existing/downloaded files. @@ -565,7 +567,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --skip-existing https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` -- Skip existing method +- <span id="archive-skip-existing-method">Skip existing method</span> By default, already existing files are determined by their name and the download of the corresponding episode is skipped. But sometimes Crunchyroll adds dubs or subs to an already existing episode and these changes aren't recognized and `--skip-existing` just skips it. @@ -575,7 +577,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --skip-existing-method audio --skip-existing-method video https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` -- Skip specials +- <span id="archive-skip-specials">Skip specials</span> If you doesn't want to download special episodes, use the `--skip-specials` flag to skip the download of them. @@ -583,7 +585,7 @@ The `archive` command lets you download episodes with multiple audios and subtit $ crunchy-cli archive --skip-specials https://www.crunchyroll.com/series/GYZJ43JMR/that-time-i-got-reincarnated-as-a-slime[S2] ``` -- Yes +- <span id="archive-yes">Yes</span> Sometimes different seasons have the same season number (e.g. Sword Art Online Alicization and Alicization War of Underworld are both marked as season 3), in such cases an interactive prompt is shown which needs user further user input to decide which season to download. The `--yes` flag suppresses this interactive prompt and just downloads all seasons. @@ -594,7 +596,7 @@ The `archive` command lets you download episodes with multiple audios and subtit If you've passed the `-q` / `--quiet` [global flag](#global-settings), this flag is automatically set. -- Threads +- <span id="archive-threads">Threads</span> To increase the download speed, video segments are downloaded simultaneously by creating multiple threads. If you want to manually specify how many threads to use when downloading, do this with the `-t` / `--threads` flag. @@ -628,7 +630,7 @@ _Using this command with the `--anonymous` flag or a non-premium account may ret **Options** -- Audio +- <span id="search-audio">Audio</span> Set the audio language to search via the `--audio` flag. Can be used multiple times. @@ -638,7 +640,7 @@ _Using this command with the `--anonymous` flag or a non-premium account may ret Default is your system locale. -- Result limit +- <span id="search-result-limit">Result limit</span> If your input is a search term instead of an url, you have multiple options to control which results to process. The `--search-top-results-limit` flag sets the limit of top search results to process. From ea39dcbc71b13048dcb27282527bab520ad42df1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Apr 2024 18:59:41 +0200 Subject: [PATCH 568/630] Embed chapters only to archive merge auto if `--sync-start` flag is set --- crunchy-cli-core/src/archive/command.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 1c18133..cf0038f 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -585,7 +585,11 @@ async fn get_format( audios: vec![(audio, single_format.audio.clone())], subtitles, metadata: DownloadFormatMetadata { - skip_events: single_format.skip_events().await?, + skip_events: if archive.include_chapters { + single_format.skip_events().await? + } else { + None + }, }, }, )); From b9f5fadbb3ac3e6739129971e90e939d157122fa Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Apr 2024 22:50:25 +0200 Subject: [PATCH 569/630] Fix archive `--language-tagging` sometimes causing crash --- crunchy-cli-core/src/archive/command.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index cf0038f..4210828 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -126,6 +126,7 @@ pub struct Archive { long_help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard; you might run in issues as there are multiple locales which resolve to the same IETF language code, e.g. 'es-LA' and 'es-ES' are both resolving to 'es')" )] + #[arg(long)] #[arg(value_parser = LanguageTagging::parse)] pub(crate) language_tagging: Option<LanguageTagging>, From 9e5feef4d41c1f3085c6009018c8b6c8f020cfa1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Apr 2024 22:58:28 +0200 Subject: [PATCH 570/630] Change archive `--sync-start` defaults --- crunchy-cli-core/src/utils/download.rs | 36 ++++++++++++-------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 122d436..85fe3dc 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1527,13 +1527,13 @@ struct SyncVideo { fn sync_videos(mut sync_videos: Vec<SyncVideo>, value: f64) -> Result<Option<HashMap<usize, u64>>> { let mut result = HashMap::new(); - let hasher = HasherConfig::new().to_hasher(); - let start_frame = 50; + let hasher = HasherConfig::new().preproc_dct().to_hasher(); + let start_frame = 300; sync_videos.sort_by_key(|sv| sv.length); let sync_base = sync_videos.remove(0); - let sync_hashes = extract_frame_hashes(&sync_base.path, start_frame, 100, &hasher)?; + let sync_hashes = extract_frame_hashes(&sync_base.path, start_frame, 50, &hasher)?; for sync_video in sync_videos { let mut highest_frame_match = f64::INFINITY; @@ -1558,22 +1558,20 @@ fn sync_videos(mut sync_videos: Vec<SyncVideo>, value: f64) -> Result<Option<Has &hasher, )?); - let check_frame_windows_result = check_frame_windows(&sync_hashes, &hashes); - if let Some(offset) = check_frame_windows_result - .iter() - .enumerate() - .find_map(|(i, cfw)| (*cfw <= value).then_some(i)) - { - result.insert(sync_video.idx, frame + offset as u64 - start_frame); + let mut check_frame_windows_result: Vec<(usize, f64)> = + check_frame_windows(&sync_hashes, &hashes) + .into_iter() + .enumerate() + .collect(); + check_frame_windows_result.sort_by(|(_, a), (_, b)| a.partial_cmp(&b).unwrap()); + if check_frame_windows_result[0].1 <= value { + result.insert( + sync_video.idx, + frame + check_frame_windows_result[0].0 as u64 - start_frame, + ); break; - } else { - let curr_highest_frame_match = *check_frame_windows_result - .iter() - .min_by(|a, b| a.total_cmp(b)) - .unwrap(); - if curr_highest_frame_match < highest_frame_match { - highest_frame_match = curr_highest_frame_match - } + } else if check_frame_windows_result[0].1 < highest_frame_match { + highest_frame_match = check_frame_windows_result[0].1 } frame = (frame + 300 - sync_hashes.len() as u64).min(sync_video.available_frames) @@ -1610,7 +1608,7 @@ fn extract_frame_hashes( .args([ "-vf", format!( - r#"select=between(n\,{}\,{}),setpts=PTS-STARTPTS"#, + r#"select=between(n\,{}\,{}),setpts=PTS-STARTPTS,scale=-1:240"#, start_frame, start_frame + frame_count ) From 0257fdea0d4128fe5c71c57c3196b93f4c133698 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 9 Apr 2024 23:00:01 +0200 Subject: [PATCH 571/630] Remove chapters if sync doesn't work --- crunchy-cli-core/src/utils/download.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 85fe3dc..9c409d2 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -392,7 +392,13 @@ impl Downloader { (*o as f64 / self.formats[0].video.0.fps().unwrap() * 1000.0) as i64, ) }) - } else if !offset_pre_checked { + } else { + for format in &mut self.formats { + format.metadata.skip_events = None + } + } + + if !offset_pre_checked { warn!("Couldn't find reliable sync positions") } } From 733d9f9787a9a71cad623b480cdb627b90b98cb1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 10 Apr 2024 00:03:22 +0200 Subject: [PATCH 572/630] Update dependencies and version --- Cargo.lock | 34 ++++++++++++++++++++++------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0d6f44..d42e96c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,9 +175,9 @@ checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" @@ -199,9 +199,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" [[package]] name = "cfg-if" @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" dependencies = [ "clap", ] @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.4.0" +version = "3.4.1" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.4.0" +version = "3.4.1" dependencies = [ "anyhow", "async-speed-limit", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ec02ad9896fe71bfc53c48e8b8949aa81ab7f3cb9ff31fd197565d6cd7922ea" +checksum = "e8aa954631407844404a4b1fd2d957b101b8b8653f2884a726b4240175355234" dependencies = [ "async-trait", "chrono", @@ -424,14 +424,15 @@ dependencies = [ "smart-default", "tokio", "tower-service", + "uuid", "webpki-roots", ] [[package]] name = "crunchyroll-rs-internal" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d7cdd1fa6a6303a56f45041060641d6b4446e8065a91f1bcb8aa4a8f1f483c" +checksum = "fc0b9e242c063f13048a53031b49a370c864db3fc6e12d573a8b297bd268b1c4" dependencies = [ "darling", "quote", @@ -2170,6 +2171,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 2bdc3bb..7960a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.4.0" +version = "3.4.1" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 1bda9e1..d006831 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.4.0" +version = "3.4.1" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.4", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.5", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From dbbb445c5521afb0aaf2214d059014d7aedb2fa2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 10 Apr 2024 00:19:12 +0200 Subject: [PATCH 573/630] Fix invalid 0% generate video file progress bar --- crunchy-cli-core/src/utils/download.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 9c409d2..daae9a5 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1516,7 +1516,7 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( // is manually set to 100% here if frame < total_frames { if let Some(p) = &progress { - p.set_position(frame) + p.set_position(total_frames) } debug!("Processed frame [{}/{} 100%]", total_frames, total_frames); } From d7dac2acd44396067b35466dd59792adcd9fa504 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 11 Apr 2024 17:06:43 +0200 Subject: [PATCH 574/630] Update dependencies and version --- Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d42e96c..a0d0c8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "async-speed-limit" @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.4.1" +version = "3.4.2" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.4.1" +version = "3.4.2" dependencies = [ "anyhow", "async-speed-limit", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8aa954631407844404a4b1fd2d957b101b8b8653f2884a726b4240175355234" +checksum = "93e6c52101e3afd7246a099478945ee01f0a5f07ee75cf44398a752e74b2bc90" dependencies = [ "async-trait", "chrono", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0b9e242c063f13048a53031b49a370c864db3fc6e12d573a8b297bd268b1c4" +checksum = "6cc8e8098c9cc87b88c8026c2dbc3bbd68ba69d26ce04dbc89963e9c2e69c7cd" dependencies = [ "darling", "quote", @@ -576,9 +576,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1410,9 +1410,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1917,9 +1917,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -1938,9 +1938,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", diff --git a/Cargo.toml b/Cargo.toml index 7960a98..356fba8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.4.1" +version = "3.4.2" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index d006831..952adc4 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.4.1" +version = "3.4.2" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.5", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.6", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From cdad7fc0006230ac401b505b9fef556c6958efab Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Apr 2024 21:18:13 +0200 Subject: [PATCH 575/630] Skip premium episode if account has no premium subscription --- crunchy-cli-core/src/archive/command.rs | 13 ++++++++---- crunchy-cli-core/src/archive/filter.rs | 27 ++++++++++++++++++++++++ crunchy-cli-core/src/download/command.rs | 13 ++++++++---- crunchy-cli-core/src/download/filter.rs | 24 +++++++++++++++++++++ 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 4210828..7fe3796 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -286,10 +286,15 @@ impl Execute for Archive { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = - ArchiveFilter::new(url_filter, self.clone(), !self.yes, self.skip_specials) - .visit(media_collection) - .await?; + let single_format_collection = ArchiveFilter::new( + url_filter, + self.clone(), + !self.yes, + self.skip_specials, + ctx.crunchy.premium().await, + ) + .visit(media_collection) + .await?; if single_format_collection.is_empty() { progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 90ab373..287bcc0 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -7,6 +7,7 @@ use anyhow::Result; use crunchyroll_rs::{Concert, Episode, Locale, Movie, MovieListing, MusicVideo, Season, Series}; use log::{info, warn}; use std::collections::{BTreeMap, HashMap}; +use std::ops::Not; enum Visited { Series, @@ -21,6 +22,7 @@ pub(crate) struct ArchiveFilter { skip_special: bool, season_episodes: HashMap<String, Vec<Episode>>, season_subtitles_missing: Vec<u32>, + seasons_with_premium: Option<Vec<u32>>, season_sorting: Vec<String>, visited: Visited, } @@ -31,6 +33,7 @@ impl ArchiveFilter { archive: Archive, interactive_input: bool, skip_special: bool, + is_premium: bool, ) -> Self { Self { url_filter, @@ -39,6 +42,7 @@ impl ArchiveFilter { skip_special, season_episodes: HashMap::new(), season_subtitles_missing: vec![], + seasons_with_premium: is_premium.not().then_some(vec![]), season_sorting: vec![], visited: Visited::None, } @@ -310,6 +314,29 @@ impl Filter for ArchiveFilter { episodes.push((episode.clone(), episode.subtitle_locales.clone())) } + if self.seasons_with_premium.is_some() { + let episode_len_before = episodes.len(); + episodes.retain(|(e, _)| !e.is_premium_only); + if episode_len_before < episodes.len() + && !self + .seasons_with_premium + .as_ref() + .unwrap() + .contains(&episode.season_number) + { + warn!( + "Skipping premium episodes in season {}", + episode.season_number + ); + self.seasons_with_premium + .as_mut() + .unwrap() + .push(episode.season_number) + } + + return Ok(None); + } + let mut relative_episode_number = None; let mut relative_sequence_number = None; // get the relative episode number. only done if the output string has the pattern to include diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 58055bd..87c0e07 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -246,10 +246,15 @@ impl Execute for Download { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = - DownloadFilter::new(url_filter, self.clone(), !self.yes, self.skip_specials) - .visit(media_collection) - .await?; + let single_format_collection = DownloadFilter::new( + url_filter, + self.clone(), + !self.yes, + self.skip_specials, + ctx.crunchy.premium().await, + ) + .visit(media_collection) + .await?; if single_format_collection.is_empty() { progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1)); diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs index 5076a33..1c62920 100644 --- a/crunchy-cli-core/src/download/filter.rs +++ b/crunchy-cli-core/src/download/filter.rs @@ -7,6 +7,7 @@ use anyhow::{bail, Result}; use crunchyroll_rs::{Concert, Episode, Movie, MovieListing, MusicVideo, Season, Series}; use log::{error, info, warn}; use std::collections::HashMap; +use std::ops::Not; pub(crate) struct DownloadFilter { url_filter: UrlFilter, @@ -15,6 +16,7 @@ pub(crate) struct DownloadFilter { skip_special: bool, season_episodes: HashMap<u32, Vec<Episode>>, season_subtitles_missing: Vec<u32>, + seasons_with_premium: Option<Vec<u32>>, season_visited: bool, } @@ -24,6 +26,7 @@ impl DownloadFilter { download: Download, interactive_input: bool, skip_special: bool, + is_premium: bool, ) -> Self { Self { url_filter, @@ -32,6 +35,7 @@ impl DownloadFilter { skip_special, season_episodes: HashMap::new(), season_subtitles_missing: vec![], + seasons_with_premium: is_premium.not().then_some(vec![]), season_visited: false, } } @@ -201,6 +205,26 @@ impl Filter for DownloadFilter { } } + if self.seasons_with_premium.is_some() && episode.is_premium_only { + if !self + .seasons_with_premium + .as_ref() + .unwrap() + .contains(&episode.season_number) + { + warn!( + "Skipping premium episodes in season {}", + episode.season_number + ); + self.seasons_with_premium + .as_mut() + .unwrap() + .push(episode.season_number) + } + + return Ok(None); + } + let mut relative_episode_number = None; let mut relative_sequence_number = None; // get the relative episode number. only done if the output string has the pattern to include From fe17f3951ea3e9d39973deffb80ea1126de89172 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Apr 2024 21:25:17 +0200 Subject: [PATCH 576/630] Update dependencies and version --- Cargo.lock | 83 ++++++++++++++++++++----------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 +- 3 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0d0c8b..682b85d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -199,9 +199,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" [[package]] name = "cfg-if" @@ -227,7 +227,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.4.2" +version = "3.4.3" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.4.2" +version = "3.4.3" dependencies = [ "anyhow", "async-speed-limit", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e6c52101e3afd7246a099478945ee01f0a5f07ee75cf44398a752e74b2bc90" +checksum = "3eaf93641a3697ba4cd6845b3a741089f4b4c692a91ed40dece6d7376c419ef9" dependencies = [ "async-trait", "chrono", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc8e8098c9cc87b88c8026c2dbc3bbd68ba69d26ce04dbc89963e9c2e69c7cd" +checksum = "48daba6fe0296c2b400cd6545cf2e8ee23870f1a5a35291fa2d61987098a5692" dependencies = [ "darling", "quote", @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "encode_unicode" @@ -2333,7 +2333,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2351,7 +2351,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2371,17 +2371,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2392,9 +2393,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -2404,9 +2405,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -2416,9 +2417,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -2428,9 +2435,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -2440,9 +2447,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -2452,9 +2459,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -2464,9 +2471,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winreg" diff --git a/Cargo.toml b/Cargo.toml index 356fba8..ee34786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.4.2" +version = "3.4.3" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 952adc4..6d6b57b 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.4.2" +version = "3.4.3" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.6", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.7", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 6515d3025f39eed68979ca1695f532b6a2a5e63b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 14 Apr 2024 21:43:24 +0200 Subject: [PATCH 577/630] Add warn message when using a non-premium account with download or archive --- crunchy-cli-core/src/archive/command.rs | 4 ++++ crunchy-cli-core/src/download/command.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 7fe3796..e2fff5c 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -271,6 +271,10 @@ impl Execute for Archive { } async fn execute(self, ctx: Context) -> Result<()> { + if !ctx.crunchy.premium().await { + warn!("You may not be able to download all requested videos when logging in anonymously or using a non-premium account") + } + let mut parsed_urls = vec![]; for (i, url) in self.urls.clone().into_iter().enumerate() { diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 87c0e07..44c3d65 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -212,6 +212,10 @@ impl Execute for Download { } async fn execute(self, ctx: Context) -> Result<()> { + if !ctx.crunchy.premium().await { + warn!("You may not be able to download all requested videos when logging in anonymously or using a non-premium account") + } + let mut parsed_urls = vec![]; let output_supports_softsubs = SOFTSUB_CONTAINERS.contains( From 4fc20c7c1cc67f4cc06a03120c10b67469210572 Mon Sep 17 00:00:00 2001 From: Amelia <afrosty.skye@gmail.com> Date: Sun, 14 Apr 2024 12:55:55 -0700 Subject: [PATCH 578/630] Support override fonts (#378) * Support override fonts * Compile fix * Actual compile fix * Use snake_case --- crunchy-cli-core/src/utils/download.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index daae9a5..a17875d 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1295,7 +1295,7 @@ const FONTS: [(&str, &str); 68] = [ ("Webdings", "webdings.woff2"), ]; lazy_static::lazy_static! { - static ref FONT_REGEX: Regex = Regex::new(r"(?m)^Style:\s.+?,(?P<font>.+?),").unwrap(); + static ref FONT_REGEX: Regex = Regex::new(r"(?m)^(?:Style:\s.+?,(?P<font>.+?),|(?:Dialogue:\s(?:.+?,)+,\{(?:\\.*)?\\fn(?P<overrideFont>[\w\s]+)(?:\\.*)?)\})").unwrap(); } /// Get the fonts used in the subtitle. @@ -1309,6 +1309,12 @@ fn get_subtitle_stats(path: &Path) -> Result<Vec<String>> { fonts.push(font_string) } } + if let Some(override_font) = capture.name("overrideFont") { + let font_string = override_font.as_str().to_string(); + if !fonts.contains(&font_string) { + fonts.push(font_string) + } + } } Ok(fonts) From 9bdd3aa85b1f3fb244f8981160ebfacfbaf71ec0 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 18 Apr 2024 18:44:31 +0200 Subject: [PATCH 579/630] Switch to `openssl-tls` on nix flake (#359) --- flake.lock | 12 ++++++------ flake.nix | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index e92c958..71f3f28 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1707877513, - "narHash": "sha256-sp0w2apswd3wv0sAEF7StOGHkns3XUQaO5erhWFZWXk=", + "lastModified": 1710534455, + "narHash": "sha256-huQT4Xs0y4EeFKn2BTBVYgEwJSv8SDlm82uWgMnCMmI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "89653a03e0915e4a872788d10680e7eec92f8600", + "rev": "9af9c1c87ed3e3ed271934cb896e0cdd33dae212", "type": "github" }, "original": { @@ -41,11 +41,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2ca105a..29d9ccd 100644 --- a/flake.nix +++ b/flake.nix @@ -26,7 +26,7 @@ }; buildNoDefaultFeatures = true; - buildFeatures = [ "openssl" ]; + buildFeatures = [ "openssl-tls" ]; nativeBuildInputs = [ pkgs.pkg-config From 8ada8223966a6db219130f739886c9f8eedfde2b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 20 Apr 2024 00:00:02 +0200 Subject: [PATCH 580/630] Remove etp-rt login --- README.md | 14 +------------- crunchy-cli-core/src/lib.rs | 13 +++++-------- crunchy-cli-core/src/login/command.rs | 10 +--------- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 09db003..e3a0c10 100644 --- a/README.md +++ b/README.md @@ -122,16 +122,6 @@ You can authenticate with your credentials (email:password) or by using a refres $ crunchy-cli --credentials "email:password" <command> ``` -- <span id="global-etp-rt">Refresh Token</span> - - To obtain a refresh token, you have to log in at [crunchyroll.com](https://www.crunchyroll.com/) and extract the `etp_rt` cookie. - The easiest way to get it is via a browser extension which lets you export your cookies, like [Cookie-Editor](https://cookie-editor.cgagnier.ca/) ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) / [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)). - When installed, look for the `etp_rt` entry and extract its value. - - ```shell - $ crunchy-cli --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" <command> - ``` - - <span id="global-anonymous">Stay Anonymous</span> Login without an account (you won't be able to access premium content): @@ -226,11 +216,9 @@ The `login` command can store your session, so you don't have to authenticate ev # save the refresh token which gets generated when login with credentials. # your email and password won't be stored at any time on disk $ crunchy-cli login --credentials "email:password" -# save etp-rt cookie -$ crunchy-cli login --etp-rt "4ebf1690-53a4-491a-a2ac-488309120f5d" ``` -With the session stored, you do not need to pass `--credentials` / `--etp-rt` / `--anonymous` anymore when you want to execute a command. +With the session stored, you do not need to pass `--credentials` / `--anonymous` anymore when you want to execute a command. ### Download diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index d8d5ec5..1ec5281 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -326,9 +326,8 @@ async fn crunchyroll_session( builder = builder.middleware(rate_limiter) } - let root_login_methods_count = cli.login_method.credentials.is_some() as u8 - + cli.login_method.etp_rt.is_some() as u8 - + cli.login_method.anonymous as u8; + let root_login_methods_count = + cli.login_method.credentials.is_some() as u8 + cli.login_method.anonymous as u8; let progress_handler = progress!("Logging in"); if root_login_methods_count == 0 { @@ -340,16 +339,16 @@ async fn crunchyroll_session( "refresh_token" => { return Ok(builder.login_with_refresh_token(token).await?) } - "etp_rt" => return Ok(builder.login_with_etp_rt(token).await?), + "etp_rt" => bail!("The stored login method (etp-rt) isn't supported anymore. Please use your credentials to login"), _ => (), } } bail!("Could not read stored session ('{}')", session) } } - bail!("Please use a login method ('--credentials', '--etp-rt' or '--anonymous')") + bail!("Please use a login method ('--credentials' or '--anonymous')") } else if root_login_methods_count > 1 { - bail!("Please use only one login method ('--credentials', '--etp-rt' or '--anonymous')") + bail!("Please use only one login method ('--credentials' or '--anonymous')") } let crunchy = if let Some(credentials) = &cli.login_method.credentials { @@ -358,8 +357,6 @@ async fn crunchyroll_session( } else { bail!("Invalid credentials format. Please provide your credentials as email:password") } - } else if let Some(etp_rt) = &cli.login_method.etp_rt { - builder.login_with_etp_rt(etp_rt).await? } else if cli.login_method.anonymous { builder.login_anonymously().await? } else { diff --git a/crunchy-cli-core/src/login/command.rs b/crunchy-cli-core/src/login/command.rs index 5642948..4da1898 100644 --- a/crunchy-cli-core/src/login/command.rs +++ b/crunchy-cli-core/src/login/command.rs @@ -25,9 +25,7 @@ impl Execute for Login { SessionToken::RefreshToken(refresh_token) => { fs::write(login_file_path, format!("refresh_token:{}", refresh_token))? } - SessionToken::EtpRt(etp_rt) => { - fs::write(login_file_path, format!("etp_rt:{}", etp_rt))? - } + SessionToken::EtpRt(_) => bail!("Login with etp_rt isn't supported anymore. Please use your credentials to login"), SessionToken::Anonymous => bail!("Anonymous login cannot be saved"), } @@ -47,12 +45,6 @@ pub struct LoginMethod { )] #[arg(global = true, long)] pub credentials: Option<String>, - #[arg(help = "Login with the etp-rt cookie")] - #[arg( - long_help = "Login with the etp-rt cookie. This can be obtained when you login on crunchyroll.com and extract it from there" - )] - #[arg(global = true, long)] - pub etp_rt: Option<String>, #[arg(help = "Login anonymously / without an account")] #[arg(global = true, long, default_value_t = false)] pub anonymous: bool, From db6e45e7f472040b725227411b70f91226255f09 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 20 Apr 2024 00:02:18 +0200 Subject: [PATCH 581/630] Update dependencies and version --- Cargo.lock | 52 ++++++++++++++++++------------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 +-- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 682b85d..3e16a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,9 +217,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.4.3" +version = "3.5.0" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.4.3" +version = "3.5.0" dependencies = [ "anyhow", "async-speed-limit", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf93641a3697ba4cd6845b3a741089f4b4c692a91ed40dece6d7376c419ef9" +checksum = "ccd0a624f3f8ec3fb7af8d83b907142aaee1858579ab697f24f05d00736e5bb2" dependencies = [ "async-trait", "chrono", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48daba6fe0296c2b400cd6545cf2e8ee23870f1a5a35291fa2d61987098a5692" +checksum = "85c3614a871ec25ab17425405b08aea3c5869597e2348302b922c2a077aa9c3a" dependencies = [ "darling", "quote", @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cafa2c33eff2857e1a14c38aa9a432aa565a01e77804a541fce7aec3affb8f8" +checksum = "79b4bdd5f1c0c7493d780c645f0bff5b9361e6408210fa88910adb181efca64c" dependencies = [ "base64 0.22.0", "base64-serde", @@ -831,9 +831,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -1375,9 +1375,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -1459,9 +1459,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ "base64 0.22.0", "bytes", @@ -1576,9 +1576,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", @@ -1668,18 +1668,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -1688,9 +1688,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1838,9 +1838,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.58" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index ee34786..9520ec9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.4.3" +version = "3.5.0" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 6d6b57b..58c7fe8 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.4.3" +version = "3.5.0" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.7", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.10.8", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 8fff807ae68b61931aa8b1c899b8c8bba4df0784 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 20 Apr 2024 00:22:46 +0200 Subject: [PATCH 582/630] Add message if stored login is expired --- crunchy-cli-core/src/lib.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/lib.rs b/crunchy-cli-core/src/lib.rs index 1ec5281..1c180e8 100644 --- a/crunchy-cli-core/src/lib.rs +++ b/crunchy-cli-core/src/lib.rs @@ -337,9 +337,19 @@ async fn crunchyroll_session( if let Some((token_type, token)) = session.split_once(':') { match token_type { "refresh_token" => { - return Ok(builder.login_with_refresh_token(token).await?) + return match builder.login_with_refresh_token(token).await { + Ok(crunchy) => Ok(crunchy), + Err(e) => { + if let Error::Request { message, .. } = &e { + if message.starts_with("invalid_grant") { + bail!("The stored login is expired, please login again") + } + } + Err(e.into()) + } + } } - "etp_rt" => bail!("The stored login method (etp-rt) isn't supported anymore. Please use your credentials to login"), + "etp_rt" => bail!("The stored login method (etp-rt) isn't supported anymore. Please login again using your credentials"), _ => (), } } From 177aa376317cbba052b8705db0a3daaaa508317b Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Sun, 21 Apr 2024 12:40:53 +0200 Subject: [PATCH 583/630] Move help for --language-tagging (#385) --- crunchy-cli-core/src/archive/command.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index e2fff5c..0303db7 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -105,11 +105,6 @@ pub struct Archive { )] #[arg(long, default_value_t = 200)] pub(crate) merge_auto_tolerance: u32, - #[arg( - long, - help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ - Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard)" - )] #[arg(help = "Tries to sync the timing of all downloaded audios to match one video")] #[arg( long_help = "Tries to sync the timing of all downloaded audios to match one video. \ @@ -122,6 +117,10 @@ pub struct Archive { #[arg(long, require_equals = true, num_args = 0..=1, default_missing_value = "7.5")] pub(crate) sync_start: Option<f64>, + #[arg( + help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ + Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard)" + )] #[arg( long_help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ Valid options are: 'default' (how Crunchyroll uses it internally), 'ietf' (according to the IETF standard; you might run in issues as there are multiple locales which resolve to the same IETF language code, e.g. 'es-LA' and 'es-ES' are both resolving to 'es')" From 4f3475131cee7df1294948aa9239eb2d45f3307d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sun, 21 Apr 2024 13:19:54 +0200 Subject: [PATCH 584/630] Disable LTO in source aur pkgbuild --- .github/scripts/PKGBUILD.source | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source index af6b8e1..4430f95 100644 --- a/.github/scripts/PKGBUILD.source +++ b/.github/scripts/PKGBUILD.source @@ -12,6 +12,8 @@ depends=('ffmpeg' 'openssl') makedepends=('cargo') source=("${pkgname}-${pkgver}.tar.gz::https://github.com/crunchy-labs/crunchy-cli/archive/refs/tags/v${pkgver}.tar.gz") sha256sums=('$CI_SHA_SUM') +# lto causes linking errors when executed by this buildscript. besides, lto is already done by cargo itself (which doesn't cause linking errors) +options=(!lto) build() { cd "$srcdir/${pkgname}-$pkgver" From 777b39aba13be2861c82d04af7120b716881c05c Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Mon, 22 Apr 2024 23:47:49 +0200 Subject: [PATCH 585/630] Fix: stop skipping every episode with archive command while using a non premium account (#388) --- crunchy-cli-core/src/archive/filter.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index 287bcc0..f638c50 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -333,8 +333,10 @@ impl Filter for ArchiveFilter { .unwrap() .push(episode.season_number) } - - return Ok(None); + + if episodes.is_empty() { + return Ok(None); + } } let mut relative_episode_number = None; From 541f0e27477848e92baf1c6b3f0a512d2d202f29 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 23 Apr 2024 16:00:42 +0200 Subject: [PATCH 586/630] Fix wrong audio and subtitle video reference number (#384) --- crunchy-cli-core/src/utils/download.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index a17875d..6882a42 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -128,6 +128,7 @@ struct FFmpegAudioMeta { path: TempPath, locale: Locale, start_time: Option<TimeDelta>, + video_idx: usize, } struct FFmpegSubtitleMeta { @@ -135,6 +136,7 @@ struct FFmpegSubtitleMeta { locale: Locale, cc: bool, start_time: Option<TimeDelta>, + video_idx: usize, } pub struct DownloadFormat { @@ -433,7 +435,7 @@ impl Downloader { } // downloads all audios - for format in &self.formats { + for (i, format) in self.formats.iter().enumerate() { for (j, (stream_data, locale)) in format.audios.iter().enumerate() { let path = self .download_audio( @@ -445,6 +447,7 @@ impl Downloader { path, locale: locale.clone(), start_time: audio_offsets.get(&j).cloned(), + video_idx: i, }) } } @@ -507,6 +510,7 @@ impl Downloader { locale: subtitle.locale.clone(), cc: !not_cc, start_time: subtitle_offsets.get(&j).cloned(), + video_idx: i, }) } } @@ -632,7 +636,11 @@ impl Downloader { if videos.len() == 1 { meta.locale.to_human_readable() } else { - format!("{} [Video: #{}]", meta.locale.to_human_readable(), i + 1,) + format!( + "{} [Video: #{}]", + meta.locale.to_human_readable(), + meta.video_idx + 1 + ) } ), ]); @@ -679,7 +687,7 @@ impl Downloader { title += " (CC)" } if videos.len() > 1 { - title += &format!(" [Video: #{}]", i + 1) + title += &format!(" [Video: #{}]", meta.video_idx + 1) } title }), From 177ceb19205698a2af09676e685feffc80d87168 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 23 Apr 2024 16:13:42 +0200 Subject: [PATCH 587/630] Update dependencies and version --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e16a61..ec4cfd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,9 +199,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" [[package]] name = "cfg-if" @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.5.0" +version = "3.5.1" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.5.0" +version = "3.5.1" dependencies = [ "anyhow", "async-speed-limit", @@ -1563,9 +1563,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -1619,9 +1619,9 @@ checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ "ring", "rustls-pki-types", @@ -1897,18 +1897,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9520ec9..f08615a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.5.0" +version = "3.5.1" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 58c7fe8..cb1c088 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.5.0" +version = "3.5.1" edition = "2021" license = "MIT" From 74aaed4e7a672789dd13c5e73627af61cd91c35c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 25 Apr 2024 00:48:37 +0200 Subject: [PATCH 588/630] Update dependencies and version --- Cargo.lock | 30 +++++++++++++------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- crunchy-cli-core/src/utils/download.rs | 3 +-- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec4cfd0..61832fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,7 +354,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.5.1" +version = "3.5.2" dependencies = [ "chrono", "clap", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.5.1" +version = "3.5.2" dependencies = [ "anyhow", "async-speed-limit", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.10.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd0a624f3f8ec3fb7af8d83b907142aaee1858579ab697f24f05d00736e5bb2" +checksum = "e63a541bdcf0170a29eab4015943e8a6a09281334b4beacd70ac5cfc1c19496b" dependencies = [ "async-trait", "chrono", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.10.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c3614a871ec25ab17425405b08aea3c5869597e2348302b922c2a077aa9c3a" +checksum = "3a9e0e09162451565645fdd4dadc6b38e09f3aafcfb477153584bedd8d62a358" dependencies = [ "darling", "quote", @@ -1613,9 +1613,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" @@ -1730,11 +1730,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "chrono", "hex", "indexmap 1.9.3", @@ -1748,9 +1748,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "c8b3a576c4eb2924262d5951a3b737ccaf16c931e39a2810c36f9a7e25575557" dependencies = [ "darling", "proc-macro2", @@ -2498,9 +2498,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index f08615a..6886f97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.5.1" +version = "3.5.2" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index cb1c088..e31ef46 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.5.1" +version = "3.5.2" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.10.8", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.11.0", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 6882a42..eb280c3 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -960,8 +960,7 @@ impl Downloader { let tempfile = tempfile(".ass")?; let (mut file, path) = tempfile.into_parts(); - let mut buf = vec![]; - subtitle.write_to(&mut buf).await?; + let mut buf = subtitle.data().await?; fix_subtitles(&mut buf, max_length); file.write_all(buf.as_slice())?; From cf8bfb02ac40e326908e273dcf9ec2a66972c623 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 25 Apr 2024 20:32:51 +0200 Subject: [PATCH 589/630] Automatically cut too long path segments --- crunchy-cli-core/src/utils/format.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 17f324f..1987e87 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -526,7 +526,33 @@ impl Format { ), ); - PathBuf::from(path) + let mut path = PathBuf::from(path); + + // make sure that every path section has a maximum of 255 characters + if path.file_name().unwrap_or_default().to_string_lossy().len() > 255 { + let name = path + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + let ext = path + .extension() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + if ext != name { + path.set_file_name(format!("{}.{}", &name[..(255 - ext.len() - 1)], ext)) + } + } + path.into_iter() + .map(|s| { + if s.len() > 255 { + s.to_string_lossy()[..255].to_string() + } else { + s.to_string_lossy().to_string() + } + }) + .collect() } pub fn visual_output(&self, dst: &Path) { From bf28dbf1ce2aa25a9bf66f6fc4be0d69a1f1f2fc Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:50:44 +0200 Subject: [PATCH 590/630] rename merge-auto-tolerance to merge-time-tolerance (#391) --- README.md | 6 +++--- crunchy-cli-core/src/archive/command.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e3a0c10..7316f57 100644 --- a/README.md +++ b/README.md @@ -471,13 +471,13 @@ The `archive` command lets you download episodes with multiple audios and subtit Default is `auto`. -- <span id="archive-merge-auto-tolerance">Merge auto tolerance</span> +- <span id="archive-merge-time-tolerance">Merge time tolerance</span> Sometimes two video tracks are downloaded with `--merge` set to `auto` even if they only differ some milliseconds in length which shouldn't be noticeable to the viewer. - To prevent this, you can specify a range in milliseconds with the `--merge-auto-tolerance` flag that only downloads one video if the length difference is in the given range. + To prevent this, you can specify a range in milliseconds with the `--merge-time-tolerance` flag that only downloads one video if the length difference is in the given range. ```shell - $ crunchy-cli archive -m auto --merge-auto-tolerance 100 https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx + $ crunchy-cli archive -m auto --merge-time-tolerance 100 https://www.crunchyroll.com/series/GY8VEQ95Y/darling-in-the-franxx ``` Default are `200` milliseconds. diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 0303db7..3525a4a 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -104,7 +104,7 @@ pub struct Archive { help = "If the merge behavior is 'auto', only download multiple video tracks if their length difference is higher than the given milliseconds" )] #[arg(long, default_value_t = 200)] - pub(crate) merge_auto_tolerance: u32, + pub(crate) merge_time_tolerance: u32, #[arg(help = "Tries to sync the timing of all downloaded audios to match one video")] #[arg( long_help = "Tries to sync the timing of all downloaded audios to match one video. \ @@ -577,7 +577,7 @@ async fn get_format( .sub(single_format.duration) .abs() .num_milliseconds() - < archive.merge_auto_tolerance.into() => + < archive.merge_time_tolerance.into() => { // If less than `audio_error` apart, use same audio. closest_format From f237033aff91c18280e924a93d30f273a0445e8f Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Sun, 28 Apr 2024 15:15:23 +0200 Subject: [PATCH 591/630] move format_time_delta to own file (#392) --- crunchy-cli-core/src/utils/download.rs | 23 ++++++----------------- crunchy-cli-core/src/utils/fmt.rs | 19 +++++++++++++++++++ crunchy-cli-core/src/utils/mod.rs | 1 + 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 crunchy-cli-core/src/utils/fmt.rs diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index eb280c3..bd7bf3d 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,5 +1,6 @@ use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::real_dedup_vec; +use crate::utils::fmt::format_time_delta; use crate::utils::log::progress; use crate::utils::os::{ cache_dir, is_special_file, temp_directory, temp_named_pipe, tempdir, tempfile, @@ -595,7 +596,7 @@ impl Downloader { for (i, meta) in videos.iter().enumerate() { if let Some(start_time) = meta.start_time { - input.extend(["-ss".to_string(), format_time_delta(start_time)]) + input.extend(["-ss".to_string(), format_time_delta(&start_time)]) } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), i.to_string()]); @@ -616,7 +617,7 @@ impl Downloader { } for (i, meta) in audios.iter().enumerate() { if let Some(start_time) = meta.start_time { - input.extend(["-ss".to_string(), format_time_delta(start_time)]) + input.extend(["-ss".to_string(), format_time_delta(&start_time)]) } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), (i + videos.len()).to_string()]); @@ -663,7 +664,7 @@ impl Downloader { if container_supports_softsubs { for (i, meta) in subtitles.iter().enumerate() { if let Some(start_time) = meta.start_time { - input.extend(["-ss".to_string(), format_time_delta(start_time)]) + input.extend(["-ss".to_string(), format_time_delta(&start_time)]) } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend([ @@ -1390,8 +1391,8 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: TimeDelta) { format!( "Dialogue: {},{},{},", layer, - format_time_delta(start), - format_time_delta(end) + format_time_delta(&start), + format_time_delta(&end) ), ) .to_string() @@ -1665,18 +1666,6 @@ fn check_frame_windows(base_hashes: &[ImageHash], check_hashes: &[ImageHash]) -> results } -fn format_time_delta(time_delta: TimeDelta) -> String { - let hours = time_delta.num_hours(); - let minutes = time_delta.num_minutes() - time_delta.num_hours() * 60; - let seconds = time_delta.num_seconds() - time_delta.num_minutes() * 60; - let milliseconds = time_delta.num_milliseconds() - time_delta.num_seconds() * 1000; - - format!( - "{}:{:0>2}:{:0>2}.{:0>3}", - hours, minutes, seconds, milliseconds - ) -} - fn len_from_segments(segments: &[StreamSegment]) -> TimeDelta { TimeDelta::milliseconds(segments.iter().map(|s| s.length.as_millis()).sum::<u128>() as i64) } diff --git a/crunchy-cli-core/src/utils/fmt.rs b/crunchy-cli-core/src/utils/fmt.rs new file mode 100644 index 0000000..cd7d81a --- /dev/null +++ b/crunchy-cli-core/src/utils/fmt.rs @@ -0,0 +1,19 @@ +use chrono::TimeDelta; + +pub fn format_time_delta(time_delta: &TimeDelta) -> String { + let negative = *time_delta < TimeDelta::zero(); + let time_delta = time_delta.abs(); + let hours = time_delta.num_hours(); + let minutes = time_delta.num_minutes() - time_delta.num_hours() * 60; + let seconds = time_delta.num_seconds() - time_delta.num_minutes() * 60; + let milliseconds = time_delta.num_milliseconds() - time_delta.num_seconds() * 1000; + + format!( + "{}{}:{:0>2}:{:0>2}.{:0>3}", + if negative { "-" } else { "" }, + hours, + minutes, + seconds, + milliseconds + ) +} diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index e5c4894..72a0908 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -3,6 +3,7 @@ pub mod context; pub mod download; pub mod ffmpeg; pub mod filter; +pub mod fmt; pub mod format; pub mod interactive_select; pub mod locale; From 72c574c883cef48f59b7dda7eae2e0598e8671fd Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Thu, 2 May 2024 00:35:13 +0200 Subject: [PATCH 592/630] Switch to audio fingerprinting based syncing (#393) * rename merge-auto-tolerance -> merge-time-tolerance * move format_time_delta to own file * switch to audio fingerprinting based syncing * move format_time_delta to own file * simpler approach to determine negative time deltas * add missing readme part for --sync-precision * fix all clippy "errors" * Use rust-native chromaprint port instead of ffmpeg * buffer with 128kb instead of 32kb * improve helps * improve help --------- Co-authored-by: bytedream <bytedream@protonmail.com> --- Cargo.lock | 96 ++---- README.md | 19 +- crunchy-cli-core/Cargo.toml | 3 +- crunchy-cli-core/src/archive/command.rs | 46 ++- crunchy-cli-core/src/archive/filter.rs | 2 +- crunchy-cli-core/src/utils/download.rs | 261 +++----------- crunchy-cli-core/src/utils/format.rs | 2 +- crunchy-cli-core/src/utils/mod.rs | 1 + crunchy-cli-core/src/utils/os.rs | 18 +- crunchy-cli-core/src/utils/sync.rs | 432 ++++++++++++++++++++++++ 10 files changed, 555 insertions(+), 325 deletions(-) create mode 100644 crunchy-cli-core/src/utils/sync.rs diff --git a/Cargo.lock b/Cargo.lock index 61832fe..26f81b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,18 +179,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "bytemuck" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.6.0" @@ -381,8 +369,6 @@ dependencies = [ "fs2", "futures-util", "http", - "image", - "image_hasher", "indicatif", "lazy_static", "log", @@ -391,6 +377,7 @@ dependencies = [ "regex", "reqwest", "rustls-native-certs", + "rusty-chromaprint", "serde", "serde_json", "serde_plain", @@ -951,32 +938,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "num-traits", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image_hasher" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481465fe767d92494987319b0b447a5829edf57f09c52bf8639396abaaeaf78" -dependencies = [ - "base64 0.22.0", - "image", - "rustdct", - "serde", - "transpose", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -1417,6 +1378,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "realfft" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953d9f7e5cdd80963547b456251296efc2626ed4e3cbf36c869d9564e0220571" +dependencies = [ + "rustfft", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -1531,21 +1501,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +[[package]] +name = "rubato" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dd52e80cfc21894deadf554a5673002938ae4625f7a283e536f9cf7c17b0d5" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustdct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b61555105d6a9bf98797c063c362a1d24ed8ab0431655e38f1cf51e52089551" -dependencies = [ - "rustfft", -] - [[package]] name = "rustfft" version = "6.2.0" @@ -1628,6 +1601,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rusty-chromaprint" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1755646867c36ecb391776deaa0b557a76d3badf20c142de7282630c34b20440" +dependencies = [ + "rubato", + "rustfft", +] + [[package]] name = "ryu" version = "1.0.17" @@ -2501,18 +2484,3 @@ name = "zeroize" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-jpeg" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" -dependencies = [ - "zune-core", -] diff --git a/README.md b/README.md index 7316f57..1fdf348 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,7 @@ The `archive` command lets you download episodes with multiple audios and subtit In the best case, when multiple audio & subtitle tracks are used, there is only one *video* track and all other languages can be stored as audio-only. But, as said, this is not always the case. With the `-m` / `--merge` flag you can define the behaviour when an episodes' video tracks differ in length. - Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` - otherwise like `audio`. + Valid options are `audio` - store one video and all other languages as audio only; `video` - store the video + audio for every language; `auto` - detect if videos differ in length: if so, behave like `video` - otherwise like `audio`; `sync` - detect if videos differ in length: if so, it tries to find the offset of matching audio parts and removes the offset from the beginning, otherwise it behaves like `audio`. Subtitles will always match the primary audio and video. ```shell @@ -482,15 +482,18 @@ The `archive` command lets you download episodes with multiple audios and subtit Default are `200` milliseconds. -- <span id="archive-sync-start">Sync start</span> +- <span id="archive-sync-tolerance">Sync tolerance</span> - If you want that all videos of the same episode should start at the same time and `--merge` doesn't fit your needs (e.g. one video has an intro, all other doesn't), you might consider using the `--sync-start`. - It tries to sync the timing of all downloaded audios to match one video. - This is done by downloading the first few segments/frames of all video tracks that differ in length and comparing them frame by frame. - The flag takes an optional value determines how accurate the syncing is, generally speaking everything over 15 begins to be more inaccurate and everything below 6 is too accurate (and won't succeed). - When the syncing fails, the command is continued as if `--sync-start` wasn't provided for this episode. + Sometimes two video tracks are downloaded with `--merge` set to `sync` because the audio fingerprinting fails to identify matching audio parts (e.g. opening). + To prevent this, you can use the `--sync-tolerance` flag to specify the difference by which two fingerprints are considered equal. - Default is `7.5`. + Default is `6`. + +- <span id="archive-sync-precision">Sync precision</span> + + If you use `--merge` set to `sync` and the syncing seems to be not accurate enough or takes to long, you can use the `--sync-precision` flag to specify the amount of offset determination runs from which the final offset is calculated. + + Default is `4`. - <span id="archive-language-tagging">Language tagging</span> diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e31ef46..517284a 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -24,14 +24,13 @@ derive_setters = "0.1" futures-util = { version = "0.3", features = ["io"] } fs2 = "0.4" http = "1.1" -image = { version = "0.25", features = ["jpeg"], default-features = false } -image_hasher = "2.0" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } +rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" serde_plain = "1.0" diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 3525a4a..77cf50f 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -90,32 +90,31 @@ pub struct Archive { pub(crate) resolution: Resolution, #[arg( - help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'audio' and 'video'" + help = "Sets the behavior of the stream merging. Valid behaviors are 'auto', 'sync', 'audio' and 'video'" )] #[arg( long_help = "Because of local restrictions (or other reasons) some episodes with different languages does not have the same length (e.g. when some scenes were cut out). \ With this flag you can set the behavior when handling multiple language. - Valid options are 'audio' (stores one video and all other languages as audio only), 'video' (stores the video + audio for every language) and 'auto' (detects if videos differ in length: if so, behave like 'video' else like 'audio')" + Valid options are 'audio' (stores one video and all other languages as audio only), 'video' (stores the video + audio for every language), 'auto' (detects if videos differ in length: if so, behave like 'video' else like 'audio') and 'sync' (detects if videos differ in length: if so, tries to find the offset of matching audio parts and removes it from the beginning, otherwise it behaves like 'audio')" )] #[arg(short, long, default_value = "auto")] #[arg(value_parser = MergeBehavior::parse)] pub(crate) merge: MergeBehavior, #[arg( - help = "If the merge behavior is 'auto', only download multiple video tracks if their length difference is higher than the given milliseconds" + help = "If the merge behavior is 'auto' or 'sync', consider videos to be of equal lengths if the difference in length is smaller than the specified milliseconds" )] #[arg(long, default_value_t = 200)] pub(crate) merge_time_tolerance: u32, - #[arg(help = "Tries to sync the timing of all downloaded audios to match one video")] #[arg( - long_help = "Tries to sync the timing of all downloaded audios to match one video. \ - This is done by downloading the first few segments/frames of all video tracks that differ in length and comparing them frame by frame. \ - The value of this flag determines how accurate the syncing is, generally speaking everything over 15 begins to be more inaccurate and everything below 6 is too accurate (and won't succeed). \ - If you want to provide a custom value to this flag, you have to set it with an equals (e.g. `--sync-start=10` instead of `--sync-start 10`). \ - When the syncing fails, the command is continued as if `--sync-start` wasn't provided for this episode - " + help = "If the merge behavior is 'sync', specify the difference by which two fingerprints are considered equal, higher values can help when the algorithm fails" )] - #[arg(long, require_equals = true, num_args = 0..=1, default_missing_value = "7.5")] - pub(crate) sync_start: Option<f64>, + #[arg(long, default_value_t = 6)] + pub(crate) sync_tolerance: u32, + #[arg( + help = "If the merge behavior is 'sync', specify the amount of offset determination runs from which the final offset is calculated, higher values will increase the time required but lead to more precise offsets" + )] + #[arg(long, default_value_t = 4)] + pub(crate) sync_precision: u32, #[arg( help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ @@ -229,18 +228,10 @@ impl Execute for Archive { } if self.include_chapters + && !matches!(self.merge, MergeBehavior::Sync) && !matches!(self.merge, MergeBehavior::Audio) - && self.sync_start.is_none() { - bail!("`--include-chapters` can only be used if `--merge` is set to 'audio' or `--sync-start` is set") - } - - if !matches!(self.merge, MergeBehavior::Auto) && self.sync_start.is_some() { - bail!("`--sync-start` can only be used if `--merge` is set to `auto`") - } - - if self.sync_start.is_some() && self.ffmpeg_preset.is_none() { - warn!("Using `--sync-start` without `--ffmpeg-preset` might produce worse sync results than with `--ffmpeg-preset` set") + bail!("`--include-chapters` can only be used if `--merge` is set to 'audio' or 'sync'") } self.audio = all_locale_in_locales(self.audio.clone()); @@ -317,7 +308,14 @@ impl Execute for Archive { .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())) .no_closed_caption(self.no_closed_caption) - .sync_start_value(self.sync_start) + .sync_tolerance(match self.merge { + MergeBehavior::Sync => Some(self.sync_tolerance), + _ => None, + }) + .sync_precision(match self.merge { + MergeBehavior::Sync => Some(self.sync_precision), + _ => None, + }) .threads(self.threads) .audio_locale_output_map( zip(self.audio.clone(), self.output_audio_locales.clone()).collect(), @@ -560,7 +558,7 @@ async fn get_format( }, }, }), - MergeBehavior::Auto => { + MergeBehavior::Auto | MergeBehavior::Sync => { let mut d_formats: Vec<(Duration, DownloadFormat)> = vec![]; for (single_format, video, audio, subtitles) in format_pairs { diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index f638c50..b08fb6c 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -333,7 +333,7 @@ impl Filter for ArchiveFilter { .unwrap() .push(episode.season_number) } - + if episodes.is_empty() { return Ok(None); } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index bd7bf3d..cfec7b4 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -2,15 +2,13 @@ use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::filter::real_dedup_vec; use crate::utils::fmt::format_time_delta; use crate::utils::log::progress; -use crate::utils::os::{ - cache_dir, is_special_file, temp_directory, temp_named_pipe, tempdir, tempfile, -}; +use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pipe, tempfile}; use crate::utils::rate_limit::RateLimiterService; +use crate::utils::sync::{sync_audios, SyncAudio}; use anyhow::{bail, Result}; use chrono::{NaiveTime, TimeDelta}; use crunchyroll_rs::media::{SkipEvents, SkipEventsEvent, StreamData, StreamSegment, Subtitle}; use crunchyroll_rs::Locale; -use image_hasher::{Hasher, HasherConfig, ImageHash}; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; @@ -39,6 +37,7 @@ pub enum MergeBehavior { Video, Audio, Auto, + Sync, } impl MergeBehavior { @@ -47,6 +46,7 @@ impl MergeBehavior { "video" => MergeBehavior::Video, "audio" => MergeBehavior::Audio, "auto" => MergeBehavior::Auto, + "sync" => MergeBehavior::Sync, _ => return Err(format!("'{}' is not a valid merge behavior", s)), }) } @@ -64,7 +64,8 @@ pub struct DownloadBuilder { force_hardsub: bool, download_fonts: bool, no_closed_caption: bool, - sync_start_value: Option<f64>, + sync_tolerance: Option<u32>, + sync_precision: Option<u32>, threads: usize, ffmpeg_threads: Option<usize>, audio_locale_output_map: HashMap<Locale, String>, @@ -84,7 +85,8 @@ impl DownloadBuilder { force_hardsub: false, download_fonts: false, no_closed_caption: false, - sync_start_value: None, + sync_tolerance: None, + sync_precision: None, threads: num_cpus::get(), ffmpeg_threads: None, audio_locale_output_map: HashMap::new(), @@ -106,7 +108,8 @@ impl DownloadBuilder { download_fonts: self.download_fonts, no_closed_caption: self.no_closed_caption, - sync_start_value: self.sync_start_value, + sync_tolerance: self.sync_tolerance, + sync_precision: self.sync_precision, download_threads: self.threads, ffmpeg_threads: self.ffmpeg_threads, @@ -165,7 +168,8 @@ pub struct Downloader { download_fonts: bool, no_closed_caption: bool, - sync_start_value: Option<f64>, + sync_tolerance: Option<u32>, + sync_precision: Option<u32>, download_threads: usize, ffmpeg_threads: Option<usize>, @@ -245,6 +249,7 @@ impl Downloader { let mut video_offset = None; let mut audio_offsets = HashMap::new(); let mut subtitle_offsets = HashMap::new(); + let mut raw_audios = vec![]; let mut videos = vec![]; let mut audios = vec![]; let mut subtitles = vec![]; @@ -263,40 +268,33 @@ impl Downloader { .max() .unwrap(); - if self.formats.len() > 1 && self.sync_start_value.is_some() { - let all_segments_count: Vec<usize> = self - .formats - .iter() - .map(|f| f.video.0.segments().len()) - .collect(); - let sync_segments = 11.max( - all_segments_count.iter().max().unwrap() - all_segments_count.iter().min().unwrap(), - ); - let mut sync_vids = vec![]; - for (i, format) in self.formats.iter().enumerate() { + // downloads all audios + for (i, format) in self.formats.iter().enumerate() { + for (stream_data, locale) in &format.audios { let path = self - .download_video( - &format.video.0, - format!("Downloading video #{} sync segments", i + 1), - Some(sync_segments), + .download_audio( + stream_data, + format!("{:<1$}", format!("Downloading {} audio", locale), fmt_space), ) .await?; - sync_vids.push(SyncVideo { + raw_audios.push(SyncAudio { + format_id: i, path, - length: len_from_segments(&format.video.0.segments()), - available_frames: (len_from_segments( - &format.video.0.segments()[0..sync_segments], - ) - .num_milliseconds() as f64 - * format.video.0.fps().unwrap() - / 1000.0) as u64, - idx: i, + locale: locale.clone(), + sample_rate: stream_data.sampling_rate().unwrap(), + video_idx: i, }) } + } + if self.formats.len() > 1 && self.sync_tolerance.is_some() { let _progress_handler = progress!("Syncing video start times (this might take some time)"); - let mut offsets = sync_videos(sync_vids, self.sync_start_value.unwrap())?; + let mut offsets = sync_audios( + &raw_audios, + self.sync_tolerance.unwrap(), + self.sync_precision.unwrap(), + )?; drop(_progress_handler); let mut offset_pre_checked = false; @@ -307,19 +305,14 @@ impl Downloader { .enumerate() .map(|(i, f)| { len_from_segments(&f.video.0.segments()) - - TimeDelta::milliseconds( - tmp_offsets - .get(&i) - .map(|o| (*o as f64 / f.video.0.fps().unwrap() * 1000.0) as i64) - .unwrap_or_default(), - ) + - tmp_offsets.get(&i).copied().unwrap_or_default() }) .collect(); let min = formats_with_offset.iter().min().unwrap(); let max = formats_with_offset.iter().max().unwrap(); if max.num_seconds() - min.num_seconds() > 15 { - warn!("Found difference of >15 seconds after sync, skipping applying it"); + warn!("Found difference of >15 seconds after sync, so the application was skipped"); offsets = None; offset_pre_checked = true } @@ -331,7 +324,7 @@ impl Downloader { let mut audio_count: usize = 0; let mut subtitle_count: usize = 0; for (i, format) in self.formats.iter().enumerate() { - let format_fps = format.video.0.fps().unwrap(); + let offset = offsets.get(&i).copied().unwrap_or_default(); let format_len = format .video .0 @@ -339,7 +332,7 @@ impl Downloader { .iter() .map(|s| s.length.as_millis()) .sum::<u128>() as u64 - - offsets.get(&i).map_or(0, |o| *o); + - offset.num_milliseconds() as u64; if format_len > root_format_length { root_format_idx = i; root_format_length = format_len; @@ -347,23 +340,13 @@ impl Downloader { for _ in &format.audios { if let Some(offset) = &offsets.get(&i) { - audio_offsets.insert( - audio_count, - TimeDelta::milliseconds( - (**offset as f64 / format_fps * 1000.0) as i64, - ), - ); + audio_offsets.insert(audio_count, **offset); } audio_count += 1 } for _ in &format.subtitles { if let Some(offset) = &offsets.get(&i) { - subtitle_offsets.insert( - subtitle_count, - TimeDelta::milliseconds( - (**offset as f64 / format_fps * 1000.0) as i64, - ), - ); + subtitle_offsets.insert(subtitle_count, **offset); } subtitle_count += 1 } @@ -390,20 +373,28 @@ impl Downloader { root_format.subtitles.extend(subtitle_append); self.formats = vec![root_format]; - video_offset = offsets.get(&root_format_idx).map(|o| { - TimeDelta::milliseconds( - (*o as f64 / self.formats[0].video.0.fps().unwrap() * 1000.0) as i64, - ) - }) + video_offset = offsets.get(&root_format_idx).copied(); + for raw_audio in raw_audios.iter_mut() { + raw_audio.video_idx = root_format_idx; + } } else { for format in &mut self.formats { format.metadata.skip_events = None } + if !offset_pre_checked { + warn!("Couldn't find reliable sync positions") + } } + } - if !offset_pre_checked { - warn!("Couldn't find reliable sync positions") - } + // add audio metadata + for raw_audio in raw_audios { + audios.push(FFmpegAudioMeta { + path: raw_audio.path, + locale: raw_audio.locale, + start_time: audio_offsets.get(&raw_audio.format_id).copied(), + video_idx: raw_audio.video_idx, + }) } // downloads all videos @@ -435,24 +426,6 @@ impl Downloader { }) } - // downloads all audios - for (i, format) in self.formats.iter().enumerate() { - for (j, (stream_data, locale)) in format.audios.iter().enumerate() { - let path = self - .download_audio( - stream_data, - format!("{:<1$}", format!("Downloading {} audio", locale), fmt_space), - ) - .await?; - audios.push(FFmpegAudioMeta { - path, - locale: locale.clone(), - start_time: audio_offsets.get(&j).cloned(), - video_idx: i, - }) - } - } - for (i, format) in self.formats.iter().enumerate() { if format.subtitles.is_empty() { continue; @@ -1538,134 +1511,6 @@ async fn ffmpeg_progress<R: AsyncReadExt + Unpin>( Ok(()) } -struct SyncVideo { - path: TempPath, - length: TimeDelta, - available_frames: u64, - idx: usize, -} - -fn sync_videos(mut sync_videos: Vec<SyncVideo>, value: f64) -> Result<Option<HashMap<usize, u64>>> { - let mut result = HashMap::new(); - let hasher = HasherConfig::new().preproc_dct().to_hasher(); - let start_frame = 300; - - sync_videos.sort_by_key(|sv| sv.length); - - let sync_base = sync_videos.remove(0); - let sync_hashes = extract_frame_hashes(&sync_base.path, start_frame, 50, &hasher)?; - - for sync_video in sync_videos { - let mut highest_frame_match = f64::INFINITY; - let mut frame = start_frame; - let mut hashes = vec![]; - - loop { - if frame == sync_video.available_frames { - debug!( - "Failed to sync videos, end of stream {} reached (highest frame match: {})", - sync_video.idx + 1, - highest_frame_match - ); - return Ok(None); - } - - hashes.drain(0..(hashes.len() as i32 - sync_hashes.len() as i32).max(0) as usize); - hashes.extend(extract_frame_hashes( - &sync_video.path, - frame, - 300 - hashes.len() as u64, - &hasher, - )?); - - let mut check_frame_windows_result: Vec<(usize, f64)> = - check_frame_windows(&sync_hashes, &hashes) - .into_iter() - .enumerate() - .collect(); - check_frame_windows_result.sort_by(|(_, a), (_, b)| a.partial_cmp(&b).unwrap()); - if check_frame_windows_result[0].1 <= value { - result.insert( - sync_video.idx, - frame + check_frame_windows_result[0].0 as u64 - start_frame, - ); - break; - } else if check_frame_windows_result[0].1 < highest_frame_match { - highest_frame_match = check_frame_windows_result[0].1 - } - - frame = (frame + 300 - sync_hashes.len() as u64).min(sync_video.available_frames) - } - } - - Ok(Some(result)) -} - -fn extract_frame_hashes( - input_file: &Path, - start_frame: u64, - frame_count: u64, - hasher: &Hasher, -) -> Result<Vec<ImageHash>> { - let frame_dir = tempdir(format!( - "{}_sync_frames", - input_file - .file_name() - .unwrap_or_default() - .to_string_lossy() - .trim_end_matches( - &input_file - .file_stem() - .unwrap_or_default() - .to_string_lossy() - .to_string() - ) - ))?; - let extract_output = Command::new("ffmpeg") - .arg("-hide_banner") - .arg("-y") - .args(["-i", input_file.to_string_lossy().to_string().as_str()]) - .args([ - "-vf", - format!( - r#"select=between(n\,{}\,{}),setpts=PTS-STARTPTS,scale=-1:240"#, - start_frame, - start_frame + frame_count - ) - .as_str(), - ]) - .args(["-vframes", frame_count.to_string().as_str()]) - .arg(format!("{}/%03d.jpg", frame_dir.path().to_string_lossy())) - .output()?; - if !extract_output.status.success() { - bail!( - "{}", - String::from_utf8_lossy(extract_output.stderr.as_slice()) - ) - } - - let mut hashes = vec![]; - for file in frame_dir.path().read_dir()? { - let file = file?; - let img = image::open(file.path())?; - hashes.push(hasher.hash_image(&img)) - } - Ok(hashes) -} - -fn check_frame_windows(base_hashes: &[ImageHash], check_hashes: &[ImageHash]) -> Vec<f64> { - let mut results = vec![]; - - for i in 0..(check_hashes.len() - base_hashes.len()) { - let check_window = &check_hashes[i..(base_hashes.len() + i)]; - let sum = std::iter::zip(base_hashes, check_window) - .map(|(a, b)| a.dist(b)) - .sum::<u32>(); - results.push(sum as f64 / check_window.len() as f64); - } - results -} - fn len_from_segments(segments: &[StreamSegment]) -> TimeDelta { TimeDelta::milliseconds(segments.iter().map(|s| s.length.as_millis()).sum::<u128>() as i64) } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 1987e87..0a71838 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -544,7 +544,7 @@ impl Format { path.set_file_name(format!("{}.{}", &name[..(255 - ext.len() - 1)], ext)) } } - path.into_iter() + path.iter() .map(|s| { if s.len() > 255 { s.to_string_lossy()[..255].to_string() diff --git a/crunchy-cli-core/src/utils/mod.rs b/crunchy-cli-core/src/utils/mod.rs index 72a0908..6260047 100644 --- a/crunchy-cli-core/src/utils/mod.rs +++ b/crunchy-cli-core/src/utils/mod.rs @@ -11,4 +11,5 @@ pub mod log; pub mod os; pub mod parse; pub mod rate_limit; +pub mod sync; pub mod video; diff --git a/crunchy-cli-core/src/utils/os.rs b/crunchy-cli-core/src/utils/os.rs index b65abc2..a216f87 100644 --- a/crunchy-cli-core/src/utils/os.rs +++ b/crunchy-cli-core/src/utils/os.rs @@ -7,7 +7,7 @@ use std::pin::Pin; use std::process::{Command, Stdio}; use std::task::{Context, Poll}; use std::{env, fs, io}; -use tempfile::{Builder, NamedTempFile, TempDir, TempPath}; +use tempfile::{Builder, NamedTempFile, TempPath}; use tokio::io::{AsyncRead, ReadBuf}; pub fn has_ffmpeg() -> bool { @@ -46,22 +46,6 @@ pub fn tempfile<S: AsRef<str>>(suffix: S) -> io::Result<NamedTempFile> { Ok(tempfile) } -/// Any tempdir should be created with this function. The prefix and directory of every directory -/// created with this function stays the same which is helpful to query all existing tempdirs and -/// e.g. remove them in a case of ctrl-c. Having one function also good to prevent mistakes like -/// setting the wrong prefix if done manually. -pub fn tempdir<S: AsRef<str>>(suffix: S) -> io::Result<TempDir> { - let tempdir = Builder::default() - .prefix(".crunchy-cli_") - .suffix(suffix.as_ref()) - .tempdir_in(temp_directory())?; - debug!( - "Created temporary directory: {}", - tempdir.path().to_string_lossy() - ); - Ok(tempdir) -} - pub fn cache_dir<S: AsRef<str>>(name: S) -> io::Result<PathBuf> { let cache_dir = temp_directory().join(format!(".crunchy-cli_{}_cache", name.as_ref())); fs::create_dir_all(&cache_dir)?; diff --git a/crunchy-cli-core/src/utils/sync.rs b/crunchy-cli-core/src/utils/sync.rs new file mode 100644 index 0000000..1e9bc42 --- /dev/null +++ b/crunchy-cli-core/src/utils/sync.rs @@ -0,0 +1,432 @@ +use std::io::Read; +use std::process::Stdio; +use std::{ + cmp, + collections::{HashMap, HashSet}, + mem, + ops::Not, + path::Path, + process::Command, +}; + +use chrono::TimeDelta; +use crunchyroll_rs::Locale; +use log::debug; +use tempfile::TempPath; + +use anyhow::{bail, Result}; +use rusty_chromaprint::{Configuration, Fingerprinter}; + +use super::fmt::format_time_delta; + +pub struct SyncAudio { + pub format_id: usize, + pub path: TempPath, + pub locale: Locale, + pub sample_rate: u32, + pub video_idx: usize, +} + +#[derive(Debug, Clone, Copy)] +struct TimeRange { + start: f64, + end: f64, +} + +pub fn sync_audios( + available_audios: &Vec<SyncAudio>, + sync_tolerance: u32, + sync_precision: u32, +) -> Result<Option<HashMap<usize, TimeDelta>>> { + let mut result: HashMap<usize, TimeDelta> = HashMap::new(); + + let mut sync_audios = vec![]; + let mut chromaprints = HashMap::new(); + let mut formats = HashSet::new(); + for audio in available_audios { + if formats.contains(&audio.format_id) { + continue; + } + formats.insert(audio.format_id); + sync_audios.push((audio.format_id, &audio.path, audio.sample_rate)); + chromaprints.insert( + audio.format_id, + generate_chromaprint( + &audio.path, + audio.sample_rate, + &TimeDelta::zero(), + &TimeDelta::zero(), + &TimeDelta::zero(), + )?, + ); + } + sync_audios.sort_by_key(|sync_audio| chromaprints.get(&sync_audio.0).unwrap().len()); + + let base_audio = sync_audios.remove(0); + + let mut start = f64::MAX; + let mut end = f64::MIN; + let mut initial_offsets = HashMap::new(); + for audio in &sync_audios { + debug!( + "Initial comparison of format {} to {}", + audio.0, &base_audio.0 + ); + + let (lhs_ranges, rhs_ranges) = compare_chromaprints( + chromaprints.get(&base_audio.0).unwrap(), + chromaprints.get(&audio.0).unwrap(), + sync_tolerance, + ); + if lhs_ranges.is_empty() || rhs_ranges.is_empty() { + bail!( + "Failed to sync videos, couldn't find matching audio parts between format {} and {}", + base_audio.0 + 1, + audio.0 + 1 + ); + } + let lhs_range = lhs_ranges[0]; + let rhs_range = rhs_ranges[0]; + start = start.min(lhs_range.start); + end = end.max(lhs_range.end); + start = start.min(rhs_range.start); + end = end.max(rhs_range.end); + let offset = TimeDelta::milliseconds(((rhs_range.start - lhs_range.start) * 1000.0) as i64); + initial_offsets.insert(audio.0, TimeDelta::zero().checked_sub(&offset).unwrap()); + debug!( + "Found initial offset of {}ms ({} - {} {}s) ({} - {} {}s) for format {} to {}", + offset.num_milliseconds(), + lhs_range.start, + lhs_range.end, + lhs_range.end - lhs_range.start, + rhs_range.start, + rhs_range.end, + rhs_range.end - rhs_range.start, + audio.0, + base_audio.0 + ); + } + + debug!( + "Found matching audio parts at {} - {}, narrowing search", + start, end + ); + + let start = TimeDelta::milliseconds((start * 1000.0) as i64 - 20000); + let end = TimeDelta::milliseconds((end * 1000.0) as i64 + 20000); + + for sync_audio in &sync_audios { + let chromaprint = generate_chromaprint( + sync_audio.1, + sync_audio.2, + &start, + &end, + initial_offsets.get(&sync_audio.0).unwrap(), + )?; + chromaprints.insert(sync_audio.0, chromaprint); + } + + let mut runs: HashMap<usize, i64> = HashMap::new(); + let iterator_range_limits: i64 = 2 ^ sync_precision as i64; + for i in -iterator_range_limits..=iterator_range_limits { + let base_offset = TimeDelta::milliseconds( + ((0.128 / iterator_range_limits as f64 * i as f64) * 1000.0) as i64, + ); + chromaprints.insert( + base_audio.0, + generate_chromaprint(base_audio.1, base_audio.2, &start, &end, &base_offset)?, + ); + for audio in &sync_audios { + let initial_offset = initial_offsets.get(&audio.0).copied().unwrap(); + let offset = find_offset( + (&base_audio.0, chromaprints.get(&base_audio.0).unwrap()), + &base_offset, + (&audio.0, chromaprints.get(&audio.0).unwrap()), + &initial_offset, + &start, + sync_tolerance, + ); + if offset.is_none() { + continue; + } + let offset = offset.unwrap(); + + result.insert( + audio.0, + result + .get(&audio.0) + .copied() + .unwrap_or_default() + .checked_add(&offset) + .unwrap(), + ); + runs.insert(audio.0, runs.get(&audio.0).copied().unwrap_or_default() + 1); + } + } + let mut result: HashMap<usize, TimeDelta> = result + .iter() + .map(|(format_id, offset)| { + ( + *format_id, + TimeDelta::milliseconds( + offset.num_milliseconds() / runs.get(format_id).copied().unwrap(), + ), + ) + }) + .collect(); + result.insert(base_audio.0, TimeDelta::milliseconds(0)); + + Ok(Some(result)) +} + +fn find_offset( + lhs: (&usize, &Vec<u32>), + lhs_shift: &TimeDelta, + rhs: (&usize, &Vec<u32>), + rhs_shift: &TimeDelta, + start: &TimeDelta, + sync_tolerance: u32, +) -> Option<TimeDelta> { + let (lhs_ranges, rhs_ranges) = compare_chromaprints(lhs.1, rhs.1, sync_tolerance); + if lhs_ranges.is_empty() || rhs_ranges.is_empty() { + return None; + } + let lhs_range = lhs_ranges[0]; + let rhs_range = rhs_ranges[0]; + let offset = rhs_range.end - lhs_range.end; + let offset = TimeDelta::milliseconds((offset * 1000.0) as i64) + .checked_add(lhs_shift)? + .checked_sub(rhs_shift)?; + debug!( + "Found offset of {}ms ({} - {} {}s) ({} - {} {}s) for format {} to {}", + offset.num_milliseconds(), + lhs_range.start + start.num_milliseconds() as f64 / 1000.0, + lhs_range.end + start.num_milliseconds() as f64 / 1000.0, + lhs_range.end - lhs_range.start, + rhs_range.start + start.num_milliseconds() as f64 / 1000.0, + rhs_range.end + start.num_milliseconds() as f64 / 1000.0, + rhs_range.end - rhs_range.start, + rhs.0, + lhs.0 + ); + Some(offset) +} + +fn generate_chromaprint( + input_file: &Path, + sample_rate: u32, + start: &TimeDelta, + end: &TimeDelta, + offset: &TimeDelta, +) -> Result<Vec<u32>> { + let mut ss_argument: &TimeDelta = &start.checked_sub(offset).unwrap(); + let mut offset_argument = &TimeDelta::zero(); + if *offset < TimeDelta::zero() { + ss_argument = start; + offset_argument = offset; + }; + + let mut printer = Fingerprinter::new(&Configuration::preset_test1()); + printer.start(sample_rate, 2)?; + + let mut command = Command::new("ffmpeg"); + command + .arg("-hide_banner") + .arg("-y") + .args(["-ss", format_time_delta(ss_argument).as_str()]); + + if end.is_zero().not() { + command.args(["-to", format_time_delta(end).as_str()]); + } + + command + .args(["-itsoffset", format_time_delta(offset_argument).as_str()]) + .args(["-i", input_file.to_string_lossy().to_string().as_str()]) + .args(["-ac", "2"]) + .args([ + "-f", + if cfg!(target_endian = "big") { + "s16be" + } else { + "s16le" + }, + ]) + .arg("-"); + + let mut handle = command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + // the stdout is read in chunks because keeping all the raw audio data in memory would take up + // a significant amount of space + let mut stdout = handle.stdout.take().unwrap(); + let mut buf: [u8; 128_000] = [0; 128_000]; + while handle.try_wait()?.is_none() { + loop { + let read_bytes = stdout.read(&mut buf)?; + if read_bytes == 0 { + break; + } + let data: [i16; 64_000] = unsafe { mem::transmute(buf) }; + printer.consume(&data[0..(read_bytes / 2)]) + } + } + + if !handle.wait()?.success() { + bail!("{}", std::io::read_to_string(handle.stderr.unwrap())?) + } + + printer.finish(); + return Ok(printer.fingerprint().into()); +} + +fn compare_chromaprints( + lhs_chromaprint: &Vec<u32>, + rhs_chromaprint: &Vec<u32>, + sync_tolerance: u32, +) -> (Vec<TimeRange>, Vec<TimeRange>) { + let lhs_inverse_index = create_inverse_index(lhs_chromaprint); + let rhs_inverse_index = create_inverse_index(rhs_chromaprint); + + let mut possible_shifts = HashSet::new(); + for lhs_pair in lhs_inverse_index { + let original_point = lhs_pair.0; + for i in -2..=2 { + let modified_point = (original_point as i32 + i) as u32; + if rhs_inverse_index.contains_key(&modified_point) { + let rhs_index = rhs_inverse_index.get(&modified_point).copied().unwrap(); + possible_shifts.insert(rhs_index as i32 - lhs_pair.1 as i32); + } + } + } + + let mut all_lhs_time_ranges = vec![]; + let mut all_rhs_time_ranges = vec![]; + for shift_amount in possible_shifts { + let time_range_pair = find_time_ranges( + lhs_chromaprint, + rhs_chromaprint, + shift_amount, + sync_tolerance, + ); + if time_range_pair.is_none() { + continue; + } + let (mut lhs_time_ranges, mut rhs_time_ranges) = time_range_pair.unwrap(); + let mut lhs_time_ranges: Vec<TimeRange> = lhs_time_ranges + .drain(..) + .filter(|time_range| { + (20.0 < (time_range.end - time_range.start)) + && ((time_range.end - time_range.start) < 180.0) + && time_range.end > 0.0 + }) + .collect(); + lhs_time_ranges.sort_by(|a, b| (b.end - b.start).total_cmp(&(a.end - a.start))); + let mut rhs_time_ranges: Vec<TimeRange> = rhs_time_ranges + .drain(..) + .filter(|time_range| { + (20.0 < (time_range.end - time_range.start)) + && ((time_range.end - time_range.start) < 180.0) + && time_range.end > 0.0 + }) + .collect(); + rhs_time_ranges.sort_by(|a, b| (b.end - b.start).total_cmp(&(a.end - a.start))); + if lhs_time_ranges.is_empty() || rhs_time_ranges.is_empty() { + continue; + } + + all_lhs_time_ranges.push(lhs_time_ranges[0]); + all_rhs_time_ranges.push(rhs_time_ranges[0]); + } + all_lhs_time_ranges.sort_by(|a, b| (a.end - a.start).total_cmp(&(b.end - b.start))); + all_lhs_time_ranges.reverse(); + all_rhs_time_ranges.sort_by(|a, b| (a.end - a.start).total_cmp(&(b.end - b.start))); + all_rhs_time_ranges.reverse(); + + (all_lhs_time_ranges, all_rhs_time_ranges) +} + +fn create_inverse_index(chromaprint: &Vec<u32>) -> HashMap<u32, usize> { + let mut inverse_index = HashMap::with_capacity(chromaprint.capacity()); + for (i, fingerprint) in chromaprint.iter().enumerate().take(chromaprint.capacity()) { + inverse_index.insert(*fingerprint, i); + } + inverse_index +} + +fn find_time_ranges( + lhs_chromaprint: &[u32], + rhs_chromaprint: &[u32], + shift_amount: i32, + sync_tolerance: u32, +) -> Option<(Vec<TimeRange>, Vec<TimeRange>)> { + let mut lhs_shift: i32 = 0; + let mut rhs_shift: i32 = 0; + if shift_amount < 0 { + lhs_shift -= shift_amount; + } else { + rhs_shift += shift_amount; + } + + let mut lhs_matching_timestamps = vec![]; + let mut rhs_matching_timestamps = vec![]; + let upper_limit = + cmp::min(lhs_chromaprint.len(), rhs_chromaprint.len()) as i32 - shift_amount.abs(); + + for i in 0..upper_limit { + let lhs_position = i + lhs_shift; + let rhs_position = i + rhs_shift; + let difference = (lhs_chromaprint[lhs_position as usize] + ^ rhs_chromaprint[rhs_position as usize]) + .count_ones(); + + if difference > sync_tolerance { + continue; + } + + lhs_matching_timestamps.push(lhs_position as f64 * 0.128); + rhs_matching_timestamps.push(rhs_position as f64 * 0.128); + } + lhs_matching_timestamps.push(f64::MAX); + rhs_matching_timestamps.push(f64::MAX); + + let lhs_time_ranges = timestamps_to_ranges(lhs_matching_timestamps); + lhs_time_ranges.as_ref()?; + let lhs_time_ranges = lhs_time_ranges.unwrap(); + let rhs_time_ranges = timestamps_to_ranges(rhs_matching_timestamps).unwrap(); + + Some((lhs_time_ranges, rhs_time_ranges)) +} + +fn timestamps_to_ranges(mut timestamps: Vec<f64>) -> Option<Vec<TimeRange>> { + if timestamps.is_empty() { + return None; + } + + timestamps.sort_by(|a, b| a.total_cmp(b)); + + let mut time_ranges = vec![]; + let mut current_range = TimeRange { + start: timestamps[0], + end: timestamps[0], + }; + + for i in 0..timestamps.len() - 1 { + let current = timestamps[i]; + let next = timestamps[i + 1]; + if next - current <= 1.0 { + current_range.end = next; + continue; + } + + time_ranges.push(current_range); + current_range.start = next; + current_range.end = next; + } + if !time_ranges.is_empty() { + Some(time_ranges) + } else { + None + } +} From 173292ff32f338cff8900b87e6d0f16ae9a5172d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 2 May 2024 17:00:58 +0200 Subject: [PATCH 593/630] Prettify negated subtitle cc boolean --- crunchy-cli-core/src/archive/command.rs | 6 +++--- crunchy-cli-core/src/download/command.rs | 4 ++-- crunchy-cli-core/src/utils/download.rs | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 77cf50f..efb54f4 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -501,15 +501,15 @@ async fn get_format( .subtitles .get(s) .cloned() - // the subtitle is probably not cc if the audio is japanese or more than one + // the subtitle is probably cc if the audio is not japanese or only one // subtitle exists for this stream .map(|l| { ( l, - single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, + single_format.audio != Locale::ja_JP && stream.subtitles.len() == 1, ) }); - let cc = stream.captions.get(s).cloned().map(|l| (l, false)); + let cc = stream.captions.get(s).cloned().map(|l| (l, true)); subtitles .into_iter() diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index 44c3d65..bb0c1fd 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -400,7 +400,7 @@ async fn get_format( subtitles: subtitle.clone().map_or(vec![], |s| { vec![( s, - single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, + single_format.audio != Locale::ja_JP && stream.subtitles.len() == 1, )] }), metadata: DownloadFormatMetadata { @@ -417,7 +417,7 @@ async fn get_format( subtitle.map_or(vec![], |s| { vec![( s, - single_format.audio == Locale::ja_JP || stream.subtitles.len() > 1, + single_format.audio != Locale::ja_JP && stream.subtitles.len() == 1, )] }), )]); diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index cfec7b4..2278bef 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -232,13 +232,13 @@ impl Downloader { if let Some(subtitle_sort) = &self.subtitle_sort { format .subtitles - .sort_by(|(a_subtitle, a_not_cc), (b_subtitle, b_not_cc)| { + .sort_by(|(a_subtitle, a_cc), (b_subtitle, b_cc)| { let ordering = subtitle_sort .iter() .position(|l| l == &a_subtitle.locale) .cmp(&subtitle_sort.iter().position(|l| l == &b_subtitle.locale)); if matches!(ordering, Ordering::Equal) { - a_not_cc.cmp(b_not_cc).reverse() + a_cc.cmp(b_cc).reverse() } else { ordering } @@ -451,8 +451,8 @@ impl Downloader { None }; - for (j, (subtitle, not_cc)) in format.subtitles.iter().enumerate() { - if !not_cc && self.no_closed_caption { + for (j, (subtitle, cc)) in format.subtitles.iter().enumerate() { + if *cc && self.no_closed_caption { continue; } @@ -462,7 +462,7 @@ impl Downloader { progress_message += ", " } progress_message += &subtitle.locale.to_string(); - if !not_cc { + if *cc { progress_message += " (CC)"; } if i.min(videos.len() - 1) != 0 { @@ -477,12 +477,12 @@ impl Downloader { debug!( "Downloaded {} subtitles{}", subtitle.locale, - (!not_cc).then_some(" (cc)").unwrap_or_default(), + cc.then_some(" (cc)").unwrap_or_default(), ); subtitles.push(FFmpegSubtitleMeta { path, locale: subtitle.locale.clone(), - cc: !not_cc, + cc: *cc, start_time: subtitle_offsets.get(&j).cloned(), video_idx: i, }) From 442173c08c7a877cac768de0af1205708d668ca1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 13:46:43 +0200 Subject: [PATCH 594/630] Fix empty subtitles if multiple subtitle formats are used (#398) --- Cargo.lock | 11 +++ crunchy-cli-core/Cargo.toml | 1 + crunchy-cli-core/src/utils/download.rs | 123 +++++++------------------ 3 files changed, 43 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26f81b9..4f7dd63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,6 +376,7 @@ dependencies = [ "num_cpus", "regex", "reqwest", + "rsubs-lib", "rustls-native-certs", "rusty-chromaprint", "serde", @@ -1501,6 +1502,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +[[package]] +name = "rsubs-lib" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7559a05635a4132b737c736ee286af83f3969cb98d9028d17d333e6b41cc5" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "rubato" version = "0.14.1" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 517284a..7fd8367 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -30,6 +30,7 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } +rsubs-lib = "0.2" rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 2278bef..082a937 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -13,6 +13,7 @@ use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; use reqwest::Client; +use rsubs_lib::{ssa, vtt}; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap}; @@ -931,13 +932,38 @@ impl Downloader { subtitle: Subtitle, max_length: TimeDelta, ) -> Result<TempPath> { + let buf = subtitle.data().await?; + let mut ass = match subtitle.format.as_str() { + "ass" => ssa::parse(String::from_utf8_lossy(&buf).to_string()), + "vtt" => vtt::parse(String::from_utf8_lossy(&buf).to_string()).to_ass(), + _ => bail!("unknown subtitle format: {}", subtitle.format), + }; + // subtitles aren't always correct sorted and video players may have issues with that. to + // prevent issues, the subtitles are sorted + ass.events + .sort_by(|a, b| a.line_start.total_ms().cmp(&b.line_start.total_ms())); + // it might be the case that the start and/or end time are greater than the actual video + // length. this might also result in issues with video players, thus the times are stripped + // to be maxim + for i in (0..ass.events.len()).rev() { + if ass.events[i].line_end.total_ms() > max_length.num_milliseconds() as u32 { + if ass.events[i].line_start.total_ms() > max_length.num_milliseconds() as u32 { + ass.events[i] + .line_start + .set_ms(max_length.num_milliseconds() as u32); + } + ass.events[i] + .line_end + .set_ms(max_length.num_milliseconds() as u32); + } else { + break; + } + } + let tempfile = tempfile(".ass")?; - let (mut file, path) = tempfile.into_parts(); + let path = tempfile.into_temp_path(); - let mut buf = subtitle.data().await?; - fix_subtitles(&mut buf, max_length); - - file.write_all(buf.as_slice())?; + ass.to_file(path.to_string_lossy().to_string().as_str())?; Ok(path) } @@ -1301,93 +1327,6 @@ fn get_subtitle_stats(path: &Path) -> Result<Vec<String>> { Ok(fonts) } -/// Fix the subtitles in multiple ways as Crunchyroll sometimes delivers them malformed. -/// -/// Look and feel fix: Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very -/// messy on some video players. See -/// [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66) for more -/// information. -/// Length fix: Sometimes subtitles have an unnecessary long entry which exceeds the video length, -/// some video players can't handle this correctly. To prevent this, the subtitles must be checked -/// if any entry is longer than the video length and if so the entry ending must be hard set to not -/// exceed the video length. See [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) -/// for more information. -/// Sort fix: Sometimes subtitle entries aren't sorted correctly by time which confuses some video -/// players. To prevent this, the subtitle entries must be manually sorted. See -/// [crunchy-labs/crunchy-cli#208](https://github.com/crunchy-labs/crunchy-cli/issues/208) for more -/// information. -fn fix_subtitles(raw: &mut Vec<u8>, max_length: TimeDelta) { - let re = Regex::new( - r"^Dialogue:\s(?P<layer>\d+),(?P<start>\d+:\d+:\d+\.\d+),(?P<end>\d+:\d+:\d+\.\d+),", - ) - .unwrap(); - - let mut entries = (vec![], vec![]); - - let mut as_lines: Vec<String> = String::from_utf8_lossy(raw.as_slice()) - .split('\n') - .map(|s| s.to_string()) - .collect(); - - for (i, line) in as_lines.iter_mut().enumerate() { - if line.trim() == "[Script Info]" { - line.push_str("\nScaledBorderAndShadow: yes") - } else if let Some(capture) = re.captures(line) { - let mut start = capture - .name("start") - .map_or(NaiveTime::default(), |s| { - NaiveTime::parse_from_str(s.as_str(), "%H:%M:%S.%f").unwrap() - }) - .signed_duration_since(NaiveTime::MIN); - let mut end = capture - .name("end") - .map_or(NaiveTime::default(), |e| { - NaiveTime::parse_from_str(e.as_str(), "%H:%M:%S.%f").unwrap() - }) - .signed_duration_since(NaiveTime::MIN); - - if start > max_length || end > max_length { - let layer = capture - .name("layer") - .map_or(0, |l| i32::from_str(l.as_str()).unwrap()); - - if start > max_length { - start = max_length; - } - if start > max_length || end > max_length { - end = max_length; - } - - *line = re - .replace( - line, - format!( - "Dialogue: {},{},{},", - layer, - format_time_delta(&start), - format_time_delta(&end) - ), - ) - .to_string() - } - entries.0.push((start, i)); - entries.1.push(i) - } - } - - entries.0.sort_by(|(a, _), (b, _)| a.cmp(b)); - for i in 0..entries.0.len() { - let (_, original_position) = entries.0[i]; - let new_position = entries.1[i]; - - if original_position != new_position { - as_lines.swap(original_position, new_position) - } - } - - *raw = as_lines.join("\n").into_bytes() -} - fn write_ffmpeg_chapters( file: &mut fs::File, video_len: TimeDelta, From 55f1e1d32db0fb05af2e203bd0629f11ed96c2b9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 20:33:19 +0200 Subject: [PATCH 595/630] Add option to overwrite git hash on build --- crunchy-cli-core/build.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/build.rs b/crunchy-cli-core/build.rs index f7d5974..b36ec8e 100644 --- a/crunchy-cli-core/build.rs +++ b/crunchy-cli-core/build.rs @@ -1,7 +1,8 @@ fn main() -> std::io::Result<()> { println!( "cargo:rustc-env=GIT_HASH={}", - get_short_commit_hash()?.unwrap_or_default() + std::env::var("CRUNCHY_CLI_GIT_HASH") + .or::<std::io::Error>(Ok(get_short_commit_hash()?.unwrap_or_default()))? ); println!( "cargo:rustc-env=BUILD_DATE={}", From dcbe433a9c1cec69bb051de7837698104d793456 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 20:33:50 +0200 Subject: [PATCH 596/630] Manually git hash when publishing source AUR package --- .github/scripts/PKGBUILD.source | 1 + .github/workflows/publish.yml | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source index 4430f95..ed19d54 100644 --- a/.github/scripts/PKGBUILD.source +++ b/.github/scripts/PKGBUILD.source @@ -21,6 +21,7 @@ build() { export CARGO_HOME="$srcdir/cargo-home" export RUSTUP_TOOLCHAIN=stable + export CRUNCHY_CLI_GIT_HASH=$CI_GIT_HASH cargo build --release } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4a477b3..2184042 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,11 +20,15 @@ jobs: curl -LO https://github.com/crunchy-labs/crunchy-cli/archive/refs/tags/${{ github.ref_name }}.tar.gz echo "CRUNCHY_CLI_SHA256=$(sha256sum ${{ github.ref_name }}.tar.gz | cut -f 1 -d ' ')" >> $GITHUB_ENV + - name: Get release commit hash + run: echo "CRUNCHY_CLI_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + - name: Generate crunchy-cli PKGBUILD env: CI_PKG_VERSION: ${{ env.RELEASE_VERSION }} CI_SHA_SUM: ${{ env.CRUNCHY_CLI_SHA256 }} - run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM' < .github/scripts/PKGBUILD.source > PKGBUILD + CI_GIT_HASH: ${{ env.CRUNCHY_CLI_GIT_HASH }} + run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_GIT_HASH' < .github/scripts/PKGBUILD.source > PKGBUILD - name: Publish crunchy-cli to AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 From 4066b8511ca076df74f01a0aa10257a2c6288154 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 20:51:31 +0200 Subject: [PATCH 597/630] Build binaries locked --- .github/scripts/PKGBUILD.source | 13 +++++++++++-- .github/workflows/ci.yml | 6 +++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/scripts/PKGBUILD.source b/.github/scripts/PKGBUILD.source index ed19d54..4b14f5b 100644 --- a/.github/scripts/PKGBUILD.source +++ b/.github/scripts/PKGBUILD.source @@ -15,14 +15,23 @@ sha256sums=('$CI_SHA_SUM') # lto causes linking errors when executed by this buildscript. besides, lto is already done by cargo itself (which doesn't cause linking errors) options=(!lto) +prepare() { + cd "$srcdir/${pkgname}-$pkgver" + + export RUSTUP_TOOLCHAIN=stable + export CARGO_HOME="$srcdir/cargo-home" + + cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" +} + build() { cd "$srcdir/${pkgname}-$pkgver" - export CARGO_HOME="$srcdir/cargo-home" export RUSTUP_TOOLCHAIN=stable + export CARGO_HOME="$srcdir/cargo-home" export CRUNCHY_CLI_GIT_HASH=$CI_GIT_HASH - cargo build --release + cargo build --frozen --release } package() { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70f4400..5429d94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: run: cargo install --force cross - name: Build - run: cross build --release --no-default-features --features openssl-tls-static --target ${{ matrix.toolchain }} + run: cross build --locked --release --no-default-features --features openssl-tls-static --target ${{ matrix.toolchain }} - name: Upload binary artifact uses: actions/upload-artifact@v4 @@ -101,7 +101,7 @@ jobs: toolchain: stable - name: Build - run: cargo build --release --target ${{ matrix.toolchain }} + run: cargo build --locked --release --target ${{ matrix.toolchain }} - name: Upload binary artifact uses: actions/upload-artifact@v4 @@ -135,7 +135,7 @@ jobs: - name: Build shell: msys2 {0} - run: cargo build --release --target x86_64-pc-windows-gnu + run: cargo build --locked --release --target x86_64-pc-windows-gnu - name: Upload binary artifact uses: actions/upload-artifact@v4 From f77804fcb59079b8506275f033beaee6376a71f2 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 20:58:46 +0200 Subject: [PATCH 598/630] Apply lints --- crunchy-cli-core/src/archive/command.rs | 2 +- crunchy-cli-core/src/utils/download.rs | 3 +- crunchy-cli-core/src/utils/ffmpeg.rs | 45 +++++++------------------ 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index efb54f4..70142ec 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -393,7 +393,7 @@ impl Execute for Archive { || (method_subtitle && subtitle_differ) { skip = false; - path = formatted_path.clone() + path.clone_from(&formatted_path) } } } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 082a937..9fe4f79 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -20,7 +20,6 @@ use std::collections::{BTreeMap, HashMap}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::{env, fs}; @@ -731,7 +730,7 @@ impl Downloader { output_presets.remove(i - remove_count); remove_count += 1; } - last = s.clone(); + last.clone_from(s); } output_presets.extend([ diff --git a/crunchy-cli-core/src/utils/ffmpeg.rs b/crunchy-cli-core/src/utils/ffmpeg.rs index a61ed93..ef73581 100644 --- a/crunchy-cli-core/src/utils/ffmpeg.rs +++ b/crunchy-cli-core/src/utils/ffmpeg.rs @@ -1,5 +1,7 @@ use lazy_static::lazy_static; use regex::Regex; +use std::fmt; +use std::fmt::Formatter; use std::str::FromStr; pub const SOFTSUB_CONTAINERS: [&str; 3] = ["mkv", "mov", "mp4"]; @@ -33,11 +35,11 @@ macro_rules! ffmpeg_enum { } } - impl ToString for $name { - fn to_string(&self) -> String { + impl fmt::Display for $name { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { $( - &$name::$field => stringify!($field).to_string().to_lowercase() + &$name::$field => write!(f, "{}", stringify!($field).to_string().to_lowercase()) ),* } } @@ -135,23 +137,16 @@ impl FFmpegPreset { for (codec, hwaccel, quality) in FFmpegPreset::available_matches() { let mut description_details = vec![]; if let Some(h) = &hwaccel { - description_details.push(format!("{} hardware acceleration", h.to_string())) + description_details.push(format!("{h} hardware acceleration")) } if let Some(q) = &quality { - description_details.push(format!("{} video quality/compression", q.to_string())) + description_details.push(format!("{q} video quality/compression")) } let description = if description_details.is_empty() { - format!( - "{} encoded with default video quality/compression", - codec.to_string() - ) + format!("{codec} encoded with default video quality/compression",) } else if description_details.len() == 1 { - format!( - "{} encoded with {}", - codec.to_string(), - description_details[0] - ) + format!("{} encoded with {}", codec, description_details[0]) } else { let first = description_details.remove(0); let last = description_details.remove(description_details.len() - 1); @@ -161,13 +156,7 @@ impl FFmpegPreset { "".to_string() }; - format!( - "{} encoded with {}{} and {}", - codec.to_string(), - first, - mid, - last - ) + format!("{codec} encoded with {first}{mid} and {last}",) }; return_values.push(format!( @@ -201,11 +190,7 @@ impl FFmpegPreset { .find(|p| p.to_string() == token.to_lowercase()) { if let Some(cc) = codec { - return Err(format!( - "cannot use multiple codecs (found {} and {})", - cc.to_string(), - c.to_string() - )); + return Err(format!("cannot use multiple codecs (found {cc} and {c})",)); } codec = Some(c) } else if let Some(h) = FFmpegHwAccel::all() @@ -214,9 +199,7 @@ impl FFmpegPreset { { if let Some(hh) = hwaccel { return Err(format!( - "cannot use multiple hardware accelerations (found {} and {})", - hh.to_string(), - h.to_string() + "cannot use multiple hardware accelerations (found {hh} and {h})", )); } hwaccel = Some(h) @@ -226,9 +209,7 @@ impl FFmpegPreset { { if let Some(qq) = quality { return Err(format!( - "cannot use multiple ffmpeg preset qualities (found {} and {})", - qq.to_string(), - q.to_string() + "cannot use multiple ffmpeg preset qualities (found {qq} and {q})", )); } quality = Some(q) From 0f7d7d928c553d4c37a4b4a1153091bc27945829 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 21:08:34 +0200 Subject: [PATCH 599/630] Add format check and linting action pipelines --- .github/workflows/ci.yml | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5429d94..7be86f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,56 @@ on: workflow_dispatch: jobs: + fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cargo cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: x86_64-unknown-linux-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Check fmt + run: cargo fmt --check + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cargo cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: x86_64-unknown-linux-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Lint + run: cargo clippy -- -D warnings + build-linux: runs-on: ubuntu-latest strategy: From fca1b74cacdad5f7c028a0c4eb9714ad0cd8dc70 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 21:10:40 +0200 Subject: [PATCH 600/630] Separate build and lint pipelines --- .github/workflows/{ci.yml => build.yml} | 52 +--------------------- .github/workflows/lint.yml | 58 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 51 deletions(-) rename .github/workflows/{ci.yml => build.yml} (78%) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/build.yml similarity index 78% rename from .github/workflows/ci.yml rename to .github/workflows/build.yml index 7be86f6..9248ca5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: ci +name: build on: push: @@ -8,56 +8,6 @@ on: workflow_dispatch: jobs: - fmt: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Cargo cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: x86_64-unknown-linux-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Check fmt - run: cargo fmt --check - - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Cargo cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: x86_64-unknown-linux-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Lint - run: cargo clippy -- -D warnings - build-linux: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..2d6eaf0 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,58 @@ +name: lint + +on: + push: + branches: + - '*' + pull_request: + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cargo cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: x86_64-unknown-linux-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Check fmt + run: cargo fmt --check + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cargo cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: x86_64-unknown-linux-gnu-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Lint + run: cargo clippy -- -D warnings From 757d3094ea28fffe718ae4720eac7785e9ff9ee4 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 3 May 2024 21:14:51 +0200 Subject: [PATCH 601/630] Rename directory for workflow resources --- .github/{scripts => workflow-resources}/PKGBUILD.binary | 0 .github/{scripts => workflow-resources}/PKGBUILD.source | 0 .github/workflows/publish.yml | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename .github/{scripts => workflow-resources}/PKGBUILD.binary (100%) rename .github/{scripts => workflow-resources}/PKGBUILD.source (100%) diff --git a/.github/scripts/PKGBUILD.binary b/.github/workflow-resources/PKGBUILD.binary similarity index 100% rename from .github/scripts/PKGBUILD.binary rename to .github/workflow-resources/PKGBUILD.binary diff --git a/.github/scripts/PKGBUILD.source b/.github/workflow-resources/PKGBUILD.source similarity index 100% rename from .github/scripts/PKGBUILD.source rename to .github/workflow-resources/PKGBUILD.source diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2184042..8f178ce 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: CI_PKG_VERSION: ${{ env.RELEASE_VERSION }} CI_SHA_SUM: ${{ env.CRUNCHY_CLI_SHA256 }} CI_GIT_HASH: ${{ env.CRUNCHY_CLI_GIT_HASH }} - run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_GIT_HASH' < .github/scripts/PKGBUILD.source > PKGBUILD + run: envsubst '$CI_PKG_VERSION,$CI_SHA_SUM,$CI_GIT_HASH' < .github/workflow-resources/PKGBUILD.source > PKGBUILD - name: Publish crunchy-cli to AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 @@ -61,7 +61,7 @@ jobs: CI_MANPAGES_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_MANPAGES_SHA256 }} CI_COMPLETIONS_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_COMPLETIONS_SHA256 }} CI_LICENSE_SHA_SUM: ${{ env.CRUNCHY_CLI_BIN_LICENSE_SHA256 }} - run: envsubst '$CI_PKG_VERSION,$CI_AMD_BINARY_SHA_SUM,$CI_ARM_BINARY_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/scripts/PKGBUILD.binary > PKGBUILD + run: envsubst '$CI_PKG_VERSION,$CI_AMD_BINARY_SHA_SUM,$CI_ARM_BINARY_SHA_SUM,$CI_COMPLETIONS_SHA_SUM,$CI_MANPAGES_SHA_SUM,$CI_LICENSE_SHA_SUM' < .github/workflow-resources/PKGBUILD.binary > PKGBUILD - name: Publish crunchy-cli-bin to AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 From dad91dba91910d5ea69600fbb28a5e521191a77a Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 4 May 2024 23:08:55 +0200 Subject: [PATCH 602/630] Rename `--sync-tolerance` to `--merge-sync-tolerance` and `--merge-sync-precision` to `--merge-sync-precision` --- README.md | 6 +++--- crunchy-cli-core/src/archive/command.rs | 12 ++++++------ crunchy-cli-core/src/utils/download.rs | 22 +++++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1fdf348..29893f9 100644 --- a/README.md +++ b/README.md @@ -482,14 +482,14 @@ The `archive` command lets you download episodes with multiple audios and subtit Default are `200` milliseconds. -- <span id="archive-sync-tolerance">Sync tolerance</span> +- <span id="archive-merge-sync-tolerance">Merge sync tolerance</span> Sometimes two video tracks are downloaded with `--merge` set to `sync` because the audio fingerprinting fails to identify matching audio parts (e.g. opening). - To prevent this, you can use the `--sync-tolerance` flag to specify the difference by which two fingerprints are considered equal. + To prevent this, you can use the `--merge-sync-tolerance` flag to specify the difference by which two fingerprints are considered equal. Default is `6`. -- <span id="archive-sync-precision">Sync precision</span> +- <span id="archive-merge-sync-precision">Merge sync precision</span> If you use `--merge` set to `sync` and the syncing seems to be not accurate enough or takes to long, you can use the `--sync-precision` flag to specify the amount of offset determination runs from which the final offset is calculated. diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 70142ec..38bddc1 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -109,12 +109,12 @@ pub struct Archive { help = "If the merge behavior is 'sync', specify the difference by which two fingerprints are considered equal, higher values can help when the algorithm fails" )] #[arg(long, default_value_t = 6)] - pub(crate) sync_tolerance: u32, + pub(crate) merge_sync_tolerance: u32, #[arg( help = "If the merge behavior is 'sync', specify the amount of offset determination runs from which the final offset is calculated, higher values will increase the time required but lead to more precise offsets" )] #[arg(long, default_value_t = 4)] - pub(crate) sync_precision: u32, + pub(crate) merge_sync_precision: u32, #[arg( help = "Specified which language tagging the audio and subtitle tracks and language specific format options should have. \ @@ -308,12 +308,12 @@ impl Execute for Archive { .audio_sort(Some(self.audio.clone())) .subtitle_sort(Some(self.subtitle.clone())) .no_closed_caption(self.no_closed_caption) - .sync_tolerance(match self.merge { - MergeBehavior::Sync => Some(self.sync_tolerance), + .merge_sync_tolerance(match self.merge { + MergeBehavior::Sync => Some(self.merge_sync_tolerance), _ => None, }) - .sync_precision(match self.merge { - MergeBehavior::Sync => Some(self.sync_precision), + .merge_sync_precision(match self.merge { + MergeBehavior::Sync => Some(self.merge_sync_precision), _ => None, }) .threads(self.threads) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 9fe4f79..565ed7d 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -64,8 +64,8 @@ pub struct DownloadBuilder { force_hardsub: bool, download_fonts: bool, no_closed_caption: bool, - sync_tolerance: Option<u32>, - sync_precision: Option<u32>, + merge_sync_tolerance: Option<u32>, + merge_sync_precision: Option<u32>, threads: usize, ffmpeg_threads: Option<usize>, audio_locale_output_map: HashMap<Locale, String>, @@ -85,8 +85,8 @@ impl DownloadBuilder { force_hardsub: false, download_fonts: false, no_closed_caption: false, - sync_tolerance: None, - sync_precision: None, + merge_sync_tolerance: None, + merge_sync_precision: None, threads: num_cpus::get(), ffmpeg_threads: None, audio_locale_output_map: HashMap::new(), @@ -108,8 +108,8 @@ impl DownloadBuilder { download_fonts: self.download_fonts, no_closed_caption: self.no_closed_caption, - sync_tolerance: self.sync_tolerance, - sync_precision: self.sync_precision, + merge_sync_tolerance: self.merge_sync_tolerance, + merge_sync_precision: self.merge_sync_precision, download_threads: self.threads, ffmpeg_threads: self.ffmpeg_threads, @@ -168,8 +168,8 @@ pub struct Downloader { download_fonts: bool, no_closed_caption: bool, - sync_tolerance: Option<u32>, - sync_precision: Option<u32>, + merge_sync_tolerance: Option<u32>, + merge_sync_precision: Option<u32>, download_threads: usize, ffmpeg_threads: Option<usize>, @@ -287,13 +287,13 @@ impl Downloader { } } - if self.formats.len() > 1 && self.sync_tolerance.is_some() { + if self.formats.len() > 1 && self.merge_sync_tolerance.is_some() { let _progress_handler = progress!("Syncing video start times (this might take some time)"); let mut offsets = sync_audios( &raw_audios, - self.sync_tolerance.unwrap(), - self.sync_precision.unwrap(), + self.merge_sync_tolerance.unwrap(), + self.merge_sync_precision.unwrap(), )?; drop(_progress_handler); From 96d3de48cf9b33ac3860c469b9bb577ec20b4f38 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 4 May 2024 23:10:35 +0200 Subject: [PATCH 603/630] Add missing code examples --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 29893f9..1735a6e 100644 --- a/README.md +++ b/README.md @@ -487,12 +487,20 @@ The `archive` command lets you download episodes with multiple audios and subtit Sometimes two video tracks are downloaded with `--merge` set to `sync` because the audio fingerprinting fails to identify matching audio parts (e.g. opening). To prevent this, you can use the `--merge-sync-tolerance` flag to specify the difference by which two fingerprints are considered equal. + ```shell + $ crunchy-cli archive -m sync --merge-sync-tolerance 3 https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + Default is `6`. - <span id="archive-merge-sync-precision">Merge sync precision</span> If you use `--merge` set to `sync` and the syncing seems to be not accurate enough or takes to long, you can use the `--sync-precision` flag to specify the amount of offset determination runs from which the final offset is calculated. + ```shell + $ crunchy-cli archive -m sync --merge-sync-precision 3 https://www.crunchyroll.com/watch/GRDQPM1ZY/alone-and-lonesome + ``` + Default is `4`. - <span id="archive-language-tagging">Language tagging</span> From 89b9c5db390691e626a18a294e3573cf73d9604c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 4 May 2024 23:36:09 +0200 Subject: [PATCH 604/630] Update dependencies and version --- Cargo.lock | 114 +++++++++++++++++++----------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 +- 3 files changed, 63 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f7dd63..b9dce7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,47 +43,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -120,9 +121,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -147,9 +148,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64-serde" @@ -187,9 +188,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -279,9 +280,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "console" @@ -342,7 +343,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.5.2" +version = "3.6.0" dependencies = [ "chrono", "clap", @@ -355,7 +356,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.5.2" +version = "3.6.0" dependencies = [ "anyhow", "async-speed-limit", @@ -392,9 +393,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63a541bdcf0170a29eab4015943e8a6a09281334b4beacd70ac5cfc1c19496b" +checksum = "58580acc9c0abf96a231ec8b1a4597ea55d9426ea17f684ce3582e2b26437bbb" dependencies = [ "async-trait", "chrono", @@ -418,9 +419,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9e0e09162451565645fdd4dadc6b38e09f3aafcfb477153584bedd8d62a358" +checksum = "ce3c844dec8a3390f8c9853b5cf1d65c3d38fd0657b8b5d0e008db8945dea326" dependencies = [ "darling", "quote", @@ -478,7 +479,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79b4bdd5f1c0c7493d780c645f0bff5b9361e6408210fa88910adb181efca64c" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "base64-serde", "chrono", "fs-err", @@ -589,9 +590,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fnv" @@ -755,9 +756,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" @@ -957,7 +958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -989,6 +990,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "iso8601" version = "0.6.1" @@ -1034,9 +1041,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libredox" @@ -1172,9 +1179,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1434,7 +1441,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "bytes", "cookie", "cookie_store", @@ -1591,7 +1598,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] @@ -1662,18 +1669,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -1724,11 +1731,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", @@ -1742,9 +1749,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b3a576c4eb2924262d5951a3b737ccaf16c931e39a2810c36f9a7e25575557" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", @@ -1792,9 +1799,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2018,16 +2025,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2138,9 +2144,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "untrusted" @@ -2492,6 +2498,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 6886f97..fe7b301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.5.2" +version = "3.6.0" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 7fd8367..896b68f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.5.2" +version = "3.6.0" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.11.0", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.11.1", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 4d1df833426f1382cf0351ffc250e11c150ce147 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Sat, 4 May 2024 23:45:30 +0200 Subject: [PATCH 605/630] Fix build badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1735a6e..45b8ea7 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ <a href="https://discord.gg/PXGPGpQxgk"> <img src="https://img.shields.io/discord/994882878125121596?label=discord&style=flat-square" alt="Discord"> </a> - <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/ci.yml"> - <img src="https://img.shields.io/github/actions/workflow/status/crunchy-labs/crunchy-cli/ci.yml?branch=master&style=flat-square" alt="CI"> + <a href="https://github.com/crunchy-labs/crunchy-cli/actions/workflows/build.yml"> + <img src="https://img.shields.io/github/actions/workflow/status/crunchy-labs/crunchy-cli/build.yml?branch=master&style=flat-square" alt="Build"> </a> </p> From ab63dcd2e010cccd4b5a99c48d6576fa6c22d664 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 6 May 2024 20:29:22 +0200 Subject: [PATCH 606/630] Update dependencies and version --- Cargo.lock | 30 +++++++++++++++--------------- Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9dce7b..44a47cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "async-speed-limit" @@ -188,9 +188,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -343,7 +343,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.0" +version = "3.6.1" dependencies = [ "chrono", "clap", @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.0" +version = "3.6.1" dependencies = [ "anyhow", "async-speed-limit", @@ -712,9 +712,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1511,9 +1511,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rsubs-lib" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df7559a05635a4132b737c736ee286af83f3969cb98d9028d17d333e6b41cc5" +checksum = "9dcca2a9560fca05de8f95bc3767e46673d4b4c1f2c7a11092e10efd95bbdf62" dependencies = [ "regex", "serde", @@ -1646,11 +1646,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -1659,9 +1659,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", diff --git a/Cargo.toml b/Cargo.toml index fe7b301..26f71f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.0" +version = "3.6.1" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 896b68f..8e1ba3a 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.0" +version = "3.6.1" edition = "2021" license = "MIT" @@ -30,7 +30,7 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } -rsubs-lib = "0.2" +rsubs-lib = ">=0.2.1" rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" From 53a710a3732047a1e08d475f6f112b440b8bde8e Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Tue, 7 May 2024 16:13:10 +0200 Subject: [PATCH 607/630] Fix audio syncing using wrong internal index (#407) --- crunchy-cli-core/src/utils/download.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 565ed7d..8a8ad57 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -321,8 +321,6 @@ impl Downloader { if let Some(offsets) = offsets { let mut root_format_idx = 0; let mut root_format_length = 0; - let mut audio_count: usize = 0; - let mut subtitle_count: usize = 0; for (i, format) in self.formats.iter().enumerate() { let offset = offsets.get(&i).copied().unwrap_or_default(); let format_len = format @@ -340,15 +338,13 @@ impl Downloader { for _ in &format.audios { if let Some(offset) = &offsets.get(&i) { - audio_offsets.insert(audio_count, **offset); + audio_offsets.insert(i, **offset); } - audio_count += 1 } for _ in &format.subtitles { if let Some(offset) = &offsets.get(&i) { - subtitle_offsets.insert(subtitle_count, **offset); + subtitle_offsets.insert(i, **offset); } - subtitle_count += 1 } } From 48bb7a5ef669d3d89ccecfbf02929adeea99a6bd Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 14 May 2024 16:11:55 +0200 Subject: [PATCH 608/630] Fix crashes when converting subtitles (#408) --- Cargo.lock | 6 ++-- crunchy-cli-core/Cargo.toml | 3 +- crunchy-cli-core/src/utils/download.rs | 39 ++++++++++++++++---------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44a47cd..cd1f282 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,6 +386,7 @@ dependencies = [ "shlex", "sys-locale", "tempfile", + "time", "tokio", "tokio-util", "tower-service", @@ -1511,12 +1512,13 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rsubs-lib" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcca2a9560fca05de8f95bc3767e46673d4b4c1f2c7a11092e10efd95bbdf62" +checksum = "f43e1a7f184bc76407dbaa67bd2aeea8a15430d7e1e498070963336d03ebedee" dependencies = [ "regex", "serde", + "time", ] [[package]] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 8e1ba3a..49f7a5e 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -30,7 +30,7 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } -rsubs-lib = ">=0.2.1" +rsubs-lib = "0.3" rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" @@ -38,6 +38,7 @@ serde_plain = "1.0" shlex = "1.3" sys-locale = "0.3" tempfile = "3.10" +time = "0.3" tokio = { version = "1.37", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } tokio-util = "0.7" tower-service = "0.3" diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 8a8ad57..43d165b 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -13,17 +13,19 @@ use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; use reqwest::Client; -use rsubs_lib::{ssa, vtt}; +use rsubs_lib::{SSA, VTT}; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap}; use std::io::Write; +use std::ops::Add; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::Duration; use std::{env, fs}; use tempfile::TempPath; +use time::Time; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use tokio::select; use tokio::sync::mpsc::unbounded_channel; @@ -929,36 +931,43 @@ impl Downloader { ) -> Result<TempPath> { let buf = subtitle.data().await?; let mut ass = match subtitle.format.as_str() { - "ass" => ssa::parse(String::from_utf8_lossy(&buf).to_string()), - "vtt" => vtt::parse(String::from_utf8_lossy(&buf).to_string()).to_ass(), + "ass" => SSA::parse(String::from_utf8_lossy(&buf))?, + "vtt" => VTT::parse(String::from_utf8_lossy(&buf))?.to_ssa(), _ => bail!("unknown subtitle format: {}", subtitle.format), }; // subtitles aren't always correct sorted and video players may have issues with that. to // prevent issues, the subtitles are sorted - ass.events - .sort_by(|a, b| a.line_start.total_ms().cmp(&b.line_start.total_ms())); + // (https://github.com/crunchy-labs/crunchy-cli/issues/208) + ass.events.sort_by(|a, b| a.start.cmp(&b.start)); // it might be the case that the start and/or end time are greater than the actual video // length. this might also result in issues with video players, thus the times are stripped - // to be maxim + // to be at most as long as `max_length` + // (https://github.com/crunchy-labs/crunchy-cli/issues/32) for i in (0..ass.events.len()).rev() { - if ass.events[i].line_end.total_ms() > max_length.num_milliseconds() as u32 { - if ass.events[i].line_start.total_ms() > max_length.num_milliseconds() as u32 { - ass.events[i] - .line_start - .set_ms(max_length.num_milliseconds() as u32); + let max_len = Time::from_hms(0, 0, 0) + .unwrap() + .add(Duration::from_millis(max_length.num_milliseconds() as u64)); + + if ass.events[i].start > max_len { + if ass.events[i].end > max_len { + ass.events[i].start = max_len } - ass.events[i] - .line_end - .set_ms(max_length.num_milliseconds() as u32); + ass.events[i].end = max_len } else { break; } } + // without this additional info, subtitle look very messy in some video player + // (https://github.com/crunchy-labs/crunchy-cli/issues/66) + ass.info + .additional_fields + .insert("ScaledBorderAndShadows".to_string(), "yes".to_string()); + let tempfile = tempfile(".ass")?; let path = tempfile.into_temp_path(); - ass.to_file(path.to_string_lossy().to_string().as_str())?; + fs::write(&path, ass.to_string())?; Ok(path) } From 817963af4fbf0eef1fde26a02f0771e343ce35d9 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 14 May 2024 21:22:23 +0200 Subject: [PATCH 609/630] Fix video containing hardsub if not requested (#415) --- crunchy-cli-core/src/download/command.rs | 20 ++++++++++++++------ crunchy-cli-core/src/utils/video.rs | 24 ++++-------------------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index bb0c1fd..a9c3acf 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -384,12 +384,20 @@ async fn get_format( let subtitle = if contains_hardsub { None } else if let Some(subtitle_locale) = &download.subtitle { - stream - .subtitles - .get(subtitle_locale) - .cloned() - // use closed captions as fallback if no actual subtitles are found - .or_else(|| stream.captions.get(subtitle_locale).cloned()) + if download.audio == Locale::ja_JP { + stream + .subtitles + .get(subtitle_locale) + // use closed captions as fallback if no actual subtitles are found + .or_else(|| stream.captions.get(subtitle_locale)) + .cloned() + } else { + stream + .captions + .get(subtitle_locale) + .or_else(|| stream.subtitles.get(subtitle_locale)) + .cloned() + } } else { None }; diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index 07f6e76..8b25791 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -5,28 +5,12 @@ use crunchyroll_rs::Locale; pub async fn stream_data_from_stream( stream: &Stream, resolution: &Resolution, - subtitle: Option<Locale>, + hardsub_subtitle: Option<Locale>, ) -> Result<Option<(StreamData, StreamData, bool)>> { - // sometimes Crunchyroll marks episodes without real subtitles that they have subtitles and - // reports that only hardsub episode are existing. the following lines are trying to prevent - // potential errors which might get caused by this incorrect reporting - // (https://github.com/crunchy-labs/crunchy-cli/issues/231) - let mut hardsub_locales: Vec<Locale> = stream.hard_subs.keys().cloned().collect(); - let (hardsub_locale, mut contains_hardsub) = if !hardsub_locales - .contains(&Locale::Custom("".to_string())) - && !hardsub_locales.contains(&Locale::Custom(":".to_string())) - { - // if only one hardsub locale exists, assume that this stream doesn't really contains hardsubs - if hardsub_locales.len() == 1 { - (Some(hardsub_locales.remove(0)), false) - } else { - // fallback to `None`. this should trigger an error message in `stream.dash_streaming_data` - // that the requested stream is not available - (None, false) - } + let (hardsub_locale, mut contains_hardsub) = if hardsub_subtitle.is_some() { + (hardsub_subtitle, true) } else { - let hardsubs_requested = subtitle.is_some(); - (subtitle, hardsubs_requested) + (None, false) }; let (mut videos, mut audios) = match stream.stream_data(hardsub_locale).await { From 590242712b00c90c6c4cd49744764a1b04b64789 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 14 May 2024 21:36:12 +0200 Subject: [PATCH 610/630] Add warning message the `--skip-existing-method` has no effect without `--skip-existing` (#418) --- crunchy-cli-core/src/archive/command.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 38bddc1..44a30de 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -234,6 +234,10 @@ impl Execute for Archive { bail!("`--include-chapters` can only be used if `--merge` is set to 'audio' or 'sync'") } + if !self.skip_existing_method.is_empty() && !self.skip_existing { + warn!("`--skip-existing-method` has no effect if `--skip-existing` is not set") + } + self.audio = all_locale_in_locales(self.audio.clone()); self.subtitle = all_locale_in_locales(self.subtitle.clone()); From a98e31f959892fed57a0510356a0c6a47a5c8672 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 14 May 2024 22:36:59 +0200 Subject: [PATCH 611/630] Only include one CC subtitle --- crunchy-cli-core/src/archive/command.rs | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 44a30de..113447f 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -501,24 +501,21 @@ async fn get_format( .subtitle .iter() .flat_map(|s| { - let subtitles = stream - .subtitles - .get(s) - .cloned() - // the subtitle is probably cc if the audio is not japanese or only one - // subtitle exists for this stream - .map(|l| { - ( - l, - single_format.audio != Locale::ja_JP && stream.subtitles.len() == 1, - ) - }); - let cc = stream.captions.get(s).cloned().map(|l| (l, true)); - + let mut subtitles = vec![]; + if let Some(caption) = stream.captions.get(s) { + subtitles.push((caption.clone(), true)) + } + if let Some(subtitle) = stream.subtitles.get(s) { + // the subtitle is probably cc if the audio is not japanese or only one subtitle + // exists for this stream + let cc = single_format.audio != Locale::ja_JP && stream.subtitles.len() == 1; + // only include the subtitles if no cc subtitle is already present or if it's + // not cc + if subtitles.is_empty() || !cc { + subtitles.push((subtitle.clone(), cc)) + } + } subtitles - .into_iter() - .chain(cc.into_iter()) - .collect::<Vec<(Subtitle, bool)>>() }) .collect(); From 5279a9b75910992ad1731557ad65ce4302bb9208 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 14 May 2024 23:42:37 +0200 Subject: [PATCH 612/630] Update dependencies and version --- Cargo.lock | 57 +++++++++++++++++++------------------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd1f282..198afc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,7 +343,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.1" +version = "3.6.2" dependencies = [ "chrono", "clap", @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.1" +version = "3.6.2" dependencies = [ "anyhow", "async-speed-limit", @@ -476,12 +476,13 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b4bdd5f1c0c7493d780c645f0bff5b9361e6408210fa88910adb181efca64c" +checksum = "876a00c22923799ac46365eb528c10134f979bf58ced5e3113de5b98d9835290" dependencies = [ "base64 0.22.1", "base64-serde", + "bytes", "chrono", "fs-err", "iso8601", @@ -581,9 +582,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1156,9 +1157,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -1345,9 +1346,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1535,9 +1536,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustfft" @@ -1606,9 +1607,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" @@ -1633,9 +1634,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -1671,18 +1672,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.200" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", @@ -1691,9 +1692,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1841,9 +1842,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -1900,18 +1901,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 26f71f3..1a1c76f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.1" +version = "3.6.2" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 49f7a5e..f3388f7 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.1" +version = "3.6.2" edition = "2021" license = "MIT" From 9819b622594e3cb2164aedcef71827618b421c41 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 17 May 2024 23:45:41 +0200 Subject: [PATCH 613/630] Fix typo in additional subtitle field (#421) --- crunchy-cli-core/src/utils/download.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index 43d165b..d54b0bc 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -962,7 +962,7 @@ impl Downloader { // (https://github.com/crunchy-labs/crunchy-cli/issues/66) ass.info .additional_fields - .insert("ScaledBorderAndShadows".to_string(), "yes".to_string()); + .insert("ScaledBorderAndShadow".to_string(), "yes".to_string()); let tempfile = tempfile(".ass")?; let path = tempfile.into_temp_path(); From 301dac478f177b70723672effa3f2651ff8419d1 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 20 May 2024 15:57:28 +0200 Subject: [PATCH 614/630] Update dependencies and version --- Cargo.lock | 99 ++++++++++++++++++------------------- Cargo.toml | 5 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 198afc9..140125d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-speed-limit" @@ -119,6 +119,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -188,9 +194,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -238,7 +244,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -343,7 +349,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.2" +version = "3.6.3" dependencies = [ "chrono", "clap", @@ -356,7 +362,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.2" +version = "3.6.3" dependencies = [ "anyhow", "async-speed-limit", @@ -441,9 +447,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -451,23 +457,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", @@ -555,9 +561,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "encode_unicode" @@ -733,15 +739,15 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap 2.2.6", "slab", @@ -979,9 +985,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -1043,9 +1049,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libredox" @@ -1059,9 +1065,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -1099,9 +1105,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -1120,7 +1126,7 @@ dependencies = [ [[package]] name = "native-tls" version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=fdba246#fdba246a79986607cbdf573733445498bb6da2a9" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=b7969a8#b7969a88210096e0570e29d42fb13533baf62aa6" dependencies = [ "libc", "log", @@ -1346,9 +1352,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -1514,8 +1520,7 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rsubs-lib" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43e1a7f184bc76407dbaa67bd2aeea8a15430d7e1e498070963336d03ebedee" +source = "git+https://github.com/crunchy-labs/rsubs-lib.git?rev=1c51f60#1c51f60b8c48f1a8f7b261372b237d89bdc17dd4" dependencies = [ "regex", "serde", @@ -1613,9 +1618,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -1672,18 +1677,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -1822,12 +1827,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -1842,9 +1841,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.63" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -1901,18 +1900,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 1a1c76f..4021ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.2" +version = "3.6.3" edition = "2021" license = "MIT" @@ -34,7 +34,8 @@ members = ["crunchy-cli-core"] [patch.crates-io] # fork of the `native-tls` crate which can use openssl as backend on every platform. this is done as `reqwest` only # supports `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "fdba246" } +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "b7969a8" } +rsubs-lib = { git = "https://github.com/crunchy-labs/rsubs-lib.git", rev = "1c51f60" } [profile.release] strip = true diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index f3388f7..4b4416b 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.2" +version = "3.6.3" edition = "2021" license = "MIT" From f7ce888329825470a42e13cf6c0dd35826783d6c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 May 2024 21:33:08 +0200 Subject: [PATCH 615/630] Bypass stream limits --- crunchy-cli-core/src/archive/command.rs | 4 ++- crunchy-cli-core/src/download/command.rs | 2 ++ crunchy-cli-core/src/utils/format.rs | 32 +++++++++++++++--------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index 113447f..d34c4b7 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -520,7 +520,9 @@ async fn get_format( .collect(); format_pairs.push((single_format, video.clone(), audio, subtitles.clone())); - single_format_to_format_pairs.push((single_format.clone(), video, subtitles)) + single_format_to_format_pairs.push((single_format.clone(), video, subtitles)); + + stream.invalidate().await? } let mut download_formats = vec![]; diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index a9c3acf..fcf069b 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -434,5 +434,7 @@ async fn get_format( subs.push(download.subtitle.clone().unwrap()) } + stream.invalidate().await?; + Ok((download_format, format)) } diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 0a71838..325c731 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -2,7 +2,7 @@ use crate::utils::filter::real_dedup_vec; use crate::utils::locale::LanguageTagging; use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; -use anyhow::{bail, Result}; +use anyhow::Result; use chrono::{Datelike, Duration}; use crunchyroll_rs::media::{Resolution, SkipEvents, Stream, StreamData, Subtitle}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; @@ -166,19 +166,27 @@ impl SingleFormat { } pub async fn stream(&self) -> Result<Stream> { - let stream = match &self.source { - MediaCollection::Episode(e) => e.stream_maybe_without_drm().await?, - MediaCollection::Movie(m) => m.stream_maybe_without_drm().await?, - MediaCollection::MusicVideo(mv) => mv.stream_maybe_without_drm().await?, - MediaCollection::Concert(c) => c.stream_maybe_without_drm().await?, - _ => unreachable!(), - }; + let mut i = 0; + loop { + let stream = match &self.source { + MediaCollection::Episode(e) => e.stream_maybe_without_drm().await, + MediaCollection::Movie(m) => m.stream_maybe_without_drm().await, + MediaCollection::MusicVideo(mv) => mv.stream_maybe_without_drm().await, + MediaCollection::Concert(c) => c.stream_maybe_without_drm().await, + _ => unreachable!(), + }; - if stream.session.uses_stream_limits { - bail!("Found a stream which probably uses DRM. DRM downloads aren't supported") + // sometimes the request to get streams fails with an 403 and the message "JWT error", + // even if the jwt (i guess the auth bearer token is meant by that) is perfectly valid. + // it's retried the request 3 times if this specific error occurs + if let Err(crunchyroll_rs::error::Error::Request { message, .. }) = &stream { + if message == "JWT error" && i < 3 { + i += 1; + continue; + } + }; + return Ok(stream?); } - - Ok(stream) } pub async fn skip_events(&self) -> Result<Option<SkipEvents>> { From cbe57e2b6e6262c5a94312a9646faea3a9f23ccb Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 May 2024 21:34:05 +0200 Subject: [PATCH 616/630] Update dependencies and version --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 140125d..5ea720f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,7 +349,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.3" +version = "3.6.4" dependencies = [ "chrono", "clap", @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.3" +version = "3.6.4" dependencies = [ "anyhow", "async-speed-limit", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "dash-mpd" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a00c22923799ac46365eb528c10134f979bf58ced5e3113de5b98d9835290" +checksum = "4618a5e165bf47b084963611bcf1d568c681f52d8a237e8862a0cd8c546ba255" dependencies = [ "base64 0.22.1", "base64-serde", @@ -1259,9 +1259,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.2.3+3.2.1" +version = "300.3.0+3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "eba8804a1c5765b18c4b3f907e6897ebabeedebc9830e1a0046c4a4cf44663e1" dependencies = [ "cc", ] diff --git a/Cargo.toml b/Cargo.toml index 4021ebc..98d2269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.3" +version = "3.6.4" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 4b4416b..8370fdc 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.3" +version = "3.6.4" edition = "2021" license = "MIT" From f8bd0929872f4c78a6d2b54eee7dacb4c94cfb9d Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Tue, 21 May 2024 21:51:18 +0200 Subject: [PATCH 617/630] Add custom error message if too many streams are active --- crunchy-cli-core/src/utils/format.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 325c731..436be78 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -2,7 +2,7 @@ use crate::utils::filter::real_dedup_vec; use crate::utils::locale::LanguageTagging; use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; -use anyhow::Result; +use anyhow::{bail, Result}; use chrono::{Datelike, Duration}; use crunchyroll_rs::media::{Resolution, SkipEvents, Stream, StreamData, Subtitle}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; @@ -176,13 +176,15 @@ impl SingleFormat { _ => unreachable!(), }; - // sometimes the request to get streams fails with an 403 and the message "JWT error", - // even if the jwt (i guess the auth bearer token is meant by that) is perfectly valid. - // it's retried the request 3 times if this specific error occurs if let Err(crunchyroll_rs::error::Error::Request { message, .. }) = &stream { + // sometimes the request to get streams fails with an 403 and the message + // "JWT error", even if the jwt (i guess the auth bearer token is meant by that) is + // perfectly valid. it's retried the request 3 times if this specific error occurs if message == "JWT error" && i < 3 { i += 1; continue; + } else if message.starts_with("TOO_MANY_ACTIVE_STREAMS") { + bail!("Too many active/parallel streams. Please close at least one stream you're watching and try again") } }; return Ok(stream?); From 5593046aae4c399c1f0568c14ff35f3d75d96edc Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 22 May 2024 16:52:43 +0200 Subject: [PATCH 618/630] Update dependencies and version --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- crunchy-cli-core/Cargo.toml | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ea720f..b24bc20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,7 +349,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.4" +version = "3.6.5" dependencies = [ "chrono", "clap", @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.4" +version = "3.6.5" dependencies = [ "anyhow", "async-speed-limit", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58580acc9c0abf96a231ec8b1a4597ea55d9426ea17f684ce3582e2b26437bbb" +checksum = "7a6754d10e1890089eb733b71aee6f4cbc18374040aedb04c4ca76020bcd9818" dependencies = [ "async-trait", "chrono", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3c844dec8a3390f8c9853b5cf1d65c3d38fd0657b8b5d0e008db8945dea326" +checksum = "ca15fa827cca647852b091006f2b592f8727e1082f812b475b3f9ebe3f59d5bf" dependencies = [ "darling", "quote", diff --git a/Cargo.toml b/Cargo.toml index 98d2269..ccf80ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.4" +version = "3.6.5" edition = "2021" license = "MIT" diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 8370fdc..98ff9d7 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.4" +version = "3.6.5" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.11.1", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.11.2", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" From 7d2ae719c8fee83f2ba5041f214b87eb53d3c33c Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 22 May 2024 16:54:58 +0200 Subject: [PATCH 619/630] Remove internal jwt error retry --- crunchy-cli-core/src/utils/format.rs | 35 +++++++++++----------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index 436be78..c5e8f3d 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -166,29 +166,20 @@ impl SingleFormat { } pub async fn stream(&self) -> Result<Stream> { - let mut i = 0; - loop { - let stream = match &self.source { - MediaCollection::Episode(e) => e.stream_maybe_without_drm().await, - MediaCollection::Movie(m) => m.stream_maybe_without_drm().await, - MediaCollection::MusicVideo(mv) => mv.stream_maybe_without_drm().await, - MediaCollection::Concert(c) => c.stream_maybe_without_drm().await, - _ => unreachable!(), - }; + let stream = match &self.source { + MediaCollection::Episode(e) => e.stream_maybe_without_drm().await, + MediaCollection::Movie(m) => m.stream_maybe_without_drm().await, + MediaCollection::MusicVideo(mv) => mv.stream_maybe_without_drm().await, + MediaCollection::Concert(c) => c.stream_maybe_without_drm().await, + _ => unreachable!(), + }; - if let Err(crunchyroll_rs::error::Error::Request { message, .. }) = &stream { - // sometimes the request to get streams fails with an 403 and the message - // "JWT error", even if the jwt (i guess the auth bearer token is meant by that) is - // perfectly valid. it's retried the request 3 times if this specific error occurs - if message == "JWT error" && i < 3 { - i += 1; - continue; - } else if message.starts_with("TOO_MANY_ACTIVE_STREAMS") { - bail!("Too many active/parallel streams. Please close at least one stream you're watching and try again") - } - }; - return Ok(stream?); - } + if let Err(crunchyroll_rs::error::Error::Request { message, .. }) = &stream { + if message.starts_with("TOO_MANY_ACTIVE_STREAMS") { + bail!("Too many active/parallel streams. Please close at least one stream you're watching and try again") + } + }; + Ok(stream?) } pub async fn skip_events(&self) -> Result<Option<SkipEvents>> { From 74e5e05b0f8b4cb83cd5708747fe81a0183b56f0 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 22 May 2024 23:59:12 +0200 Subject: [PATCH 620/630] Invalidate stream when using search command (#428) --- crunchy-cli-core/src/search/format.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 7ea84d8..ee855b2 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -464,7 +464,9 @@ impl Format { if !stream_empty { for (_, episodes) in tree.iter_mut() { for (episode, streams) in episodes { - streams.push(episode.stream_maybe_without_drm().await?) + let stream = episode.stream_maybe_without_drm().await?; + stream.clone().invalidate().await?; + streams.push(stream) } } } else { From a1c7b2069d79ecc634e3ba216fb86e63135d9501 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 23 May 2024 00:01:42 +0200 Subject: [PATCH 621/630] Update dependencies and version --- Cargo.lock | 17 +++++++++-------- Cargo.toml | 3 +-- crunchy-cli-core/Cargo.toml | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b24bc20..8400022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,7 +349,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.5" +version = "3.6.6" dependencies = [ "chrono", "clap", @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.5" +version = "3.6.6" dependencies = [ "anyhow", "async-speed-limit", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6754d10e1890089eb733b71aee6f4cbc18374040aedb04c4ca76020bcd9818" +checksum = "1d33b8d77c80dea79e66993cb67963b2171dcf0b8fbc87591c58f2dadfea8da2" dependencies = [ "async-trait", "chrono", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca15fa827cca647852b091006f2b592f8727e1082f812b475b3f9ebe3f59d5bf" +checksum = "fa51945265f25c45f7d53bd70e5263dd023c0be45e38eaba886a971cb645d797" dependencies = [ "darling", "quote", @@ -1519,8 +1519,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rsubs-lib" -version = "0.3.0" -source = "git+https://github.com/crunchy-labs/rsubs-lib.git?rev=1c51f60#1c51f60b8c48f1a8f7b261372b237d89bdc17dd4" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01f7609f0b1bc4fe24b352e8d1792c7d71cc43aea797e14b87974cd009ab402" dependencies = [ "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index ccf80ec..f263e59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.5" +version = "3.6.6" edition = "2021" license = "MIT" @@ -35,7 +35,6 @@ members = ["crunchy-cli-core"] # fork of the `native-tls` crate which can use openssl as backend on every platform. this is done as `reqwest` only # supports `rustls` and `native-tls` as tls backend native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "b7969a8" } -rsubs-lib = { git = "https://github.com/crunchy-labs/rsubs-lib.git", rev = "1c51f60" } [profile.release] strip = true diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 98ff9d7..56964ec 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.5" +version = "3.6.6" edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.11.2", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.11.3", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" @@ -30,7 +30,7 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } -rsubs-lib = "0.3" +rsubs-lib = "~0.3.1" rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" From 67c267be2005d18d39c814087db905a801d41fc5 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 24 May 2024 22:05:04 +0200 Subject: [PATCH 622/630] Remove unused variable --- crunchy-cli-core/src/utils/format.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index c5e8f3d..f0a002c 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -4,7 +4,7 @@ use crate::utils::log::tab_info; use crate::utils::os::{is_special_file, sanitize}; use anyhow::{bail, Result}; use chrono::{Datelike, Duration}; -use crunchyroll_rs::media::{Resolution, SkipEvents, Stream, StreamData, Subtitle}; +use crunchyroll_rs::media::{SkipEvents, Stream, StreamData, Subtitle}; use crunchyroll_rs::{Concert, Episode, Locale, MediaCollection, Movie, MusicVideo}; use log::{debug, info}; use std::cmp::Ordering; @@ -354,8 +354,6 @@ pub struct Format { pub locales: Vec<(Locale, Vec<Locale>)>, - // deprecated - pub resolution: Resolution, pub width: u64, pub height: u64, pub fps: f64, @@ -401,7 +399,6 @@ impl Format { title: first_format.title, description: first_format.description, locales, - resolution: first_stream.resolution().unwrap(), width: first_stream.resolution().unwrap().width, height: first_stream.resolution().unwrap().height, fps: first_stream.fps().unwrap(), @@ -449,11 +446,11 @@ impl Format { ) .replace( "{width}", - &sanitize(self.resolution.width.to_string(), true, universal), + &sanitize(self.width.to_string(), true, universal), ) .replace( "{height}", - &sanitize(self.resolution.height.to_string(), true, universal), + &sanitize(self.height.to_string(), true, universal), ) .replace("{series_id}", &sanitize(&self.series_id, true, universal)) .replace( @@ -589,7 +586,7 @@ impl Format { .collect::<Vec<String>>() .join(", ") ); - tab_info!("Resolution: {}", self.resolution); + tab_info!("Resolution: {}x{}", self.height, self.width); tab_info!("FPS: {:.2}", self.fps) } From fb8e53564442653bcaa972bf8143066b42cb49d3 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 24 May 2024 22:09:23 +0200 Subject: [PATCH 623/630] Fix subtitle title not being human-readable --- crunchy-cli-core/src/utils/download.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index d54b0bc..accefce 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -654,7 +654,7 @@ impl Downloader { metadata.extend([ format!("-metadata:s:s:{}", i), format!("title={}", { - let mut title = meta.locale.to_string(); + let mut title = meta.locale.to_human_readable(); if meta.cc { title += " (CC)" } From e7ac6d8874418c3311d433855f5991ea6f0be187 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 24 May 2024 22:17:25 +0200 Subject: [PATCH 624/630] Deprecate search stream.is_drm option --- crunchy-cli-core/src/search/command.rs | 4 ++++ crunchy-cli-core/src/search/format.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crunchy-cli-core/src/search/command.rs b/crunchy-cli-core/src/search/command.rs index c29ce34..8032bed 100644 --- a/crunchy-cli-core/src/search/command.rs +++ b/crunchy-cli-core/src/search/command.rs @@ -111,6 +111,10 @@ impl Execute for Search { warn!("Using `search` anonymously or with a non-premium account may return incomplete results") } + if self.output.contains("{{stream.is_drm}}") { + warn!("The `{{{{stream.is_drm}}}}` option is deprecated as it isn't reliable anymore and will be removed soon") + } + let input = if crunchyroll_rs::parse::parse_url(&self.input).is_some() { match parse_url(&ctx.crunchy, self.input.clone(), true).await { Ok(ok) => vec![ok], diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index ee855b2..10f4624 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -173,7 +173,7 @@ impl From<&Stream> for FormatStream { Self { locale: value.audio_locale.clone(), dash_url: value.url.clone(), - is_drm: value.session.uses_stream_limits, + is_drm: false, } } } From 287df843828e106d4fef2661c796ffaa69742caf Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Fri, 14 Jun 2024 00:17:08 +0200 Subject: [PATCH 625/630] Rework episode filtering --- Cargo.lock | 16 +- crunchy-cli-core/Cargo.toml | 2 +- crunchy-cli-core/src/archive/command.rs | 47 ++- crunchy-cli-core/src/archive/filter.rs | 466 ----------------------- crunchy-cli-core/src/archive/mod.rs | 1 - crunchy-cli-core/src/download/command.rs | 53 ++- crunchy-cli-core/src/download/filter.rs | 307 --------------- crunchy-cli-core/src/download/mod.rs | 1 - crunchy-cli-core/src/search/format.rs | 28 +- crunchy-cli-core/src/utils/filter.rs | 425 +++++++++++++++++++-- crunchy-cli-core/src/utils/format.rs | 2 + 11 files changed, 515 insertions(+), 833 deletions(-) delete mode 100644 crunchy-cli-core/src/archive/filter.rs delete mode 100644 crunchy-cli-core/src/download/filter.rs diff --git a/Cargo.lock b/Cargo.lock index 8400022..7fdb3d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d33b8d77c80dea79e66993cb67963b2171dcf0b8fbc87591c58f2dadfea8da2" +checksum = "d6e38c223aecf65c9c9bec50764beea5dc70b6c97cd7f767bf6860f2fc8e0a07" dependencies = [ "async-trait", "chrono", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "crunchyroll-rs-internal" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa51945265f25c45f7d53bd70e5263dd023c0be45e38eaba886a971cb645d797" +checksum = "144a38040a21aaa456741a9f6749354527bb68ad3bb14210e0bbc40fbd95186c" dependencies = [ "darling", "quote", @@ -1967,9 +1967,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -1984,9 +1984,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 56964ec..5c7b901 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -16,7 +16,7 @@ anyhow = "1.0" async-speed-limit = "0.4" clap = { version = "4.5", features = ["derive", "string"] } chrono = "0.4" -crunchyroll-rs = { version = "0.11.3", features = ["experimental-stabilizations", "tower"] } +crunchyroll-rs = { version = "0.11.4", features = ["experimental-stabilizations", "tower"] } ctrlc = "3.4" dialoguer = { version = "0.11", default-features = false } dirs = "5.0" diff --git a/crunchy-cli-core/src/archive/command.rs b/crunchy-cli-core/src/archive/command.rs index d34c4b7..0d1b3a4 100644 --- a/crunchy-cli-core/src/archive/command.rs +++ b/crunchy-cli-core/src/archive/command.rs @@ -1,10 +1,9 @@ -use crate::archive::filter::ArchiveFilter; use crate::utils::context::Context; use crate::utils::download::{ DownloadBuilder, DownloadFormat, DownloadFormatMetadata, MergeBehavior, }; use crate::utils::ffmpeg::FFmpegPreset; -use crate::utils::filter::Filter; +use crate::utils::filter::{Filter, FilterMediaScope}; use crate::utils::format::{Format, SingleFormat}; use crate::utils::locale::{all_locale_in_locales, resolve_locales, LanguageTagging}; use crate::utils::log::progress; @@ -284,9 +283,49 @@ impl Execute for Archive { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = ArchiveFilter::new( + let single_format_collection = Filter::new( url_filter, - self.clone(), + self.audio.clone(), + self.subtitle.clone(), + |scope, locales| { + let audios = locales.into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", "); + match scope { + FilterMediaScope::Series(series) => warn!("Series {} is not available with {} audio", series.title, audios), + FilterMediaScope::Season(season) => warn!("Season {} is not available with {} audio", season.season_number, audios), + FilterMediaScope::Episode(episodes) => { + if episodes.len() == 1 { + warn!("Episode {} is not available with {} audio", episodes[0].sequence_number, audios) + } else if episodes.len() == 2 { + warn!("Season {} is only available with {} audio from episode {} to {}", episodes[0].season_number, audios, episodes[0].sequence_number, episodes[1].sequence_number) + } else { + unimplemented!() + } + } + } + Ok(true) + }, + |scope, locales| { + let subtitles = locales.into_iter().map(|l| l.to_string()).collect::<Vec<String>>().join(", "); + match scope { + FilterMediaScope::Series(series) => warn!("Series {} is not available with {} subtitles", series.title, subtitles), + FilterMediaScope::Season(season) => warn!("Season {} is not available with {} subtitles", season.season_number, subtitles), + FilterMediaScope::Episode(episodes) => { + if episodes.len() == 1 { + warn!("Episode {} of season {} is not available with {} subtitles", episodes[0].sequence_number, episodes[0].season_title, subtitles) + } else if episodes.len() == 2 { + warn!("Season {} of season {} is only available with {} subtitles from episode {} to {}", episodes[0].season_number, episodes[0].season_title, subtitles, episodes[0].sequence_number, episodes[1].sequence_number) + } else { + unimplemented!() + } + } + } + Ok(true) + }, + |season| { + warn!("Skipping premium episodes in season {season}"); + Ok(()) + }, + Format::has_relative_fmt(&self.output), !self.yes, self.skip_specials, ctx.crunchy.premium().await, diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs deleted file mode 100644 index b08fb6c..0000000 --- a/crunchy-cli-core/src/archive/filter.rs +++ /dev/null @@ -1,466 +0,0 @@ -use crate::archive::command::Archive; -use crate::utils::filter::{real_dedup_vec, Filter}; -use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; -use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; -use crate::utils::parse::{fract, UrlFilter}; -use anyhow::Result; -use crunchyroll_rs::{Concert, Episode, Locale, Movie, MovieListing, MusicVideo, Season, Series}; -use log::{info, warn}; -use std::collections::{BTreeMap, HashMap}; -use std::ops::Not; - -enum Visited { - Series, - Season, - None, -} - -pub(crate) struct ArchiveFilter { - url_filter: UrlFilter, - archive: Archive, - interactive_input: bool, - skip_special: bool, - season_episodes: HashMap<String, Vec<Episode>>, - season_subtitles_missing: Vec<u32>, - seasons_with_premium: Option<Vec<u32>>, - season_sorting: Vec<String>, - visited: Visited, -} - -impl ArchiveFilter { - pub(crate) fn new( - url_filter: UrlFilter, - archive: Archive, - interactive_input: bool, - skip_special: bool, - is_premium: bool, - ) -> Self { - Self { - url_filter, - archive, - interactive_input, - skip_special, - season_episodes: HashMap::new(), - season_subtitles_missing: vec![], - seasons_with_premium: is_premium.not().then_some(vec![]), - season_sorting: vec![], - visited: Visited::None, - } - } -} - -impl Filter for ArchiveFilter { - type T = Vec<SingleFormat>; - type Output = SingleFormatCollection; - - async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { - // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the - // audio is matching only if the field is populated - if !series.audio_locales.is_empty() { - let missing_audio = missing_locales(&series.audio_locales, &self.archive.audio); - if !missing_audio.is_empty() { - warn!( - "Series {} is not available with {} audio", - series.title, - missing_audio - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - let missing_subtitle = - missing_locales(&series.subtitle_locales, &self.archive.subtitle); - if !missing_subtitle.is_empty() { - warn!( - "Series {} is not available with {} subtitles", - series.title, - missing_subtitle - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - self.visited = Visited::Series - } - - let mut seasons = series.seasons().await?; - let mut remove_ids = vec![]; - for season in seasons.iter_mut() { - if !self.url_filter.is_season_valid(season.season_number) - || (!season - .audio_locales - .iter() - .any(|l| self.archive.audio.contains(l)) - && !season - .available_versions() - .await? - .iter() - .any(|l| self.archive.audio.contains(l))) - { - remove_ids.push(season.id.clone()); - } - } - - seasons.retain(|s| !remove_ids.contains(&s.id)); - - let duplicated_seasons = get_duplicated_seasons(&seasons); - if !duplicated_seasons.is_empty() { - if self.interactive_input { - check_for_duplicated_seasons(&mut seasons); - } else { - info!( - "Found duplicated seasons: {}", - duplicated_seasons - .iter() - .map(|d| d.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - } - - Ok(seasons) - } - - async fn visit_season(&mut self, mut season: Season) -> Result<Vec<Episode>> { - if !self.url_filter.is_season_valid(season.season_number) { - return Ok(vec![]); - } - - let mut seasons = season.version(self.archive.audio.clone()).await?; - if self - .archive - .audio - .iter() - .any(|l| season.audio_locales.contains(l)) - { - seasons.insert(0, season.clone()); - } - - if !matches!(self.visited, Visited::Series) { - let mut audio_locales: Vec<Locale> = seasons - .iter() - .flat_map(|s| s.audio_locales.clone()) - .collect(); - real_dedup_vec(&mut audio_locales); - let missing_audio = missing_locales(&audio_locales, &self.archive.audio); - if !missing_audio.is_empty() { - warn!( - "Season {} is not available with {} audio", - season.season_number, - missing_audio - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - let subtitle_locales: Vec<Locale> = seasons - .iter() - .flat_map(|s| s.subtitle_locales.clone()) - .collect(); - let missing_subtitle = missing_locales(&subtitle_locales, &self.archive.subtitle); - if !missing_subtitle.is_empty() { - warn!( - "Season {} is not available with {} subtitles", - season.season_number, - missing_subtitle - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - self.visited = Visited::Season - } - - let mut episodes = vec![]; - for season in seasons { - self.season_sorting.push(season.id.clone()); - let season_locale = if season.audio_locales.len() < 2 { - Some( - season - .audio_locales - .first() - .cloned() - .unwrap_or(Locale::ja_JP), - ) - } else { - None - }; - let mut eps = season.episodes().await?; - let before_len = eps.len(); - - for mut ep in eps.clone() { - if let Some(l) = &season_locale { - if &ep.audio_locale == l { - continue; - } - eps.remove(eps.iter().position(|p| p.id == ep.id).unwrap()); - } else { - let mut requested_locales = self.archive.audio.clone(); - if let Some(idx) = requested_locales.iter().position(|p| p == &ep.audio_locale) - { - requested_locales.remove(idx); - } else { - eps.remove(eps.iter().position(|p| p.id == ep.id).unwrap()); - } - eps.extend(ep.version(self.archive.audio.clone()).await?); - } - } - if eps.len() < before_len { - if eps.is_empty() { - if matches!(self.visited, Visited::Series) { - warn!( - "Season {} is not available with {} audio", - season.season_number, - season_locale.unwrap_or(Locale::ja_JP) - ) - } - } else { - let last_episode = eps.last().unwrap(); - warn!( - "Season {} is only available with {} audio until episode {} ({})", - season.season_number, - season_locale.unwrap_or(Locale::ja_JP), - last_episode.sequence_number, - last_episode.title - ) - } - } - episodes.extend(eps) - } - - if Format::has_relative_fmt(&self.archive.output) { - for episode in episodes.iter() { - self.season_episodes - .entry(episode.season_id.clone()) - .or_default() - .push(episode.clone()) - } - } - - Ok(episodes) - } - - async fn visit_episode(&mut self, mut episode: Episode) -> Result<Option<Self::T>> { - if !self - .url_filter - .is_episode_valid(episode.sequence_number, episode.season_number) - { - return Ok(None); - } - - // skip the episode if it's a special - if self.skip_special - && (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0) - { - return Ok(None); - } - - let mut episodes = vec![]; - if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) { - if self.archive.audio.contains(&episode.audio_locale) { - episodes.push((episode.clone(), episode.subtitle_locales.clone())) - } - episodes.extend( - episode - .version(self.archive.audio.clone()) - .await? - .into_iter() - .map(|e| (e.clone(), e.subtitle_locales.clone())), - ); - let audio_locales: Vec<Locale> = episodes - .iter() - .map(|(e, _)| e.audio_locale.clone()) - .collect(); - let missing_audio = missing_locales(&audio_locales, &self.archive.audio); - if !missing_audio.is_empty() { - warn!( - "Episode {} is not available with {} audio", - episode.sequence_number, - missing_audio - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - - let mut subtitle_locales: Vec<Locale> = - episodes.iter().flat_map(|(_, s)| s.clone()).collect(); - real_dedup_vec(&mut subtitle_locales); - let missing_subtitles = missing_locales(&subtitle_locales, &self.archive.subtitle); - if !missing_subtitles.is_empty() - && !self - .season_subtitles_missing - .contains(&episode.season_number) - { - warn!( - "Episode {} is not available with {} subtitles", - episode.sequence_number, - missing_subtitles - .into_iter() - .map(|l| l.to_string()) - .collect::<Vec<String>>() - .join(", ") - ); - self.season_subtitles_missing.push(episode.season_number) - } - } else { - episodes.push((episode.clone(), episode.subtitle_locales.clone())) - } - - if self.seasons_with_premium.is_some() { - let episode_len_before = episodes.len(); - episodes.retain(|(e, _)| !e.is_premium_only); - if episode_len_before < episodes.len() - && !self - .seasons_with_premium - .as_ref() - .unwrap() - .contains(&episode.season_number) - { - warn!( - "Skipping premium episodes in season {}", - episode.season_number - ); - self.seasons_with_premium - .as_mut() - .unwrap() - .push(episode.season_number) - } - - if episodes.is_empty() { - return Ok(None); - } - } - - let mut relative_episode_number = None; - let mut relative_sequence_number = None; - // get the relative episode number. only done if the output string has the pattern to include - // the relative episode number as this requires some extra fetching - if Format::has_relative_fmt(&self.archive.output) { - let season_eps = match self.season_episodes.get(&episode.season_id) { - Some(eps) => eps, - None => { - self.season_episodes.insert( - episode.season_id.clone(), - episode.season().await?.episodes().await?, - ); - self.season_episodes.get(&episode.season_id).unwrap() - } - }; - let mut non_integer_sequence_number_count = 0; - for (i, ep) in season_eps.iter().enumerate() { - if ep.sequence_number.fract() != 0.0 || ep.sequence_number == 0.0 { - non_integer_sequence_number_count += 1; - } - if ep.id == episode.id { - relative_episode_number = Some(i + 1); - relative_sequence_number = Some( - (i + 1 - non_integer_sequence_number_count) as f32 - + fract(ep.sequence_number), - ); - break; - } - } - if relative_episode_number.is_none() || relative_sequence_number.is_none() { - warn!( - "Failed to get relative episode number for episode {} ({}) of {} season {}", - episode.sequence_number, - episode.title, - episode.series_title, - episode.season_number, - ) - } - } - - Ok(Some( - episodes - .into_iter() - .map(|(e, s)| { - SingleFormat::new_from_episode( - e, - s, - relative_episode_number.map(|n| n as u32), - relative_sequence_number, - ) - }) - .collect(), - )) - } - - async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { - Ok(movie_listing.movies().await?) - } - - async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>> { - Ok(Some(vec![SingleFormat::new_from_movie(movie, vec![])])) - } - - async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>> { - Ok(Some(vec![SingleFormat::new_from_music_video(music_video)])) - } - - async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>> { - Ok(Some(vec![SingleFormat::new_from_concert(concert)])) - } - - async fn finish(self, input: Vec<Self::T>) -> Result<Self::Output> { - let flatten_input: Self::T = input.into_iter().flatten().collect(); - - let mut single_format_collection = SingleFormatCollection::new(); - - let mut pre_sorted: BTreeMap<String, Self::T> = BTreeMap::new(); - for data in flatten_input { - pre_sorted - .entry(data.identifier.clone()) - .or_insert(vec![]) - .push(data) - } - - let mut sorted: Vec<(String, Self::T)> = pre_sorted.into_iter().collect(); - sorted.sort_by(|(_, a), (_, b)| { - self.season_sorting - .iter() - .position(|p| p == &a.first().unwrap().season_id) - .unwrap() - .cmp( - &self - .season_sorting - .iter() - .position(|p| p == &b.first().unwrap().season_id) - .unwrap(), - ) - }); - - for (_, mut data) in sorted { - data.sort_by(|a, b| { - self.archive - .audio - .iter() - .position(|p| p == &a.audio) - .unwrap_or(usize::MAX) - .cmp( - &self - .archive - .audio - .iter() - .position(|p| p == &b.audio) - .unwrap_or(usize::MAX), - ) - }); - single_format_collection.add_single_formats(data) - } - - Ok(single_format_collection) - } -} - -fn missing_locales<'a>(available: &[Locale], searched: &'a [Locale]) -> Vec<&'a Locale> { - searched.iter().filter(|p| !available.contains(p)).collect() -} diff --git a/crunchy-cli-core/src/archive/mod.rs b/crunchy-cli-core/src/archive/mod.rs index c3544a4..670d0c2 100644 --- a/crunchy-cli-core/src/archive/mod.rs +++ b/crunchy-cli-core/src/archive/mod.rs @@ -1,4 +1,3 @@ mod command; -mod filter; pub use command::Archive; diff --git a/crunchy-cli-core/src/download/command.rs b/crunchy-cli-core/src/download/command.rs index fcf069b..8e3794f 100644 --- a/crunchy-cli-core/src/download/command.rs +++ b/crunchy-cli-core/src/download/command.rs @@ -1,8 +1,7 @@ -use crate::download::filter::DownloadFilter; use crate::utils::context::Context; use crate::utils::download::{DownloadBuilder, DownloadFormat, DownloadFormatMetadata}; use crate::utils::ffmpeg::{FFmpegPreset, SOFTSUB_CONTAINERS}; -use crate::utils::filter::Filter; +use crate::utils::filter::{Filter, FilterMediaScope}; use crate::utils::format::{Format, SingleFormat}; use crate::utils::locale::{resolve_locales, LanguageTagging}; use crate::utils::log::progress; @@ -14,7 +13,7 @@ use anyhow::bail; use anyhow::Result; use crunchyroll_rs::media::Resolution; use crunchyroll_rs::Locale; -use log::{debug, warn}; +use log::{debug, error, warn}; use std::collections::HashMap; use std::path::Path; @@ -250,9 +249,53 @@ impl Execute for Download { for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() { let progress_handler = progress!("Fetching series details"); - let single_format_collection = DownloadFilter::new( + let single_format_collection = Filter::new( url_filter, - self.clone(), + vec![self.audio.clone()], + self.subtitle.as_ref().map_or(vec![], |s| vec![s.clone()]), + |scope, locales| { + match scope { + FilterMediaScope::Series(series) => bail!("Series {} is not available with {} audio", series.title, locales[0]), + FilterMediaScope::Season(season) => { + error!("Season {} is not available with {} audio", season.season_number, locales[0]); + Ok(false) + } + FilterMediaScope::Episode(episodes) => { + if episodes.len() == 1 { + warn!("Episode {} of season {} is not available with {} audio", episodes[0].sequence_number, episodes[0].season_title, locales[0]) + } else if episodes.len() == 2 { + warn!("Season {} is only available with {} audio from episode {} to {}", episodes[0].season_number, locales[0], episodes[0].sequence_number, episodes[1].sequence_number) + } else { + unimplemented!() + } + Ok(false) + } + } + }, + |scope, locales| { + match scope { + FilterMediaScope::Series(series) => bail!("Series {} is not available with {} subtitles", series.title, locales[0]), + FilterMediaScope::Season(season) => { + warn!("Season {} is not available with {} subtitles", season.season_number, locales[0]); + Ok(false) + }, + FilterMediaScope::Episode(episodes) => { + if episodes.len() == 1 { + warn!("Episode {} of season {} is not available with {} subtitles", episodes[0].sequence_number, episodes[0].season_title, locales[0]) + } else if episodes.len() == 2 { + warn!("Season {} is only available with {} subtitles from episode {} to {}", episodes[0].season_number, locales[0], episodes[0].sequence_number, episodes[1].sequence_number) + } else { + unimplemented!() + } + Ok(false) + } + } + }, + |season| { + warn!("Skipping premium episodes in season {season}"); + Ok(()) + }, + Format::has_relative_fmt(&self.output), !self.yes, self.skip_specials, ctx.crunchy.premium().await, diff --git a/crunchy-cli-core/src/download/filter.rs b/crunchy-cli-core/src/download/filter.rs deleted file mode 100644 index 1c62920..0000000 --- a/crunchy-cli-core/src/download/filter.rs +++ /dev/null @@ -1,307 +0,0 @@ -use crate::download::Download; -use crate::utils::filter::Filter; -use crate::utils::format::{Format, SingleFormat, SingleFormatCollection}; -use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; -use crate::utils::parse::{fract, UrlFilter}; -use anyhow::{bail, Result}; -use crunchyroll_rs::{Concert, Episode, Movie, MovieListing, MusicVideo, Season, Series}; -use log::{error, info, warn}; -use std::collections::HashMap; -use std::ops::Not; - -pub(crate) struct DownloadFilter { - url_filter: UrlFilter, - download: Download, - interactive_input: bool, - skip_special: bool, - season_episodes: HashMap<u32, Vec<Episode>>, - season_subtitles_missing: Vec<u32>, - seasons_with_premium: Option<Vec<u32>>, - season_visited: bool, -} - -impl DownloadFilter { - pub(crate) fn new( - url_filter: UrlFilter, - download: Download, - interactive_input: bool, - skip_special: bool, - is_premium: bool, - ) -> Self { - Self { - url_filter, - download, - interactive_input, - skip_special, - season_episodes: HashMap::new(), - season_subtitles_missing: vec![], - seasons_with_premium: is_premium.not().then_some(vec![]), - season_visited: false, - } - } -} - -impl Filter for DownloadFilter { - type T = SingleFormat; - type Output = SingleFormatCollection; - - async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { - // `series.audio_locales` isn't always populated b/c of crunchyrolls api. so check if the - // audio is matching only if the field is populated - if !series.audio_locales.is_empty() && !series.audio_locales.contains(&self.download.audio) - { - error!( - "Series {} is not available with {} audio", - series.title, self.download.audio - ); - return Ok(vec![]); - } - - let mut seasons = vec![]; - for mut season in series.seasons().await? { - if !self.url_filter.is_season_valid(season.season_number) { - continue; - } - - if !season - .audio_locales - .iter() - .any(|l| l == &self.download.audio) - { - if season - .available_versions() - .await? - .iter() - .any(|l| l == &self.download.audio) - { - season = season - .version(vec![self.download.audio.clone()]) - .await? - .remove(0) - } else { - error!( - "Season {} - '{}' is not available with {} audio", - season.season_number, - season.title, - self.download.audio.clone(), - ); - continue; - } - } - - seasons.push(season) - } - - let duplicated_seasons = get_duplicated_seasons(&seasons); - if !duplicated_seasons.is_empty() { - if self.interactive_input { - check_for_duplicated_seasons(&mut seasons); - } else { - info!( - "Found duplicated seasons: {}", - duplicated_seasons - .iter() - .map(|d| d.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) - } - } - - Ok(seasons) - } - - async fn visit_season(&mut self, season: Season) -> Result<Vec<Episode>> { - self.season_visited = true; - - let mut episodes = season.episodes().await?; - - if Format::has_relative_fmt(&self.download.output) { - for episode in episodes.iter() { - self.season_episodes - .entry(episode.season_number) - .or_default() - .push(episode.clone()) - } - } - - episodes.retain(|e| { - self.url_filter - .is_episode_valid(e.sequence_number, season.season_number) - }); - - Ok(episodes) - } - - async fn visit_episode(&mut self, mut episode: Episode) -> Result<Option<Self::T>> { - if !self - .url_filter - .is_episode_valid(episode.sequence_number, episode.season_number) - { - return Ok(None); - } - - // skip the episode if it's a special - if self.skip_special - && (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0) - { - return Ok(None); - } - - // check if the audio locale is correct. - // should only be incorrect if the console input was a episode url. otherwise - // `DownloadFilter::visit_season` returns the correct episodes with matching audio - if episode.audio_locale != self.download.audio { - // check if any other version (same episode, other language) of this episode is available - // with the requested audio. if not, return an error - if !episode - .available_versions() - .await? - .contains(&self.download.audio) - { - let error_message = format!( - "Episode {} ({}) of {} season {} is not available with {} audio", - episode.sequence_number, - episode.title, - episode.series_title, - episode.season_number, - self.download.audio - ); - // sometimes a series randomly has episode in an other language. if this is the case, - // only error if the input url was a episode url - if self.season_visited { - warn!("{}", error_message); - return Ok(None); - } else { - bail!("{}", error_message) - } - } - // overwrite the current episode with the other version episode - episode = episode - .version(vec![self.download.audio.clone()]) - .await? - .remove(0) - } - - // check if the subtitles are supported - if let Some(subtitle_locale) = &self.download.subtitle { - if !episode.subtitle_locales.contains(subtitle_locale) { - // if the episode doesn't have the requested subtitles, print a error. to print this - // error only once per season, it's checked if an error got printed before by looking - // up if the season id is present in `self.season_subtitles_missing`. if not, print - // the error and add the season id to `self.season_subtitles_missing`. if it is - // present, skip the error printing - if !self - .season_subtitles_missing - .contains(&episode.season_number) - { - self.season_subtitles_missing.push(episode.season_number); - error!( - "{} season {} is not available with {} subtitles", - episode.series_title, episode.season_number, subtitle_locale - ); - } - return Ok(None); - } - } - - if self.seasons_with_premium.is_some() && episode.is_premium_only { - if !self - .seasons_with_premium - .as_ref() - .unwrap() - .contains(&episode.season_number) - { - warn!( - "Skipping premium episodes in season {}", - episode.season_number - ); - self.seasons_with_premium - .as_mut() - .unwrap() - .push(episode.season_number) - } - - return Ok(None); - } - - let mut relative_episode_number = None; - let mut relative_sequence_number = None; - // get the relative episode number. only done if the output string has the pattern to include - // the relative episode number as this requires some extra fetching - if Format::has_relative_fmt(&self.download.output) { - let season_eps = match self.season_episodes.get(&episode.season_number) { - Some(eps) => eps, - None => { - self.season_episodes.insert( - episode.season_number, - episode.season().await?.episodes().await?, - ); - self.season_episodes.get(&episode.season_number).unwrap() - } - }; - let mut non_integer_sequence_number_count = 0; - for (i, ep) in season_eps.iter().enumerate() { - if ep.sequence_number.fract() != 0.0 || ep.sequence_number == 0.0 { - non_integer_sequence_number_count += 1; - } - if ep.id == episode.id { - relative_episode_number = Some(i + 1); - relative_sequence_number = Some( - (i + 1 - non_integer_sequence_number_count) as f32 - + fract(ep.sequence_number), - ); - break; - } - } - if relative_episode_number.is_none() || relative_sequence_number.is_none() { - warn!( - "Failed to get relative episode number for episode {} ({}) of {} season {}", - episode.sequence_number, - episode.title, - episode.series_title, - episode.season_number, - ) - } - } - - Ok(Some(SingleFormat::new_from_episode( - episode.clone(), - self.download.subtitle.clone().map_or(vec![], |s| { - if episode.subtitle_locales.contains(&s) { - vec![s] - } else { - vec![] - } - }), - relative_episode_number.map(|n| n as u32), - relative_sequence_number, - ))) - } - - async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { - Ok(movie_listing.movies().await?) - } - - async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>> { - Ok(Some(SingleFormat::new_from_movie(movie, vec![]))) - } - - async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>> { - Ok(Some(SingleFormat::new_from_music_video(music_video))) - } - - async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>> { - Ok(Some(SingleFormat::new_from_concert(concert))) - } - - async fn finish(self, input: Vec<Self::T>) -> Result<Self::Output> { - let mut single_format_collection = SingleFormatCollection::new(); - - for data in input { - single_format_collection.add_single_formats(vec![data]) - } - - Ok(single_format_collection) - } -} diff --git a/crunchy-cli-core/src/download/mod.rs b/crunchy-cli-core/src/download/mod.rs index 696872e..47ca304 100644 --- a/crunchy-cli-core/src/download/mod.rs +++ b/crunchy-cli-core/src/download/mod.rs @@ -1,4 +1,3 @@ mod command; -mod filter; pub use command::Download; diff --git a/crunchy-cli-core/src/search/format.rs b/crunchy-cli-core/src/search/format.rs index 10f4624..cf3c5bc 100644 --- a/crunchy-cli-core/src/search/format.rs +++ b/crunchy-cli-core/src/search/format.rs @@ -241,14 +241,6 @@ macro_rules! must_match_if_true { }; } -macro_rules! self_and_versions { - ($var:expr => $audio:expr) => {{ - let mut items = vec![$var.clone()]; - items.extend($var.clone().version($audio).await?); - items - }}; -} - pub struct Format { pattern: Vec<(Range<usize>, Scope, String)>, pattern_count: HashMap<Scope, u32>, @@ -421,7 +413,15 @@ impl Format { }; let mut seasons = vec![]; for season in tmp_seasons { - seasons.extend(self_and_versions!(season => self.filter_options.audio.clone())) + seasons.push(season.clone()); + for version in season.versions { + if season.id == version.id { + continue; + } + if self.filter_options.audio.contains(&version.audio_locale) { + seasons.push(version.season().await?) + } + } } tree.extend( self.filter_options @@ -435,7 +435,15 @@ impl Format { if !episode_empty || !stream_empty { match &media_collection { MediaCollection::Episode(episode) => { - let episodes = self_and_versions!(episode => self.filter_options.audio.clone()); + let mut episodes = vec![episode.clone()]; + for version in &episode.versions { + if episode.id == version.id { + continue; + } + if self.filter_options.audio.contains(&version.audio_locale) { + episodes.push(version.episode().await?) + } + } tree.push(( Season::default(), episodes diff --git a/crunchy-cli-core/src/utils/filter.rs b/crunchy-cli-core/src/utils/filter.rs index 63fac9d..b95596e 100644 --- a/crunchy-cli-core/src/utils/filter.rs +++ b/crunchy-cli-core/src/utils/filter.rs @@ -1,24 +1,397 @@ +use crate::utils::format::{SingleFormat, SingleFormatCollection}; +use crate::utils::interactive_select::{check_for_duplicated_seasons, get_duplicated_seasons}; +use crate::utils::parse::{fract, UrlFilter}; use anyhow::Result; use crunchyroll_rs::{ - Concert, Episode, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series, + Concert, Episode, Locale, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series, }; +use log::{info, warn}; +use std::collections::{BTreeMap, HashMap}; +use std::ops::Not; -pub trait Filter { - type T: Send + Sized; - type Output: Send + Sized; +pub(crate) enum FilterMediaScope<'a> { + Series(&'a Series), + Season(&'a Season), + /// Always contains 1 or 2 episodes. + /// - 1: The episode's audio is completely missing + /// - 2: The requested audio is only available from first entry to last entry + Episode(Vec<&'a Episode>), +} - async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>>; - async fn visit_season(&mut self, season: Season) -> Result<Vec<Episode>>; - async fn visit_episode(&mut self, episode: Episode) -> Result<Option<Self::T>>; - async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>>; - async fn visit_movie(&mut self, movie: Movie) -> Result<Option<Self::T>>; - async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Option<Self::T>>; - async fn visit_concert(&mut self, concert: Concert) -> Result<Option<Self::T>>; +pub(crate) struct Filter { + url_filter: UrlFilter, - async fn visit(mut self, media_collection: MediaCollection) -> Result<Self::Output> - where - Self: Send + Sized, - { + skip_specials: bool, + interactive_input: bool, + + relative_episode_number: bool, + + audio_locales: Vec<Locale>, + subtitle_locales: Vec<Locale>, + + audios_missing: fn(FilterMediaScope, Vec<&Locale>) -> Result<bool>, + subtitles_missing: fn(FilterMediaScope, Vec<&Locale>) -> Result<bool>, + no_premium: fn(u32) -> Result<()>, + + is_premium: bool, + + series_visited: bool, + season_episodes: HashMap<String, Vec<Episode>>, + season_with_premium: Option<Vec<u32>>, + season_sorting: Vec<String>, +} + +impl Filter { + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + url_filter: UrlFilter, + audio_locales: Vec<Locale>, + subtitle_locales: Vec<Locale>, + audios_missing: fn(FilterMediaScope, Vec<&Locale>) -> Result<bool>, + subtitles_missing: fn(FilterMediaScope, Vec<&Locale>) -> Result<bool>, + no_premium: fn(u32) -> Result<()>, + relative_episode_number: bool, + interactive_input: bool, + skip_specials: bool, + is_premium: bool, + ) -> Self { + Self { + url_filter, + audio_locales, + subtitle_locales, + relative_episode_number, + interactive_input, + audios_missing, + subtitles_missing, + no_premium, + is_premium, + series_visited: false, + season_episodes: HashMap::new(), + skip_specials, + season_with_premium: is_premium.not().then_some(vec![]), + season_sorting: vec![], + } + } + + async fn visit_series(&mut self, series: Series) -> Result<Vec<Season>> { + // the audio locales field isn't always populated + if !series.audio_locales.is_empty() { + let missing_audios = missing_locales(&series.audio_locales, &self.audio_locales); + if !missing_audios.is_empty() + && !(self.audios_missing)(FilterMediaScope::Series(&series), missing_audios)? + { + return Ok(vec![]); + } + let missing_subtitles = + missing_locales(&series.subtitle_locales, &self.subtitle_locales); + if !missing_subtitles.is_empty() + && !(self.subtitles_missing)(FilterMediaScope::Series(&series), missing_subtitles)? + { + return Ok(vec![]); + } + } + + let mut seasons = vec![]; + for season in series.seasons().await? { + if !self.url_filter.is_season_valid(season.season_number) { + continue; + } + let missing_audios = missing_locales( + &season + .versions + .iter() + .map(|l| l.audio_locale.clone()) + .collect::<Vec<Locale>>(), + &self.audio_locales, + ); + if !missing_audios.is_empty() + && !(self.audios_missing)(FilterMediaScope::Season(&season), missing_audios)? + { + return Ok(vec![]); + } + seasons.push(season) + } + + let duplicated_seasons = get_duplicated_seasons(&seasons); + if !duplicated_seasons.is_empty() { + if self.interactive_input { + check_for_duplicated_seasons(&mut seasons) + } else { + info!( + "Found duplicated seasons: {}", + duplicated_seasons + .iter() + .map(|d| d.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) + } + } + + self.series_visited = true; + + Ok(seasons) + } + + async fn visit_season(&mut self, season: Season) -> Result<Vec<Episode>> { + if !self.url_filter.is_season_valid(season.season_number) { + return Ok(vec![]); + } + + let mut seasons = vec![]; + if self + .audio_locales + .iter() + .any(|l| season.audio_locales.contains(l)) + { + seasons.push(season.clone()) + } + for version in season.versions { + if season.id == version.id { + continue; + } + if self.audio_locales.contains(&version.audio_locale) { + seasons.push(version.season().await?) + } + } + + let mut episodes = vec![]; + for season in seasons { + self.season_sorting.push(season.id.clone()); + let mut eps = season.episodes().await?; + + // removes any episode that does not have the audio locale of the season. yes, this is + // the case sometimes + if season.audio_locales.len() < 2 { + let season_locale = season + .audio_locales + .first() + .cloned() + .unwrap_or(Locale::ja_JP); + eps.retain(|e| e.audio_locale == season_locale) + } + + if eps.len() < season.number_of_episodes as usize + && !(self.audios_missing)( + FilterMediaScope::Episode(vec![eps.first().unwrap(), eps.last().unwrap()]), + vec![&eps.first().unwrap().audio_locale], + )? + { + return Ok(vec![]); + } + + episodes.extend(eps) + } + + if self.relative_episode_number { + for episode in &episodes { + self.season_episodes + .entry(episode.season_id.clone()) + .or_default() + .push(episode.clone()) + } + } + + Ok(episodes) + } + + async fn visit_episode(&mut self, episode: Episode) -> Result<Vec<SingleFormat>> { + if !self + .url_filter + .is_episode_valid(episode.sequence_number, episode.season_number) + { + return Ok(vec![]); + } + + // skip the episode if it's a special + if self.skip_specials + && (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0) + { + return Ok(vec![]); + } + + let mut episodes = vec![]; + if !self.series_visited { + if self.audio_locales.contains(&episode.audio_locale) { + episodes.push(episode.clone()) + } + for version in &episode.versions { + // `episode` is also a version of itself. the if block above already adds the + // episode if it matches the requested audio, so it doesn't need to be requested + // here again + if version.id == episode.id { + continue; + } + if self.audio_locales.contains(&version.audio_locale) { + episodes.push(version.episode().await?) + } + } + + let audio_locales: Vec<Locale> = + episodes.iter().map(|e| e.audio_locale.clone()).collect(); + let missing_audios = missing_locales(&audio_locales, &self.audio_locales); + if !missing_audios.is_empty() + && !(self.audios_missing)( + FilterMediaScope::Episode(vec![&episode]), + missing_audios, + )? + { + return Ok(vec![]); + } + + let mut subtitle_locales: Vec<Locale> = episodes + .iter() + .flat_map(|e| e.subtitle_locales.clone()) + .collect(); + subtitle_locales.sort(); + subtitle_locales.dedup(); + let missing_subtitles = missing_locales(&subtitle_locales, &self.subtitle_locales); + if !missing_subtitles.is_empty() + && !(self.subtitles_missing)( + FilterMediaScope::Episode(vec![&episode]), + missing_subtitles, + )? + { + return Ok(vec![]); + } + } else { + episodes.push(episode.clone()) + } + + if let Some(seasons_with_premium) = &mut self.season_with_premium { + let episodes_len_before = episodes.len(); + episodes.retain(|e| !e.is_premium_only && !self.is_premium); + if episodes_len_before < episodes.len() + && !seasons_with_premium.contains(&episode.season_number) + { + (self.no_premium)(episode.season_number)?; + seasons_with_premium.push(episode.season_number) + } + + if episodes.is_empty() { + return Ok(vec![]); + } + } + + let mut relative_episode_number = None; + let mut relative_sequence_number = None; + if self.relative_episode_number { + let season_eps = match self.season_episodes.get(&episode.season_id) { + Some(eps) => eps, + None => { + self.season_episodes.insert( + episode.season_id.clone(), + episode.season().await?.episodes().await?, + ); + self.season_episodes.get(&episode.season_id).unwrap() + } + }; + let mut non_integer_sequence_number_count = 0; + for (i, ep) in season_eps.iter().enumerate() { + if ep.sequence_number != 0.0 || ep.sequence_number.fract() == 0.0 { + non_integer_sequence_number_count += 1 + } + if ep.id == episode.id { + relative_episode_number = Some(i + 1); + relative_sequence_number = Some( + (i + 1 - non_integer_sequence_number_count) as f32 + + fract(ep.sequence_number), + ); + break; + } + } + if relative_episode_number.is_none() || relative_sequence_number.is_none() { + warn!( + "Failed to get relative episode number for episode {} ({}) of {} season {}", + episode.sequence_number, + episode.title, + episode.series_title, + episode.season_number, + ) + } + } + + Ok(episodes + .into_iter() + .map(|e| { + SingleFormat::new_from_episode( + e.clone(), + e.subtitle_locales, + relative_episode_number.map(|n| n as u32), + relative_sequence_number, + ) + }) + .collect()) + } + + async fn visit_movie_listing(&mut self, movie_listing: MovieListing) -> Result<Vec<Movie>> { + Ok(movie_listing.movies().await?) + } + + async fn visit_movie(&mut self, movie: Movie) -> Result<Vec<SingleFormat>> { + Ok(vec![SingleFormat::new_from_movie(movie, vec![])]) + } + + async fn visit_music_video(&mut self, music_video: MusicVideo) -> Result<Vec<SingleFormat>> { + Ok(vec![SingleFormat::new_from_music_video(music_video)]) + } + + async fn visit_concert(&mut self, concert: Concert) -> Result<Vec<SingleFormat>> { + Ok(vec![SingleFormat::new_from_concert(concert)]) + } + + async fn finish(self, input: Vec<Vec<SingleFormat>>) -> Result<SingleFormatCollection> { + let flatten_input: Vec<SingleFormat> = input.into_iter().flatten().collect(); + + let mut single_format_collection = SingleFormatCollection::new(); + + let mut pre_sorted: BTreeMap<String, Vec<SingleFormat>> = BTreeMap::new(); + for data in flatten_input { + pre_sorted + .entry(data.identifier.clone()) + .or_default() + .push(data) + } + + let mut sorted: Vec<(String, Vec<SingleFormat>)> = pre_sorted.into_iter().collect(); + sorted.sort_by(|(_, a), (_, b)| { + self.season_sorting + .iter() + .position(|p| p == &a.first().unwrap().season_id) + .unwrap() + .cmp( + &self + .season_sorting + .iter() + .position(|p| p == &b.first().unwrap().season_id) + .unwrap(), + ) + }); + + for (_, mut data) in sorted { + data.sort_by(|a, b| { + self.audio_locales + .iter() + .position(|p| p == &a.audio) + .unwrap_or(usize::MAX) + .cmp( + &self + .audio_locales + .iter() + .position(|p| p == &b.audio) + .unwrap_or(usize::MAX), + ) + }); + single_format_collection.add_single_formats(data) + } + + Ok(single_format_collection) + } + + pub(crate) async fn visit( + mut self, + media_collection: MediaCollection, + ) -> Result<SingleFormatCollection> { let mut items = vec![media_collection]; let mut result = vec![]; @@ -42,9 +415,7 @@ pub trait Filter { .collect::<Vec<MediaCollection>>(), ), MediaCollection::Episode(episode) => { - if let Some(t) = self.visit_episode(episode).await? { - result.push(t) - } + result.push(self.visit_episode(episode).await?) } MediaCollection::MovieListing(movie_listing) => new_items.extend( self.visit_movie_listing(movie_listing) @@ -53,20 +424,12 @@ pub trait Filter { .map(|m| m.into()) .collect::<Vec<MediaCollection>>(), ), - MediaCollection::Movie(movie) => { - if let Some(t) = self.visit_movie(movie).await? { - result.push(t) - } - } + MediaCollection::Movie(movie) => result.push(self.visit_movie(movie).await?), MediaCollection::MusicVideo(music_video) => { - if let Some(t) = self.visit_music_video(music_video).await? { - result.push(t) - } + result.push(self.visit_music_video(music_video).await?) } MediaCollection::Concert(concert) => { - if let Some(t) = self.visit_concert(concert).await? { - result.push(t) - } + result.push(self.visit_concert(concert).await?) } } } @@ -76,8 +439,10 @@ pub trait Filter { self.finish(result).await } +} - async fn finish(self, input: Vec<Self::T>) -> Result<Self::Output>; +fn missing_locales<'a>(available: &[Locale], searched: &'a [Locale]) -> Vec<&'a Locale> { + searched.iter().filter(|p| !available.contains(p)).collect() } /// Remove all duplicates from a [`Vec`]. diff --git a/crunchy-cli-core/src/utils/format.rs b/crunchy-cli-core/src/utils/format.rs index f0a002c..33ce261 100644 --- a/crunchy-cli-core/src/utils/format.rs +++ b/crunchy-cli-core/src/utils/format.rs @@ -12,6 +12,7 @@ use std::collections::BTreeMap; use std::env; use std::path::{Path, PathBuf}; +#[allow(dead_code)] #[derive(Clone)] pub struct SingleFormat { pub identifier: String, @@ -347,6 +348,7 @@ impl Iterator for SingleFormatCollectionIterator { } } +#[allow(dead_code)] #[derive(Clone)] pub struct Format { pub title: String, From 8047680799d88521232986e83091b06a5253022b Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 19 Jun 2024 23:18:35 +0200 Subject: [PATCH 626/630] Add drm check --- crunchy-cli-core/src/utils/video.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crunchy-cli-core/src/utils/video.rs b/crunchy-cli-core/src/utils/video.rs index 8b25791..a15296c 100644 --- a/crunchy-cli-core/src/utils/video.rs +++ b/crunchy-cli-core/src/utils/video.rs @@ -27,6 +27,11 @@ pub async fn stream_data_from_stream( } } .unwrap(); + + if videos.iter().any(|v| v.drm.is_some()) || audios.iter().any(|v| v.drm.is_some()) { + bail!("Stream is DRM protected") + } + videos.sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); audios.sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); From 509683d23a5689dc958a79440227585c9d383c30 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Wed, 19 Jun 2024 23:38:57 +0200 Subject: [PATCH 627/630] Update dependencies and version --- Cargo.lock | 12 ++++++------ Cargo.toml | 8 ++++---- crunchy-cli-core/Cargo.toml | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fdb3d9..d01a80c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,7 +349,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crunchy-cli" -version = "3.6.6" +version = "3.6.7" dependencies = [ "chrono", "clap", @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "crunchy-cli-core" -version = "3.6.6" +version = "3.6.7" dependencies = [ "anyhow", "async-speed-limit", @@ -1125,8 +1125,8 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" -source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=b7969a8#b7969a88210096e0570e29d42fb13533baf62aa6" +version = "0.2.12" +source = "git+https://github.com/crunchy-labs/rust-not-so-native-tls.git?rev=c7ac566#c7ac566559d441bbc3e5e5bd04fb7162c38d88b0" dependencies = [ "libc", "log", @@ -1519,9 +1519,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rsubs-lib" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01f7609f0b1bc4fe24b352e8d1792c7d71cc43aea797e14b87974cd009ab402" +checksum = "8c9f50e3fbcbf1f0bd109954e2dd813d1715c7b4a92a7bf159a85dea49e9d863" dependencies = [ "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index f263e59..c1e28bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli" authors = ["Crunchy Labs Maintainers"] -version = "3.6.6" +version = "3.6.7" edition = "2021" license = "MIT" @@ -14,9 +14,9 @@ openssl-tls = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli- openssl-tls-static = ["dep:native-tls-crate", "native-tls-crate/openssl", "crunchy-cli-core/openssl-tls-static"] [dependencies] -tokio = { version = "1.37", features = ["macros", "rt-multi-thread", "time"], default-features = false } +tokio = { version = "1.38", features = ["macros", "rt-multi-thread", "time"], default-features = false } -native-tls-crate = { package = "native-tls", version = "0.2.11", optional = true } +native-tls-crate = { package = "native-tls", version = "0.2.12", optional = true } crunchy-cli-core = { path = "./crunchy-cli-core" } @@ -34,7 +34,7 @@ members = ["crunchy-cli-core"] [patch.crates-io] # fork of the `native-tls` crate which can use openssl as backend on every platform. this is done as `reqwest` only # supports `rustls` and `native-tls` as tls backend -native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "b7969a8" } +native-tls = { git = "https://github.com/crunchy-labs/rust-not-so-native-tls.git", rev = "c7ac566" } [profile.release] strip = true diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5c7b901..399053f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crunchy-cli-core" authors = ["Crunchy Labs Maintainers"] -version = "3.6.6" +version = "3.6.7" edition = "2021" license = "MIT" @@ -30,7 +30,7 @@ log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } -rsubs-lib = "~0.3.1" +rsubs-lib = "~0.3.2" rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" @@ -39,7 +39,7 @@ shlex = "1.3" sys-locale = "0.3" tempfile = "3.10" time = "0.3" -tokio = { version = "1.37", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } +tokio = { version = "1.38", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } tokio-util = "0.7" tower-service = "0.3" rustls-native-certs = { version = "0.7", optional = true } From 756022b955633c73c2920091b3f02d37ab791004 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Thu, 20 Jun 2024 00:12:06 +0200 Subject: [PATCH 628/630] Fix panic when in anonymously --- crunchy-cli-core/src/utils/filter.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/crunchy-cli-core/src/utils/filter.rs b/crunchy-cli-core/src/utils/filter.rs index b95596e..3388741 100644 --- a/crunchy-cli-core/src/utils/filter.rs +++ b/crunchy-cli-core/src/utils/filter.rs @@ -171,13 +171,23 @@ impl Filter { eps.retain(|e| e.audio_locale == season_locale) } - if eps.len() < season.number_of_episodes as usize - && !(self.audios_missing)( - FilterMediaScope::Episode(vec![eps.first().unwrap(), eps.last().unwrap()]), - vec![&eps.first().unwrap().audio_locale], - )? - { - return Ok(vec![]); + #[allow(clippy::if_same_then_else)] + if eps.len() < season.number_of_episodes as usize { + if eps.is_empty() + && !(self.audios_missing)( + FilterMediaScope::Season(&season), + season.audio_locales.iter().collect(), + )? + { + return Ok(vec![]); + } else if !eps.is_empty() + && !(self.audios_missing)( + FilterMediaScope::Episode(vec![eps.first().unwrap(), eps.last().unwrap()]), + vec![&eps.first().unwrap().audio_locale], + )? + { + return Ok(vec![]); + } } episodes.extend(eps) From 2cf9125de3f42052d3fb58f5c3f7325876837c75 Mon Sep 17 00:00:00 2001 From: bytedream <bytedream@protonmail.com> Date: Mon, 1 Jul 2024 16:37:53 +0200 Subject: [PATCH 629/630] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 45b8ea7..1ae2645 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -> ~~This project has been sunset as Crunchyroll moved to a DRM-only system. See [#362](https://github.com/crunchy-labs/crunchy-cli/issues/362).~~ -> -> Well there is one endpoint which still has DRM-free streams, I guess I still have a bit time until (finally) everything is DRM-only. +# This project has been sunset as Crunchyroll moved to a DRM-only system. See [#362](https://github.com/crunchy-labs/crunchy-cli/issues/362). # crunchy-cli From 4332b1beef7007129578cc7e253c99c96b5f6e39 Mon Sep 17 00:00:00 2001 From: Simon <47527944+Frooastside@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:43:16 +0200 Subject: [PATCH 630/630] not add start time when syncing (#442) * not add start time when syncing * use itsoffset for all syncing related time shifts --- crunchy-cli-core/src/utils/download.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index accefce..2e8f321 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -322,20 +322,14 @@ impl Downloader { if let Some(offsets) = offsets { let mut root_format_idx = 0; - let mut root_format_length = 0; + let mut root_format_offset = u64::MAX; + for (i, format) in self.formats.iter().enumerate() { let offset = offsets.get(&i).copied().unwrap_or_default(); - let format_len = format - .video - .0 - .segments() - .iter() - .map(|s| s.length.as_millis()) - .sum::<u128>() as u64 - - offset.num_milliseconds() as u64; - if format_len > root_format_length { + let format_offset = offset.num_milliseconds() as u64; + if format_offset < root_format_offset { root_format_idx = i; - root_format_length = format_len; + root_format_offset = format_offset; } for _ in &format.audios { @@ -567,7 +561,7 @@ impl Downloader { for (i, meta) in videos.iter().enumerate() { if let Some(start_time) = meta.start_time { - input.extend(["-ss".to_string(), format_time_delta(&start_time)]) + input.extend(["-itsoffset".to_string(), format_time_delta(&start_time)]) } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), i.to_string()]); @@ -588,7 +582,7 @@ impl Downloader { } for (i, meta) in audios.iter().enumerate() { if let Some(start_time) = meta.start_time { - input.extend(["-ss".to_string(), format_time_delta(&start_time)]) + input.extend(["-itsoffset".to_string(), format_time_delta(&start_time)]) } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend(["-map".to_string(), (i + videos.len()).to_string()]); @@ -635,7 +629,7 @@ impl Downloader { if container_supports_softsubs { for (i, meta) in subtitles.iter().enumerate() { if let Some(start_time) = meta.start_time { - input.extend(["-ss".to_string(), format_time_delta(&start_time)]) + input.extend(["-itsoffset".to_string(), format_time_delta(&start_time)]) } input.extend(["-i".to_string(), meta.path.to_string_lossy().to_string()]); maps.extend([