1
+ use once_cell:: sync:: Lazy ;
1
2
use reqwest:: {
2
- header:: { HeaderMap , HeaderValue , COOKIE } ,
3
+ header:: { HeaderMap , HeaderName , HeaderValue , COOKIE } ,
3
4
Client ,
4
5
} ;
5
6
use reqwest_middleware:: { ClientBuilder , ClientWithMiddleware } ;
@@ -17,11 +18,12 @@ use crate::{
17
18
constants:: BASE_URL ,
18
19
info_extras:: { get_media, get_related_videos} ,
19
20
stream:: { NonLiveStream , NonLiveStreamOptions , Stream } ,
20
- structs:: { PlayerResponse , VideoError , VideoInfo , VideoOptions } ,
21
+ structs:: { PlayerResponse , VideoError , VideoInfo , VideoOptions , YTConfig } ,
21
22
utils:: {
22
23
between, choose_format, clean_video_details, get_functions, get_html, get_html5player,
23
- get_random_v6_ip, get_video_id, is_not_yet_broadcasted, is_play_error, is_private_video,
24
- is_rental, parse_live_video_formats, parse_video_formats, sort_formats,
24
+ get_random_v6_ip, get_video_id, get_ytconfig, is_age_restricted_from_html,
25
+ is_not_yet_broadcasted, is_play_error, is_private_video, is_rental,
26
+ parse_live_video_formats, parse_video_formats, sort_formats,
25
27
} ,
26
28
} ;
27
29
@@ -120,7 +122,7 @@ impl Video {
120
122
121
123
let response = get_html ( client, url_parsed. as_str ( ) , None ) . await ?;
122
124
123
- let ( player_response, initial_response) : ( PlayerResponse , serde_json:: Value ) = {
125
+ let ( mut player_response, initial_response) : ( PlayerResponse , serde_json:: Value ) = {
124
126
let document = Html :: parse_document ( & response) ;
125
127
let scripts_selector = Selector :: parse ( "script" ) . unwrap ( ) ;
126
128
let player_response_string = document
@@ -158,14 +160,23 @@ impl Video {
158
160
return Err ( VideoError :: VideoNotFound ) ;
159
161
}
160
162
161
- if is_private_video ( & player_response) {
163
+ let is_age_restricted = is_age_restricted_from_html ( & player_response, & response) ;
164
+
165
+ if is_private_video ( & player_response) && !is_age_restricted {
162
166
return Err ( VideoError :: VideoIsPrivate ) ;
163
167
}
164
168
165
- if player_response. streaming_data . is_none ( )
166
- || is_rental ( & player_response)
167
- || is_not_yet_broadcasted ( & player_response)
168
- {
169
+ if is_age_restricted {
170
+ let embed_ytconfig = self . get_embeded_ytconfig ( & response) . await ?;
171
+
172
+ let player_response_new =
173
+ serde_json:: from_str :: < PlayerResponse > ( & embed_ytconfig) . unwrap ( ) ;
174
+
175
+ player_response. streaming_data = player_response_new. streaming_data ;
176
+ player_response. storyboards = player_response_new. storyboards ;
177
+ }
178
+
179
+ if is_rental ( & player_response) || is_not_yet_broadcasted ( & player_response) {
169
180
return Err ( VideoError :: VideoSourceNotFound ) ;
170
181
}
171
182
@@ -456,6 +467,68 @@ impl Video {
456
467
pub ( crate ) fn get_options ( & self ) -> VideoOptions {
457
468
self . options . clone ( )
458
469
}
470
+
471
+ #[ cfg_attr( feature = "performance_analysis" , flamer:: flame) ]
472
+ async fn get_embeded_ytconfig ( & self , html : & str ) -> Result < String , VideoError > {
473
+ let ytcfg = get_ytconfig ( html) ?;
474
+
475
+ // This client can access age restricted videos (unless the uploader has disabled the 'allow embedding' option)
476
+ // See: https://github.com/yt-dlp/yt-dlp/blob/28d485714fef88937c82635438afba5db81f9089/yt_dlp/extractor/youtube.py#L231
477
+ let query = serde_json:: json!( {
478
+ "context" : {
479
+ "client" : {
480
+ "clientName" : "TVHTML5_SIMPLY_EMBEDDED_PLAYER" ,
481
+ "clientVersion" : "2.0" ,
482
+ "hl" : "en" ,
483
+ "clientScreen" : "EMBED" ,
484
+ } ,
485
+ "thirdParty" : {
486
+ "embedUrl" : "https://google.com" ,
487
+ } ,
488
+ } ,
489
+ "playbackContext" : {
490
+ "contentPlaybackContext" : {
491
+ "signatureTimestamp" : ytcfg. sts. unwrap_or( 0 ) ,
492
+ "html5Preference" : "HTML5_PREF_WANTS" ,
493
+ } ,
494
+ } ,
495
+ "videoId" : self . get_video_id( ) ,
496
+ } ) ;
497
+
498
+ static CONFIGS : Lazy < ( HeaderMap , & str ) > = Lazy :: new ( || {
499
+ use std:: str:: FromStr ;
500
+
501
+ ( HeaderMap :: from_iter ( [
502
+ ( HeaderName :: from_str ( "content-type" ) . unwrap ( ) , HeaderValue :: from_str ( "application/json" ) . unwrap ( ) ) ,
503
+ ( HeaderName :: from_str ( "X-Youtube-Client-Name" ) . unwrap ( ) , HeaderValue :: from_str ( "85" ) . unwrap ( ) ) ,
504
+ ( HeaderName :: from_str ( "X-Youtube-Client-Version" ) . unwrap ( ) , HeaderValue :: from_str ( "2.0" ) . unwrap ( ) ) ,
505
+ ( HeaderName :: from_str ( "Origin" ) . unwrap ( ) , HeaderValue :: from_str ( "https://www.youtube.com" ) . unwrap ( ) ) ,
506
+ ( HeaderName :: from_str ( "User-Agent" ) . unwrap ( ) , HeaderValue :: from_str ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3513.0 Safari/537.36" ) . unwrap ( ) ) ,
507
+ ( HeaderName :: from_str ( "Referer" ) . unwrap ( ) , HeaderValue :: from_str ( "https://www.youtube.com/" ) . unwrap ( ) ) ,
508
+ ( HeaderName :: from_str ( "Accept" ) . unwrap ( ) , HeaderValue :: from_str ( "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ) . unwrap ( ) ) ,
509
+ ( HeaderName :: from_str ( "Accept-Language" ) . unwrap ( ) , HeaderValue :: from_str ( "en-US,en;q=0.5" ) . unwrap ( ) ) ,
510
+ ( HeaderName :: from_str ( "Accept-Encoding" ) . unwrap ( ) , HeaderValue :: from_str ( "gzip, deflate" ) . unwrap ( ) ) ,
511
+ ] ) , "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" )
512
+ } ) ;
513
+
514
+ let response = self
515
+ . client
516
+ . post ( "https://www.youtube.com/youtubei/v1/player" )
517
+ . headers ( CONFIGS . 0 . clone ( ) )
518
+ . query ( & [ ( "key" , CONFIGS . 1 ) ] )
519
+ . json ( & query)
520
+ . send ( )
521
+ . await
522
+ . map_err ( VideoError :: ReqwestMiddleware ) ?;
523
+
524
+ let response = response
525
+ . error_for_status ( )
526
+ . map_err ( VideoError :: Reqwest ) ?
527
+ . text ( )
528
+ . await ?;
529
+
530
+ Ok ( response)
531
+ }
459
532
}
460
533
461
534
async fn get_m3u8 (
0 commit comments