refactor: replace string concatenations with f-strings
All checks were successful
CI / test (push) Successful in 50s
All checks were successful
CI / test (push) Successful in 50s
This commit is contained in:
118
youtube/watch.py
118
youtube/watch.py
@@ -53,7 +53,7 @@ def get_video_sources(info, target_resolution):
|
||||
if fmt['acodec'] and fmt['vcodec']:
|
||||
if fmt.get('audio_track_is_default', True) is False:
|
||||
continue
|
||||
source = {'type': 'video/' + fmt['ext'],
|
||||
source = {'type': f"video/{fmt['ext']}",
|
||||
'quality_string': short_video_quality_string(fmt)}
|
||||
source['quality_string'] += ' (integrated)'
|
||||
source.update(fmt)
|
||||
@@ -70,10 +70,10 @@ def get_video_sources(info, target_resolution):
|
||||
if fmt['acodec'] and not fmt['vcodec'] and (fmt['audio_bitrate'] or fmt['bitrate']):
|
||||
if fmt['bitrate']:
|
||||
fmt['audio_bitrate'] = int(fmt['bitrate']/1000)
|
||||
source = {'type': 'audio/' + fmt['ext'],
|
||||
source = {'type': f"audio/{fmt['ext']}",
|
||||
'quality_string': audio_quality_string(fmt)}
|
||||
source.update(fmt)
|
||||
source['mime_codec'] = source['type'] + '; codecs="' + source['acodec'] + '"'
|
||||
source['mime_codec'] = f"{source['type']}; codecs=\"{source['acodec']}\""
|
||||
tid = fmt.get('audio_track_id') or 'default'
|
||||
if tid not in audio_by_track:
|
||||
audio_by_track[tid] = {
|
||||
@@ -85,11 +85,11 @@ def get_video_sources(info, target_resolution):
|
||||
elif all(fmt[attr] for attr in ('vcodec', 'quality', 'width', 'fps', 'file_size')):
|
||||
if codec_name(fmt['vcodec']) == 'unknown':
|
||||
continue
|
||||
source = {'type': 'video/' + fmt['ext'],
|
||||
source = {'type': f"video/{fmt['ext']}",
|
||||
'quality_string': short_video_quality_string(fmt)}
|
||||
source.update(fmt)
|
||||
source['mime_codec'] = source['type'] + '; codecs="' + source['vcodec'] + '"'
|
||||
quality = str(fmt['quality']) + 'p' + str(fmt['fps'])
|
||||
source['mime_codec'] = f"{source['type']}; codecs=\"{source['vcodec']}\""
|
||||
quality = f"{fmt['quality']}p{fmt['fps']}"
|
||||
video_only_sources.setdefault(quality, []).append(source)
|
||||
|
||||
audio_tracks = []
|
||||
@@ -141,7 +141,7 @@ def get_video_sources(info, target_resolution):
|
||||
|
||||
def video_rank(src):
|
||||
''' Sort by settings preference. Use file size as tiebreaker '''
|
||||
setting_name = 'codec_rank_' + codec_name(src['vcodec'])
|
||||
setting_name = f'codec_rank_{codec_name(src["vcodec"])}'
|
||||
return (settings.current_settings_dict[setting_name],
|
||||
src['file_size'])
|
||||
pair_info['videos'].sort(key=video_rank)
|
||||
@@ -183,7 +183,7 @@ def make_caption_src(info, lang, auto=False, trans_lang=None):
|
||||
if auto:
|
||||
label += ' (Automatic)'
|
||||
if trans_lang:
|
||||
label += ' -> ' + trans_lang
|
||||
label += f' -> {trans_lang}'
|
||||
|
||||
# Try to use Android caption URL directly (no PO Token needed)
|
||||
caption_url = None
|
||||
@@ -204,7 +204,7 @@ def make_caption_src(info, lang, auto=False, trans_lang=None):
|
||||
else:
|
||||
caption_url += '&fmt=vtt'
|
||||
if trans_lang:
|
||||
caption_url += '&tlang=' + trans_lang
|
||||
caption_url += f'&tlang={trans_lang}'
|
||||
url = util.prefix_url(caption_url)
|
||||
else:
|
||||
# Fallback to old method
|
||||
@@ -357,10 +357,10 @@ def decrypt_signatures(info, video_id):
|
||||
|
||||
player_name = info['player_name']
|
||||
if player_name in decrypt_cache:
|
||||
print('Using cached decryption function for: ' + player_name)
|
||||
print(f'Using cached decryption function for: {player_name}')
|
||||
info['decryption_function'] = decrypt_cache[player_name]
|
||||
else:
|
||||
base_js = util.fetch_url(info['base_js'], debug_name='base.js', report_text='Fetched player ' + player_name)
|
||||
base_js = util.fetch_url(info['base_js'], debug_name='base.js', report_text=f'Fetched player {player_name}')
|
||||
base_js = base_js.decode('utf-8')
|
||||
err = yt_data_extract.extract_decryption_function(info, base_js)
|
||||
if err:
|
||||
@@ -387,11 +387,11 @@ def fetch_player_response(client, video_id):
|
||||
def fetch_watch_page_info(video_id, playlist_id, index):
|
||||
# bpctr=9999999999 will bypass are-you-sure dialogs for controversial
|
||||
# videos
|
||||
url = 'https://m.youtube.com/embed/' + video_id + '?bpctr=9999999999'
|
||||
url = f'https://m.youtube.com/embed/{video_id}?bpctr=9999999999'
|
||||
if playlist_id:
|
||||
url += '&list=' + playlist_id
|
||||
url += f'&list={playlist_id}'
|
||||
if index:
|
||||
url += '&index=' + index
|
||||
url += f'&index={index}'
|
||||
|
||||
headers = (
|
||||
('Accept', '*/*'),
|
||||
@@ -493,7 +493,7 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||
# Register HLS audio tracks for proxy access
|
||||
added = 0
|
||||
for lang, track in info['hls_audio_tracks'].items():
|
||||
ck = video_id + '_' + lang
|
||||
ck = f"{video_id}_{lang}"
|
||||
from youtube.hls_cache import register_track
|
||||
register_track(ck, track['hls_url'],
|
||||
video_id=video_id, track_id=lang)
|
||||
@@ -502,7 +502,7 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||
'audio_track_id': lang,
|
||||
'audio_track_name': track['name'],
|
||||
'audio_track_is_default': track['is_default'],
|
||||
'itag': 'hls_' + lang,
|
||||
'itag': f'hls_{lang}',
|
||||
'ext': 'mp4',
|
||||
'audio_bitrate': 128,
|
||||
'bitrate': 128000,
|
||||
@@ -516,7 +516,7 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||
'fps': None,
|
||||
'init_range': {'start': 0, 'end': 0},
|
||||
'index_range': {'start': 0, 'end': 0},
|
||||
'url': '/ytl-api/audio-track?id=' + urllib.parse.quote(ck),
|
||||
'url': f'/ytl-api/audio-track?id={urllib.parse.quote(ck)}',
|
||||
's': None,
|
||||
'sp': None,
|
||||
'quality': None,
|
||||
@@ -538,11 +538,11 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||
|
||||
# Register HLS manifest for proxying
|
||||
if info['hls_manifest_url']:
|
||||
ck = video_id + '_video'
|
||||
ck = f"{video_id}_video"
|
||||
from youtube.hls_cache import register_track
|
||||
register_track(ck, info['hls_manifest_url'], video_id=video_id, track_id='video')
|
||||
# Use proxy URL instead of direct Google Video URL
|
||||
info['hls_manifest_url'] = '/ytl-api/hls-manifest?id=' + urllib.parse.quote(ck)
|
||||
info['hls_manifest_url'] = f'/ytl-api/hls-manifest?id={urllib.parse.quote(ck)}'
|
||||
|
||||
# Fallback to 'ios' if no valid URLs are found
|
||||
if not info.get('formats') or info.get('player_urls_missing'):
|
||||
@@ -566,7 +566,7 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||
if info.get('formats'):
|
||||
decryption_error = decrypt_signatures(info, video_id)
|
||||
if decryption_error:
|
||||
info['playability_error'] = 'Error decrypting url signatures: ' + decryption_error
|
||||
info['playability_error'] = f'Error decrypting url signatures: {decryption_error}'
|
||||
|
||||
# check if urls ready (non-live format) in former livestream
|
||||
# urls not ready if all of them have no filesize
|
||||
@@ -623,9 +623,9 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||
|
||||
def video_quality_string(format):
|
||||
if format['vcodec']:
|
||||
result = str(format['width'] or '?') + 'x' + str(format['height'] or '?')
|
||||
result = f"{format['width'] or '?'}x{format['height'] or '?'}"
|
||||
if format['fps']:
|
||||
result += ' ' + str(format['fps']) + 'fps'
|
||||
result += f" {format['fps']}fps"
|
||||
return result
|
||||
elif format['acodec']:
|
||||
return 'audio only'
|
||||
@@ -634,7 +634,7 @@ def video_quality_string(format):
|
||||
|
||||
|
||||
def short_video_quality_string(fmt):
|
||||
result = str(fmt['quality'] or '?') + 'p'
|
||||
result = f"{fmt['quality'] or '?'}p"
|
||||
if fmt['fps']:
|
||||
result += str(fmt['fps'])
|
||||
if fmt['vcodec'].startswith('av01'):
|
||||
@@ -642,18 +642,18 @@ def short_video_quality_string(fmt):
|
||||
elif fmt['vcodec'].startswith('avc'):
|
||||
result += ' h264'
|
||||
else:
|
||||
result += ' ' + fmt['vcodec']
|
||||
result += f" {fmt['vcodec']}"
|
||||
return result
|
||||
|
||||
|
||||
def audio_quality_string(fmt):
|
||||
if fmt['acodec']:
|
||||
if fmt['audio_bitrate']:
|
||||
result = '%d' % fmt['audio_bitrate'] + 'k'
|
||||
result = f"{fmt['audio_bitrate']}k"
|
||||
else:
|
||||
result = '?k'
|
||||
if fmt['audio_sample_rate']:
|
||||
result += ' ' + '%.3G' % (fmt['audio_sample_rate']/1000) + 'kHz'
|
||||
result += f" {'%.3G' % (fmt['audio_sample_rate']/1000)}kHz"
|
||||
return result
|
||||
elif fmt['vcodec']:
|
||||
return 'video only'
|
||||
@@ -737,9 +737,9 @@ def get_audio_track():
|
||||
seg = line if line.startswith('http') else urljoin(playlist_base, line)
|
||||
# Always use &seg= parameter, never &url= for segments
|
||||
playlist_lines.append(
|
||||
base_url + '/ytl-api/audio-track?id='
|
||||
+ urllib.parse.quote(cache_key)
|
||||
+ '&seg=' + urllib.parse.quote(seg, safe='')
|
||||
f'{base_url}/ytl-api/audio-track?id='
|
||||
f'{urllib.parse.quote(cache_key)}'
|
||||
f'&seg={urllib.parse.quote(seg, safe="")}'
|
||||
)
|
||||
|
||||
playlist = '\n'.join(playlist_lines)
|
||||
@@ -797,9 +797,7 @@ def get_audio_track():
|
||||
return url
|
||||
if not url.startswith('http://') and not url.startswith('https://'):
|
||||
url = urljoin(playlist_base, url)
|
||||
return (base_url + '/ytl-api/audio-track?id='
|
||||
+ urllib.parse.quote(cache_key)
|
||||
+ '&seg=' + urllib.parse.quote(url, safe=''))
|
||||
return f'{base_url}/ytl-api/audio-track?id={urllib.parse.quote(cache_key)}&seg={urllib.parse.quote(url, safe="")}'
|
||||
|
||||
playlist_lines = []
|
||||
for line in playlist.split('\n'):
|
||||
@@ -812,7 +810,7 @@ def get_audio_track():
|
||||
if line.startswith('#') and 'URI=' in line:
|
||||
def rewrite_uri_attr(match):
|
||||
uri = match.group(1)
|
||||
return 'URI="' + proxy_url(uri) + '"'
|
||||
return f'URI="{proxy_url(uri)}"'
|
||||
line = _re.sub(r'URI="([^"]+)"', rewrite_uri_attr, line)
|
||||
playlist_lines.append(line)
|
||||
elif line.startswith('#'):
|
||||
@@ -883,9 +881,7 @@ def get_audio_track():
|
||||
if segment_url.startswith('/ytl-api/audio-track'):
|
||||
return segment_url
|
||||
base_url = request.url_root.rstrip('/')
|
||||
return (base_url + '/ytl-api/audio-track?id='
|
||||
+ urllib.parse.quote(cache_key)
|
||||
+ '&seg=' + urllib.parse.quote(segment_url))
|
||||
return f'{base_url}/ytl-api/audio-track?id={urllib.parse.quote(cache_key)}&seg={urllib.parse.quote(segment_url)}'
|
||||
|
||||
playlist_lines = []
|
||||
for line in playlist.split('\n'):
|
||||
@@ -949,14 +945,10 @@ def get_hls_manifest():
|
||||
|
||||
if is_audio_track:
|
||||
# Audio track playlist - proxy through audio-track endpoint
|
||||
return (base_url + '/ytl-api/audio-track?id='
|
||||
+ urllib.parse.quote(cache_key)
|
||||
+ '&url=' + urllib.parse.quote(url, safe=''))
|
||||
return f'{base_url}/ytl-api/audio-track?id={urllib.parse.quote(cache_key)}&url={urllib.parse.quote(url, safe="")}'
|
||||
else:
|
||||
# Video segment or variant playlist - proxy through audio-track endpoint
|
||||
return (base_url + '/ytl-api/audio-track?id='
|
||||
+ urllib.parse.quote(cache_key)
|
||||
+ '&seg=' + urllib.parse.quote(url, safe=''))
|
||||
return f'{base_url}/ytl-api/audio-track?id={urllib.parse.quote(cache_key)}&seg={urllib.parse.quote(url, safe="")}'
|
||||
|
||||
# Parse and rewrite the manifest
|
||||
manifest_lines = []
|
||||
@@ -974,7 +966,7 @@ def get_hls_manifest():
|
||||
nonlocal rewritten_count
|
||||
uri = match.group(1)
|
||||
rewritten_count += 1
|
||||
return 'URI="' + rewrite_url(uri, is_audio_track=True) + '"'
|
||||
return f'URI="{rewrite_url(uri, is_audio_track=True)}"'
|
||||
line = _re.sub(r'URI="([^"]+)"', rewrite_media_uri, line)
|
||||
manifest_lines.append(line)
|
||||
elif line.startswith('#'):
|
||||
@@ -1053,7 +1045,7 @@ def get_storyboard_vtt():
|
||||
ts = 0 # current timestamp
|
||||
|
||||
for i in range(storyboard.storyboard_count):
|
||||
url = '/' + storyboard.url.replace("$M", str(i))
|
||||
url = f'/{storyboard.url.replace("$M", str(i))}'
|
||||
interval = storyboard.interval
|
||||
w, h = storyboard.width, storyboard.height
|
||||
w_cnt, h_cnt = storyboard.width_cnt, storyboard.height_cnt
|
||||
@@ -1078,7 +1070,7 @@ def get_watch_page(video_id=None):
|
||||
if not video_id:
|
||||
return flask.render_template('error.html', error_message='Missing video id'), 404
|
||||
if len(video_id) < 11:
|
||||
return flask.render_template('error.html', error_message='Incomplete video id (too short): ' + video_id), 404
|
||||
return flask.render_template('error.html', error_message=f'Incomplete video id (too short): {video_id}'), 404
|
||||
|
||||
time_start_str = request.args.get('t', '0s')
|
||||
time_start = 0
|
||||
@@ -1141,9 +1133,9 @@ def get_watch_page(video_id=None):
|
||||
util.prefix_urls(item)
|
||||
util.add_extra_html_info(item)
|
||||
if playlist_id:
|
||||
item['url'] += '&list=' + playlist_id
|
||||
item['url'] += f'&list={playlist_id}'
|
||||
if item['index']:
|
||||
item['url'] += '&index=' + str(item['index'])
|
||||
item['url'] += f'&index={item["index"]}'
|
||||
info['playlist']['author_url'] = util.prefix_url(
|
||||
info['playlist']['author_url'])
|
||||
if settings.img_prefix:
|
||||
@@ -1159,16 +1151,16 @@ def get_watch_page(video_id=None):
|
||||
filename = title
|
||||
ext = fmt.get('ext')
|
||||
if ext:
|
||||
filename += '.' + ext
|
||||
filename += f'.{ext}'
|
||||
fmt['url'] = fmt['url'].replace(
|
||||
'/videoplayback',
|
||||
'/videoplayback/name/' + filename)
|
||||
f'/videoplayback/name/{filename}')
|
||||
|
||||
download_formats = []
|
||||
|
||||
for format in (info['formats'] + info['hls_formats']):
|
||||
if format['acodec'] and format['vcodec']:
|
||||
codecs_string = format['acodec'] + ', ' + format['vcodec']
|
||||
codecs_string = f"{format['acodec']}, {format['vcodec']}"
|
||||
else:
|
||||
codecs_string = format['acodec'] or format['vcodec'] or '?'
|
||||
download_formats.append({
|
||||
@@ -1247,12 +1239,9 @@ def get_watch_page(video_id=None):
|
||||
for source in subtitle_sources:
|
||||
best_caption_parse = urllib.parse.urlparse(
|
||||
source['url'].lstrip('/'))
|
||||
transcript_url = (util.URL_ORIGIN
|
||||
+ '/watch/transcript'
|
||||
+ best_caption_parse.path
|
||||
+ '?' + best_caption_parse.query)
|
||||
transcript_url = f'{util.URL_ORIGIN}/watch/transcript{best_caption_parse.path}?{best_caption_parse.query}'
|
||||
other_downloads.append({
|
||||
'label': 'Video Transcript: ' + source['label'],
|
||||
'label': f'Video Transcript: {source["label"]}',
|
||||
'ext': 'txt',
|
||||
'url': transcript_url
|
||||
})
|
||||
@@ -1263,7 +1252,7 @@ def get_watch_page(video_id=None):
|
||||
template_name = 'watch.html'
|
||||
return flask.render_template(template_name,
|
||||
header_playlist_names = local_playlist.get_playlist_names(),
|
||||
uploader_channel_url = ('/' + info['author_url']) if info['author_url'] else '',
|
||||
uploader_channel_url = f'/{info["author_url"]}' if info['author_url'] else '',
|
||||
time_published = info['time_published'],
|
||||
view_count = (lambda x: '{:,}'.format(x) if x is not None else "")(info.get("view_count", None)),
|
||||
like_count = (lambda x: '{:,}'.format(x) if x is not None else "")(info.get("like_count", None)),
|
||||
@@ -1305,10 +1294,10 @@ def get_watch_page(video_id=None):
|
||||
ip_address = info['ip_address'] if settings.route_tor else None,
|
||||
invidious_used = info['invidious_used'],
|
||||
invidious_reload_button = info['invidious_reload_button'],
|
||||
video_url = util.URL_ORIGIN + '/watch?v=' + video_id,
|
||||
video_url = f'{util.URL_ORIGIN}/watch?v={video_id}',
|
||||
video_id = video_id,
|
||||
storyboard_url = (util.URL_ORIGIN + '/ytl-api/storyboard.vtt?' +
|
||||
urlencode([('spec_url', info['storyboard_spec_url'])])
|
||||
storyboard_url = (f'{util.URL_ORIGIN}/ytl-api/storyboard.vtt?'
|
||||
f'{urlencode([("spec_url", info["storyboard_spec_url"])])}'
|
||||
if info['storyboard_spec_url'] else None),
|
||||
|
||||
js_data = {
|
||||
@@ -1335,7 +1324,7 @@ def get_watch_page(video_id=None):
|
||||
|
||||
@yt_app.route('/api/<path:dummy>')
|
||||
def get_captions(dummy):
|
||||
url = 'https://www.youtube.com' + request.full_path
|
||||
url = f'https://www.youtube.com{request.full_path}'
|
||||
try:
|
||||
result = util.fetch_url(url, headers=util.mobile_ua)
|
||||
result = result.replace(b"align:start position:0%", b"")
|
||||
@@ -1350,12 +1339,9 @@ inner_timestamp_removal_reg = re.compile(r'<[^>]+>')
|
||||
@yt_app.route('/watch/transcript/<path:caption_path>')
|
||||
def get_transcript(caption_path):
|
||||
try:
|
||||
captions = util.fetch_url('https://www.youtube.com/'
|
||||
+ caption_path
|
||||
+ '?' + request.environ['QUERY_STRING']).decode('utf-8')
|
||||
captions = util.fetch_url(f'https://www.youtube.com/{caption_path}?{request.environ["QUERY_STRING"]}').decode('utf-8')
|
||||
except util.FetchError as e:
|
||||
msg = ('Error retrieving captions: ' + str(e) + '\n\n'
|
||||
+ 'The caption url may have expired.')
|
||||
msg = f'Error retrieving captions: {e}\n\nThe caption url may have expired.'
|
||||
print(msg)
|
||||
return flask.Response(
|
||||
msg,
|
||||
@@ -1403,7 +1389,7 @@ def get_transcript(caption_path):
|
||||
result = ''
|
||||
for seg in segments:
|
||||
if seg['text'] != ' ':
|
||||
result += seg['begin'] + ' ' + seg['text'] + '\r\n'
|
||||
result += f"{seg['begin']} {seg['text']}\r\n"
|
||||
|
||||
return flask.Response(result.encode('utf-8'),
|
||||
mimetype='text/plain;charset=UTF-8')
|
||||
|
||||
Reference in New Issue
Block a user