From 5d490de7f17a2ffdc497685c0f76b8a3f64ffa22 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Sun, 9 Mar 2025 19:46:44 +0100 Subject: [PATCH] Replace --list-chapters and --list-formats with --info, include additional infos, update README, and some minor fixes and improvements --- README.md | 79 ++++++++++++++++++------------------------ cli/cli.go | 85 ++++++++++++++++++++++++++-------------------- core/gtv_api.go | 4 ++- core/gtv_common.go | 52 +++++++++++++++------------- 4 files changed, 114 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 38ebb28..dc970fa 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ Definetly not a commandline downloader for https://gronkh.tv risen from the dead - Specify a start- and stop-timestamp to download only a portion of the video - Download a specific chapter - Continuable Downloads +- Show infos about that Episode ## Known Issues -- You may get a "Windows Defender SmartScreen prevented an unrecognized app from starting" warning when running a new version for the first time - 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` @@ -36,70 +36,59 @@ Run `lurch-dl --help` to see available options. ### Examples -Download a video in its best available format (Windows): +Download a video in its best available format: ``` -.\lurch-dl.exe --url https://gronkh.tv/streams/777 +./lurch-dl.exe --url https://gronkh.tv/streams/777 -Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND SOLLTE VERBOTEN WERDEN!! ⭐ ️ 247 auf @GronkhTV ⭐ ️ !comic !archiv !a -Format: 1080p60 -Downloaded 0.43% at 10.00 MB/s -... +Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND ... +Format: 1080p60 +Output: GTV0777, 2023-11-09 - DIESER STREAM IST [...].ts + +Downloaded 0.32% at 10.00 MB/s ... ``` -Continue a download (Windows): +Continue a download: ``` -.\lurch-dl.exe --url https://gronkh.tv/streams/777 --continue - -Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND SOLLTE VERBOTEN WERDEN!! ⭐ ️ 247 auf @GronkhTV ⭐ ️ !comic !archiv !a -Format: 1080p60 -Downloaded 0.68% at 10.00 MB/s -... -``` - -List all chapters (Windows): - -``` -.\lurch-dl.exe --url https://gronkh.tv/streams/777 --list-chapters - -GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND SOLLTE VERBOTEN WERDEN!! ⭐ ️ 247 auf @GronkhTV ⭐ ️ !comic !archiv !a - -Chapters: - 1 0s Just Chatting - 2 2h53m7s Alan Wake II - 3 9h35m0s Just Chatting +./lurch-dl.exe --url https://gronkh.tv/streams/777 --continue ``` Download a specific chapter (Windows): ``` -.\lurch-dl.exe --url https://gronkh.tv/streams/777 --chapter 2 +./lurch-dl.exe --url https://gronkh.tv/streams/777 --chapter 2 -GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND SOLLTE VERBOTEN WERDEN!! ⭐ ️ 247 auf @GronkhTV ⭐ ️ !comic !archiv !a -Format: 1080p60 -Chapter: 2. Alan Wake II +Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND ... +Format: 1080p60 +Chapter: 2. Alan Wake II +Output: GTV0777 - 2. Alan Wake II.ts -Downloaded 3.22% at 10.00 MB/s -... +Downloaded 0.33% at 4.28 MB/s ... ``` -Specify a start- and stop-timestamp (Linux): +Specify a start- and stop-timestamp: ``` ./lurch-dl --url https://gronkh.tv/streams/777 --start 5h6m41s --stop 5h6m58s -... ``` List all available formats for a video (Linux): ``` -./lurch-dl --url https://gronkh.tv/streams/777 --list-formats +./lurch-dl --url https://gronkh.tv/streams/777 --info -Available formats: - - 1080p60 - - 720p - - 360p +Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND ... +Episode: 777 +Length: 9h48m55s +Views: 45424 +Timestamp: 2023-11-09T18:23:01Z +Tags: - +Formats: 1080p60, 720p, 360p +Chapters: + 1 0s Just Chatting + 2 2h53m7s Alan Wake II + 3 9h35m0s Just Chatting ``` Download the video in a specific format (Linux): @@ -107,17 +96,15 @@ Download the video in a specific format (Linux): ``` ./lurch-dl --url https://gronkh.tv/streams/777 --format 720p -Title: GTV0777, 2023-11-09 - DIESER STREAM IST ILLEGAL UND SOLLTE VERBOTEN WERDEN!! ⭐ ️ 247 auf @GronkhTV ⭐ ️ !comic !archiv !a -Format: 720p -Downloaded 0.32% at 10.00 MB/s -... +[...] +Format: 720p +[...] ``` Specify a filename (Windows): ``` -.\lurch-dl.exe --url https://gronkh.tv/streams/777 --output Stream777.ts -... +./lurch-dl.exe --url https://gronkh.tv/streams/777 --output Stream777.ts ``` diff --git a/cli/cli.go b/cli/cli.go index 5b72940..eb0b1d8 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -47,9 +47,9 @@ type Arguments struct { Overwrite bool `json:"overwrite"` ContinueDl bool `json:"continue"` // - Help bool `json:"-"` - ListChapters bool `json:"-"` - ListFormats bool `json:"-"` + Help bool `json:"-"` + VideoInfo bool `json:"-"` + ListFormats bool `json:"-"` UnparsedChapterNum int `json:"chapter_num"` // Parsed Video core.GtvVideo `json:"-"` @@ -63,8 +63,7 @@ func CliShowHelp() { fmt.Println(` lurch-dl --url string The url to the video [-h --help] Show this help and exit - [--list-chapters] List chapters and exit - [--list-formats] List available formats and exit + [--info] Show video info (chapters, formats, length, ...) [--chapter int] The chapter you want to download The calculated start and stop timestamps can be overwritten by --start and --stop @@ -91,8 +90,7 @@ func CliParseArguments() (Arguments, error) { a := Arguments{} flag.BoolVar(&a.Help, "h", false, "") flag.BoolVar(&a.Help, "help", false, "") - flag.BoolVar(&a.ListChapters, "list-chapters", false, "") - flag.BoolVar(&a.ListFormats, "list-formats", 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", "") @@ -107,8 +105,8 @@ func CliParseArguments() (Arguments, error) { if err != nil { return a, err } - if a.Video.Class != "streams" { - return a, errors.New("video category '" + a.Video.Class + "' not supported") + if a.Video.Category != "streams" { + return a, errors.New("video category '" + a.Video.Category + "' not supported") } if a.TimestampStart == "" { a.StartDuration = -1 @@ -166,7 +164,7 @@ func CliRun() int { return 1 } fmt.Print("\n") - fmt.Println(streamEp.Title) + fmt.Printf("Title: %s\n", streamEp.Title) // Check and list chapters/formats and exit if args.ChapterIdx >= 0 { if args.ChapterIdx >= len(streamEp.Chapters) { @@ -175,15 +173,26 @@ func CliRun() int { return 1 } } - if args.ListChapters || args.ListFormats { - if args.ListChapters { + if args.VideoInfo { + fmt.Printf("Episode: %s\n", streamEp.Episode) + fmt.Printf("Length: %s\n", streamEp.Length) + fmt.Printf("Views: %d\n", streamEp.Views) + fmt.Printf("Timestamp: %s\n", streamEp.Timestamp) + if len(streamEp.Tags) > 0 { + fmt.Print("Tags: ") + for i, t := range streamEp.Tags { + if i == 0 { + fmt.Print(t.Title) + } else { + fmt.Print(", ", t.Title) + } + } fmt.Print("\n") - CliAvailableChapters(streamEp.Chapters) - } - if args.ListFormats { - fmt.Print("\n") - CliAvailableFormats(streamEp.Formats) + } else { + fmt.Println("Tags: -") } + CliAvailableFormats(streamEp.Formats) + CliAvailableChapters(streamEp.Chapters) return 0 } format, err := streamEp.GetFormatByName(args.FormatName) @@ -192,21 +201,22 @@ func CliRun() int { CliAvailableFormats(streamEp.Formats) return 1 } - cli.InfoMessage(fmt.Sprintf("Format: %v", format.Name)) + 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] - cli.InfoMessage(fmt.Sprintf("Chapter: %v. %v", args.UnparsedChapterNum, targetChapter.Title)) + fmt.Printf("Chapter: %v. %v\n", args.UnparsedChapterNum, targetChapter.Title) } // We already set the output file correctly so we can output it if args.OutputFile == "" { args.OutputFile = streamEp.GetProposedFilename(args.ChapterIdx) } // Start Download - cli.InfoMessage(fmt.Sprintf("Output: %v", args.OutputFile)) + fmt.Printf("Output: %v\n", args.OutputFile) fmt.Print("\n") successful := false + aborted := false for p := range api.DownloadEpisode( streamEp, targetChapter, @@ -218,18 +228,24 @@ func CliRun() int { args.StopDuration, args.Ratelimit, make(chan os.Signal, 1), - ) { + ) { // Iterate over download progress if p.Error != nil { cli.ErrorMessage(p.Error) return 1 } - if p.Success { successful = true } - if p.Aborted { cli.Aborted() } else { + if p.Success { + successful = true + } else if p.Aborted { + aborted = true + } else { cli.DownloadProgress(p.Progress, p.Rate, p.Delaying, p.Waiting, p.Retries, p.Title) } } fmt.Print("\n") - if !successful { + if aborted { + fmt.Print("\nAborted. ") + return 130 + } else if !successful { cli.ErrorMessage(errors.New("download failed")) return 1 } else { return 0 } @@ -238,15 +254,20 @@ func CliRun() int { func CliAvailableChapters(chapters []core.Chapter) { fmt.Println("Chapters:") for _, f := range chapters { - fmt.Printf("%3d %10s\t%s\n", f.Index+1, f.Offset, f.Title) + fmt.Printf(" %3d %10s\t%s\n", f.Index+1, f.Offset, f.Title) } } func CliAvailableFormats(formats []core.VideoFormat) { - fmt.Println("Available formats:") - for _, f := range formats { - fmt.Println(" - " + f.Name) + fmt.Print("Formats: ") + for i, f := range formats { + if i == 0 { + fmt.Print(f.Name) + } else { + fmt.Print(", ", f.Name) + } } + fmt.Print("\n") } type Cli struct{} @@ -270,14 +291,6 @@ func (cli *Cli) DownloadProgress(progress float32, rate float64, delaying bool, } } -func (cli *Cli) Aborted() { - fmt.Print("\nAborted. ") -} - -func (cli *Cli) InfoMessage(msg string) { - fmt.Println(msg) -} - func (cli *Cli) ErrorMessage(err error) { fmt.Print("\n") fmt.Println("An error occured:", err) diff --git a/core/gtv_api.go b/core/gtv_api.go index 24f34f2..b77a1d4 100644 --- a/core/gtv_api.go +++ b/core/gtv_api.go @@ -64,6 +64,8 @@ func (api *GtvApi) GetStreamEpisode(episode string) (StreamEpisode, error) { // Title json.Unmarshal(info_data, &ep) ep.Title = strings.ToValidUTF8(ep.Title, "") + // Length + ep.Length = ep.Length * time.Second // Sort Chapters, correct offset and set index sort.Slice(ep.Chapters, func(i int, j int) bool { return ep.Chapters[i].Offset < ep.Chapters[j].Offset @@ -194,7 +196,7 @@ func (api *GtvApi) DownloadEpisode( // Handle Interrupts <-interruptChan keyboardInterrupt = true - yield(DownloadProgress{Progress: progress, Rate: actualRate, Retries: 0, Title: ep.Title}) + yield(DownloadProgress{Aborted: true, Progress: progress, Rate: actualRate, Retries: 0, Title: ep.Title}) }() for i, chunk := range chunklist.Chunks { if i < nextChunk { diff --git a/core/gtv_common.go b/core/gtv_common.go index b3b3595..9ee0b5a 100644 --- a/core/gtv_common.go +++ b/core/gtv_common.go @@ -13,8 +13,26 @@ var videoUrlRegex = regexp.MustCompile(`gronkh\.tv\/([a-z]+)\/([0-9]+)`) // +type VideoFormat struct { + Name string `json:"format"` + Url string `json:"url"` +} + +type Chapter struct { + Index int `json:"index"` + Title string `json:"title"` + Offset time.Duration `json:"offset"` +} + +type VideoTag struct { + Id int `json:"id"` + Title string `json:"title"` +} + +// + type GtvVideo struct { - Class string `json:"class"` + Category string `json:"category"` Id string `json:"id"` } @@ -24,20 +42,13 @@ func ParseGtvVideoUrl(url string) (GtvVideo, error) { if len(match) < 2 { return video, errors.New("Could not parse URL " + url) } - video.Class = match[1] + video.Category = match[1] video.Id = match[2] return video, nil } // -type VideoFormat struct { - Name string `json:"format"` - Url string `json:"url"` -} - -// - type ChunkList struct { BaseUrl string Chunks []string @@ -65,21 +76,16 @@ func (cl *ChunkList) Cut(from time.Duration, to time.Duration) ChunkList { // -type Chapter struct { - Index int `json:"index"` - Title string `json:"title"` - Offset time.Duration `json:"offset"` -} - -// - type StreamEpisode struct { - Episode string `json:"episode"` - Formats []VideoFormat `json:"formats"` - Title string `json:"title"` - // ProposedFilename string `json:"proposed_filename"` - PlaylistUrl string `json:"playlist_url"` - Chapters []Chapter `json:"chapters"` + Episode string `json:"episode"` + Title string `json:"title"` + Formats []VideoFormat `json:"formats"` + Chapters []Chapter `json:"chapters"` + PlaylistUrl string `json:"playlist_url"` + Length time.Duration `json:"source_length"` + Views int `json:"views"` + Timestamp string `json:"created_at"` + Tags []VideoTag `json:"tags"` } func (ep *StreamEpisode) GetFormatByName(formatName string) (VideoFormat, error) {