From 50ad959a8051fec95f26b573f9fe067bdf3fdf6a Mon Sep 17 00:00:00 2001 From: Astounds Date: Sat, 25 Apr 2026 01:02:17 -0500 Subject: refactor: replace string concatenations with f-strings --- youtube/watch.py | 118 ++++++++++++++++++++++++------------------------------- 1 file changed, 52 insertions(+), 66 deletions(-) (limited to 'youtube/watch.py') diff --git a/youtube/watch.py b/youtube/watch.py index 7f87215..9d1e442 100644 --- a/youtube/watch.py +++ b/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/') 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/') 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') -- cgit v1.2.3