From f2075173135b422599ef4c3e5014652b1310f818 Mon Sep 17 00:00:00 2001 From: Billaids Date: Fri, 7 Jan 2022 15:13:44 +0100 Subject: [PATCH 1/6] fix(client): drop web and switch to android client Signed-off-by: Billaids --- client.go | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/client.go b/client.go index 2f12a759..961510a4 100644 --- a/client.go +++ b/client.go @@ -38,7 +38,7 @@ func (c *Client) GetVideoContext(ctx context.Context, url string) (*Video, error } func (c *Client) videoFromID(ctx context.Context, id string) (*Video, error) { - body, err := c.videoDataByInnertube(ctx, id, Web) + body, err := c.videoDataByInnertube(ctx, id, Android) if err != nil { return nil, err } @@ -115,8 +115,8 @@ type innertubeClient struct { type ClientType string const ( - Web ClientType = "WEB" - EmbeddedClient ClientType = "WEB_EMBEDDED_PLAYER" + Android ClientType = "ANDROID" + EmbeddedClient ClientType = "ANDROID_EMBEDDED_PLAYER" ) func (c *Client) videoDataByInnertube(ctx context.Context, id string, clientType ClientType) ([]byte, error) { @@ -157,13 +157,14 @@ func (c *Client) videoDataByInnertube(ctx context.Context, id string, clientType } var innertubeClientInfo = map[ClientType]map[string]string{ - // might add ANDROID and other in future, but i don't see reason yet - Web: { - "version": "2.20210617.01.00", + // kanged from + // https://github.com/yt-dlp/yt-dlp/blob/11aa91a12f95821500fa064402a3e2c046b072fb/yt_dlp/extractor/youtube.py#L120-L140 + Android: { + "version": "16.20", "key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", }, EmbeddedClient: { - "version": "1.19700101", + "version": "16.20", // seems like same key works for both clients "key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", }, @@ -172,8 +173,8 @@ var innertubeClientInfo = map[ClientType]map[string]string{ func prepareInnertubeData(videoID string, sts string, clientType ClientType) (innertubeRequest, string) { cInfo, ok := innertubeClientInfo[clientType] if !ok { - // if provided clientType not exist - use Web as fallback option - clientType = Web + // if provided clientType not exist - use Android as fallback option + clientType = Android cInfo = innertubeClientInfo[clientType] } @@ -251,6 +252,13 @@ func (c *Client) GetStreamContext(ctx context.Context, video *Video, format *For go c.download(req, w, format) + if format.ContentLength == 0 { + format.ContentLength, err = c.getStreamSize(video, format) + if err != nil { + return nil, 0, err + } + } + return r, format.ContentLength, nil } @@ -376,3 +384,14 @@ func (c *Client) httpGetBodyBytes(ctx context.Context, url string) ([]byte, erro return io.ReadAll(resp.Body) } + +// credit @aykxt +func (c *Client) getStreamSize(video *Video, format *Format) (int64, error) { + url, err := c.GetStreamURL(video, format) + if err != nil { + return 0, err + } + + resp, err := http.Head(url) + return resp.ContentLength, err +} From 28eb60d887e51cf373675cb9de9863ea2434d45e Mon Sep 17 00:00:00 2001 From: Billaids Date: Fri, 7 Jan 2022 15:15:42 +0100 Subject: [PATCH 2/6] feat(client): improve getStreamSize Signed-off-by: Billaids --- client.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index 961510a4..9403b8d9 100644 --- a/client.go +++ b/client.go @@ -253,7 +253,7 @@ func (c *Client) GetStreamContext(ctx context.Context, video *Video, format *For go c.download(req, w, format) if format.ContentLength == 0 { - format.ContentLength, err = c.getStreamSize(video, format) + format.ContentLength, err = c.getStreamSize(video, format, url) if err != nil { return nil, 0, err } @@ -386,11 +386,7 @@ func (c *Client) httpGetBodyBytes(ctx context.Context, url string) ([]byte, erro } // credit @aykxt -func (c *Client) getStreamSize(video *Video, format *Format) (int64, error) { - url, err := c.GetStreamURL(video, format) - if err != nil { - return 0, err - } +func (c *Client) getStreamSize(video *Video, format *Format, url string) (int64, error) { resp, err := http.Head(url) return resp.ContentLength, err From 4a911d9616ddcc8c5ddfdc728860a78143e7f8ec Mon Sep 17 00:00:00 2001 From: Julian Kornberger Date: Sat, 8 Jan 2022 09:25:11 +0100 Subject: [PATCH 3/6] Fix one test --- itag_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itag_test.go b/itag_test.go index 66e83bcb..464c6b21 100644 --- a/itag_test.go +++ b/itag_test.go @@ -14,5 +14,5 @@ func TestYoutube_GetItagInfo(t *testing.T) { url := "https://www.youtube.com/watch?v=rFejpH_tAHM" video, err := client.GetVideo(url) require.NoError(err) - require.Len(video.Formats, 24) + require.Len(video.Formats, 26) } From efde8ec806d53251ab896f0a9003f9f63f98baac Mon Sep 17 00:00:00 2001 From: Julian Kornberger Date: Sat, 8 Jan 2022 09:36:39 +0100 Subject: [PATCH 4/6] Use context for HEAD request --- client.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index 9403b8d9..85e6a3a3 100644 --- a/client.go +++ b/client.go @@ -253,7 +253,7 @@ func (c *Client) GetStreamContext(ctx context.Context, video *Video, format *For go c.download(req, w, format) if format.ContentLength == 0 { - format.ContentLength, err = c.getStreamSize(video, format, url) + format.ContentLength, err = c.httpGetContentLength(ctx, video, format, url) if err != nil { return nil, 0, err } @@ -357,6 +357,11 @@ func (c *Client) httpDo(req *http.Request) (*http.Response, error) { // httpGet does a HTTP GET request, checks the response to be a 200 OK and returns it func (c *Client) httpGet(ctx context.Context, url string) (*http.Response, error) { + return c.httpDoWithoutBody(ctx, http.MethodGet, url) +} + +// httpGet does a HTTP request, checks the response to be a 200 OK and returns it +func (c *Client) httpDoWithoutBody(ctx context.Context, method, url string) (*http.Response, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err @@ -386,8 +391,8 @@ func (c *Client) httpGetBodyBytes(ctx context.Context, url string) ([]byte, erro } // credit @aykxt -func (c *Client) getStreamSize(video *Video, format *Format, url string) (int64, error) { - - resp, err := http.Head(url) +func (c *Client) httpGetContentLength(ctx context.Context, video *Video, format *Format, url string) (int64, error) { + resp, err := c.httpDoWithoutBody(ctx, http.MethodHead, url) + resp.Body.Close() return resp.ContentLength, err } From 27cee142906c26974b4a832f196013fd04d9c14f Mon Sep 17 00:00:00 2001 From: Billaids Date: Mon, 24 Jan 2022 09:25:08 +0100 Subject: [PATCH 5/6] fix: adjustments for using an android client [WIP] * we can't get the publishdate from the android response, have to add maybe another http request for it Signed-off-by: Billaids --- client.go | 25 +- client_test.go | 8 +- cmd/youtubedr/info.go | 12 +- response_data.go | 806 +++++++++++++++++++++++++++++++++++++----- video.go | 13 +- 5 files changed, 756 insertions(+), 108 deletions(-) diff --git a/client.go b/client.go index 85e6a3a3..c0698740 100644 --- a/client.go +++ b/client.go @@ -8,6 +8,7 @@ import ( "io" "log" "net/http" + "strconv" ) // Client offers methods to download video metadata and video streams. @@ -252,14 +253,20 @@ func (c *Client) GetStreamContext(ctx context.Context, video *Video, format *For go c.download(req, w, format) - if format.ContentLength == 0 { - format.ContentLength, err = c.httpGetContentLength(ctx, video, format, url) + contentLength, err := strconv.ParseInt(format.ContentLength, 10, 64) + if err != nil { + return nil, 0, err + } + + if contentLength == 0 { + contentLength, err := c.httpGetContentLength(ctx, video, format, url) + format.ContentLength = fmt.Sprint(contentLength) if err != nil { return nil, 0, err } } - - return r, format.ContentLength, nil + format.ContentLength = fmt.Sprint(contentLength) + return r, contentLength, nil } func (c *Client) download(req *http.Request, w *io.PipeWriter, format *Format) { @@ -284,8 +291,14 @@ func (c *Client) download(req *http.Request, w *io.PipeWriter, format *Format) { } defer w.Close() + + contentLength, err := strconv.ParseInt(format.ContentLength, 10, 64) + if err != nil { + return + } + //nolint:revive,errcheck - if format.ContentLength == 0 { + if contentLength == 0 { resp, err := c.httpDo(req) if err != nil { w.CloseWithError(err) @@ -300,7 +313,7 @@ func (c *Client) download(req *http.Request, w *io.PipeWriter, format *Format) { //nolint:revive,errcheck // load all the chunks - for pos := int64(0); pos < format.ContentLength; { + for pos := int64(0); pos < contentLength; { written, err := loadChunk(pos) if err != nil { w.CloseWithError(err) diff --git a/client_test.go b/client_test.go index 310f6e87..9f2e9ffa 100644 --- a/client_test.go +++ b/client_test.go @@ -87,8 +87,8 @@ func TestGetVideoWithoutManifestURL(t *testing.T) { require.NotNil(video) assert.NotEmpty(video.Thumbnails) - assert.Greater(len(video.Thumbnails), 0) - assert.NotEmpty(video.Thumbnails[0].URL) + assert.Greater(len(video.Thumbnails.Thumbnails), 0) + assert.NotEmpty(video.Thumbnails.Thumbnails[0].URL) assert.Empty(video.HLSManifestURL) assert.Empty(video.DASHManifestURL) @@ -108,8 +108,8 @@ func TestGetVideoWithManifestURL(t *testing.T) { require.NotNil(video) assert.NotEmpty(video.Thumbnails) - assert.Greater(len(video.Thumbnails), 0) - assert.NotEmpty(video.Thumbnails[0].URL) + assert.Greater(len(video.Thumbnails.Thumbnails), 0) + assert.NotEmpty(video.Thumbnails.Thumbnails[0].URL) assert.NotEmpty(video.HLSManifestURL) assert.NotEmpty(video.DASHManifestURL) } diff --git a/cmd/youtubedr/info.go b/cmd/youtubedr/info.go index 171fa0b7..1d299e3d 100644 --- a/cmd/youtubedr/info.go +++ b/cmd/youtubedr/info.go @@ -58,10 +58,14 @@ var infoCmd = &cobra.Command{ bitrate = format.Bitrate } - size := format.ContentLength - if size == 0 { + contentLength, err := strconv.ParseInt(format.ContentLength, 10, 64) + if err != nil { + return + } + + if contentLength == 0 { // Some formats don't have this information - size = int64(float64(bitrate) * video.Duration.Seconds() / 8) + contentLength = int64(float64(bitrate) * video.Duration.Seconds() / 8) } videoInfo.Formats = append(videoInfo.Formats, VideoFormat{ @@ -70,7 +74,7 @@ var infoCmd = &cobra.Command{ VideoQuality: format.QualityLabel, AudioQuality: strings.ToLower(strings.TrimPrefix(format.AudioQuality, "AUDIO_QUALITY_")), AudioChannels: format.AudioChannels, - Size: size, + Size: contentLength, Bitrate: bitrate, MimeType: format.MimeType, }) diff --git a/response_data.go b/response_data.go index 34d091cf..0bee0cc9 100644 --- a/response_data.go +++ b/response_data.go @@ -1,112 +1,742 @@ package youtube +// type playerResponseData struct { +// PlayabilityStatus struct { +// Status string `json:"status"` +// Reason string `json:"reason"` +// PlayableInEmbed bool `json:"playableInEmbed"` +// Miniplayer struct { +// MiniplayerRenderer struct { +// PlaybackMode string `json:"playbackMode"` +// } `json:"miniplayerRenderer"` +// } `json:"miniplayer"` +// ContextParams string `json:"contextParams"` +// } `json:"playabilityStatus"` +// StreamingData struct { +// ExpiresInSeconds string `json:"expiresInSeconds"` +// Formats []Format `json:"formats"` +// AdaptiveFormats []Format `json:"adaptiveFormats"` +// DashManifestURL string `json:"dashManifestUrl"` +// HlsManifestURL string `json:"hlsManifestUrl"` +// } `json:"streamingData"` +// VideoDetails struct { +// VideoID string `json:"videoId"` +// Title string `json:"title"` +// LengthSeconds string `json:"lengthSeconds"` +// Keywords []string `json:"keywords"` +// ChannelID string `json:"channelId"` +// IsOwnerViewing bool `json:"isOwnerViewing"` +// ShortDescription string `json:"shortDescription"` +// IsCrawlable bool `json:"isCrawlable"` +// Thumbnail struct { +// Thumbnails []Thumbnail `json:"thumbnails"` +// } `json:"thumbnail"` +// AverageRating float64 `json:"averageRating"` +// AllowRatings bool `json:"allowRatings"` +// ViewCount string `json:"viewCount"` +// Author string `json:"author"` +// IsPrivate bool `json:"isPrivate"` +// IsUnpluggedCorpus bool `json:"isUnpluggedCorpus"` +// IsLiveContent bool `json:"isLiveContent"` +// } `json:"videoDetails"` +// Microformat struct { +// PlayerMicroformatRenderer struct { +// Thumbnail struct { +// Thumbnails []struct { +// URL string `json:"url"` +// Width int `json:"width"` +// Height int `json:"height"` +// } `json:"thumbnails"` +// } `json:"thumbnail"` +// Title struct { +// SimpleText string `json:"simpleText"` +// } `json:"title"` +// Description struct { +// SimpleText string `json:"simpleText"` +// } `json:"description"` +// LengthSeconds string `json:"lengthSeconds"` +// OwnerProfileURL string `json:"ownerProfileUrl"` +// ExternalChannelID string `json:"externalChannelId"` +// IsFamilySafe bool `json:"isFamilySafe"` +// AvailableCountries []string `json:"availableCountries"` +// IsUnlisted bool `json:"isUnlisted"` +// HasYpcMetadata bool `json:"hasYpcMetadata"` +// ViewCount string `json:"viewCount"` +// Category string `json:"category"` +// PublishDate string `json:"publishDate"` +// OwnerChannelName string `json:"ownerChannelName"` +// UploadDate string `json:"uploadDate"` +// } `json:"playerMicroformatRenderer"` +// } `json:"microformat"` +// } + +// type Format struct { +// ItagNo int `json:"itag"` +// URL string `json:"url"` +// MimeType string `json:"mimeType"` +// Quality string `json:"quality"` +// Cipher string `json:"signatureCipher"` +// Bitrate int `json:"bitrate"` +// FPS int `json:"fps"` +// Width int `json:"width"` +// Height int `json:"height"` +// LastModified string `json:"lastModified"` +// ContentLength int64 `json:"contentLength,string"` +// QualityLabel string `json:"qualityLabel"` +// ProjectionType string `json:"projectionType"` +// AverageBitrate int `json:"averageBitrate"` +// AudioQuality string `json:"audioQuality"` +// ApproxDurationMs string `json:"approxDurationMs"` +// AudioSampleRate string `json:"audioSampleRate"` +// AudioChannels int `json:"audioChannels"` + +// // InitRange is only available for adaptive formats +// InitRange *struct { +// Start string `json:"start"` +// End string `json:"end"` +// } `json:"initRange"` + +// // IndexRange is only available for adaptive formats +// IndexRange *struct { +// Start string `json:"start"` +// End string `json:"end"` +// } `json:"indexRange"` +// } + +// type Thumbnails []Thumbnail + +// type Thumbnail struct { +// URL string +// Width uint +// Height uint +// } + type playerResponseData struct { - PlayabilityStatus struct { - Status string `json:"status"` - Reason string `json:"reason"` - PlayableInEmbed bool `json:"playableInEmbed"` - Miniplayer struct { - MiniplayerRenderer struct { - PlaybackMode string `json:"playbackMode"` - } `json:"miniplayerRenderer"` - } `json:"miniplayer"` - ContextParams string `json:"contextParams"` - } `json:"playabilityStatus"` - StreamingData struct { - ExpiresInSeconds string `json:"expiresInSeconds"` - Formats []Format `json:"formats"` - AdaptiveFormats []Format `json:"adaptiveFormats"` - DashManifestURL string `json:"dashManifestUrl"` - HlsManifestURL string `json:"hlsManifestUrl"` - } `json:"streamingData"` - VideoDetails struct { - VideoID string `json:"videoId"` - Title string `json:"title"` - LengthSeconds string `json:"lengthSeconds"` - Keywords []string `json:"keywords"` - ChannelID string `json:"channelId"` - IsOwnerViewing bool `json:"isOwnerViewing"` - ShortDescription string `json:"shortDescription"` - IsCrawlable bool `json:"isCrawlable"` - Thumbnail struct { - Thumbnails []Thumbnail `json:"thumbnails"` - } `json:"thumbnail"` - AverageRating float64 `json:"averageRating"` - AllowRatings bool `json:"allowRatings"` - ViewCount string `json:"viewCount"` - Author string `json:"author"` - IsPrivate bool `json:"isPrivate"` - IsUnpluggedCorpus bool `json:"isUnpluggedCorpus"` - IsLiveContent bool `json:"isLiveContent"` - } `json:"videoDetails"` - Microformat struct { - PlayerMicroformatRenderer struct { - Thumbnail struct { - Thumbnails []struct { - URL string `json:"url"` - Width int `json:"width"` - Height int `json:"height"` - } `json:"thumbnails"` - } `json:"thumbnail"` - Title struct { - SimpleText string `json:"simpleText"` - } `json:"title"` - Description struct { - SimpleText string `json:"simpleText"` - } `json:"description"` - LengthSeconds string `json:"lengthSeconds"` - OwnerProfileURL string `json:"ownerProfileUrl"` - ExternalChannelID string `json:"externalChannelId"` - IsFamilySafe bool `json:"isFamilySafe"` - AvailableCountries []string `json:"availableCountries"` - IsUnlisted bool `json:"isUnlisted"` - HasYpcMetadata bool `json:"hasYpcMetadata"` - ViewCount string `json:"viewCount"` - Category string `json:"category"` - PublishDate string `json:"publishDate"` - OwnerChannelName string `json:"ownerChannelName"` - UploadDate string `json:"uploadDate"` - } `json:"playerMicroformatRenderer"` - } `json:"microformat"` + ResponseContext ResponseContext `json:"responseContext"` + TrackingParams string `json:"trackingParams"` + AdBreakParams string `json:"adBreakParams"` + PlayabilityStatus PlayabilityStatus `json:"playabilityStatus"` + StreamingData StreamingData `json:"streamingData"` + HeartbeatParams HeartbeatParams `json:"heartbeatParams,omitempty"` + PlaybackTracking PlaybackTracking `json:"playbackTracking"` + VideoDetails VideoDetails `json:"videoDetails"` + Annotations []Annotations `json:"annotations"` + PlayerConfig PlayerConfig `json:"playerConfig"` + Storyboards Storyboards `json:"storyboards"` + Attestation Attestation `json:"attestation"` + PlayerSettingsMenuData PlayerSettingsMenuData `json:"playerSettingsMenuData"` + Captions Captions `json:"captions"` +} +type Params struct { + Key string `json:"key"` + Value string `json:"value"` +} +type ServiceTrackingParams struct { + Service string `json:"service"` + Params []Params `json:"params"` +} +type ResponseContext struct { + VisitorData string `json:"visitorData"` + MaxAgeSeconds int `json:"maxAgeSeconds"` + ServiceTrackingParams []ServiceTrackingParams `json:"serviceTrackingParams"` +} +type LiveStreamabilityRenderer struct { + VideoID string `json:"videoId"` + Params string `json:"params"` + BroadcastID string `json:"broadcastId"` + PollDelayMs string `json:"pollDelayMs"` +} +type LiveStreamability struct { + ButtonRenderer ButtonRenderer `json:"buttonRenderer"` + LiveStreamabilityRenderer LiveStreamabilityRenderer `json:"liveStreamabilityRenderer"` +} +type YpcGetOfflineUpsellEndpoint struct { + Params string `json:"params"` +} +type ServiceEndpoint struct { + ClickTrackingParams string `json:"clickTrackingParams"` + YpcGetOfflineUpsellEndpoint YpcGetOfflineUpsellEndpoint `json:"ypcGetOfflineUpsellEndpoint"` +} +type ButtonRenderer struct { + ServiceEndpoint ServiceEndpoint `json:"serviceEndpoint"` + TrackingParams string `json:"trackingParams"` +} +type Offlineability struct { + ButtonRenderer ButtonRenderer `json:"buttonRenderer"` +} +type MiniplayerRenderer struct { + PlaybackMode string `json:"playbackMode"` +} +type Miniplayer struct { + MiniplayerRenderer MiniplayerRenderer `json:"miniplayerRenderer"` +} +type PlayabilityStatus struct { + Status string `json:"status"` + PlayableInEmbed bool `json:"playableInEmbed"` + LiveStreamability LiveStreamability `json:"liveStreamability"` + Offlineability Offlineability `json:"offlineability"` + Miniplayer Miniplayer `json:"miniplayer"` + ContextParams string `json:"contextParams"` + Reason string `json:"reason"` + Watermark Watermark `json:"watermark"` + ErrorScreen ErrorScreen `json:"errorScreen"` } - type Format struct { ItagNo int `json:"itag"` URL string `json:"url"` + Cipher string `json:"signatureCipher,omitempty"` MimeType string `json:"mimeType"` - Quality string `json:"quality"` - Cipher string `json:"signatureCipher"` Bitrate int `json:"bitrate"` - FPS int `json:"fps"` Width int `json:"width"` Height int `json:"height"` LastModified string `json:"lastModified"` - ContentLength int64 `json:"contentLength,string"` + ContentLength string `json:"contentLength,omitempty"` + Quality string `json:"quality"` + FPS int `json:"fps"` QualityLabel string `json:"qualityLabel"` ProjectionType string `json:"projectionType"` - AverageBitrate int `json:"averageBitrate"` + AverageBitrate int `json:"averageBitrate,omitempty"` AudioQuality string `json:"audioQuality"` ApproxDurationMs string `json:"approxDurationMs"` AudioSampleRate string `json:"audioSampleRate"` AudioChannels int `json:"audioChannels"` +} +type InitRange struct { + Start string `json:"start"` + End string `json:"end"` +} +type IndexRange struct { + Start string `json:"start"` + End string `json:"end"` +} +type ColorInfo struct { + Primaries string `json:"primaries"` + TransferCharacteristics string `json:"transferCharacteristics"` + MatrixCoefficients string `json:"matrixCoefficients"` +} +type AdaptiveFormats struct { + Itag int `json:"itag"` + URL string `json:"url"` + MimeType string `json:"mimeType"` + Bitrate int `json:"bitrate"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + InitRange InitRange `json:"initRange,omitempty"` + IndexRange IndexRange `json:"indexRange,omitempty"` + LastModified string `json:"lastModified"` + ContentLength string `json:"contentLength,omitempty"` + Quality string `json:"quality"` + FPS int `json:"fps,omitempty"` + QualityLabel string `json:"qualityLabel,omitempty"` + ProjectionType string `json:"projectionType"` + TargetDurationSec int `json:"targetDurationSec"` + MaxDvrDurationSec int `json:"maxDvrDurationSec"` + AverageBitrate int `json:"averageBitrate,omitempty"` + ApproxDurationMs string `json:"approxDurationMs,omitempty"` + ColorInfo ColorInfo `json:"colorInfo,omitempty"` + HighReplication bool `json:"highReplication,omitempty"` + AudioQuality string `json:"audioQuality,omitempty"` + AudioSampleRate string `json:"audioSampleRate,omitempty"` + AudioChannels int `json:"audioChannels,omitempty"` +} +type StreamingData struct { + ExpiresInSeconds string `json:"expiresInSeconds"` + Formats []Format `json:"formats"` + AdaptiveFormats []AdaptiveFormats `json:"adaptiveFormats"` + OnesieStreamingURL string `json:"onesieStreamingUrl"` + DashManifestURL string `json:"dashManifestUrl"` + HlsManifestURL string `json:"hlsManifestUrl"` +} +type HeartbeatParams struct { + IntervalMilliseconds string `json:"intervalMilliseconds"` + SoftFailOnError bool `json:"softFailOnError"` + HeartbeatServerData string `json:"heartbeatServerData"` +} +type Headers struct { + HeaderType string `json:"headerType"` +} +type VideostatsPlaybackURL struct { + BaseURL string `json:"baseUrl"` + Headers []Headers `json:"headers"` +} +type VideostatsDelayplayURL struct { + BaseURL string `json:"baseUrl"` + ElapsedMediaTimeSeconds int `json:"elapsedMediaTimeSeconds"` + Headers []Headers `json:"headers"` +} +type VideostatsWatchtimeURL struct { + BaseURL string `json:"baseUrl"` + Headers []Headers `json:"headers"` +} +type PtrackingURL struct { + BaseURL string `json:"baseUrl"` + Headers []Headers `json:"headers"` +} +type QoeURL struct { + BaseURL string `json:"baseUrl"` + Headers []Headers `json:"headers"` +} +type AtrURL struct { + BaseURL string `json:"baseUrl"` + ElapsedMediaTimeSeconds int `json:"elapsedMediaTimeSeconds"` + Headers []Headers `json:"headers"` +} +type EngageURL struct { + BaseURL string `json:"baseUrl"` + Headers []Headers `json:"headers"` +} +type YoutubeRemarketingURL struct { + BaseURL string `json:"baseUrl"` + ElapsedMediaTimeSeconds int `json:"elapsedMediaTimeSeconds"` + Headers []Headers `json:"headers"` +} +type PlaybackTracking struct { + VideostatsPlaybackURL VideostatsPlaybackURL `json:"videostatsPlaybackUrl"` + VideostatsDelayplayURL VideostatsDelayplayURL `json:"videostatsDelayplayUrl"` + VideostatsWatchtimeURL VideostatsWatchtimeURL `json:"videostatsWatchtimeUrl"` + PtrackingURL PtrackingURL `json:"ptrackingUrl"` + QoeURL QoeURL `json:"qoeUrl"` + AtrURL AtrURL `json:"atrUrl"` + EngageURL EngageURL `json:"engageUrl"` + VideostatsScheduledFlushWalltimeSeconds []int `json:"videostatsScheduledFlushWalltimeSeconds"` + VideostatsDefaultFlushIntervalSeconds int `json:"videostatsDefaultFlushIntervalSeconds"` + YoutubeRemarketingURL YoutubeRemarketingURL `json:"youtubeRemarketingUrl"` +} +type Thumbnail struct { + URL string `json:"url"` + Width int `json:"width"` + Height int `json:"height"` +} +type Thumbnails struct { + Thumbnails []Thumbnail `json:"thumbnails"` +} +type VideoDetails struct { + VideoID string `json:"videoId"` + Title string `json:"title"` + LengthSeconds string `json:"lengthSeconds"` + IsLive bool `json:"isLive,omitempty"` + Keywords []string `json:"keywords"` + ChannelID string `json:"channelId"` + IsOwnerViewing bool `json:"isOwnerViewing"` + ShortDescription string `json:"shortDescription"` + IsCrawlable bool `json:"isCrawlable"` + IsLiveDvrEnabled bool `json:"isLiveDvrEnabled,omitempty"` + Thumbnails Thumbnails `json:"thumbnail"` + LiveChunkReadahead int `json:"liveChunkReadahead,omitempty"` + AllowRatings bool `json:"allowRatings"` + ViewCount string `json:"viewCount"` + Author string `json:"author"` + IsPrivate bool `json:"isPrivate"` + IsUnpluggedCorpus bool `json:"isUnpluggedCorpus"` + IsLiveContent bool `json:"isLiveContent"` + IsLowLatencyLiveStream bool `json:"isLowLatencyLiveStream,omitempty"` + LatencyClass string `json:"latencyClass,omitempty"` +} +type Watermark struct { + Thumbnails []Thumbnails `json:"thumbnails"` +} +type FeaturedChannel struct { + StartTimeMs string `json:"startTimeMs"` + EndTimeMs string `json:"endTimeMs"` + Watermark Watermark `json:"watermark"` + TrackingParams string `json:"trackingParams"` +} +type PlayerAnnotationsExpandedRenderer struct { + FeaturedChannel FeaturedChannel `json:"featuredChannel"` + AllowSwipeDismiss bool `json:"allowSwipeDismiss"` +} +type Annotations struct { + PlayerAnnotationsExpandedRenderer PlayerAnnotationsExpandedRenderer `json:"playerAnnotationsExpandedRenderer"` +} +type AudioConfig struct { + LoudnessDb float64 `json:"loudnessDb"` + PerceptualLoudnessDb float64 `json:"perceptualLoudnessDb"` + EnablePerFormatLoudness bool `json:"enablePerFormatLoudness"` +} +type ExoPlayerConfig struct { + UseExoPlayer bool `json:"useExoPlayer"` + UseAdaptiveBitrate bool `json:"useAdaptiveBitrate"` + MaxInitialByteRate int `json:"maxInitialByteRate"` + MinDurationForQualityIncreaseMs int `json:"minDurationForQualityIncreaseMs"` + MaxDurationForQualityDecreaseMs int `json:"maxDurationForQualityDecreaseMs"` + MinDurationToRetainAfterDiscardMs int `json:"minDurationToRetainAfterDiscardMs"` + LowWatermarkMs int `json:"lowWatermarkMs"` + HighWatermarkMs int `json:"highWatermarkMs"` + LowPoolLoad float64 `json:"lowPoolLoad"` + HighPoolLoad float64 `json:"highPoolLoad"` + SufficientBandwidthOverhead float64 `json:"sufficientBandwidthOverhead"` + BufferChunkSizeKb int `json:"bufferChunkSizeKb"` + HTTPConnectTimeoutMs int `json:"httpConnectTimeoutMs"` + HTTPReadTimeoutMs int `json:"httpReadTimeoutMs"` + NumAudioSegmentsPerFetch int `json:"numAudioSegmentsPerFetch"` + NumVideoSegmentsPerFetch int `json:"numVideoSegmentsPerFetch"` + MinDurationForPlaybackStartMs int `json:"minDurationForPlaybackStartMs"` + EnableExoplayerReuse bool `json:"enableExoplayerReuse"` + UseRadioTypeForInitialQualitySelection bool `json:"useRadioTypeForInitialQualitySelection"` + BlacklistFormatOnError bool `json:"blacklistFormatOnError"` + EnableBandaidHTTPDataSource bool `json:"enableBandaidHttpDataSource"` + HTTPLoadTimeoutMs int `json:"httpLoadTimeoutMs"` + CanPlayHdDrm bool `json:"canPlayHdDrm"` + VideoBufferSegmentCount int `json:"videoBufferSegmentCount"` + AudioBufferSegmentCount int `json:"audioBufferSegmentCount"` + UseAbruptSplicing bool `json:"useAbruptSplicing"` + MinRetryCount int `json:"minRetryCount"` + MinChunksNeededToPreferOffline int `json:"minChunksNeededToPreferOffline"` + SecondsToMaxAggressiveness int `json:"secondsToMaxAggressiveness"` + EnableSurfaceviewResizeWorkaround bool `json:"enableSurfaceviewResizeWorkaround"` + EnableVp9IfThresholdsPass bool `json:"enableVp9IfThresholdsPass"` + MatchQualityToViewportOnUnfullscreen bool `json:"matchQualityToViewportOnUnfullscreen"` + LowAudioQualityConnTypes []string `json:"lowAudioQualityConnTypes"` + UseDashForLiveStreams bool `json:"useDashForLiveStreams"` + EnableLibvpxVideoTrackRenderer bool `json:"enableLibvpxVideoTrackRenderer"` + LowAudioQualityBandwidthThresholdBps int `json:"lowAudioQualityBandwidthThresholdBps"` + EnableVariableSpeedPlayback bool `json:"enableVariableSpeedPlayback"` + PreferOnesieBufferedFormat bool `json:"preferOnesieBufferedFormat"` + MinimumBandwidthSampleBytes int `json:"minimumBandwidthSampleBytes"` + UseDashForOtfAndCompletedLiveStreams bool `json:"useDashForOtfAndCompletedLiveStreams"` + DisableCacheAwareVideoFormatEvaluation bool `json:"disableCacheAwareVideoFormatEvaluation"` + UseLiveDvrForDashLiveStreams bool `json:"useLiveDvrForDashLiveStreams"` + CronetResetTimeoutOnRedirects bool `json:"cronetResetTimeoutOnRedirects"` + EmitVideoDecoderChangeEvents bool `json:"emitVideoDecoderChangeEvents"` + OnesieVideoBufferLoadTimeoutMs string `json:"onesieVideoBufferLoadTimeoutMs"` + OnesieVideoBufferReadTimeoutMs string `json:"onesieVideoBufferReadTimeoutMs"` + LibvpxEnableGl bool `json:"libvpxEnableGl"` + EnableVp9EncryptedIfThresholdsPass bool `json:"enableVp9EncryptedIfThresholdsPass"` + EnableOpus bool `json:"enableOpus"` + UsePredictedBuffer bool `json:"usePredictedBuffer"` + MaxReadAheadMediaTimeMs int `json:"maxReadAheadMediaTimeMs"` + UseMediaTimeCappedLoadControl bool `json:"useMediaTimeCappedLoadControl"` + AllowCacheOverrideToLowerQualitiesWithinRange int `json:"allowCacheOverrideToLowerQualitiesWithinRange"` + AllowDroppingUndecodedFrames bool `json:"allowDroppingUndecodedFrames"` + MinDurationForPlaybackRestartMs int `json:"minDurationForPlaybackRestartMs"` + ServerProvidedBandwidthHeader string `json:"serverProvidedBandwidthHeader"` + LiveOnlyPegStrategy string `json:"liveOnlyPegStrategy"` + EnableRedirectorHostFallback bool `json:"enableRedirectorHostFallback"` + EnableHighlyAvailableFormatFallbackOnPcr bool `json:"enableHighlyAvailableFormatFallbackOnPcr"` + RecordTrackRendererTimingEvents bool `json:"recordTrackRendererTimingEvents"` + MinErrorsForRedirectorHostFallback int `json:"minErrorsForRedirectorHostFallback"` + NonHardwareMediaCodecNames []string `json:"nonHardwareMediaCodecNames"` + EnableVp9IfInHardware bool `json:"enableVp9IfInHardware"` + EnableVp9EncryptedIfInHardware bool `json:"enableVp9EncryptedIfInHardware"` + UseOpusMedAsLowQualityAudio bool `json:"useOpusMedAsLowQualityAudio"` + MinErrorsForPcrFallback int `json:"minErrorsForPcrFallback"` + UseStickyRedirectHTTPDataSource bool `json:"useStickyRedirectHttpDataSource"` + OnlyVideoBandwidth bool `json:"onlyVideoBandwidth"` + UseRedirectorOnNetworkChange bool `json:"useRedirectorOnNetworkChange"` + EnableMaxReadaheadAbrThreshold bool `json:"enableMaxReadaheadAbrThreshold"` + CacheCheckDirectoryWritabilityOnce bool `json:"cacheCheckDirectoryWritabilityOnce"` + PredictorType string `json:"predictorType"` + SlidingPercentile float64 `json:"slidingPercentile"` + SlidingWindowSize int `json:"slidingWindowSize"` + MaxFrameDropIntervalMs int `json:"maxFrameDropIntervalMs"` + IgnoreLoadTimeoutForFallback bool `json:"ignoreLoadTimeoutForFallback"` + ServerBweMultiplier int `json:"serverBweMultiplier"` + DrmMaxKeyfetchDelayMs int `json:"drmMaxKeyfetchDelayMs"` + MaxResolutionForWhiteNoise int `json:"maxResolutionForWhiteNoise"` + WhiteNoiseRenderEffectMode string `json:"whiteNoiseRenderEffectMode"` + EnableLibvpxHdr bool `json:"enableLibvpxHdr"` + EnableCacheAwareStreamSelection bool `json:"enableCacheAwareStreamSelection"` + UseExoCronetDataSource bool `json:"useExoCronetDataSource"` + WhiteNoiseScale int `json:"whiteNoiseScale"` + WhiteNoiseOffset int `json:"whiteNoiseOffset"` + PreventVideoFrameLaggingWithLibvpx bool `json:"preventVideoFrameLaggingWithLibvpx"` + EnableMediaCodecHdr bool `json:"enableMediaCodecHdr"` + EnableMediaCodecSwHdr bool `json:"enableMediaCodecSwHdr"` + LiveOnlyWindowChunks int `json:"liveOnlyWindowChunks"` + BearerMinDurationToRetainAfterDiscardMs []int `json:"bearerMinDurationToRetainAfterDiscardMs"` + ForceWidevineL3 bool `json:"forceWidevineL3"` + UseAverageBitrate bool `json:"useAverageBitrate"` + UseMedialibAudioTrackRendererForLive bool `json:"useMedialibAudioTrackRendererForLive"` + UseExoPlayerV2 bool `json:"useExoPlayerV2"` + EnableManifestlessResumeVideo bool `json:"enableManifestlessResumeVideo"` + LogMediaRequestEventsToCsi bool `json:"logMediaRequestEventsToCsi"` + OnesieFixNonZeroStartTimeFormatSelection bool `json:"onesieFixNonZeroStartTimeFormatSelection"` + LiveOnlyReadaheadStepSizeChunks int `json:"liveOnlyReadaheadStepSizeChunks"` + LiveOnlyBufferHealthHalfLifeSeconds int `json:"liveOnlyBufferHealthHalfLifeSeconds"` + LiveOnlyMinBufferHealthRatio float64 `json:"liveOnlyMinBufferHealthRatio"` + LiveOnlyMinLatencyToSeekRatio int `json:"liveOnlyMinLatencyToSeekRatio"` + ManifestlessPartialChunkStrategy string `json:"manifestlessPartialChunkStrategy"` + IgnoreViewportSizeWhenSticky bool `json:"ignoreViewportSizeWhenSticky"` + EnableLibvpxFallback bool `json:"enableLibvpxFallback"` + DisableLibvpxLoopFilter bool `json:"disableLibvpxLoopFilter"` + EnableVpxMediaView bool `json:"enableVpxMediaView"` + HdrMinScreenBrightness int `json:"hdrMinScreenBrightness"` + HdrMaxScreenBrightnessThreshold int `json:"hdrMaxScreenBrightnessThreshold"` + OnesieDataSourceAboveCacheDataSource bool `json:"onesieDataSourceAboveCacheDataSource"` + HTTPNonplayerLoadTimeoutMs int `json:"httpNonplayerLoadTimeoutMs"` + NumVideoSegmentsPerFetchStrategy string `json:"numVideoSegmentsPerFetchStrategy"` + MaxVideoDurationPerFetchMs int `json:"maxVideoDurationPerFetchMs"` + MaxVideoEstimatedLoadDurationMs int `json:"maxVideoEstimatedLoadDurationMs"` + EstimatedServerClockHalfLife int `json:"estimatedServerClockHalfLife"` + EstimatedServerClockStrictOffset bool `json:"estimatedServerClockStrictOffset"` + MinReadAheadMediaTimeMs int `json:"minReadAheadMediaTimeMs"` + ReadAheadGrowthRate int `json:"readAheadGrowthRate"` + UseDynamicReadAhead bool `json:"useDynamicReadAhead"` + UseYtVodMediaSourceForV2 bool `json:"useYtVodMediaSourceForV2"` + EnableV2Gapless bool `json:"enableV2Gapless"` + UseLiveHeadTimeMillis bool `json:"useLiveHeadTimeMillis"` + AllowTrackSelectionWithUpdatedVideoItagsForExoV2 bool `json:"allowTrackSelectionWithUpdatedVideoItagsForExoV2"` + MaxAllowableTimeBeforeMediaTimeUpdateSec int `json:"maxAllowableTimeBeforeMediaTimeUpdateSec"` + EnableDynamicHdr bool `json:"enableDynamicHdr"` + V2PerformEarlyStreamSelection bool `json:"v2PerformEarlyStreamSelection"` + V2UsePlaybackStreamSelectionResult bool `json:"v2UsePlaybackStreamSelectionResult"` + V2MinTimeBetweenAbrReevaluationMs int `json:"v2MinTimeBetweenAbrReevaluationMs"` + AvoidReusePlaybackAcrossLoadvideos bool `json:"avoidReusePlaybackAcrossLoadvideos"` + EnableInfiniteNetworkLoadingRetries bool `json:"enableInfiniteNetworkLoadingRetries"` + ReportExoPlayerStateOnTransition bool `json:"reportExoPlayerStateOnTransition"` + ManifestlessSequenceMethod string `json:"manifestlessSequenceMethod"` + UseLiveHeadWindow bool `json:"useLiveHeadWindow"` + EnableDynamicHdrInHardware bool `json:"enableDynamicHdrInHardware"` + UltralowAudioQualityBandwidthThresholdBps int `json:"ultralowAudioQualityBandwidthThresholdBps"` + RetryLiveNetNocontentWithDelay bool `json:"retryLiveNetNocontentWithDelay"` + IgnoreUnneededSeeksToLiveHead bool `json:"ignoreUnneededSeeksToLiveHead"` + AdaptiveLiveHeadWindow bool `json:"adaptiveLiveHeadWindow"` + DrmMetricsQoeLoggingFraction float64 `json:"drmMetricsQoeLoggingFraction"` + LiveNetNocontentMaximumErrors int `json:"liveNetNocontentMaximumErrors"` + WaitForDrmLicenseBeforeProcessingAndroidStuckBufferfull bool `json:"waitForDrmLicenseBeforeProcessingAndroidStuckBufferfull"` + UseTimeSeriesBufferPrediction bool `json:"useTimeSeriesBufferPrediction"` +} +type PlaybackStartConfig struct { + StartTimeToleranceBeforeMs string `json:"startTimeToleranceBeforeMs"` +} +type AdRequestConfig struct { + FilterTimeEventsOnDelta int `json:"filterTimeEventsOnDelta"` + UseCriticalExecOnAdsPrep bool `json:"useCriticalExecOnAdsPrep"` + UserCriticalExecOnAdsProcessing bool `json:"userCriticalExecOnAdsProcessing"` + EnableCountdownNextToThumbnailAndroid bool `json:"enableCountdownNextToThumbnailAndroid"` + PreskipScalingFactorAndroid float64 `json:"preskipScalingFactorAndroid"` + PreskipPaddingAndroid int `json:"preskipPaddingAndroid"` +} +type NetworkProtocolConfig struct { + UseQuic bool `json:"useQuic"` +} +type AndroidCronetResponsePriority struct { + PriorityValue string `json:"priorityValue"` +} +type AndroidMetadataNetworkConfig struct { + CoalesceRequests bool `json:"coalesceRequests"` +} +type AndroidNetworkStackConfig struct { + NetworkStack string `json:"networkStack"` + AndroidCronetResponsePriority AndroidCronetResponsePriority `json:"androidCronetResponsePriority"` + AndroidMetadataNetworkConfig AndroidMetadataNetworkConfig `json:"androidMetadataNetworkConfig"` +} +type LidarSdkConfig struct { + EnableActiveViewReporter bool `json:"enableActiveViewReporter"` + UseMediaTime bool `json:"useMediaTime"` + SendTosMetrics bool `json:"sendTosMetrics"` + UsePlayerState bool `json:"usePlayerState"` + EnableIosAppStateCheck bool `json:"enableIosAppStateCheck"` + EnableImprovedSizeReportingAndroid bool `json:"enableImprovedSizeReportingAndroid"` + EnableIsAndroidVideoAlwaysMeasurable bool `json:"enableIsAndroidVideoAlwaysMeasurable"` +} +type InitialBandwidthEstimates struct { + DetailedNetworkType string `json:"detailedNetworkType"` + BandwidthBps string `json:"bandwidthBps"` +} +type AndroidMedialibConfig struct { + IsItag18MainProfile bool `json:"isItag18MainProfile"` + DashManifestVersion int `json:"dashManifestVersion"` + InitialBandwidthEstimates []InitialBandwidthEstimates `json:"initialBandwidthEstimates"` + ViewportSizeFraction float64 `json:"viewportSizeFraction"` + SelectLowQualityStreamsWithHighBitrates bool `json:"selectLowQualityStreamsWithHighBitrates"` + EnablePrerollPrebuffer bool `json:"enablePrerollPrebuffer"` + PrebufferOptimizeForViewportSize bool `json:"prebufferOptimizeForViewportSize"` + HpqViewportSizeFraction float64 `json:"hpqViewportSizeFraction"` +} +type PlayerControlsConfig struct { + ShowCachedInTimebar bool `json:"showCachedInTimebar"` +} +type VariableSpeedConfig struct { + ShowVariableSpeedDisabledDialog bool `json:"showVariableSpeedDisabledDialog"` +} +type DecodeQualityConfig struct { + MaximumVideoDecodeVerticalResolution int `json:"maximumVideoDecodeVerticalResolution"` +} +type VrConfig struct { + AllowVr bool `json:"allowVr"` + AllowSubtitles bool `json:"allowSubtitles"` + ShowHqButton bool `json:"showHqButton"` + SphericalDirectionLoggingEnabled bool `json:"sphericalDirectionLoggingEnabled"` + EnableAndroidVr180MagicWindow bool `json:"enableAndroidVr180MagicWindow"` + EnableAndroidMagicWindowEduOverlay bool `json:"enableAndroidMagicWindowEduOverlay"` + MagicWindowEduOverlayText string `json:"magicWindowEduOverlayText"` + MagicWindowEduOverlayAnimationURL string `json:"magicWindowEduOverlayAnimationUrl"` + EnableMagicWindowZoom bool `json:"enableMagicWindowZoom"` +} +type QoeStatsClientConfig struct { + BatchedEntriesPeriodMs string `json:"batchedEntriesPeriodMs"` +} +type AndroidPlayerStatsConfig struct { + UsePblForAttestationReporting bool `json:"usePblForAttestationReporting"` + UsePblForHeartbeatReporting bool `json:"usePblForHeartbeatReporting"` + UsePblForPlaybacktrackingReporting bool `json:"usePblForPlaybacktrackingReporting"` + UsePblForQoeReporting bool `json:"usePblForQoeReporting"` + ChangeCpnOnFatalPlaybackError bool `json:"changeCpnOnFatalPlaybackError"` +} +type StickyQualitySelectionConfig struct { + StickySelectionType string `json:"stickySelectionType"` + ExpirationTimeSinceLastManualVideoQualitySelectionMs string `json:"expirationTimeSinceLastManualVideoQualitySelectionMs"` + ExpirationTimeSinceLastPlaybackStartMs string `json:"expirationTimeSinceLastPlaybackStartMs"` + StickyCeilingOverridesSimpleBitrateCap bool `json:"stickyCeilingOverridesSimpleBitrateCap"` +} +type AdSurveyRequestConfig struct { + UseGetRequests bool `json:"useGetRequests"` +} +type LivePlayerConfig struct { + LiveReadaheadSeconds int `json:"liveReadaheadSeconds"` + LiveHeadWindowSeconds int `json:"liveHeadWindowSeconds"` +} +type RetryConfig struct { + RetryEligibleErrors []string `json:"retryEligibleErrors"` + RetryUnderSameConditionAttempts int `json:"retryUnderSameConditionAttempts"` + RetryWithNewSurfaceAttempts int `json:"retryWithNewSurfaceAttempts"` + ProgressiveFallbackOnNonNetworkErrors bool `json:"progressiveFallbackOnNonNetworkErrors"` + L3FallbackOnDrmErrors bool `json:"l3FallbackOnDrmErrors"` + RetryAfterCacheRemoval bool `json:"retryAfterCacheRemoval"` + WidevineL3EnforcedFallbackOnDrmErrors bool `json:"widevineL3EnforcedFallbackOnDrmErrors"` + ExoProxyableFormatFallback bool `json:"exoProxyableFormatFallback"` + MaxPlayerRetriesWhenNetworkUnavailable int `json:"maxPlayerRetriesWhenNetworkUnavailable"` + RetryWithLibvpx bool `json:"retryWithLibvpx"` + SuppressFatalErrorAfterStop bool `json:"suppressFatalErrorAfterStop"` + FallbackFromHfrToSfrOnFormatDecodeError bool `json:"fallbackFromHfrToSfrOnFormatDecodeError"` +} +type CmsPathProbeConfig struct { + CmsPathProbeDelayMs int `json:"cmsPathProbeDelayMs"` +} +type DynamicReadaheadConfig struct { + MaxReadAheadMediaTimeMs int `json:"maxReadAheadMediaTimeMs"` + MinReadAheadMediaTimeMs int `json:"minReadAheadMediaTimeMs"` + ReadAheadGrowthRateMs int `json:"readAheadGrowthRateMs"` + ReadAheadWatermarkMarginRatio int `json:"readAheadWatermarkMarginRatio"` + MinReadAheadWatermarkMarginMs int `json:"minReadAheadWatermarkMarginMs"` + MaxReadAheadWatermarkMarginMs int `json:"maxReadAheadWatermarkMarginMs"` + ShouldIncorporateNetworkActiveState bool `json:"shouldIncorporateNetworkActiveState"` +} +type MediaUstreamerRequestConfig struct { + EnableVideoPlaybackRequest bool `json:"enableVideoPlaybackRequest"` + VideoPlaybackUstreamerConfig string `json:"videoPlaybackUstreamerConfig"` + VideoPlaybackPostEmptyBody bool `json:"videoPlaybackPostEmptyBody"` + IsVideoPlaybackRequestIdempotent bool `json:"isVideoPlaybackRequestIdempotent"` +} +type PredictedReadaheadConfig struct { + MinReadaheadMs int `json:"minReadaheadMs"` + MaxReadaheadMs int `json:"maxReadaheadMs"` +} +type MediaFetchRetryConfig struct { + InitialDelayMs int `json:"initialDelayMs"` + BackoffFactor float64 `json:"backoffFactor"` + MaximumDelayMs int `json:"maximumDelayMs"` + JitterFactor float64 `json:"jitterFactor"` +} +type NextRequestPolicy struct { + TargetAudioReadaheadMs int `json:"targetAudioReadaheadMs"` + TargetVideoReadaheadMs int `json:"targetVideoReadaheadMs"` +} +type ServerReadaheadConfig struct { + Enable bool `json:"enable"` + NextRequestPolicy NextRequestPolicy `json:"nextRequestPolicy"` +} +type MediaCommonConfig struct { + DynamicReadaheadConfig DynamicReadaheadConfig `json:"dynamicReadaheadConfig"` + MediaUstreamerRequestConfig MediaUstreamerRequestConfig `json:"mediaUstreamerRequestConfig"` + PredictedReadaheadConfig PredictedReadaheadConfig `json:"predictedReadaheadConfig"` + MediaFetchRetryConfig MediaFetchRetryConfig `json:"mediaFetchRetryConfig"` + MediaFetchMaximumServerErrors int `json:"mediaFetchMaximumServerErrors"` + MediaFetchMaximumNetworkErrors int `json:"mediaFetchMaximumNetworkErrors"` + MediaFetchMaximumErrors int `json:"mediaFetchMaximumErrors"` + ServerReadaheadConfig ServerReadaheadConfig `json:"serverReadaheadConfig"` +} +type PlayerGestureConfig struct { + DownAndOutLandscapeAllowed bool `json:"downAndOutLandscapeAllowed"` + DownAndOutPortraitAllowed bool `json:"downAndOutPortraitAllowed"` +} +type PlayerConfig struct { + AudioConfig AudioConfig `json:"audioConfig"` + ExoPlayerConfig ExoPlayerConfig `json:"exoPlayerConfig"` + PlaybackStartConfig PlaybackStartConfig `json:"playbackStartConfig"` + AdRequestConfig AdRequestConfig `json:"adRequestConfig"` + NetworkProtocolConfig NetworkProtocolConfig `json:"networkProtocolConfig"` + AndroidNetworkStackConfig AndroidNetworkStackConfig `json:"androidNetworkStackConfig"` + LidarSdkConfig LidarSdkConfig `json:"lidarSdkConfig"` + AndroidMedialibConfig AndroidMedialibConfig `json:"androidMedialibConfig"` + PlayerControlsConfig PlayerControlsConfig `json:"playerControlsConfig"` + VariableSpeedConfig VariableSpeedConfig `json:"variableSpeedConfig"` + DecodeQualityConfig DecodeQualityConfig `json:"decodeQualityConfig"` + VrConfig VrConfig `json:"vrConfig"` + QoeStatsClientConfig QoeStatsClientConfig `json:"qoeStatsClientConfig"` + AndroidPlayerStatsConfig AndroidPlayerStatsConfig `json:"androidPlayerStatsConfig"` + StickyQualitySelectionConfig StickyQualitySelectionConfig `json:"stickyQualitySelectionConfig"` + AdSurveyRequestConfig AdSurveyRequestConfig `json:"adSurveyRequestConfig"` + LivePlayerConfig LivePlayerConfig `json:"livePlayerConfig,omitempty"` + RetryConfig RetryConfig `json:"retryConfig"` + CmsPathProbeConfig CmsPathProbeConfig `json:"cmsPathProbeConfig"` + MediaCommonConfig MediaCommonConfig `json:"mediaCommonConfig"` + PlayerGestureConfig PlayerGestureConfig `json:"playerGestureConfig"` +} +type PlayerStoryboardSpecRenderer struct { + Spec string `json:"spec"` + RecommendedLevel int `json:"recommendedLevel"` +} +type Storyboards struct { + PlayerStoryboardSpecRenderer PlayerStoryboardSpecRenderer `json:"playerStoryboardSpecRenderer"` +} +type PlayerAttestationRenderer struct { + Challenge string `json:"challenge"` +} +type Attestation struct { + PlayerAttestationRenderer PlayerAttestationRenderer `json:"playerAttestationRenderer"` +} +type Visibility struct { + Types string `json:"types"` +} +type LoggingDirectives struct { + TrackingParams string `json:"trackingParams"` + Visibility Visibility `json:"visibility"` +} +type PlayerSettingsMenuData struct { + LoggingDirectives LoggingDirectives `json:"loggingDirectives"` +} - // InitRange is only available for adaptive formats - InitRange *struct { - Start string `json:"start"` - End string `json:"end"` - } `json:"initRange"` +type Runs struct { + Text string `json:"text"` +} - // IndexRange is only available for adaptive formats - IndexRange *struct { - Start string `json:"start"` - End string `json:"end"` - } `json:"indexRange"` +type Name struct { + Runs []Runs `json:"runs"` } -type Thumbnails []Thumbnail +type CaptionTracks struct { + BaseURL string `json:"baseUrl"` + Name Name `json:"name"` + VssID string `json:"vssId"` + LanguageCode string `json:"languageCode"` + Kind string `json:"kind"` + IsTranslatable bool `json:"isTranslatable"` +} -type Thumbnail struct { - URL string - Width uint - Height uint +type AudioTracks struct { + CaptionTrackIndices []int `json:"captionTrackIndices"` +} + +type PlayerCaptionsTracklistRenderer struct { + CaptionTracks []CaptionTracks `json:"captionTracks"` + AudioTracks []AudioTracks `json:"audioTracks"` + DefaultAudioTrackIndex int `json:"defaultAudioTrackIndex"` +} + +type Captions struct { + PlayerCaptionsTracklistRenderer PlayerCaptionsTracklistRenderer `json:"playerCaptionsTracklistRenderer"` +} + +type PlayerErrorMessageRenderer struct { + Reason Reason `json:"reason"` + Thumbnail Thumbnail `json:"thumbnail"` +} +type ErrorScreen struct { + PlayerErrorMessageRenderer PlayerErrorMessageRenderer `json:"playerErrorMessageRenderer"` +} + +type Reason struct { + Runs []Runs `json:"runs"` } diff --git a/video.go b/video.go index 716283c9..f4c16ad6 100644 --- a/video.go +++ b/video.go @@ -95,18 +95,19 @@ func (v *Video) extractDataFromPlayerResponse(prData playerResponseData) error { v.Title = prData.VideoDetails.Title v.Description = prData.VideoDetails.ShortDescription v.Author = prData.VideoDetails.Author - v.Thumbnails = prData.VideoDetails.Thumbnail.Thumbnails + v.Thumbnails = prData.VideoDetails.Thumbnails - if seconds, _ := strconv.Atoi(prData.Microformat.PlayerMicroformatRenderer.LengthSeconds); seconds > 0 { + if seconds, _ := strconv.Atoi(prData.VideoDetails.LengthSeconds); seconds > 0 { v.Duration = time.Duration(seconds) * time.Second } - if str := prData.Microformat.PlayerMicroformatRenderer.PublishDate; str != "" { - v.PublishDate, _ = time.Parse(dateFormat, str) - } + // couldn't find any publishdate for now, have to add an another request + // if str := prData.VideoDetails.Microformat.PlayerMicroformatRenderer.PublishDate; str != "" { + // v.PublishDate, _ = time.Parse(dateFormat, str) + // } // Assign Streams - v.Formats = append(prData.StreamingData.Formats, prData.StreamingData.AdaptiveFormats...) + v.Formats = append(prData.StreamingData.Formats, prData.StreamingData.Formats...) if len(v.Formats) == 0 { return errors.New("no formats found in the server's answer") } From 0440d435c983f7985b1ea1cbe7fef81a77248ab1 Mon Sep 17 00:00:00 2001 From: Billaids Date: Mon, 24 Jan 2022 09:29:23 +0100 Subject: [PATCH 6/6] refactor: clean up response_data Signed-off-by: Billaids --- response_data.go | 111 ----------------------------------------------- 1 file changed, 111 deletions(-) diff --git a/response_data.go b/response_data.go index 0bee0cc9..85eae016 100644 --- a/response_data.go +++ b/response_data.go @@ -1,116 +1,5 @@ package youtube -// type playerResponseData struct { -// PlayabilityStatus struct { -// Status string `json:"status"` -// Reason string `json:"reason"` -// PlayableInEmbed bool `json:"playableInEmbed"` -// Miniplayer struct { -// MiniplayerRenderer struct { -// PlaybackMode string `json:"playbackMode"` -// } `json:"miniplayerRenderer"` -// } `json:"miniplayer"` -// ContextParams string `json:"contextParams"` -// } `json:"playabilityStatus"` -// StreamingData struct { -// ExpiresInSeconds string `json:"expiresInSeconds"` -// Formats []Format `json:"formats"` -// AdaptiveFormats []Format `json:"adaptiveFormats"` -// DashManifestURL string `json:"dashManifestUrl"` -// HlsManifestURL string `json:"hlsManifestUrl"` -// } `json:"streamingData"` -// VideoDetails struct { -// VideoID string `json:"videoId"` -// Title string `json:"title"` -// LengthSeconds string `json:"lengthSeconds"` -// Keywords []string `json:"keywords"` -// ChannelID string `json:"channelId"` -// IsOwnerViewing bool `json:"isOwnerViewing"` -// ShortDescription string `json:"shortDescription"` -// IsCrawlable bool `json:"isCrawlable"` -// Thumbnail struct { -// Thumbnails []Thumbnail `json:"thumbnails"` -// } `json:"thumbnail"` -// AverageRating float64 `json:"averageRating"` -// AllowRatings bool `json:"allowRatings"` -// ViewCount string `json:"viewCount"` -// Author string `json:"author"` -// IsPrivate bool `json:"isPrivate"` -// IsUnpluggedCorpus bool `json:"isUnpluggedCorpus"` -// IsLiveContent bool `json:"isLiveContent"` -// } `json:"videoDetails"` -// Microformat struct { -// PlayerMicroformatRenderer struct { -// Thumbnail struct { -// Thumbnails []struct { -// URL string `json:"url"` -// Width int `json:"width"` -// Height int `json:"height"` -// } `json:"thumbnails"` -// } `json:"thumbnail"` -// Title struct { -// SimpleText string `json:"simpleText"` -// } `json:"title"` -// Description struct { -// SimpleText string `json:"simpleText"` -// } `json:"description"` -// LengthSeconds string `json:"lengthSeconds"` -// OwnerProfileURL string `json:"ownerProfileUrl"` -// ExternalChannelID string `json:"externalChannelId"` -// IsFamilySafe bool `json:"isFamilySafe"` -// AvailableCountries []string `json:"availableCountries"` -// IsUnlisted bool `json:"isUnlisted"` -// HasYpcMetadata bool `json:"hasYpcMetadata"` -// ViewCount string `json:"viewCount"` -// Category string `json:"category"` -// PublishDate string `json:"publishDate"` -// OwnerChannelName string `json:"ownerChannelName"` -// UploadDate string `json:"uploadDate"` -// } `json:"playerMicroformatRenderer"` -// } `json:"microformat"` -// } - -// type Format struct { -// ItagNo int `json:"itag"` -// URL string `json:"url"` -// MimeType string `json:"mimeType"` -// Quality string `json:"quality"` -// Cipher string `json:"signatureCipher"` -// Bitrate int `json:"bitrate"` -// FPS int `json:"fps"` -// Width int `json:"width"` -// Height int `json:"height"` -// LastModified string `json:"lastModified"` -// ContentLength int64 `json:"contentLength,string"` -// QualityLabel string `json:"qualityLabel"` -// ProjectionType string `json:"projectionType"` -// AverageBitrate int `json:"averageBitrate"` -// AudioQuality string `json:"audioQuality"` -// ApproxDurationMs string `json:"approxDurationMs"` -// AudioSampleRate string `json:"audioSampleRate"` -// AudioChannels int `json:"audioChannels"` - -// // InitRange is only available for adaptive formats -// InitRange *struct { -// Start string `json:"start"` -// End string `json:"end"` -// } `json:"initRange"` - -// // IndexRange is only available for adaptive formats -// IndexRange *struct { -// Start string `json:"start"` -// End string `json:"end"` -// } `json:"indexRange"` -// } - -// type Thumbnails []Thumbnail - -// type Thumbnail struct { -// URL string -// Width uint -// Height uint -// } - type playerResponseData struct { ResponseContext ResponseContext `json:"responseContext"` TrackingParams string `json:"trackingParams"`