diff options
Diffstat (limited to 'youtube/__init__.py')
| -rw-r--r-- | youtube/__init__.py | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/youtube/__init__.py b/youtube/__init__.py new file mode 100644 index 0000000..a8a725d --- /dev/null +++ b/youtube/__init__.py @@ -0,0 +1,203 @@ +from youtube import util +from .get_app_version import app_version +import flask +from flask import request +import jinja2 +import settings +import traceback +import re +from sys import exc_info +from flask_babel import Babel + +yt_app = flask.Flask(__name__) +yt_app.config['TEMPLATES_AUTO_RELOAD'] = True +yt_app.url_map.strict_slashes = False +# yt_app.jinja_env.trim_blocks = True +# yt_app.jinja_env.lstrip_blocks = True + +# Configure Babel for i18n +import os +yt_app.config['BABEL_DEFAULT_LOCALE'] = 'en' +# Use absolute path for translations directory to avoid issues with package structure changes +_app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +yt_app.config['BABEL_TRANSLATION_DIRECTORIES'] = os.path.join(_app_root, 'translations') + +def get_locale(): + """Determine the best locale based on user preference or browser settings""" + # Check if user has a language preference in settings + if hasattr(settings, 'language') and settings.language: + locale = settings.language + print(f'[i18n] Using user preference: {locale}') + return locale + # Otherwise, use browser's Accept-Language header + # Only match languages with available translations + locale = request.accept_languages.best_match(['en', 'es']) + print(f'[i18n] Using browser language: {locale}') + return locale or 'en' + +babel = Babel(yt_app, locale_selector=get_locale) + + +yt_app.add_url_rule('/settings', 'settings_page', settings.settings_page, methods=['POST', 'GET']) + + +@yt_app.route('/') +def homepage(): + return flask.render_template('home.html', title="YT Local") + + +@yt_app.route('/licenses') +def licensepage(): + return flask.render_template( + 'licenses.html', + title="Licenses - YT Local" + ) + + +theme_names = { + 0: 'light_theme', + 1: 'gray_theme', + 2: 'dark_theme', +} + + +@yt_app.context_processor +def inject_theme_preference(): + return { + 'theme_path': '/youtube.com/static/' + theme_names[settings.theme] + '.css', + 'settings': settings, + # Detect version + 'current_version': app_version()['version'], + 'current_branch': app_version()['branch'], + 'current_commit': app_version()['commit'], + } + + +@yt_app.template_filter('commatize') +def commatize(num): + if num is None: + return '' + if isinstance(num, str): + try: + num = int(num) + except ValueError: + return num + return '{:,}'.format(num) + + +def timestamp_replacement(match): + time_seconds = 0 + for part in match.group(0).split(':'): + time_seconds = 60*time_seconds + int(part) + return ( + """ + <a href="#" id="timestamp%s">%s</a> + <script> + // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + (function main() { + 'use strict'; + const player = document.getElementById('js-video-player'); + const a = document.getElementById('timestamp%s'); + a.addEventListener('click', function(event) { + player.currentTime = %s + }); + }()); + // @license-end + </script> + """ % ( + str(time_seconds), + match.group(0), + str(time_seconds), + str(time_seconds) + ) + ) + + +TIMESTAMP_RE = re.compile(r'\b(\d?\d:)?\d?\d:\d\d\b') + + +@yt_app.template_filter('timestamps') +def timestamps(text): + return TIMESTAMP_RE.sub(timestamp_replacement, text) + + +@yt_app.errorhandler(500) +def error_page(e): + slim = request.args.get('slim', False) # whether it was an ajax request + if (exc_info()[0] == util.FetchError + and exc_info()[1].code == '429' + and settings.route_tor + ): + error_message = ('Error: YouTube blocked the request because the Tor' + ' exit node is overutilized. Try getting a new exit node by' + ' using the New Identity button in the Tor Browser.') + if exc_info()[1].error_message: + error_message += '\n\n' + exc_info()[1].error_message + if exc_info()[1].ip: + error_message += '\n\nExit node IP address: ' + exc_info()[1].ip + return flask.render_template('error.html', error_message=error_message, slim=slim), 502 + elif exc_info()[0] == util.FetchError and exc_info()[1].error_message: + # Handle specific error codes with user-friendly messages + error_code = exc_info()[1].code + error_msg = exc_info()[1].error_message + + if error_code == '400': + error_message = (f'Error: Bad Request (400)\n\n{error_msg}\n\n' + 'This usually means the URL or parameters are invalid. ' + 'Try going back and trying a different option.') + elif error_code == '404': + error_message = 'Error: The page you are looking for isn\'t here.' + else: + error_message = f'Error: {error_code} - {error_msg}' + + return (flask.render_template( + 'error.html', + error_message=error_message, + slim=slim + ), 502) + elif (exc_info()[0] == util.FetchError + and exc_info()[1].code == '404' + ): + error_message = ('Error: The page you are looking for isn\'t here.') + return flask.render_template('error.html', + error_code=exc_info()[1].code, + error_message=error_message, + slim=slim), 404 + return flask.render_template('error.html', traceback=traceback.format_exc(), + error_code=exc_info()[1].code, + slim=slim), 500 + # return flask.render_template('error.html', traceback=traceback.format_exc(), slim=slim), 500 + + +font_choices = { + 0: 'initial', + 1: '"liberation serif", "times new roman", calibri, carlito, serif', + 2: 'arial, "liberation sans", sans-serif', + 3: 'verdana, sans-serif', + 4: 'tahoma, sans-serif', +} + + +@yt_app.route('/shared.css') +def get_css(): + return flask.Response( + flask.render_template( + 'shared.css', + font_family=font_choices[settings.font] + ), + mimetype='text/css', + ) + + +# This is okay because the flask urlize function puts the href as the first +# property +YOUTUBE_LINK_RE = re.compile(r'<a href="(' + util.YOUTUBE_URL_RE_STR + ')"') +old_urlize = jinja2.filters.urlize + + +def prefix_urlize(*args, **kwargs): + result = old_urlize(*args, **kwargs) + return YOUTUBE_LINK_RE.sub(r'<a href="/\1"', result) + + +jinja2.filters.urlize = prefix_urlize |
