diff --git a/cli/cli.go b/cli/cli.go index ef1f7c5..5030f27 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -3,7 +3,6 @@ package main import ( - "errors" "flag" "fmt" "os" @@ -50,12 +49,11 @@ var Arguments struct { Help bool `json:"-"` VideoInfo bool `json:"-"` ListFormats bool `json:"-"` - UnparsedChapterNum int `json:"chapter_num"` + ChapterNum int `json:"chapter_num"` // Parsed Video core.GtvVideo `json:"-"` StartDuration time.Duration `json:"-"` StopDuration time.Duration `json:"-"` - ChapterIdx int `json:"-"` Ratelimit float64 `json:"-"` } @@ -91,7 +89,7 @@ func CliParseArguments() error { flag.BoolVar(&Arguments.Help, "help", false, "") flag.BoolVar(&Arguments.VideoInfo, "info", false, "") flag.StringVar(&Arguments.Url, "url", "", "") - flag.IntVar(&Arguments.UnparsedChapterNum, "chapter", 0, "") // 0 -> chapter idx -1 -> complete stream + flag.IntVar(&Arguments.ChapterNum, "chapter", 0, "") // 0 -> chapter idx -1 -> complete stream flag.StringVar(&Arguments.FormatName, "format", "auto", "") flag.StringVar(&Arguments.OutputFile, "output", "", "") flag.StringVar(&Arguments.TimestampStart, "start", "", "") @@ -104,9 +102,6 @@ func CliParseArguments() error { if err != nil { return err } - if Arguments.Video.Category != "streams" { - return errors.New("video category '" + Arguments.Video.Category + "' not supported") - } if Arguments.TimestampStart == "" { Arguments.StartDuration = -1 } else { @@ -123,10 +118,9 @@ func CliParseArguments() error { return err } } - Arguments.ChapterIdx = Arguments.UnparsedChapterNum - 1 Arguments.Ratelimit = ratelimitMbs * 1_000_000.0 // MB/s -> B/s if Arguments.Ratelimit <= 0 { - return errors.New("the value of --max-rate must be greater than 0") + return &GenericCliAgumentError{Msg: "the value of --max-rate must be greater than 0"} } return err } @@ -162,13 +156,16 @@ func CliRun() int { fmt.Print("\n") fmt.Printf("Title: %s\n", streamEp.Title) // Check and list chapters/formats and exit - if Arguments.ChapterIdx >= 0 { - if Arguments.ChapterIdx >= len(streamEp.Chapters) { - CliErrorMessage(&core.ChapterNotFoundError{ChapterNum: Arguments.UnparsedChapterNum}) - CliAvailableChapters(streamEp.Chapters) - return 1 - } + targetChapter, err := streamEp.GetChapterByNumber(Arguments.ChapterNum) + if err != nil { + CliErrorMessage(err) + CliAvailableChapters(streamEp.Chapters) + return 1 } + if Arguments.ChapterNum > 0 && len(streamEp.Chapters) > 0 { + fmt.Printf("Chapter: %v. %v\n", Arguments.ChapterNum, targetChapter.Title) + } + // Video Info if Arguments.VideoInfo { fmt.Printf("Episode: %s\n", streamEp.Episode) fmt.Printf("Length: %s\n", streamEp.Length) @@ -198,22 +195,16 @@ func CliRun() int { return 1 } fmt.Printf("Format: %v\n", format.Name) - // chapter - targetChapter := core.Chapter{Index: -1} // set Index to -1 for noop - if len(streamEp.Chapters) > 0 && Arguments.ChapterIdx >= 0 { - targetChapter = streamEp.Chapters[Arguments.ChapterIdx] - fmt.Printf("Chapter: %v. %v\n", Arguments.UnparsedChapterNum, targetChapter.Title) - } // We already set the output file correctly so we can output it if Arguments.OutputFile == "" { - Arguments.OutputFile = streamEp.GetProposedFilename(Arguments.ChapterIdx) + Arguments.OutputFile = streamEp.GetProposedFilename(targetChapter) } // Start Download fmt.Printf("Output: %v\n", Arguments.OutputFile) fmt.Print("\n") successful := false aborted := false - for p := range core.DownloadEpisode( + for p := range core.DownloadStreamEpisode( streamEp, targetChapter, Arguments.FormatName, @@ -242,7 +233,7 @@ func CliRun() int { fmt.Print("\nAborted. ") return 130 } else if !successful { - CliErrorMessage(errors.New("download failed")) + CliErrorMessage(&GenericDownloadError{}) return 1 } else { return 0 } } diff --git a/cli/errors.go b/cli/errors.go new file mode 100644 index 0000000..941ac6f --- /dev/null +++ b/cli/errors.go @@ -0,0 +1,15 @@ +package main + +type GenericCliAgumentError struct { + Msg string +} + +func (err *GenericCliAgumentError) Error() string { + return err.Msg +} + +type GenericDownloadError struct {} + +func (err *GenericDownloadError) Error() string { + return "download failed" +} diff --git a/core/errors.go b/core/errors.go index b9e5240..38ce24d 100644 --- a/core/errors.go +++ b/core/errors.go @@ -35,7 +35,7 @@ type FileExistsError struct { } func (err *FileExistsError) Error() string { - return "File '" + err.Filename + "' already exists. See the available options on how to proceed." + return "file '" + err.Filename + "' already exists - see the available options on how to proceed" } type FormatNotFoundError struct { @@ -43,7 +43,7 @@ type FormatNotFoundError struct { } func (err *FormatNotFoundError) Error() string { - return "Format " + err.FormatName + " is not available." + return "format " + err.FormatName + " is not available" } type ChapterNotFoundError struct { @@ -51,5 +51,27 @@ type ChapterNotFoundError struct { } func (err *ChapterNotFoundError) Error() string { - return fmt.Sprintf("Chapter %v not found.", err.ChapterNum) + return fmt.Sprintf("chapter %v not found", err.ChapterNum) +} + +type VideoCategoryUnsupportedError struct { + Category string +} + +func (err *VideoCategoryUnsupportedError) Error() string { + return fmt.Sprintf("video category '%v' not supported", err.Category) +} + +type GtvVideoUrlParseError struct { + Url string +} + +func (err *GtvVideoUrlParseError) Error() string { + return fmt.Sprintf("Could not parse URL %v", err.Url) +} + +type DownloadInfoFileReadError struct {} + +func (err *DownloadInfoFileReadError) Error() string { + return "could not read download info file, can't continue download" } diff --git a/core/gtv_api.go b/core/gtv_api.go index 471a561..f41a9ac 100644 --- a/core/gtv_api.go +++ b/core/gtv_api.go @@ -4,7 +4,6 @@ package core import ( "encoding/json" - "errors" "fmt" "io" "iter" @@ -113,7 +112,7 @@ func GetStreamChunkList(video VideoFormat) (ChunkList, error) { return chunklist, err } -func DownloadEpisode( +func DownloadStreamEpisode( ep StreamEpisode, chapter Chapter, formatName string, @@ -128,7 +127,7 @@ func DownloadEpisode( return func (yield func(DownloadProgress) bool) { // Set automatic values if outputFile == "" { - outputFile = ep.GetProposedFilename(chapter.Index) + outputFile = ep.GetProposedFilename(chapter) } if chapter.Index >= 0 { if startDuration < 0 { @@ -167,7 +166,7 @@ func DownloadEpisode( if continueDl { infoFileData, err := os.ReadFile(infoFilename) if err != nil { - yield(DownloadProgress{Error: errors.New("could not access download info file, can't continue download")}) + yield(DownloadProgress{Error: &DownloadInfoFileReadError{}}) return } i, err := strconv.ParseInt(string(infoFileData), 10, 32) diff --git a/core/gtv_common.go b/core/gtv_common.go index 9ee0b5a..436e7d5 100644 --- a/core/gtv_common.go +++ b/core/gtv_common.go @@ -3,7 +3,6 @@ package core import ( - "errors" "fmt" "regexp" "time" @@ -40,10 +39,13 @@ func ParseGtvVideoUrl(url string) (GtvVideo, error) { video := GtvVideo{} match := videoUrlRegex.FindStringSubmatch(url) if len(match) < 2 { - return video, errors.New("Could not parse URL " + url) + return video, &GtvVideoUrlParseError{Url: url} } video.Category = match[1] video.Id = match[2] + if video.Category != "streams" { + return video, &VideoCategoryUnsupportedError{Category: video.Category} + } return video, nil } @@ -109,9 +111,21 @@ func (ep *StreamEpisode) GetFormatByName(formatName string) (VideoFormat, error) } } -func (ep *StreamEpisode) GetProposedFilename(chapterIdx int) string { - if chapterIdx >= 0 && chapterIdx < len(ep.Chapters) { - return fmt.Sprintf("GTV%04s - %v. %s.ts", ep.Episode, chapterIdx+1, sanitizeUnicodeFilename(ep.Chapters[chapterIdx].Title)) +func (ep *StreamEpisode) GetChapterByNumber(number int) (Chapter, error) { + chapter := Chapter{Index: -1} // set Index to -1 for noop + idx := number-1 + if idx >= 0 && idx >= len(ep.Chapters) { + return chapter, &ChapterNotFoundError{ChapterNum: number} + } + if len(ep.Chapters) > 0 && idx >= 0 { + chapter = ep.Chapters[idx] + } + return chapter, nil +} + +func (ep *StreamEpisode) GetProposedFilename(chapter Chapter) string { + if chapter.Index >= 0 && chapter.Index < len(ep.Chapters) { + return fmt.Sprintf("GTV%04s - %v. %s.ts", ep.Episode, chapter.Index, sanitizeUnicodeFilename(ep.Chapters[chapter.Index].Title)) } else { return sanitizeUnicodeFilename(ep.Title) + ".ts" }