From 2f0542eaed0c5be6b7c714301ff4d2aeb6b201ab Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Sun, 9 Mar 2025 20:26:44 +0100 Subject: [PATCH 1/6] Remove windows support --- README.md | 10 +++++----- VERSION | 2 +- release-cli.sh | 3 --- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ab8c727..766d603 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Definetly not a commandline downloader for https://gronkh.tv risen from the dead ## Supported Platforms -Tested on Linux and Windows (64bit). +Only compatible with Linux. ## Download / Installation @@ -39,7 +39,7 @@ Run `lurch-dl --help` to see available options. Download a video in its best available format: ``` -./lurch-dl.exe --url https://gronkh.tv/streams/777 +./lurch-dl --url https://gronkh.tv/streams/777 Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND ... Format: 1080p60 @@ -51,13 +51,13 @@ Downloaded 0.32% at 10.00 MB/s ... Continue a download: ``` -./lurch-dl.exe --url https://gronkh.tv/streams/777 --continue +./lurch-dl --url https://gronkh.tv/streams/777 --continue ``` Download a specific chapter: ``` -./lurch-dl.exe --url https://gronkh.tv/streams/777 --chapter 2 +./lurch-dl --url https://gronkh.tv/streams/777 --chapter 2 Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND ... Format: 1080p60 @@ -104,7 +104,7 @@ Format: 720p Specify a filename: ``` -./lurch-dl.exe --url https://gronkh.tv/streams/777 --output Stream777.ts +./lurch-dl --url https://gronkh.tv/streams/777 --output Stream777.ts ``` diff --git a/VERSION b/VERSION index 50aea0e..7c32728 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0 \ No newline at end of file +2.1.1 \ No newline at end of file diff --git a/release-cli.sh b/release-cli.sh index 24af3d6..04f6958 100755 --- a/release-cli.sh +++ b/release-cli.sh @@ -17,9 +17,6 @@ NAME_BASE="lurch-dl_v${VERSION}" echo "Building ${NAME_BASE} into ${OUTPUT_DIR}" -GOOS=windows GOARCH=386 OUTPUT_FILE=${NAME_BASE}_32bit.exe gobuild -GOOS=windows GOARCH=amd64 OUTPUT_FILE=${NAME_BASE}_64bit.exe gobuild -GOOS=windows GOARCH=arm64 OUTPUT_FILE=${NAME_BASE}_arm64.exe gobuild GOOS=linux GOARCH=386 OUTPUT_FILE=${NAME_BASE}_linux_i386 gobuild GOOS=linux GOARCH=amd64 OUTPUT_FILE=${NAME_BASE}_linux_amd64 gobuild GOOS=linux GOARCH=arm OUTPUT_FILE=${NAME_BASE}_linux_arm gobuild From 38ec0729ba6c5dfb867343707a7444947d57eeb0 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Sun, 9 Mar 2025 20:29:12 +0100 Subject: [PATCH 2/6] Update README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 766d603..4f3c09d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Definetly not a commandline downloader for https://gronkh.tv risen from the dead - Downloads are capped to 10 Mbyte/s and buffering is simulated to pre-empt IP blocking due to API ratelimiting - Start- and stop-timestamps are not very accurate (± 8 seconds) - Some videoplayers may have problems with the resulting file. To fix this, you can use ffmpeg to rewrite the video into a MKV-File: `ffmpeg -i video.ts -acodec copy -vcodec copy video.mkv` -- Emojis and other Unicode characters don't get displayed properly in a Powershell Console ## Supported Platforms From 424e912f6c4e6579c27788e7beb37edcfc72ef36 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Fri, 14 Mar 2025 19:47:23 +0100 Subject: [PATCH 3/6] Remove the usage of custom types where it isn't required --- cli/cli.go | 128 ++++++++++++++++++++++------------------------ core/gtv_api.go | 22 +++++--- core/interface.go | 13 ----- 3 files changed, 77 insertions(+), 86 deletions(-) delete mode 100644 core/interface.go diff --git a/cli/cli.go b/cli/cli.go index eb0b1d8..ef1f7c5 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -38,7 +38,7 @@ func XtermSetTitle(title string) { // Commandline -type Arguments struct { +var Arguments struct { Url string `json:"url"` FormatName string `json:"format_name"` OutputFile string `json:"output_file"` @@ -84,96 +84,92 @@ lurch-dl --url string The url to the video Version: ` + core.Version) } -func CliParseArguments() (Arguments, error) { +func CliParseArguments() error { var err error var ratelimitMbs float64 - a := Arguments{} - flag.BoolVar(&a.Help, "h", false, "") - flag.BoolVar(&a.Help, "help", false, "") - flag.BoolVar(&a.VideoInfo, "info", false, "") - flag.StringVar(&a.Url, "url", "", "") - flag.IntVar(&a.UnparsedChapterNum, "chapter", 0, "") // 0 -> chapter idx -1 -> complete stream - flag.StringVar(&a.FormatName, "format", "auto", "") - flag.StringVar(&a.OutputFile, "output", "", "") - flag.StringVar(&a.TimestampStart, "start", "", "") - flag.StringVar(&a.TimestampStop, "stop", "", "") - flag.BoolVar(&a.Overwrite, "overwrite", false, "") - flag.BoolVar(&a.ContinueDl, "continue", false, "") + flag.BoolVar(&Arguments.Help, "h", false, "") + 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.StringVar(&Arguments.FormatName, "format", "auto", "") + flag.StringVar(&Arguments.OutputFile, "output", "", "") + flag.StringVar(&Arguments.TimestampStart, "start", "", "") + flag.StringVar(&Arguments.TimestampStop, "stop", "", "") + flag.BoolVar(&Arguments.Overwrite, "overwrite", false, "") + flag.BoolVar(&Arguments.ContinueDl, "continue", false, "") flag.Float64Var(&ratelimitMbs, "max-rate", 10.0, "") flag.Parse() - a.Video, err = core.ParseGtvVideoUrl(a.Url) + Arguments.Video, err = core.ParseGtvVideoUrl(Arguments.Url) if err != nil { - return a, err + return err } - if a.Video.Category != "streams" { - return a, errors.New("video category '" + a.Video.Category + "' not supported") + if Arguments.Video.Category != "streams" { + return errors.New("video category '" + Arguments.Video.Category + "' not supported") } - if a.TimestampStart == "" { - a.StartDuration = -1 + if Arguments.TimestampStart == "" { + Arguments.StartDuration = -1 } else { - a.StartDuration, err = time.ParseDuration(a.TimestampStart) + Arguments.StartDuration, err = time.ParseDuration(Arguments.TimestampStart) if err != nil { - return a, err + return err } } - if a.TimestampStop == "" { - a.StopDuration = -1 + if Arguments.TimestampStop == "" { + Arguments.StopDuration = -1 } else { - a.StopDuration, err = time.ParseDuration(a.TimestampStop) + Arguments.StopDuration, err = time.ParseDuration(Arguments.TimestampStop) if err != nil { - return a, err + return err } } - a.ChapterIdx = a.UnparsedChapterNum - 1 - a.Ratelimit = ratelimitMbs * 1_000_000.0 // MB/s -> B/s - if a.Ratelimit <= 0 { - return a, errors.New("the value of --max-rate must be greater than 0") + 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 a, err + return err } // Main func CliRun() int { - cli := Cli{} defer fmt.Print("\n") // cli arguments & help text flag.Usage = CliShowHelp - args, err := CliParseArguments() - if args.Help { + err := CliParseArguments() + if Arguments.Help { CliShowHelp() return 0 - } else if args.Url == "" || err != nil { + } else if Arguments.Url == "" || err != nil { CliShowHelp() if err != nil { - cli.ErrorMessage(err) + CliErrorMessage(err) } return 1 } // detect terminal features XtermDetectFeatures() - // - api := core.GtvApi{}; // Get video metadata if CliXtermTitle { XtermSetTitle("lurch-dl - Fetching video metadata ...") } - streamEp, err := api.GetStreamEpisode(args.Video.Id) + streamEp, err := core.GetStreamEpisode(Arguments.Video.Id) if err != nil { - cli.ErrorMessage(err) + CliErrorMessage(err) return 1 } fmt.Print("\n") fmt.Printf("Title: %s\n", streamEp.Title) // Check and list chapters/formats and exit - if args.ChapterIdx >= 0 { - if args.ChapterIdx >= len(streamEp.Chapters) { - cli.ErrorMessage(&core.ChapterNotFoundError{ChapterNum: args.UnparsedChapterNum}) + if Arguments.ChapterIdx >= 0 { + if Arguments.ChapterIdx >= len(streamEp.Chapters) { + CliErrorMessage(&core.ChapterNotFoundError{ChapterNum: Arguments.UnparsedChapterNum}) CliAvailableChapters(streamEp.Chapters) return 1 } } - if args.VideoInfo { + if Arguments.VideoInfo { fmt.Printf("Episode: %s\n", streamEp.Episode) fmt.Printf("Length: %s\n", streamEp.Length) fmt.Printf("Views: %d\n", streamEp.Views) @@ -195,42 +191,42 @@ func CliRun() int { CliAvailableChapters(streamEp.Chapters) return 0 } - format, err := streamEp.GetFormatByName(args.FormatName) + format, err := streamEp.GetFormatByName(Arguments.FormatName) if err != nil { - cli.ErrorMessage(err) + CliErrorMessage(err) CliAvailableFormats(streamEp.Formats) 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 && args.ChapterIdx >= 0 { - targetChapter = streamEp.Chapters[args.ChapterIdx] - fmt.Printf("Chapter: %v. %v\n", args.UnparsedChapterNum, targetChapter.Title) + 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 args.OutputFile == "" { - args.OutputFile = streamEp.GetProposedFilename(args.ChapterIdx) + if Arguments.OutputFile == "" { + Arguments.OutputFile = streamEp.GetProposedFilename(Arguments.ChapterIdx) } // Start Download - fmt.Printf("Output: %v\n", args.OutputFile) + fmt.Printf("Output: %v\n", Arguments.OutputFile) fmt.Print("\n") successful := false aborted := false - for p := range api.DownloadEpisode( + for p := range core.DownloadEpisode( streamEp, targetChapter, - args.FormatName, - args.OutputFile, - args.Overwrite, - args.ContinueDl, - args.StartDuration, - args.StopDuration, - args.Ratelimit, + Arguments.FormatName, + Arguments.OutputFile, + Arguments.Overwrite, + Arguments.ContinueDl, + Arguments.StartDuration, + Arguments.StopDuration, + Arguments.Ratelimit, make(chan os.Signal, 1), ) { // Iterate over download progress if p.Error != nil { - cli.ErrorMessage(p.Error) + CliErrorMessage(p.Error) return 1 } if p.Success { @@ -238,7 +234,7 @@ func CliRun() int { } else if p.Aborted { aborted = true } else { - cli.DownloadProgress(p.Progress, p.Rate, p.Delaying, p.Waiting, p.Retries, p.Title) + CliDownloadProgress(p.Progress, p.Rate, p.Delaying, p.Waiting, p.Retries, p.Title) } } fmt.Print("\n") @@ -246,7 +242,7 @@ func CliRun() int { fmt.Print("\nAborted. ") return 130 } else if !successful { - cli.ErrorMessage(errors.New("download failed")) + CliErrorMessage(errors.New("download failed")) return 1 } else { return 0 } } @@ -270,9 +266,7 @@ func CliAvailableFormats(formats []core.VideoFormat) { fmt.Print("\n") } -type Cli struct{} - -func (cli *Cli) DownloadProgress(progress float32, rate float64, delaying bool, waiting bool, retries int, title string) { +func CliDownloadProgress(progress float32, rate float64, delaying bool, waiting bool, retries int, title string) { if retries > 0 { if retries == 1 { fmt.Print("\n") @@ -291,7 +285,7 @@ func (cli *Cli) DownloadProgress(progress float32, rate float64, delaying bool, } } -func (cli *Cli) ErrorMessage(err error) { +func CliErrorMessage(err error) { fmt.Print("\n") fmt.Println("An error occured:", err) } diff --git a/core/gtv_api.go b/core/gtv_api.go index b77a1d4..471a561 100644 --- a/core/gtv_api.go +++ b/core/gtv_api.go @@ -25,6 +25,18 @@ const RatelimitDelayAfter = 5.0 // in Seconds; Delay the next chunk download aft const ApiBaseurlStreamEpisodeInfo = "https://api.gronkh.tv/v1/video/info?episode=%s" const ApiBaseurlStreamEpisodePlInfo = "https://api.gronkh.tv/v1/video/playlist?episode=%s" +type DownloadProgress struct { + Aborted bool + Error error + Success bool + Delaying bool + Progress float32 + Rate float64 + Retries int + Title string + Waiting bool +} + var ApiHeadersBase = http.Header{ "User-Agent": {"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0"}, "Accept-Language": {"de,en-US;q=0.7,en;q=0.3"}, @@ -48,9 +60,7 @@ var ApiHeadersVideoAdditional = http.Header{ "Accept": {"*/*"}, } -type GtvApi struct{} - -func (api *GtvApi) GetStreamEpisode(episode string) (StreamEpisode, error) { +func GetStreamEpisode(episode string) (StreamEpisode, error) { ep := StreamEpisode{} ep.Episode = episode info_data, err := httpGet( @@ -93,7 +103,7 @@ func (api *GtvApi) GetStreamEpisode(episode string) (StreamEpisode, error) { return ep, err } -func (api *GtvApi) GetStreamChunkList(video VideoFormat) (ChunkList, error) { +func GetStreamChunkList(video VideoFormat) (ChunkList, error) { baseUrl := video.Url[:strings.LastIndex(video.Url, "/")] data, err := httpGet(video.Url, []http.Header{ApiHeadersBase, ApiHeadersMetaAdditional}, time.Second*10) if err != nil { @@ -103,7 +113,7 @@ func (api *GtvApi) GetStreamChunkList(video VideoFormat) (ChunkList, error) { return chunklist, err } -func (api *GtvApi) DownloadEpisode( +func DownloadEpisode( ep StreamEpisode, chapter Chapter, formatName string, @@ -181,7 +191,7 @@ func (api *GtvApi) DownloadEpisode( } // download format, _ := ep.GetFormatByName(formatName) // we don't have to check the error, as it was already checked by CliRun() - chunklist, err := api.GetStreamChunkList(format) + chunklist, err := GetStreamChunkList(format) chunklist = chunklist.Cut(startDuration, stopDuration) if err != nil { yield(DownloadProgress{Error: err}) diff --git a/core/interface.go b/core/interface.go deleted file mode 100644 index 26bb1a2..0000000 --- a/core/interface.go +++ /dev/null @@ -1,13 +0,0 @@ -package core - -type DownloadProgress struct { - Aborted bool - Error error - Success bool - Delaying bool - Progress float32 - Rate float64 - Retries int - Title string - Waiting bool -} From ee3518ab5c268f32bad62911295f44f3cd7a207d Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Fri, 14 Mar 2025 23:20:47 +0100 Subject: [PATCH 4/6] Refactoring, added remaining custom errors --- cli/cli.go | 39 +++++++++++++++------------------------ cli/errors.go | 15 +++++++++++++++ core/errors.go | 28 +++++++++++++++++++++++++--- core/gtv_api.go | 7 +++---- core/gtv_common.go | 24 +++++++++++++++++++----- 5 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 cli/errors.go 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" } From 727575ff2eec5a9a67aa282a5142b40e39c440db Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Sat, 15 Mar 2025 17:13:38 +0100 Subject: [PATCH 5/6] Bump version to 2.1.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7c32728..8f9174b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.1 \ No newline at end of file +2.1.2 \ No newline at end of file From 965c30c5011f6d334806fff8db96e691d8315fda Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Tue, 5 Aug 2025 21:05:08 +0200 Subject: [PATCH 6/6] Restructure and rewrite some parts of the README --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4f3c09d..f15d89b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -
what could it be? +Definitely not an unofficial commandline downloader for https://gronkh.tv -
+## Compatibility -Definetly not a commandline downloader for https://gronkh.tv risen from the dead. +This tool is only compatible with recent Linux-based operating systems. +To run it on Windows, make use of WSL. ## Features @@ -13,26 +14,27 @@ Definetly not a commandline downloader for https://gronkh.tv risen from the dead - Continuable Downloads - Show infos about that Episode -## Known Issues +## Known Issues / Limitations -- Downloads are capped to 10 Mbyte/s and buffering is simulated to pre-empt IP blocking due to API ratelimiting -- Start- and stop-timestamps are not very accurate (± 8 seconds) -- Some videoplayers may have problems with the resulting file. To fix this, you can use ffmpeg to rewrite the video into a MKV-File: `ffmpeg -i video.ts -acodec copy -vcodec copy video.mkv` - -## Supported Platforms - -Only compatible with Linux. +- Downloads are **capped to 10 Mbyte/s by default** and buffering is simulated to pre-empt IP blocking due to API rate-limiting +- Because of the length of video chunks, **start- and stop-timestamps are inaccurate** (± 8 seconds) +- **Some videoplayers may have problems with the downloaded video file**. To fix this, you can use ffmpeg to rewrite the video into a MKV-File: + `ffmpeg -i video.ts -acodec copy -vcodec copy video.mkv` ## Download / Installation -New versions will appear under [Releases](https://github.com/ChaoticByte/lurch-dl/releases). Just download the application and run it via the terminal/cmd/powershell/... +New versions will appear under [Releases](https://github.com/ChaoticByte/lurch-dl/releases). +Just download the application and run it via your favourite terminal emulator. -On Linux, you may have to mark the file as executable before being able to run it. +> Note: **You may have to mark the file as executable before being able to run it.** + +## Usage -## Cli Usage Run `lurch-dl --help` to see available options. +> Note: This tool runs entirely on the command line. + ### Examples Download a video in its best available format: @@ -105,5 +107,3 @@ Specify a filename: ``` ./lurch-dl --url https://gronkh.tv/streams/777 --output Stream777.ts ``` - -