mirror of
https://github.com/crunchy-labs/crunchy-cli.git
synced 2026-01-21 12:12:00 -06:00
Version 1.2.0
This commit is contained in:
parent
10a58ae932
commit
4242a2f4cf
4 changed files with 192 additions and 38 deletions
2
Makefile
2
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION=1.1.0
|
VERSION=1.2.0
|
||||||
BINARY_NAME=crunchy
|
BINARY_NAME=crunchy
|
||||||
VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION)
|
VERSION_BINARY_NAME=$(BINARY_NAME)-v$(VERSION)
|
||||||
|
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -26,6 +26,8 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http
|
||||||
•
|
•
|
||||||
<a href="#-credits">Credits 🙏</a>
|
<a href="#-credits">Credits 🙏</a>
|
||||||
•
|
•
|
||||||
|
<a href="#-notice">Notice 🗒️</a>
|
||||||
|
•
|
||||||
<a href="#-license">License ⚖</a>
|
<a href="#-license">License ⚖</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -36,9 +38,9 @@ A [Go](https://golang.org) library & cli for the undocumented [crunchyroll](http
|
||||||
|
|
||||||
#### Get the executable
|
#### Get the executable
|
||||||
- 📥 Download the latest binaries [here](https://github.com/ByteDream/crunchyroll-go/releases/latest) or get it from below
|
- 📥 Download the latest binaries [here](https://github.com/ByteDream/crunchyroll-go/releases/latest) or get it from below
|
||||||
- [Linux (x64)](https://github.com/ByteDream/crunchyroll-go/releases/download/v1.1.0/crunchy-v1.1.0_linux)
|
- [Linux (x64)](https://github.com/ByteDream/crunchyroll-go/releases/download/v1.2.0/crunchy-v1.2.0_linux)
|
||||||
- [Windows (x64)](https://github.com/ByteDream/crunchyroll-go/releases/download/v1.1.0/crunchy-v1.1.0_windows.exe)
|
- [Windows (x64)](https://github.com/ByteDream/crunchyroll-go/releases/download/v1.2.0/crunchy-v1.2.0_windows.exe)
|
||||||
- [MacOS (x64)](https://github.com/ByteDream/crunchyroll-go/releases/download/v1.1.0/crunchy-v1.1.0_darwin)
|
- [MacOS (x64)](https://github.com/ByteDream/crunchyroll-go/releases/download/v1.2.0/crunchy-v1.2.0_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/)
|
- 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
|
$ yay -S crunchyroll-go
|
||||||
|
|
@ -111,6 +113,7 @@ $ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/da
|
||||||
- `--audio` » forces audio of the video(s)
|
- `--audio` » forces audio of the video(s)
|
||||||
- `--subtitle` » forces subtitle of the video(s)
|
- `--subtitle` » forces subtitle of the video(s)
|
||||||
- `--no-hardsub` » forces that the subtitles are stored as a separate file and are not directly embedded into the video
|
- `--no-hardsub` » forces that the subtitles are stored as a separate file and are not directly embedded into the video
|
||||||
|
- `--only-sub` » downloads only the subtitles without the corresponding video
|
||||||
|
|
||||||
- `-d`, `--directory` » directory to download the video(s) to
|
- `-d`, `--directory` » directory to download the video(s) to
|
||||||
- `-o`, `--output` » name of the output file
|
- `-o`, `--output` » name of the output file
|
||||||
|
|
@ -119,6 +122,8 @@ $ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/da
|
||||||
|
|
||||||
- `--alternative-progress` » shows an alternative, not so user-friendly progress instead of the progress bar
|
- `--alternative-progress` » shows an alternative, not so user-friendly progress instead of the progress bar
|
||||||
|
|
||||||
|
- `-g`, `--goroutines` » sets how many parallel segment downloads should be used
|
||||||
|
|
||||||
#### Help
|
#### Help
|
||||||
- General help
|
- General help
|
||||||
```
|
```
|
||||||
|
|
@ -329,6 +334,10 @@ $ go test .
|
||||||
- [m3u8](https://github.com/grafov/m3u8) (not the m3u8 library from above) » mpeg stream info library
|
- [m3u8](https://github.com/grafov/m3u8) (not the m3u8 library from above) » mpeg stream info library
|
||||||
- [cobra](https://github.com/spf13/cobra) » cli library
|
- [cobra](https://github.com/spf13/cobra) » cli library
|
||||||
|
|
||||||
|
# 🗒️ Notice
|
||||||
|
|
||||||
|
I would really appreciate if someone rewrites the complete cli. I'm not satisfied with it's current structure but at the moment I have no time and no desire to do it myself.
|
||||||
|
|
||||||
# ⚖ License
|
# ⚖ License
|
||||||
|
|
||||||
This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the [LICENSE](LICENSE) file
|
This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the [LICENSE](LICENSE) file
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ var (
|
||||||
audioFlag string
|
audioFlag string
|
||||||
subtitleFlag string
|
subtitleFlag string
|
||||||
noHardsubFlag bool
|
noHardsubFlag bool
|
||||||
|
onlySubFlag bool
|
||||||
|
|
||||||
directoryFlag string
|
directoryFlag string
|
||||||
outputFlag string
|
outputFlag string
|
||||||
|
|
@ -31,6 +32,8 @@ var (
|
||||||
resolutionFlag string
|
resolutionFlag string
|
||||||
|
|
||||||
alternativeProgressFlag bool
|
alternativeProgressFlag bool
|
||||||
|
|
||||||
|
goroutinesFlag int
|
||||||
)
|
)
|
||||||
|
|
||||||
var getCmd = &cobra.Command{
|
var getCmd = &cobra.Command{
|
||||||
|
|
@ -67,6 +70,7 @@ func init() {
|
||||||
getCmd.Flags().StringVarP(&audioFlag, "audio", "a", "", "The locale of the audio. Available locales: "+strings.Join(allLocalesAsStrings(), ", "))
|
getCmd.Flags().StringVarP(&audioFlag, "audio", "a", "", "The locale of the audio. Available locales: "+strings.Join(allLocalesAsStrings(), ", "))
|
||||||
getCmd.Flags().StringVarP(&subtitleFlag, "subtitle", "s", "", "The locale of the subtitle. Available locales: "+strings.Join(allLocalesAsStrings(), ", "))
|
getCmd.Flags().StringVarP(&subtitleFlag, "subtitle", "s", "", "The locale of the subtitle. Available locales: "+strings.Join(allLocalesAsStrings(), ", "))
|
||||||
getCmd.Flags().BoolVar(&noHardsubFlag, "no-hardsub", false, "Same as '-s', but the subtitles are not stored in the video itself, but in a separate file")
|
getCmd.Flags().BoolVar(&noHardsubFlag, "no-hardsub", false, "Same as '-s', but the subtitles are not stored in the video itself, but in a separate file")
|
||||||
|
getCmd.Flags().BoolVar(&onlySubFlag, "only-sub", false, "Downloads only the subtitles without the corresponding video")
|
||||||
|
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
getCmd.Flags().StringVarP(&directoryFlag, "directory", "d", cwd, "The directory to download the file to")
|
getCmd.Flags().StringVarP(&directoryFlag, "directory", "d", cwd, "The directory to download the file to")
|
||||||
|
|
@ -87,15 +91,19 @@ func init() {
|
||||||
"\tAvailable common-use words: best (best available resolution), worst (worst available resolution)\n")
|
"\tAvailable common-use words: best (best available resolution), worst (worst available resolution)\n")
|
||||||
|
|
||||||
getCmd.Flags().BoolVar(&alternativeProgressFlag, "alternative-progress", false, "Shows an alternative, not so user-friendly progress instead of the progress bar")
|
getCmd.Flags().BoolVar(&alternativeProgressFlag, "alternative-progress", false, "Shows an alternative, not so user-friendly progress instead of the progress bar")
|
||||||
|
|
||||||
|
// TODO: Rename this to something understandable (for "normal" users)
|
||||||
|
getCmd.Flags().IntVarP(&goroutinesFlag, "goroutines", "g", 4, "Sets how many parallel segment downloads should be used")
|
||||||
}
|
}
|
||||||
|
|
||||||
type episodeInformation struct {
|
type episodeInformation struct {
|
||||||
Format *crunchyroll.Format
|
Format *crunchyroll.Format
|
||||||
Title string
|
Title string
|
||||||
URL string
|
URL string
|
||||||
SeriesTitle string
|
SeriesTitle string
|
||||||
SeasonNum int
|
SeasonNum int
|
||||||
EpisodeNum int
|
EpisodeNum int
|
||||||
|
AllSubtitles []*crunchyroll.Subtitle
|
||||||
}
|
}
|
||||||
|
|
||||||
type information struct {
|
type information struct {
|
||||||
|
|
@ -113,9 +121,23 @@ type information struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func download(urls []string) {
|
func download(urls []string) {
|
||||||
if path.Ext(outputFlag) != ".ts" && !hasFFmpeg() {
|
switch path.Ext(outputFlag) {
|
||||||
out.Fatalf("The file ending for the output file (%s) is not `.ts`. "+
|
case ".ts":
|
||||||
"Install ffmpeg (https://ffmpeg.org/download.html) use other media file endings (e.g. `.mp4`)\n", outputFlag)
|
// checks if only subtitles should be downloaded and if so, if the output flag has the default value
|
||||||
|
if onlySubFlag && outputFlag == "{title}.ts" {
|
||||||
|
outputFlag = "{title}.ass"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case ".ass":
|
||||||
|
if !onlySubFlag {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if !hasFFmpeg() {
|
||||||
|
out.Fatalf("The file ending for the output file (%s) is not `.ts`. "+
|
||||||
|
"Install ffmpeg (https://ffmpeg.org/download.html) use other media file endings (e.g. `.mp4`)\n", outputFlag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
allEpisodes, total, successes := parseURLs(urls)
|
allEpisodes, total, successes := parseURLs(urls)
|
||||||
out.Infof("%d of %d episodes could be parsed\n", successes, total)
|
out.Infof("%d of %d episodes could be parsed\n", successes, total)
|
||||||
|
|
@ -124,12 +146,22 @@ func download(urls []string) {
|
||||||
if len(allEpisodes) == 0 {
|
if len(allEpisodes) == 0 {
|
||||||
out.Fatalf("Nothing to download, aborting\n")
|
out.Fatalf("Nothing to download, aborting\n")
|
||||||
}
|
}
|
||||||
out.Infof("Downloads:")
|
if onlySubFlag {
|
||||||
|
out.Infof("Downloads (only subtitles):")
|
||||||
|
} else {
|
||||||
|
out.Infof("Downloads:")
|
||||||
|
}
|
||||||
for i, episode := range allEpisodes {
|
for i, episode := range allEpisodes {
|
||||||
video := episode.Format.Video
|
video := episode.Format.Video
|
||||||
out.Infof("\t%d. %s » %spx, %.2f FPS, %s audio (%s S%02dE%02d)\n",
|
if onlySubFlag && subtitleFlag == "" {
|
||||||
i+1, episode.Title, video.Resolution, video.FrameRate, utils.LocaleLanguage(episode.Format.AudioLocale), episode.SeriesTitle, episode.SeasonNum, episode.EpisodeNum)
|
out.Infof("\t%d. %s » %spx, %.2f FPS (%s S%02dE%02d)\n",
|
||||||
|
i+1, episode.Title, video.Resolution, video.FrameRate, episode.SeriesTitle, episode.SeasonNum, episode.EpisodeNum)
|
||||||
|
} else {
|
||||||
|
out.Infof("\t%d. %s » %spx, %.2f FPS, %s audio (%s S%02dE%02d)\n",
|
||||||
|
i+1, episode.Title, video.Resolution, video.FrameRate, utils.LocaleLanguage(episode.Format.AudioLocale), episode.SeriesTitle, episode.SeasonNum, episode.EpisodeNum)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
out.Empty()
|
||||||
|
|
||||||
if fileInfo, stat := os.Stat(directoryFlag); os.IsNotExist(stat) {
|
if fileInfo, stat := os.Stat(directoryFlag); os.IsNotExist(stat) {
|
||||||
if err := os.MkdirAll(directoryFlag, 0777); err != nil {
|
if err := os.MkdirAll(directoryFlag, 0777); err != nil {
|
||||||
|
|
@ -195,13 +227,63 @@ func download(urls []string) {
|
||||||
filename = strings.ReplaceAll(filename, char, "")
|
filename = strings.ReplaceAll(filename, char, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Empty()
|
if onlySubFlag {
|
||||||
if downloadFormat(episode.Format, filename, info) {
|
var found bool
|
||||||
success++
|
if subtitleFlag == "" {
|
||||||
|
for _, formatSubtitle := range episode.AllSubtitles {
|
||||||
|
ext := path.Ext(filename)
|
||||||
|
base := strings.TrimSuffix(filename, ext)
|
||||||
|
|
||||||
|
originalSubtitleFilename := fmt.Sprintf("%s_%s%s", base, formatSubtitle.Locale, ext)
|
||||||
|
subtitleFilename, changed := freeFileName(originalSubtitleFilename)
|
||||||
|
if changed {
|
||||||
|
out.Infof("The file %s already exist, renaming the download file to %s", originalSubtitleFilename, subtitleFilename)
|
||||||
|
}
|
||||||
|
file, err := os.Create(subtitleFilename)
|
||||||
|
if err != nil {
|
||||||
|
out.Errf("Failed to open subtitle file for locale %s: %v", formatSubtitle.Locale, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = formatSubtitle.Download(file); err != nil {
|
||||||
|
out.Errf("Error while downloading %s subtitles: %s", formatSubtitle.Locale, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, formatSubtitle := range episode.Format.Subtitles {
|
||||||
|
if formatSubtitle.Locale == subtitle {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
out.Errf("Failed to open file %s: %v", filename, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err = formatSubtitle.Download(file); err != nil {
|
||||||
|
out.Errf("Error while downloading subtitles: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
out.Infof("Downloaded subtitles for %s", episode.Title)
|
||||||
|
success++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if downloadFormat(episode.Format, episode.AllSubtitles, filename, info) {
|
||||||
|
success++
|
||||||
|
}
|
||||||
|
out.Empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Infof("Downloaded %d out of %d videos\n", success, len(allEpisodes))
|
if onlySubFlag {
|
||||||
|
out.Infof("Downloaded all %d out of %d video subtitles\n", success, len(allEpisodes))
|
||||||
|
} else {
|
||||||
|
out.Infof("Downloaded %d out of %d videos\n", success, len(allEpisodes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseURLs(urls []string) (allEpisodes []episodeInformation, total, successes int) {
|
func parseURLs(urls []string) (allEpisodes []episodeInformation, total, successes int) {
|
||||||
|
|
@ -259,7 +341,7 @@ func parseURLs(urls []string) (allEpisodes []episodeInformation, total, successe
|
||||||
allEpisodes = append(allEpisodes, parsed...)
|
allEpisodes = append(allEpisodes, parsed...)
|
||||||
} else if _, _, ok = crunchyroll.MatchEpisode(url); ok {
|
} else if _, _, ok = crunchyroll.MatchEpisode(url); ok {
|
||||||
out.Debugf("Parsed url %d as episode\n", i+1)
|
out.Debugf("Parsed url %d as episode\n", i+1)
|
||||||
if episode := parseEpisodes(dupe.(*utils.EpisodeStructure), url); (episodeInformation{}) != episode {
|
if episode := parseEpisodes(dupe.(*utils.EpisodeStructure), url); episode.Format != nil {
|
||||||
allEpisodes = append(allEpisodes, episode)
|
allEpisodes = append(allEpisodes, episode)
|
||||||
localSuccesses++
|
localSuccesses++
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -325,6 +407,13 @@ func parseVideo(videoStructure utils.VideoStructure, url string) (episodeInforma
|
||||||
info.SeasonNum, info.EpisodeNum = 1, 1
|
info.SeasonNum, info.EpisodeNum = 1, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, audioFormat := range formats {
|
||||||
|
if audioFormat.AudioLocale == crunchyroll.JP {
|
||||||
|
info.AllSubtitles = audioFormat.Subtitles
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
episodeInformations = append(episodeInformations, info)
|
episodeInformations = append(episodeInformations, info)
|
||||||
out.Debugf("Successful parsed %s\n", title)
|
out.Debugf("Successful parsed %s\n", title)
|
||||||
}
|
}
|
||||||
|
|
@ -338,18 +427,27 @@ func parseEpisodes(episodeStructure *utils.EpisodeStructure, url string) episode
|
||||||
episode, _ := episodeStructure.GetEpisodeByURL(url)
|
episode, _ := episodeStructure.GetEpisodeByURL(url)
|
||||||
ordered, _ := episodeStructure.OrderFormatsByEpisodeNumber()
|
ordered, _ := episodeStructure.OrderFormatsByEpisodeNumber()
|
||||||
|
|
||||||
|
var subtitles []*crunchyroll.Subtitle
|
||||||
formats := ordered[episode.EpisodeNumber]
|
formats := ordered[episode.EpisodeNumber]
|
||||||
|
for _, format := range formats {
|
||||||
|
if format.AudioLocale == crunchyroll.JP {
|
||||||
|
subtitles = format.Subtitles
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out.Debugf("Found %d formats\n", len(formats))
|
out.Debugf("Found %d formats\n", len(formats))
|
||||||
if format := findFormat(formats, episode.Title); format != nil {
|
if format := findFormat(formats, episode.Title); format != nil {
|
||||||
episode, _ = episodeStructure.GetEpisodeByFormat(format)
|
episode, _ = episodeStructure.GetEpisodeByFormat(format)
|
||||||
out.Debugf("Found matching episode %s\n", episode.Title)
|
out.Debugf("Found matching episode %s\n", episode.Title)
|
||||||
return episodeInformation{
|
return episodeInformation{
|
||||||
Format: format,
|
Format: format,
|
||||||
Title: episode.Title,
|
AllSubtitles: subtitles,
|
||||||
URL: url,
|
Title: episode.Title,
|
||||||
SeriesTitle: episode.SeriesTitle,
|
URL: url,
|
||||||
SeasonNum: episode.SeasonNumber,
|
SeriesTitle: episode.SeriesTitle,
|
||||||
EpisodeNum: episode.EpisodeNumber,
|
SeasonNum: episode.SeasonNumber,
|
||||||
|
EpisodeNum: episode.EpisodeNumber,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return episodeInformation{}
|
return episodeInformation{}
|
||||||
|
|
@ -357,6 +455,13 @@ func parseEpisodes(episodeStructure *utils.EpisodeStructure, url string) episode
|
||||||
|
|
||||||
func findFormat(formats []*crunchyroll.Format, name string) (format *crunchyroll.Format) {
|
func findFormat(formats []*crunchyroll.Format, name string) (format *crunchyroll.Format) {
|
||||||
formatStructure := utils.NewFormatStructure(formats)
|
formatStructure := utils.NewFormatStructure(formats)
|
||||||
|
|
||||||
|
// if the only sub flag is given the japanese format gets returned because it has all subtitles available
|
||||||
|
if onlySubFlag {
|
||||||
|
jpFormat, _ := formatStructure.FilterFormatsByAudio(crunchyroll.JP)
|
||||||
|
return jpFormat[0]
|
||||||
|
}
|
||||||
|
|
||||||
var audioLocale, subtitleLocale crunchyroll.LOCALE
|
var audioLocale, subtitleLocale crunchyroll.LOCALE
|
||||||
|
|
||||||
if audioFlag != "" {
|
if audioFlag != "" {
|
||||||
|
|
@ -414,7 +519,7 @@ func findFormat(formats []*crunchyroll.Format, name string) (format *crunchyroll
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadFormat(format *crunchyroll.Format, outFile string, info information) bool {
|
func downloadFormat(format *crunchyroll.Format, subtitles []*crunchyroll.Subtitle, outFile string, info information) bool {
|
||||||
oldOutFile := outFile
|
oldOutFile := outFile
|
||||||
outFile, changed := freeFileName(outFile)
|
outFile, changed := freeFileName(outFile)
|
||||||
ext := path.Ext(outFile)
|
ext := path.Ext(outFile)
|
||||||
|
|
@ -451,27 +556,29 @@ func downloadFormat(format *crunchyroll.Format, outFile string, info information
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if ext == ".ts" {
|
if ext == ".ts" {
|
||||||
file, err := os.Create(outFile)
|
var file *os.File
|
||||||
|
file, err = os.Create(outFile)
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out.Errf("Could not create file '%s' to download episode '%s' (%s): %s, skipping\n", outFile, info.Title, info.OriginalURL, err)
|
out.Errf("Could not create file '%s' to download episode '%s' (%s): %s, skipping\n", outFile, info.Title, info.OriginalURL, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = format.Download(file, downloadProgress)
|
err = format.DownloadGoroutines(file, goroutinesFlag, downloadProgress)
|
||||||
// newline to avoid weird output
|
// newline to avoid weird output
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
} else {
|
} else {
|
||||||
tempDir, err := os.MkdirTemp("", "crunchy_")
|
var tempDir string
|
||||||
|
tempDir, err = os.MkdirTemp("", "crunchy_")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out.Errln("Failed to create temp download dir. Skipping")
|
out.Errln("Failed to create temp download dir. Skipping")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var segmentCount int
|
var segmentCount int
|
||||||
err = format.DownloadSegments(tempDir, 4, func(segment *m3u8.MediaSegment, current, total int, file *os.File, err error) error {
|
err = format.DownloadSegments(tempDir, goroutinesFlag, func(segment *m3u8.MediaSegment, current, total int, file *os.File) error {
|
||||||
segmentCount++
|
segmentCount++
|
||||||
return downloadProgress(segment, current, total, file, err)
|
return downloadProgress(segment, current, total, file)
|
||||||
})
|
})
|
||||||
// newline to avoid weird output
|
// newline to avoid weird output
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
@ -483,19 +590,49 @@ func downloadFormat(format *crunchyroll.Format, outFile string, info information
|
||||||
defer os.Remove(f.Name())
|
defer os.Remove(f.Name())
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
cmd := exec.Command("ffmpeg",
|
args := []string{
|
||||||
"-f", "concat",
|
"-f", "concat",
|
||||||
"-safe", "0",
|
"-safe", "0",
|
||||||
"-i", f.Name(),
|
"-i", f.Name(),
|
||||||
"-c", "copy",
|
}
|
||||||
outFile)
|
if ext == ".mkv" && subtitleFlag == "" {
|
||||||
|
// this saves all subtitles into a mkv file. see https://github.com/ByteDream/crunchyroll-go/issues/5 for some details
|
||||||
|
|
||||||
|
ffmpegInput := make([]string, 0)
|
||||||
|
ffmpegMap := []string{"-map", "0"}
|
||||||
|
ffmpegMetadata := make([]string, 0)
|
||||||
|
for i, subtitle := range subtitles {
|
||||||
|
subtitleFilepath := filepath.Join(cleanupPath, fmt.Sprintf("%s.%s", subtitle.Locale, subtitle.Format))
|
||||||
|
|
||||||
|
var file *os.File
|
||||||
|
file, err = os.Create(subtitleFilepath)
|
||||||
|
if err != nil {
|
||||||
|
out.Errf("Could not create file to download %s subtitles to: %v", subtitle.Locale, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = subtitle.Download(file); err != nil {
|
||||||
|
out.Errf("Failed to download subtitles: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ffmpegInput = append(ffmpegInput, "-i", subtitleFilepath)
|
||||||
|
ffmpegMap = append(ffmpegMap, "-map", strconv.Itoa(i+1))
|
||||||
|
ffmpegMetadata = append(ffmpegMetadata, fmt.Sprintf("-metadata:s:s:%d", i), fmt.Sprintf("language=%s", strings.Split(string(subtitle.Locale), "-")[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, ffmpegInput...)
|
||||||
|
args = append(args, ffmpegMap...)
|
||||||
|
args = append(args, ffmpegMetadata...)
|
||||||
|
}
|
||||||
|
args = append(args, "-c", "copy", outFile)
|
||||||
|
|
||||||
|
cmd := exec.Command("ffmpeg", args...)
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
}
|
}
|
||||||
os.RemoveAll(cleanupPath)
|
os.RemoveAll(cleanupPath)
|
||||||
cleanupPath = ""
|
cleanupPath = ""
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out.Errln("Failed to download video, skipping")
|
out.Errf("Failed to download video, skipping: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if info.Subtitle == "" {
|
if info.Subtitle == "" {
|
||||||
out.Infof("Downloaded '%s' as '%s' with %s audio locale\n", info.Title, outFile, strings.ToLower(utils.LocaleLanguage(info.Audio)))
|
out.Infof("Downloaded '%s' as '%s' with %s audio locale\n", info.Title, outFile, strings.ToLower(utils.LocaleLanguage(info.Audio)))
|
||||||
|
|
@ -525,7 +662,7 @@ func downloadFormat(format *crunchyroll.Format, outFile string, info information
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadProgress(segment *m3u8.MediaSegment, current, total int, file *os.File, err error) error {
|
func downloadProgress(segment *m3u8.MediaSegment, current, total int, file *os.File) error {
|
||||||
if cleanupPath == "" {
|
if cleanupPath == "" {
|
||||||
cleanupPath = path.Dir(file.Name())
|
cleanupPath = path.Dir(file.Name())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,10 @@ The directory to download all files to.
|
||||||
Same as '-s', but the subtitles are not stored in the video itself, but in a separate file.
|
Same as '-s', but the subtitles are not stored in the video itself, but in a separate file.
|
||||||
.TP
|
.TP
|
||||||
|
|
||||||
|
\fB--only-sub\fR
|
||||||
|
Downloads only the subtitles without the corresponding video.
|
||||||
|
.TP
|
||||||
|
|
||||||
\fB-o, --output OUTPUT\fR
|
\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.
|
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.
|
{title} » Title of the video.
|
||||||
|
|
@ -91,6 +95,10 @@ The video resolution. Can either be specified via the pixels (e.g. 1920x1080), t
|
||||||
|
|
||||||
\fB-s, --subtitle LOCALE\fR
|
\fB-s, --subtitle LOCALE\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-LA, es-ES, fr-FR, pt-BR, it-IT, de-DE, ru-RU, ar-ME.
|
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-LA, es-ES, fr-FR, pt-BR, it-IT, de-DE, ru-RU, ar-ME.
|
||||||
|
.TP
|
||||||
|
|
||||||
|
\fB-g, --goroutines GOROUTINES\fR
|
||||||
|
Sets the number of parallel downloads for the segments the final video is made of.
|
||||||
|
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
Login via crunchyroll account email and password.
|
Login via crunchyroll account email and password.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue