3 from net
import request_decoded
4 from contextlib
import ExitStack
6 from urllib
.parse
import urlsplit
, parse_qs
8 from datetime
import timedelta
11 from time
import strftime
, gmtime
13 def main(yt
, client
='TVHTML5_SIMPLY_EMBEDDED_PLAYER', version
=None, kbps
=inf
,
14 *, cache
=False, audio
=False, separate
=False):
16 'TVHTML5_SIMPLY_EMBEDDED_PLAYER': "2.0",
21 version
= CLIENTS
[client
]
23 key
= 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
26 "clientVersion": version
28 if client
== 'ANDROID':
29 agent
= f
'com.google.android.youtube/{version}'
30 client_obj
["androidSdkVersion"] = 31
36 if split
.netloc
== 'youtu.be':
39 assert ('.' + split
.netloc
).endswith('.youtube.com')
40 if split
.path
in {'/watch', '/watch_popup'}:
41 [yt
] = parse_qs(split
.query
)['v']
43 assert split
.path
.startswith((
44 '/shorts/', '/live/', '/v/', '/embed/'))
45 yt
= split
.path
.split('/', 2)[2]
46 assert split
.scheme
in {'http', 'https'}
47 assert re
.fullmatch(r
'[\w-]{11}', yt
, re
.ASCII
)
49 with
ExitStack() as cleanup
:
50 [header
, yt
] = request_decoded(
51 'https://www.youtube.com/youtubei/v1/player',
53 ('User-Agent', agent
),
54 ('Content-Type', 'application/json'),
55 ('X-Goog-Api-Key', key
),
58 "context": {"client": client_obj
},
59 "videoId": yt
}).encode('ascii'),
60 types
=('application/json',),
61 cleanup
=cleanup
, cache
=cache
)
64 if 'videoDetails' in yt
:
65 d
= yt
['videoDetails']
67 stderr
.write(f
'{d["title"]}\n')
68 if 'shortDescription' in d
:
69 stderr
.write(f
'{d["shortDescription"]}\n')
70 length
= timedelta(seconds
=int(d
["lengthSeconds"]))
71 stderr
.write(f
'{length}')
73 stderr
.write(f
', {d["viewCount"]} views, author {d["author"]}')
74 for attr
in ('isPrivate', 'isUnpluggedCorpus', 'isLiveContent'):
76 stderr
.write(f
', {attr}')
77 for attr
in ('isCrawlable', 'allowRatings'):
79 stderr
.write(f
', not {attr}')
82 s
= yt
['playabilityStatus']
83 if s
['status'] != 'OK':
84 stderr
.write(f
'{s["status"]}: ')
85 if 'reasonTitle' in s
:
86 stderr
.write(f
'{s["reasonTitle"]}\n')
88 stderr
.write(f
'{s["reason"]}\n')
91 stderr
.write(f
'{t}\n')
92 if 'reasonTitle' not in s
and 'errorScreen' in s \
93 and 'playerErrorMessageRenderer' in s
['errorScreen']:
94 s
= s
['errorScreen']['playerErrorMessageRenderer']
95 for field
in ('reason', 'subreason'):
98 t
= ''.join(t
['text'] for t
in s
[field
]['runs'])
100 t
= s
[field
]['simpleText']
101 stderr
.write(f
'{t}\n')
103 [t
] = s
['learnMore']['runs']
104 t
= t
['navigationEndpoint']['urlEndpoint']['url']
105 stderr
.write(f
'{t}\n')
106 if 'streamingData' not in yt
:
108 yt
= yt
['streamingData']
112 for formats
in ('formats', 'adaptiveFormats'):
113 for format
in yt
.get(formats
, ()):
114 mod_min
= min(mod_min
, int(format
['lastModified']))
115 mod_max
= max(mod_max
, int(format
['lastModified']))
116 stderr
.write(f
'{format_time(mod_min)} to {format_time(mod_max)}\n')
118 if audio
or separate
:
119 yt
= yt
['adaptiveFormats']
121 if 'formats' not in yt
:
122 stderr
.write(f
'streamingData: {yt.keys()}\n')
126 [type, subtype
] = format
['mimeType'].split('/', 1)
127 types
.setdefault(type, list()).append(format
)
128 CODECS
= r
' *[^/]+/[^ ;]+ *; *codecs *= *"([^"\\]+)" *'
129 codec
= re
.fullmatch(CODECS
, format
['mimeType']).group(1)
131 for codec
in codec
.split(','):
132 [codec
, sep
, rest
] = codec
.strip().partition('.')
134 'mp4v': 1600, 'avc1': 900, 'vp9': 600, 'av01': 400,
135 'mp4a': 140, 'opus': 130,
137 best
+= BEST_kbps
[codec
]
139 bitrate
= format
['bitrate'] / 1000
141 bitrate
= int(format
['contentLength']) * 8
142 bitrate
/= int(format
['approxDurationMs'])
143 format
['qv'] = bitrate
/ best
146 print(best_url(types
['video'], limit
=float(kbps
) * 1e3
))
147 if audio
or separate
:
148 print(best_url(types
['audio'], limit
=float(kbps
) * 1e3
))
150 def best_url(yt
, limit
):
154 abr
= s
['averageBitrate']
157 abr
= int(s
['contentLength']) * 8
158 abr
/= int(s
['approxDurationMs']) * 1e-3
161 # First, prioritize streams within the bitrate limit, followed by
162 # each stream with the lowest bitrate exceeding that limit. Then,
163 # prioritize streams that meet the minimum quality requirement,
164 # followed by each stream with the highest quality under that
165 # minimum. Finally, prioritize each stream with the lowest bitrate.
166 return (+max(abr
, limit
), -min(s
['qv'], 1), +abr
)
167 yt
= sorted(yt
, key
=key
)
169 stderr
.write(f
'{s["quality"]}: {s["mimeType"]} ')
172 abr
= s
["averageBitrate"] / 1000
174 abr
= int(s
['contentLength']) * 8
175 abr
/= int(s
['approxDurationMs'])
176 stderr
.write(f
'{abr:.0f} ')
182 stderr
.write(f
'{s["bitrate"] / 1000:.0f} ')
183 stderr
.write(f
'kb/s {s["qv"]:.1%}\n')
186 stderr
.write('^^^\n')
191 [secs
, us
] = divmod(us
, 10**6)
192 return f
'{strftime("%Y-%m-%d %H:%M:%S", gmtime(secs))}.{us:06d}Z'
194 if __name__
== '__main__':
195 from clifunc
import run