From 25df562862eef7aeda340b4181a962b4f935a356 Mon Sep 17 00:00:00 2001 From: Kethsar Date: Sun, 19 Nov 2023 00:24:10 -0800 Subject: [PATCH] Add --members-only option Allows downloading members streams only when monitoring a channel --- Info.go | 1 + README.md | 9 +++++++ main.go | 8 ++++++ player_response.go | 63 +++++++++++++++++++++++++--------------------- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/Info.go b/Info.go index d385f49..1f7adca 100644 --- a/Info.go +++ b/Info.go @@ -164,6 +164,7 @@ type DownloadInfo struct { LiveURL bool AudioOnly bool VideoOnly bool + MembersOnly bool InfoPrinted bool Thumbnail string diff --git a/README.md b/README.md index 8812a1d..6e92d6b 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,11 @@ Options: Keep the final stream audio and video files after muxing them instead of deleting them. + --members-only + Only download members-only streams. Can only be used with channel URLs + such as /live, /streams, etc, and requires cookies. + Useful when monitoring channels and you only want membership streams. + --merge Automatically run the ffmpeg command for the downloaded streams when manually cancelling the download. You will be prompted otherwise. @@ -95,6 +100,10 @@ Options: Be careful to monitor your disk usage when using this to avoid filling your drive while away. + --newline + Print every message to a new line, instead of some messages reusing one + line. + --no-audio Do not download the audio stream diff --git a/main.go b/main.go index 1c361d7..047b8bc 100644 --- a/main.go +++ b/main.go @@ -99,6 +99,11 @@ Options: Keep the final stream audio and video files after muxing them instead of deleting them. + --members-only + Only download members-only streams. Can only be used with channel URLs + such as /live, /streams, etc, and requires cookies. + Useful when monitoring channels and you only want membership streams. + --merge Automatically run the ffmpeg command for the downloaded streams when manually cancelling the download. You will be prompted otherwise. @@ -381,6 +386,7 @@ var ( monitorChannel bool vp9 bool h264 bool + membersOnly bool cancelled = false ) @@ -432,6 +438,7 @@ func init() { cliFlags.BoolVar(&keepTSFiles, "keep-ts-files", false, "Keep the raw .ts files instead of deleting them after muxing.") cliFlags.BoolVar(&separateAudio, "separate-audio", false, "Save a copy of the audio separately along with the muxed file.") cliFlags.BoolVar(&monitorChannel, "monitor-channel", false, "Continually monitor a channel for streams.") + cliFlags.BoolVar(&membersOnly, "members-only", false, "Only download members-only streams when waiting on a channel URL such as /live.") cliFlags.StringVar(&cookieFile, "c", "", "Cookies to be used when downloading.") cliFlags.StringVar(&cookieFile, "cookies", "", "Cookies to be used when downloading.") cliFlags.StringVar(&fnameFormat, "o", DefaultFilenameFormat, "Filename output format.") @@ -508,6 +515,7 @@ func run() int { info.H264 = h264 info.RetrySecs = retrySecs info.FragMaxTries = fragMaxTries + info.MembersOnly = membersOnly if doWait { info.Wait = ActionDo diff --git a/player_response.go b/player_response.go index 3fc0f36..9f43635 100644 --- a/player_response.go +++ b/player_response.go @@ -135,9 +135,6 @@ type YtInitialData struct { Richgridrenderer struct { Contents []RichGridContent `json:"contents"` } `json:"richGridRenderer"` - SectionListRenderer struct { - Contents []SectionListContent `json:"contents"` - } `json:"sectionListRenderer"` } `json:"content"` } `json:"tabRenderer"` } `json:"tabs"` @@ -155,26 +152,16 @@ type RichGridContent struct { Style string `json:"style"` } `json:"thumbnailOverlayTimeStatusRenderer"` } `json:"thumbnailOverlays"` + Badges []struct { + Metadatabadgerenderer struct { + Style string `json:"style"` + } `json:"metadataBadgeRenderer"` + } `json:"badges"` } `json:"videoRenderer"` } `json:"content"` } `json:"richItemRenderer"` } -type SectionListContent struct { - ItemSectionRenderer struct { - Contents []struct { - Videorenderer struct { - Videoid string `json:"videoId"` - Thumbnailoverlays []struct { - Thumbnailoverlaytimestatusrenderer struct { - Style string `json:"style"` - } `json:"thumbnailOverlayTimeStatusRenderer"` - } `json:"thumbnailOverlays"` - } `json:"videoRenderer"` - } `json:"contents"` - } `json:"itemSectionRenderer"` -} - // Search the given HTML for the player response object func GetJsonFromHtml(htmlData []byte, jsonDecl []byte) []byte { var objData []byte @@ -217,13 +204,19 @@ func GetJsonFromHtml(htmlData []byte, jsonDecl []byte) []byte { } } -func GetNewestStreamFromStreams(liveUrl string) string { +func (di *DownloadInfo) GetNewestStreamFromStreams() string { + // Surely there won't be more than 5 simultaneous streams when looking for membership streams, right? + const MAX_STREAM_ITEM_CHECK = 5 + streamUrl := "" + if !di.LiveURL { + return streamUrl + } + initialData := &YtInitialData{} var contents []RichGridContent - streamsUrl := strings.Replace(liveUrl, "/live", "/streams", 1) + streamsUrl := strings.Replace(di.URL, "/live", "/streams", 1) streamsHtml := DownloadData(streamsUrl) ytInitialData := GetJsonFromHtml(streamsHtml, ytInitialDataDecl) - streamUrl := "" err := json.Unmarshal(ytInitialData, initialData) if err != nil { @@ -236,8 +229,25 @@ func GetNewestStreamFromStreams(liveUrl string) string { } } - for _, content := range contents { + for i, content := range contents { + if i >= MAX_STREAM_ITEM_CHECK { + break + } + videoRenderer := content.Richitemrenderer.Content.Videorenderer + if di.MembersOnly { + mengen := false + for _, badge := range videoRenderer.Badges { + if badge.Metadatabadgerenderer.Style == "BADGE_STYLE_TYPE_MEMBERS_ONLY" { + mengen = true + break + } + } + + if !mengen { + continue + } + } for _, thumbnailRenderer := range videoRenderer.Thumbnailoverlays { if thumbnailRenderer.Thumbnailoverlaytimestatusrenderer.Style == "LIVE" { @@ -315,18 +325,14 @@ func (di *DownloadInfo) GetVideoHtml() []byte { var videoHtml []byte if di.LiveURL { - streamUrl := "" - - if len(streamUrl) == 0 { - streamUrl = GetNewestStreamFromStreams(di.URL) - } + streamUrl := di.GetNewestStreamFromStreams() if len(streamUrl) > 0 { videoHtml = DownloadData(streamUrl) } } - if len(videoHtml) == 0 { + if len(videoHtml) == 0 && !di.MembersOnly { videoHtml = DownloadData(di.URL) } @@ -581,6 +587,7 @@ func (di *DownloadInfo) GetPlayablePlayerResponse() (retrieved int, pr *PlayerRe // player response returned from /live does not include full information if isLiveURL { di.URL = fmt.Sprintf("https://www.youtube.com/watch?v=%s", di.VideoID) + di.MembersOnly = false isLiveURL = false continue }