diff options
Diffstat (limited to 'youtube/templates')
| -rw-r--r-- | youtube/templates/base.html | 80 | ||||
| -rw-r--r-- | youtube/templates/channel.html | 39 | ||||
| -rw-r--r-- | youtube/templates/comments.html | 8 | ||||
| -rw-r--r-- | youtube/templates/comments_page.html | 2 | ||||
| -rw-r--r-- | youtube/templates/common_elements.html | 43 | ||||
| -rw-r--r-- | youtube/templates/embed.html | 97 | ||||
| -rw-r--r-- | youtube/templates/error.html | 26 | ||||
| -rw-r--r-- | youtube/templates/home.html | 2 | ||||
| -rw-r--r-- | youtube/templates/licenses.html | 50 | ||||
| -rw-r--r-- | youtube/templates/local_playlist.html | 28 | ||||
| -rw-r--r-- | youtube/templates/playlist.html | 12 | ||||
| -rw-r--r-- | youtube/templates/search.html | 6 | ||||
| -rw-r--r-- | youtube/templates/settings.html | 26 | ||||
| -rw-r--r-- | youtube/templates/shared.css | 5 | ||||
| -rw-r--r-- | youtube/templates/subscription_manager.html | 25 | ||||
| -rw-r--r-- | youtube/templates/subscriptions.html | 4 | ||||
| -rw-r--r-- | youtube/templates/subscriptions.xml | 9 | ||||
| -rw-r--r-- | youtube/templates/unsubscribe_verify.html | 8 | ||||
| -rw-r--r-- | youtube/templates/watch.html | 366 |
19 files changed, 527 insertions, 309 deletions
diff --git a/youtube/templates/base.html b/youtube/templates/base.html index 7b32d76..a67e745 100644 --- a/youtube/templates/base.html +++ b/youtube/templates/base.html @@ -1,77 +1,97 @@ +{% if settings.app_public %} + {% set app_url = settings.app_url|string %} +{% else %} + {% set app_url = settings.app_url|string + ':' + settings.port_number|string %} +{% endif %} <!DOCTYPE html> <html lang="en"> <head> - <meta charset="UTF-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}"/> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; media-src 'self' blob: {{ app_url }}/* data: https://*.googlevideo.com; img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com; connect-src 'self' https://*.googlevideo.com; font-src 'self' data:; worker-src 'self' blob:;"> <title>{{ page_title }}</title> - <link title="Youtube local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml"/> - <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"/> - <link href="/youtube.com/static/normalize.css" rel="stylesheet"/> - <link href="{{ theme_path }}" rel="stylesheet"/> + <link title="YT Local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml"> + <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"> + <link href="/youtube.com/static/normalize.css" rel="stylesheet"> + <link href="{{ theme_path }}" rel="stylesheet"> + <link href="/youtube.com/shared.css" rel="stylesheet"> {% block style %} {{ style }} {% endblock %} + + {% if js_data %} + <script> + // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + data = {{ js_data|tojson }}; + // @license-end + </script> + {% endif %} + <script> + // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + // Image prefix for thumbnails + let settings_img_prefix = "{{ settings.img_prefix or '' }}"; + // @license-end + </script> </head> <body> <header class="header"> <nav class="home"> - <a href="/youtube.com" id="home-link">YouTube Local</a> + <a href="/youtube.com" id="home-link">YT Local</a> </nav> - <form class="form" id="site-search" action="/youtube.com/search"> - <input type="search" name="query" class="search-box" value="{{ search_box_value }}" - {{ "autofocus" if request.path == "/" else "" }} placeholder="Type to search..."> - <button type="submit" value="Search" class="search-button">Search</button> + <form class="form" id="site-search" action="/youtube.com/results"> + <input type="search" name="search_query" class="search-box" value="{{ search_box_value }}" + {{ "autofocus" if (request.path in ("/", "/results") or error_message) else "" }} required placeholder="{{ _('Type to search...') }}"> + <button type="submit" value="Search" class="search-button">{{ _('Search') }}</button> <!-- options --> <div class="dropdown"> <!-- hidden box --> - <input id="options-toggle-cbox" class="opt-box" role="button" type="checkbox"> + <input id="options-toggle-cbox" class="opt-box" type="checkbox"> <!-- end hidden box --> - <label class="dropdown-label" for="options-toggle-cbox">Options</label> + <label class="dropdown-label" for="options-toggle-cbox">{{ _('Options') }}</label> <div class="dropdown-content"> - <h3>Sort by</h3> + <h3>{{ _('Sort by') }}</h3> <div class="option"> <input type="radio" id="sort_relevance" name="sort" value="0"> - <label for="sort_relevance">Relevance</label> + <label for="sort_relevance">{{ _('Relevance') }}</label> </div> <div class="option"> <input type="radio" id="sort_upload_date" name="sort" value="2"> - <label for="sort_upload_date">Upload date</label> + <label for="sort_upload_date">{{ _('Upload date') }}</label> </div> <div class="option"> <input type="radio" id="sort_view_count" name="sort" value="3"> - <label for="sort_view_count">View count</label> + <label for="sort_view_count">{{ _('View count') }}</label> </div> <div class="option"> <input type="radio" id="sort_rating" name="sort" value="1"> - <label for="sort_rating">Rating</label> + <label for="sort_rating">{{ _('Rating') }}</label> </div> - <h3>Upload date</h3> + <h3>{{ _('Upload date') }}</h3> <div class="option"> <input type="radio" id="time_any" name="time" value="0"> - <label for="time_any">Any</label> + <label for="time_any">{{ _('Any') }}</label> </div> <div class="option"> <input type="radio" id="time_last_hour" name="time" value="1"> - <label for="time_last_hour">Last hour</label> + <label for="time_last_hour">{{ _('Last hour') }}</label> </div> <div class="option"> <input type="radio" id="time_today" name="time" value="2"> - <label for="time_today">Today</label> + <label for="time_today">{{ _('Today') }}</label> </div> <div class="option"> <input type="radio" id="time_this_week" name="time" value="3"> - <label for="time_this_week">This week</label> + <label for="time_this_week">{{ _('This week') }}</label> </div> <div class="option"> <input type="radio" id="time_this_month" name="time" value="4"> - <label for="time_this_month">This month</label> + <label for="time_this_month">{{ _('This month') }}</label> </div> <div class="option"> <input type="radio" id="time_this_year" name="time" value="5"> - <label for="time_this_year">This year</label> + <label for="time_this_year">{{ _('This year') }}</label> </div> <h3>Type</h3> @@ -119,7 +139,7 @@ {% if header_playlist_names is defined %} <form class="playlist" id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self"> - <input class="play-box" name="playlist_name" id="playlist-name-selection" list="playlist-options" type="search" placeholder="I added your playlist..."> + <input class="play-box" name="playlist_name" id="playlist-name-selection" list="playlist-options" type="search" placeholder="Add name of your playlist..."> <datalist class="play-hidden" id="playlist-options"> {% for playlist_name in header_playlist_names %} <option value="{{ playlist_name }}">{{ playlist_name }}</option> @@ -127,7 +147,7 @@ </datalist> <button class="play-add" type="submit" id="playlist-add-button" name="action" value="add">+List</button> <div class="play-clean"> - <button type="reset" id="item-selection-reset">Clear selection</button> + <button type="reset" id="item-selection-reset">Clear</button> </div> </form> <script src="/youtube.com/static/js/playlistadd.js"></script> @@ -150,8 +170,8 @@ </div> <div> <p>This site is Free/Libre Software</p> - {% if current_commit and current_version %} - <p>Current version: {{ current_version }}-{{ current_commit }} @ {{ current_branch }}</p> + {% if current_commit != None %} + <p>Current version: {{ current_commit }} @ {{ current_branch }}</p> {% else %} <p>Current version: {{ current_version }}</p> {% endif %} diff --git a/youtube/templates/channel.html b/youtube/templates/channel.html index 294f1df..274b727 100644 --- a/youtube/templates/channel.html +++ b/youtube/templates/channel.html @@ -1,21 +1,21 @@ {% if current_tab == 'search' %} {% set page_title = search_box_value + ' - Page ' + page_number|string %} {% else %} - {% set page_title = channel_name + ' - Channel' %} + {% set page_title = channel_name|string + ' - Channel' %} {% endif %} {% extends "base.html" %} {% import "common_elements.html" as common_elements %} {% block style %} - <link href="/youtube.com/static/message_box.css" rel="stylesheet"/> - <link href="/youtube.com/static/channel.css" rel="stylesheet"/> + <link href="/youtube.com/static/message_box.css" rel="stylesheet"> + <link href="/youtube.com/static/channel.css" rel="stylesheet"> {% endblock style %} {% block main %} <div class="author-container"> <div class="author"> - <img alt="{{ channel_name }}" src="{{ avatar }}"/> + <img alt="{{ channel_name }}" src="{{ avatar }}"> <h2>{{ channel_name }}</h2> </div> <div class="summary"> @@ -33,7 +33,7 @@ <hr/> <nav class="channel-tabs"> - {% for tab_name in ('Videos', 'Playlists', 'About') %} + {% for tab_name in ('Videos', 'Shorts', 'Streams', 'Playlists', 'About') %} {% if tab_name.lower() == current_tab %} <a class="tab page-button">{{ tab_name }}</a> {% else %} @@ -51,8 +51,11 @@ <ul> {% for (before_text, stat, after_text) in [ ('Joined ', date_joined, ''), - ('', view_count|commatize, ' views'), + ('', approx_view_count, ' views'), ('', approx_subscriber_count, ' subscribers'), + ('', approx_video_count, ' videos'), + ('Country: ', country, ''), + ('Canonical Url: ', canonical_url, ''), ] %} {% if stat %} <li>{{ before_text + stat|string + after_text }}</li> @@ -65,7 +68,11 @@ <hr> <ul> {% for text, url in links %} - <li><a href="{{ url }}">{{ text }}</a></li> + {% if url %} + <li><a href="{{ url }}">{{ text }}</a></li> + {% else %} + <li>{{ text }}</li> + {% endif %} {% endfor %} </ul> </div> @@ -73,11 +80,15 @@ <!-- new--> <div id="links-metadata"> - {% if current_tab == 'videos' %} - {% set sorts = [('1', 'views'), ('2', 'oldest'), ('3', 'newest')] %} - <div id="number-of-results">{{ number_of_videos }} videos</div> + {% if current_tab in ('videos', 'shorts', 'streams') %} + {% set sorts = [('3', 'newest'), ('4', 'newest - no shorts')] %} + {% if current_tab in ('shorts', 'streams') and not is_last_page %} + <div id="number-of-results">{{ number_of_videos }}+ videos</div> + {% else %} + <div id="number-of-results">{{ number_of_videos }} videos</div> + {% endif %} {% elif current_tab == 'playlists' %} - {% set sorts = [('2', 'oldest'), ('3', 'newest'), ('4', 'last video added')] %} + {% set sorts = [('3', 'newest'), ('4', 'last video added')] %} {% if items %} <h2 class="page-number">Page {{ page_number }}</h2> {% else %} @@ -110,13 +121,13 @@ <hr/> <footer class="pagination-container"> - {% if current_tab == 'videos' and current_sort.__str__() == '2' %} + {% if current_tab in ('shorts', 'streams') %} <nav class="next-previous-button-row"> - {{ common_elements.next_previous_ctoken_buttons(None, ctoken, channel_url + '/' + current_tab, parameters_dictionary) }} + {{ common_elements.next_previous_buttons(is_last_page, channel_url + '/' + current_tab, parameters_dictionary) }} </nav> {% elif current_tab == 'videos' %} <nav class="pagination-list"> - {{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary, include_ends=(current_sort.__str__() == '3')) }} + {{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary, include_ends=(current_sort.__str__() in '34')) }} </nav> {% elif current_tab == 'playlists' or current_tab == 'search' %} <nav class="next-previous-button-row"> diff --git a/youtube/templates/comments.html b/youtube/templates/comments.html index 7d0ef85..dac0d23 100644 --- a/youtube/templates/comments.html +++ b/youtube/templates/comments.html @@ -3,13 +3,13 @@ {% macro render_comment(comment, include_avatar, timestamp_links=False) %} <div class="comment-container"> <div class="comment"> - <a class="author-avatar" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}"> + <a class="author-avatar" href="{{ comment['author_url'] or '#' }}" title="{{ comment['author'] }}"> {% if include_avatar %} <img class="author-avatar-img" alt="{{ comment['author'] }}" src="{{ comment['author_avatar'] }}"> {% endif %} </a> <address class="author-name"> - <a class="author" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}">{{ comment['author'] }}</a> + <a class="author" href="{{ comment['author_url'] or '#' }}" title="{{ comment['author'] }}">{{ comment['author'] }}</a> </address> <a class="permalink" href="{{ comment['permalink'] }}" title="permalink"> <span>{{ comment['time_published'] }}</span> @@ -21,7 +21,7 @@ <span class="comment-text">{{ common_elements.text_runs(comment['text']) }}</span> {% endif %} - <span class="comment-likes">{{ comment['likes_text'] if comment['like_count'] else ''}}</span> + <span class="comment-likes">{{ comment['likes_text'] if comment['approx_like_count'] else ''}}</span> <div class="button-row"> {% if comment['reply_count'] %} {% if settings.use_comments_js and comment['replies_url'] %} @@ -58,7 +58,7 @@ {% endfor %} </div> {% if 'more_comments_url' is in comments_info %} - <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a> + <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">{{ _('More comments') }}</a> {% endif %} {% endif %} diff --git a/youtube/templates/comments_page.html b/youtube/templates/comments_page.html index 09230f5..3764b10 100644 --- a/youtube/templates/comments_page.html +++ b/youtube/templates/comments_page.html @@ -1,4 +1,4 @@ -{% set page_title = ('Replies' if comments_info['is_replies'] else 'Comments page ' + comments_info['page_number']) %} +{% set page_title = ('Replies' if comments_info['is_replies'] else 'Comments page ' + comments_info['page_number']|string) %} {% import "comments.html" as comments with context %} {% if not slim %} diff --git a/youtube/templates/common_elements.html b/youtube/templates/common_elements.html index 94554d4..1b4b08c 100644 --- a/youtube/templates/common_elements.html +++ b/youtube/templates/common_elements.html @@ -20,14 +20,14 @@ {{ info['error'] }} {% else %} <div class="item-video {{ info['type'] + '-item' }}"> - <a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}"> + <a class="thumbnail-box" href="{{ info['url'] or '#' }}" title="{{ info['title'] }}"> <div class="thumbnail {% if info['type'] == 'channel' %} channel {% endif %}"> {% if lazy_load %} - <img class="thumbnail-img lazy" alt=" " data-src="{{ info['thumbnail'] }}"> + <img class="thumbnail-img lazy" alt=" " data-src="{{ info['thumbnail'] }}" onerror="thumbnail_fallback(this)"> {% elif info['type'] == 'channel' %} - <img class="thumbnail-img channel" alt=" " src="{{ info['thumbnail'] }}"> + <img class="thumbnail-img channel" alt=" " src="{{ info['thumbnail'] }}" onerror="thumbnail_fallback(this)"> {% else %} - <img class="thumbnail-img" alt=" " src="{{ info['thumbnail'] }}"> + <img class="thumbnail-img" alt=" " src="{{ info['thumbnail'] }}" onerror="thumbnail_fallback(this)"> {% endif %} {% if info['type'] != 'channel' %} @@ -35,19 +35,32 @@ {% endif %} </div> </a> - <h4 class="title"><a href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a></h4> + <h4 class="title"><a href="{{ info['url'] or '#' }}" title="{{ info['title'] }}">{{ info['title'] }}</a></h4> {% if include_author %} + {% set author_description = info['author'] %} + {% set AUTHOR_DESC_LENGTH = 35 %} + {% if author_description != None %} + {% if author_description|length >= AUTHOR_DESC_LENGTH %} + {% set author_description = author_description[:AUTHOR_DESC_LENGTH].split(' ')[:-1]|join(' ') %} + {% if not author_description[-1] in ['.', '?', ':', '!'] %} + {% set author_more = author_description + '…' %} + {% set author_description = author_more|replace('"','') %} + {% endif %} + {% endif %} + {% endif %} {% if info.get('author_url') %} - <address title="{{ info['author'] }}"><b><a href="{{ info['author_url'] }}">{{ info['author'] }}</a></b></address> + <address title="{{ info['author'] }}"><b><a href="{{ info['author_url'] }}">{{ author_description }}</a></b></address> {% else %} - <address title="{{ info['author'] }}"><b>{{ info['author'] }}</b></address> + <address title="{{ info['author'] }}"><b>{{ author_description }}</b></address> {% endif %} {% endif %} <div class="stats {{'horizontal-stats' if horizontal else 'vertical-stats'}}"> {% if info['type'] == 'channel' %} - <div>{{ info['approx_subscriber_count'] }} subscribers</div> + {% if info.get('approx_subscriber_count') %} + <div>{{ info['approx_subscriber_count'] }} subscribers</div> + {% endif %} <div>{{ info['video_count']|commatize }} videos</div> {% else %} {% if info.get('time_published') %} @@ -104,17 +117,18 @@ {% set parameters_dictionary = parameters_dictionary.to_dict() %} {% if current_page != 1 %} - {% set _ = parameters_dictionary.__setitem__('page', current_page - 1) %} - <a class="page-link previous-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Previous page</a> + {% set _ = parameters_dictionary.__setitem__('page', current_page - 1) %} + <a class="page-link previous-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Previous page</a> {% endif %} {% if not is_last_page %} - {% set _ = parameters_dictionary.__setitem__('page', current_page + 1) %} - <a class="page-link next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a> + {% set _ = parameters_dictionary.__setitem__('page', current_page + 1) %} + <a class="page-link next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a> {% endif %} +{% endmacro %} - {% macro next_previous_ctoken_buttons(prev_ctoken, next_ctoken, url, parameters_dictionary) %} - {% set parameters_dictionary = parameters_dictionary.to_dict() %} +{% macro next_previous_ctoken_buttons(prev_ctoken, next_ctoken, url, parameters_dictionary) %} + {% set parameters_dictionary = parameters_dictionary.to_dict() %} {% if prev_ctoken %} {% set _ = parameters_dictionary.__setitem__('ctoken', prev_ctoken) %} @@ -125,5 +139,4 @@ {% set _ = parameters_dictionary.__setitem__('ctoken', next_ctoken) %} <a class="page-link next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a> {% endif %} - {% endmacro %} {% endmacro %} diff --git a/youtube/templates/embed.html b/youtube/templates/embed.html index 728791b..b1ad63f 100644 --- a/youtube/templates/embed.html +++ b/youtube/templates/embed.html @@ -1,11 +1,16 @@ <!DOCTYPE html> -<html lang="es"> +<html lang="en"> <head> - <meta charset="UTF-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; media-src 'self' https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}"/> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' blob: https://*.googlevideo.com; img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com; connect-src 'self' https://*.googlevideo.com; font-src 'self' data:;"> <title>{{ title }}</title> - <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"/> + <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"> + {% if settings.use_video_player == 2 %} + <!-- plyr --> + <link href="/youtube.com/static/modules/plyr/plyr.css" rel="stylesheet"> + <!-- /plyr --> + {% endif %} <style> body { margin: 0rem; @@ -15,14 +20,23 @@ width: 100%; height: auto; } + /* Prevent this div from blocking right-click menu for video + e.g. Firefox playback speed options */ + .plyr__poster { + display: none !important; + } </style> + {% if js_data %} + <script> + // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + data = {{ js_data|tojson }}; + // @license-end + </script> + {% endif %} </head> <body> - <video controls autofocus onmouseleave="{{ title }}" + <video id="js-video-player" controls autofocus onmouseleave="{{ title }}" oncontextmenu="{{ title }}" onmouseenter="{{ title }}" title="{{ title }}"> - {% for video_source in video_sources %} - <source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}"> - {% endfor %} {% for source in subtitle_sources %} {% if source['on'] %} <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default> @@ -30,6 +44,71 @@ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}"> {% endif %} {% endfor %} + + {% if uni_sources %} + {% for source in uni_sources %} + <source src="{{ source['url'] }}" type="{{ source['type'] }}" title="{{ source['quality_string'] }}"> + {% endfor %} + {% endif %} </video> + + <script> + // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + let storyboard_url = {{ storyboard_url | tojson }}; + let hls_manifest_url = {{ hls_manifest_url | tojson }}; + let hls_unavailable = {{ hls_unavailable | tojson }}; + let playback_mode = {{ playback_mode | tojson }}; + let pair_sources = {{ pair_sources | tojson }}; + let pair_idx = {{ pair_idx | tojson }}; + // @license-end + </script> + + {% set hls_should_work = (playback_mode == 'hls' or playback_mode == 'auto') and not hls_unavailable %} + {% set use_dash = not hls_should_work %} + + {% if not use_dash %} + <script src="/youtube.com/static/js/hls.min.js" + integrity="sha512-CSVqc4a7tn+tizDNt+eDoVn2fXYAwMDpCLrwGlWrOktNfZQ9gp4dKKScElMeRlrIifhliXs0a06BLaUgmMlCUw==" + crossorigin="anonymous"></script> + {% endif %} + + <script src="/youtube.com/static/js/common.js"></script> + + {% if settings.use_video_player == 0 %} + <!-- Native player --> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 1 %} + <!-- Native player with hotkeys --> + <script src="/youtube.com/static/js/hotkeys.js"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 2 %} + <!-- plyr --> + <script src="/youtube.com/static/modules/plyr/plyr.min.js" + integrity="sha512-l6ZzdXpfMHRfifqaR79wbYCEWjLDMI9DnROvb+oLkKq6d7MGroGpMbI7HFpicvmAH/2aQO+vJhewq8rhysrImw==" + crossorigin="anonymous"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/plyr.dash.start.js"></script> + {% else %} + <script src="/youtube.com/static/js/plyr.hls.start.js"></script> + {% endif %} + <!-- /plyr --> + {% endif %} + + {% if use_dash %} + <script src="/youtube.com/static/js/av-merge.js"></script> + {% endif %} + + <!-- Storyboard Preview Thumbnails (native players only; Plyr handles this internally) --> + {% if settings.use_video_player != 2 and settings.native_player_storyboard %} + <script src="/youtube.com/static/js/storyboard-preview.js"></script> + {% endif %} </body> </html> diff --git a/youtube/templates/error.html b/youtube/templates/error.html index 7256aee..97f8ca9 100644 --- a/youtube/templates/error.html +++ b/youtube/templates/error.html @@ -1,22 +1,36 @@ -{% set page_title = 'Error' %} +{% if error_code %} + {% set page_title = 'Error: ' ~ error_code %} +{% else %} + {% set page_title = 'Error' %} +{% endif %} {% if not slim %} {% extends "base.html" %} {% endif %} -{% block style %} - <link href="/youtube.com/static/home.css" rel="stylesheet"> -{% endblock style %} +{% if traceback %} + {% block style %} + <link href="/youtube.com/static/home.css" rel="stylesheet"> + {% endblock style %} +{% endif %} {% block main %} {% if traceback %} <div class="code-error" id="error-box"> <h1>500 Uncaught exception:</h1> <div class="code-box"><code>{{ traceback }}</code></div> - <p>Please report this issue at <a href="https://libregit.org/heckyel/yt-local/issues" target="_blank" rel="noopener noreferrer">https://libregit.org/heckyel/yt-local/issues</a></p> + <p>Please report this issue at <a href="https://todo.sr.ht/~heckyel/yt-local" target="_blank" rel="noopener noreferrer">https://todo.sr.ht/~heckyel/yt-local</a></p> <p>Remember to include the traceback in your issue and redact any information in it you do not want to share</p> </div> {% else %} - <div id="error-message">{{ error_message }}</div> + <section id="error-message" class="comments-area"> + <div class="comments"> + <div class="comment-container"> + <div class="comment"> + <span class="comment-text">{{ error_message }}</span> + </div> + </div> + </div> + </section> {% endif %} {% endblock %} diff --git a/youtube/templates/home.html b/youtube/templates/home.html index ef1f029..0adac56 100644 --- a/youtube/templates/home.html +++ b/youtube/templates/home.html @@ -1,7 +1,7 @@ {% set page_title = title %} {% extends "base.html" %} {% block style %} - <link href="/youtube.com/static/home.css" rel="stylesheet"/> + <link href="/youtube.com/static/home.css" rel="stylesheet"> {% endblock style %} {% block main %} <ul> diff --git a/youtube/templates/licenses.html b/youtube/templates/licenses.html index dc883a8..e2af1bf 100644 --- a/youtube/templates/licenses.html +++ b/youtube/templates/licenses.html @@ -1,7 +1,7 @@ {% set page_title = title %} {% extends "base.html" %} {% block style %} - <link href="/youtube.com/static/license.css" rel="stylesheet"/> + <link href="/youtube.com/static/license.css" rel="stylesheet"> {% endblock style %} {% block main %} <table id="jslicense-labels1" class="table"> @@ -15,6 +15,11 @@ </thead> <tbody> <tr> + <td data-label="File"><a href="/youtube.com/static/js/av-merge.js">av-merge.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/av-merge.js">av-merge.js</a></td> + </tr> + <tr> <td data-label="File"><a href="/youtube.com/static/js/comments.js">comments.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> <td data-label="Source"><a href="/youtube.com/static/js/comments.js">comments.js</a></td> @@ -25,6 +30,11 @@ <td data-label="Source"><a href="/youtube.com/static/js/common.js">common.js</a></td> </tr> <tr> + <td data-label="File"><a href="/youtube.com/static/js/hls.min.js">hls.min.js</a></td> + <td data-label="License"><a href="https://spdx.org/licenses/BSD-3-Clause.html">BSD-3-Clause</a></td> + <td data-label="Source"><a href="https://github.com/video-dev/hls.js/tree/v1.6.15/src">hls.js v1.6.15 source</a></td> + </tr> + <tr> <td data-label="File"><a href="/youtube.com/static/js/hotkeys.js">hotkeys.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> <td data-label="Source"><a href="/youtube.com/static/js/hotkeys.js">hotkeys.js</a></td> @@ -35,15 +45,45 @@ <td data-label="Source"><a href="/youtube.com/static/js/playlistadd.js">playlistadd.js</a></td> </tr> <tr> - <td data-label="File"><a href="/youtube.com/static/js/speedyplay.js">speedyplay.js</a></td> + <td data-label="File"><a href="/youtube.com/static/js/plyr.dash.start.js">plyr.dash.start.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/plyr.dash.start.js">plyr.dash.start.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/plyr.hls.start.js">plyr.hls.start.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/plyr.hls.start.js">plyr.hls.start.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/sponsorblock.js">sponsorblock.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/sponsorblock.js">sponsorblock.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/storyboard-preview.js">storyboard-preview.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> - <td data-label="Source"><a href="/youtube.com/static/js/speedyplay.js">speedyplay.js</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/storyboard-preview.js">storyboard-preview.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/modules/plyr/plyr.min.js">plyr.min.js</a></td> + <td data-label="License"><a href="https://spdx.org/licenses/MIT.html">Expat</a></td> + <td data-label="Source"><a href="/youtube.com/static/modules/plyr/plyr.js">plyr.js</a></td> </tr> - <tr> + <tr> <td data-label="File"><a href="/youtube.com/static/js/transcript-table.js">transcript-table.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> <td data-label="Source"><a href="/youtube.com/static/js/transcript-table.js">transcript-table.js</a></td> - </tr> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/watch.dash.js">watch.dash.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/watch.dash.js">watch.dash.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/watch.hls.js">watch.hls.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/watch.hls.js">watch.hls.js</a></td> + </tr> </tbody> </table> {% endblock main %} diff --git a/youtube/templates/local_playlist.html b/youtube/templates/local_playlist.html index 765ac66..3286f67 100644 --- a/youtube/templates/local_playlist.html +++ b/youtube/templates/local_playlist.html @@ -2,13 +2,29 @@ {% extends "base.html" %} {% import "common_elements.html" as common_elements %} {% block style %} - <link href="/youtube.com/static/message_box.css" rel="stylesheet"/> - <link href="/youtube.com/static/local_playlist.css" rel="stylesheet"/> + <link href="/youtube.com/static/message_box.css" rel="stylesheet"> + <link href="/youtube.com/static/local_playlist.css" rel="stylesheet"> {% endblock style %} {% block main %} <div class="playlist-metadata"> <h2 class="play-title">{{ playlist_name }}</h2> + + <div id="export-options"> + <form id="playlist-export" method="post"> + <select id="export-type" name="export_format"> + <option value="json">JSON</option> + <option value="ids">Video id list (txt)</option> + <option value="urls">Video url list (txt)</option> + </select> + <button type="submit" id="playlist-export-button" name="action" value="export">Export</button> + </form> + </div> + </div> + + <form id="playlist-remove" action="/youtube.com/edit_playlist" method="post" target="_self"></form> + <div class="playlist-metadata" id="video-remove-container"> + <button id="removePlayList" type="submit" name="action" value="remove_playlist" form="playlist-remove" formaction="">Remove playlist</button> <input type="hidden" name="playlist_page" value="{{ playlist_name }}" form="playlist-edit"> <button class="play-action" type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button> </div> @@ -17,6 +33,14 @@ {{ common_elements.item(video_info) }} {% endfor %} </div> + <script> + // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later + const deletePlayList = document.getElementById('removePlayList'); + deletePlayList.addEventListener('click', (event) => { + return confirm('You are about to permanently delete {{ playlist_name }}\n\nOnce a playlist is permanently deleted, it cannot be recovered.') + }); + // @license-end + </script> <footer class="pagination-container"> <nav class="pagination-list"> {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }} diff --git a/youtube/templates/playlist.html b/youtube/templates/playlist.html index c154207..dc9c37a 100644 --- a/youtube/templates/playlist.html +++ b/youtube/templates/playlist.html @@ -2,19 +2,25 @@ {% extends "base.html" %} {% import "common_elements.html" as common_elements %} {% block style %} - <link href="/youtube.com/static/message_box.css" rel="stylesheet"/> - <link href="/youtube.com/static/playlist.css" rel="stylesheet"/> + <link href="/youtube.com/static/message_box.css" rel="stylesheet"> + <link href="/youtube.com/static/playlist.css" rel="stylesheet"> {% endblock style %} {% block main %} <div class="playlist-metadata"> <div class="author"> - <img alt="{{ title }}" src="{{ thumbnail }}"/> + {% if thumbnail %} + <img alt="{{ title }}" src="{{ thumbnail }}"> + {% endif %} <h2>{{ title }}</h2> </div> <div class="summary"> + {% if author_url %} <a class="playlist-author" href="{{ author_url }}">{{ author }}</a> + {% else %} + <span class="playlist-author">{{ author }}</span> + {% endif %} </div> <div class="playlist-stats"> <div>{{ video_count|commatize }} videos</div> diff --git a/youtube/templates/search.html b/youtube/templates/search.html index 4238d70..af87c90 100644 --- a/youtube/templates/search.html +++ b/youtube/templates/search.html @@ -3,8 +3,8 @@ {% extends "base.html" %} {% import "common_elements.html" as common_elements %} {% block style %} - <link href="/youtube.com/static/message_box.css" rel="stylesheet"/> - <link href="/youtube.com/static/search.css" rel="stylesheet"/> + <link href="/youtube.com/static/message_box.css" rel="stylesheet"> + <link href="/youtube.com/static/search.css" rel="stylesheet"> {% endblock style %} {% block main %} @@ -28,7 +28,7 @@ <!-- /video item --> <footer class="pagination-container"> <nav class="pagination-list"> - {{ common_elements.page_buttons(estimated_pages, '/https://www.youtube.com/search', parameters_dictionary) }} + {{ common_elements.page_buttons(estimated_pages, '/https://www.youtube.com/results', parameters_dictionary) }} </nav> </footer> {% endblock main %} diff --git a/youtube/templates/settings.html b/youtube/templates/settings.html index 7a7ce9e..940eeae 100644 --- a/youtube/templates/settings.html +++ b/youtube/templates/settings.html @@ -1,21 +1,21 @@ {% set page_title = 'Settings' %} {% extends "base.html" %} {% block style %} - <link href="/youtube.com/static/settings.css" rel="stylesheet"/> + <link href="/youtube.com/static/settings.css" rel="stylesheet"> {% endblock style %} {% block main %} <form method="POST" class="settings-form"> {% for categ in categories %} - <h2>{{ categ|capitalize }}</h2> + <h2>{{ _(categ|capitalize) }}</h2> <ul class="settings-list"> {% for setting_name, setting_info, value in settings_by_category[categ] %} {% if not setting_info.get('hidden', false) %} <li class="setting-item"> {% if 'label' is in(setting_info) %} - <label for="{{ 'setting_' + setting_name }}">{{ setting_info['label'] }}</label> + <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ _(setting_info['label']) }}</label> {% else %} - <label for="{{ 'setting_' + setting_name }}">{{ setting_name.replace('_', ' ')|capitalize }}</label> + <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ _(setting_name.replace('_', ' ')|capitalize) }}</label> {% endif %} {% if setting_info['type'].__name__ == 'bool' %} @@ -24,24 +24,32 @@ {% if 'options' is in(setting_info) %} <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}"> {% for option in setting_info['options'] %} - <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option> + <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ _(option[1]) }}</option> {% endfor %} </select> {% else %} <input type="number" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}" step="1"> {% endif %} {% elif setting_info['type'].__name__ == 'float' %} - + <input type="number" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}" step="0.01"> {% elif setting_info['type'].__name__ == 'str' %} - <input type="text" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}"> + {% if 'options' is in(setting_info) %} + <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}"> + {% for option in setting_info['options'] %} + <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ _(option[1]) }}</option> + {% endfor %} + </select> + {% else %} + <input type="text" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}"> + {% endif %} {% else %} - <span>Error: Unknown setting type: setting_info['type'].__name__</span> + <span>Error: Unknown setting type: {{ setting_info['type'].__name__ }}</span> {% endif %} </li> {% endif %} {% endfor %} </ul> {% endfor %} - <input type="submit" value="Save settings"> + <input type="submit" value="{{ _('Save settings') }}"> </form> {% endblock main %} diff --git a/youtube/templates/shared.css b/youtube/templates/shared.css new file mode 100644 index 0000000..8f12651 --- /dev/null +++ b/youtube/templates/shared.css @@ -0,0 +1,5 @@ +html { + font-family: {{ font_family }}; + background: var(--background); + color: var(--text); +} diff --git a/youtube/templates/subscription_manager.html b/youtube/templates/subscription_manager.html index 92cd024..96082c3 100644 --- a/youtube/templates/subscription_manager.html +++ b/youtube/templates/subscription_manager.html @@ -1,7 +1,7 @@ {% set page_title = 'Subscription Manager' %} {% extends "base.html" %} {% block style %} - <link href="/youtube.com/static/subscription_manager.css" rel="stylesheet"/> + <link href="/youtube.com/static/subscription_manager.css" rel="stylesheet"> {% endblock style %} @@ -19,14 +19,25 @@ <div class="import-export"> <form class="subscriptions-import-form" enctype="multipart/form-data" action="/youtube.com/import_subscriptions" method="POST"> <h2>Import subscriptions</h2> - <input type="file" id="subscriptions-import" accept="application/json, application/xml, text/x-opml" name="subscriptions_file"> - <input type="submit" value="Import" class="import-submit-button"> + <div class="subscriptions-import-options"> + <input type="file" id="subscriptions-import" accept="application/json, application/xml, text/x-opml, text/csv" name="subscriptions_file" required> + <input type="submit" value="Import"> + </div> </form> - <!--<ul class="subscriptions-export-links"> - <li><a href="/youtube.com/subscriptions.opml">Export subscriptions (OPML)</a></li> - <li><a href="/youtube.com/subscriptions.xml">Export subscriptions (RSS)</a></li> - </ul>--> + <form class="subscriptions-export-form" action="/youtube.com/export_subscriptions" method="POST"> + <h2>Export subscriptions</h2> + <div class="subscriptions-export-options"> + <select id="export-type" name="export_format" title="Export format"> + <option value="json_newpipe">JSON (NewPipe)</option> + <option value="json_google_takeout">JSON (Old Google Takeout Format)</option> + <option value="opml">OPML (RSS, no tags)</option> + </select> + <label for="include-muted">Include muted</label> + <input id="include-muted" type="checkbox" name="include_muted" checked> + <input type="submit" value="Export"> + </div> + </form> </div> <hr> diff --git a/youtube/templates/subscriptions.html b/youtube/templates/subscriptions.html index b528e5c..2823e8d 100644 --- a/youtube/templates/subscriptions.html +++ b/youtube/templates/subscriptions.html @@ -7,8 +7,8 @@ {% import "common_elements.html" as common_elements %} {% block style %} - <link href="/youtube.com/static/message_box.css" rel="stylesheet"/> - <link href="/youtube.com/static/subscription.css" rel="stylesheet"/> + <link href="/youtube.com/static/message_box.css" rel="stylesheet"> + <link href="/youtube.com/static/subscription.css" rel="stylesheet"> {% endblock style %} {% block main %} diff --git a/youtube/templates/subscriptions.xml b/youtube/templates/subscriptions.xml new file mode 100644 index 0000000..5365da1 --- /dev/null +++ b/youtube/templates/subscriptions.xml @@ -0,0 +1,9 @@ +<opml version="1.1"> + <body> + <outline text="YouTube Subscriptions" title="YouTube Subscriptions"> + {% for sub in sub_list %} + <outline text="{{sub['channel_name']}}" title="{{sub['channel_name']}}" type="rss" xmlUrl="https://www.youtube.com/feeds/videos.xml?channel_id={{sub['channel_id']}}" /> + {%- endfor %} + </outline> + </body> +</opml> diff --git a/youtube/templates/unsubscribe_verify.html b/youtube/templates/unsubscribe_verify.html index 98581c0..e899783 100644 --- a/youtube/templates/unsubscribe_verify.html +++ b/youtube/templates/unsubscribe_verify.html @@ -1,17 +1,19 @@ {% set page_title = 'Unsubscribe?' %} {% extends "base.html" %} +{% block style %} + <link href="/youtube.com/static/unsubscribe.css" rel="stylesheet"/> +{% endblock style %} {% block main %} - <span>Are you sure you want to unsubscribe from these channels?</span> + <p>Are you sure you want to unsubscribe from these channels?</p> <form class="subscriptions-import-form" action="/youtube.com/subscription_manager" method="POST"> {% for channel_id, channel_name in unsubscribe_list %} <input type="hidden" name="channel_ids" value="{{ channel_id }}"> {% endfor %} - <input type="hidden" name="action" value="unsubscribe"> <input type="submit" value="Yes, unsubscribe"> </form> - <ul> + <ul class="list-channel"> {% for channel_id, channel_name in unsubscribe_list %} <li><a href="{{ '/https://www.youtube.com/channel/' + channel_id }}" title="{{ channel_name }}">{{ channel_name }}</a></li> {% endfor %} diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 7184872..6b4f48f 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -3,8 +3,14 @@ {% import "common_elements.html" as common_elements %} {% import "comments.html" as comments with context %} {% block style %} - <link href="/youtube.com/static/message_box.css" rel="stylesheet"/> - <link href="/youtube.com/static/watch.css" rel="stylesheet"/> + <link href="/youtube.com/static/message_box.css" rel="stylesheet"> + <link href="/youtube.com/static/watch.css" rel="stylesheet"> + {% if settings.use_video_player == 2 %} + <!-- plyr --> + <link href="/youtube.com/static/modules/plyr/plyr.css" rel="stylesheet"> + <link href="/youtube.com/static/modules/plyr/custom_plyr.css" rel="stylesheet"> + <!-- /plyr --> + {% endif %} {% endblock style %} {% block main %} @@ -17,22 +23,9 @@ {% endif %} </span> </div> - {% elif (video_sources.__len__() == 0 or live) and hls_formats.__len__() != 0 %} - <div class="live-url-choices"> - <span>Copy a url into your video player:</span> - <ol> - {% for fmt in hls_formats %} - <li class="url-choice"><div class="url-choice-label">{{ fmt['video_quality'] }}: </div><input class="url-choice-copy" value="{{ fmt['url'] }}" readonly onclick="this.select();"></li> - {% endfor %} - </ol> - </div> {% else %} <figure class="sc-video"> - <video id="js-video-player" playsinline controls> - {% for video_source in video_sources %} - <source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}"> - {% endfor %} - + <video id="js-video-player" playsinline controls {{ 'autoplay' if settings.autoplay_videos }}> {% for source in subtitle_sources %} {% if source['on'] %} <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default> @@ -40,14 +33,19 @@ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}"> {% endif %} {% endfor %} + + {% if uni_sources %} + {% for source in uni_sources %} + <source src="{{ source['url'] }}" type="{{ source['type'] }}" title="{{ source['quality_string'] }}"> + {% endfor %} + {% endif %} </video> + {% if hls_unavailable and not uni_sources %} + <div class="playability-error"> + <span>Error: HLS streams unavailable. Video may not play without JavaScript fallback.</span> + </div> + {% endif %} </figure> - - {% if time_start != 0 %} - <script> - document.getElementById('js-video-player').currentTime = {{ time_start|tojson }}; - </script> - {% endif %} {% endif %} <div class="sc-info"> @@ -72,34 +70,73 @@ <address class="v-uploaded">Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address> <span class="v-views">{{ view_count }} views</span> <time class="v-published" datetime="{{ time_published_utc }}">Published on {{ time_published }}</time> - <span class="v-likes-dislikes">{{ like_count }} likes {{ dislike_count }} dislikes</span> + <span class="v-likes-dislikes">{{ like_count }} likes</span> <div class="external-player-controls"> <input class="speed" id="speed-control" type="text" title="Video speed"> - <script src="/youtube.com/static/js/speedyplay.js"></script> + {% if settings.use_video_player < 2 %} + <!-- Quality selector (populated by JS: HLS adds Auto+levels, DASH adds discrete sources) --> + <select id="quality-select" autocomplete="off"> + </select> + {% else %} + <select id="quality-select" autocomplete="off" style="display: none;"> + </select> + {% endif %} + {% if settings.use_video_player != 2 %} + {% if audio_tracks|length > 1 %} + <select id="audio-track-select" autocomplete="off"> + {% for track in audio_tracks %} + <option value="{{ track['id'] }}" {{ 'selected' if track['is_default'] else '' }}>{{ track['name'] }}</option> + {% endfor %} + </select> + {% endif %} + {% endif %} </div> - <input class="v-checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox"> + <span class="v-direct-link"><a href="https://youtu.be/{{ video_id }}" rel="noopener noreferrer" target="_blank">{{ _('Direct Link') }}</a></span> + + {% if settings.use_video_download != 0 %} <details class="v-download"> - <summary class="download-dropdown-label">Download</summary> - <ul class="download-dropdown-content"> - {% for format in download_formats %} - <li class="download-format"> - <a class="download-link" href="{{ format['url'] }}" download="{{ title }}.{{ format['ext'] }}"> - {{ format['ext'] }} {{ format['video_quality'] }} {{ format['audio_quality'] }} {{ format['file_size'] }} {{ format['codecs'] }} - </a> - </li> - {% endfor %} - {% for download in other_downloads %} - <li class="download-format"> - <a href="{{ download['url'] }}" download> - {{ download['ext'] }} {{ download['label'] }} - </a> - </li> - {% endfor %} - </ul> + <summary class="download-dropdown-label">{{ _('Download') }}</summary> + <div class="download-table-container"> + <table class="download-table" aria-label="Download formats"> + <thead> + <tr> + <th scope="col">{{ _('Ext') }}</th> + <th scope="col">{{ _('Video') }}</th> + <th scope="col">{{ _('Audio') }}</th> + <th scope="col">{{ _('Size') }}</th> + <th scope="col">{{ _('Codecs') }}</th> + <th scope="col">{{ _('Link') }}</th> + </tr> + </thead> + <tbody> + {% for format in download_formats %} + <tr> + <td data-label="{{ _('Ext') }}">{{ format['ext'] }}</td> + <td data-label="{{ _('Video') }}">{{ format['video_quality'] }}</td> + <td data-label="{{ _('Audio') }}">{{ format['audio_quality'] }}</td> + <td data-label="{{ _('Size') }}">{{ format['file_size'] }}</td> + <td data-label="{{ _('Codecs') }}">{{ format['codecs'] }}</td> + <td data-label="{{ _('Link') }}"><a class="download-link" href="{{ format['url'] }}" download="{{ title }}.{{ format['ext'] }}" aria-label="{{ _('Download') }} {{ format['ext'] }} {{ format['video_quality'] }} {{ format['audio_quality'] }}">{{ _('Download') }}</a></td> + </tr> + {% endfor %} + {% for download in other_downloads %} + <tr> + <td data-label="{{ _('Ext') }}">{{ download['ext'] }}</td> + <td data-label="{{ _('Video') }}" colspan="3">{{ download['label'] }}</td> + <td data-label="{{ _('Codecs') }}">{{ download.get('codecs', 'N/A') }}</td> + <td data-label="{{ _('Link') }}"><a class="download-link" href="{{ download['url'] }}" download aria-label="{{ _('Download') }} {{ download['label'] }}">{{ _('Download') }}</a></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> </details> + {% else %} + <span class="v-download"></span> + {% endif %} <span class="v-description">{{ common_elements.text_runs(description)|escape|urlize|timestamps|safe }}</span> <div class="v-music-list"> @@ -115,7 +152,11 @@ {% for track in music_list %} <tr> {% for attribute in music_attributes %} - <td>{{ track.get(attribute.lower(), '') }}</td> + {% if attribute.lower() == 'title' and track['url'] is not none %} + <td><a href="{{ track['url'] }}">{{ track.get(attribute.lower(), '') }}</a></td> + {% else %} + <td>{{ track.get(attribute.lower(), '') }}</td> + {% endif %} {% endfor %} </tr> {% endfor %} @@ -123,7 +164,7 @@ {% endif %} </div> <details class="v-more-info"> - <summary>More info</summary> + <summary>{{ _('More info') }}</summary> <div class="more-info-content"> <p>Tor exit node: {{ ip_address }}</p> {% if invidious_used %} @@ -143,154 +184,39 @@ <!-- playlist --> {% if playlist %} - <div class="site-playlist"> - <div class="playlist-header"> - <a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a> - <ul class="playlist-metadata"> - <li>Autoplay: <input type="checkbox" id="autoplay-toggle"></li> - {% if playlist['current_index'] is none %} - <li>[Error!]/{{ playlist['video_count'] }}</li> - {% else %} - <li>{{ playlist['current_index']+1 }}/{{ playlist['video_count'] }}</li> - {% endif %} - <li><a href="{{ playlist['author_url'] }}" title="{{ playlist['author'] }}">{{ playlist['author'] }}</a></li> - </ul> + <div class="site-playlist"> + <div class="playlist-header"> + <a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a> + <ul class="playlist-metadata"> + <li><label for="playlist-autoplay-toggle">{{ _('AutoNext') }}: </label><input id="playlist-autoplay-toggle" type="checkbox" class="autoplay-toggle"></li> + {% if playlist['current_index'] is none %} + <li>[Error!]/{{ playlist['video_count'] }}</li> + {% else %} + <li>{{ playlist['current_index']+1 }}/{{ playlist['video_count'] }}</li> + {% endif %} + {% if playlist['author_url'] %} + <li><a href="{{ playlist['author_url'] }}" title="{{ playlist['author'] }}">{{ playlist['author'] }}</a></li> + {% elif playlist['author'] %} + <li>{{ playlist['author'] }}</li> + {% endif %} + </ul> + </div> + <nav class="playlist-videos"> + {% for info in playlist['items'] %} + {# non-lazy load for 5 videos surrounding current video #} + {# for non-js browsers or old such that IntersectionObserver doesn't work #} + {# -10 is sentinel to not load anything if there's no current_index for some reason #} + {% if (playlist.get('current_index', -10) - loop.index0)|abs is lt(5) %} + {{ common_elements.item(info, include_badges=false, lazy_load=false) }} + {% else %} + {{ common_elements.item(info, include_badges=false, lazy_load=true) }} + {% endif %} + {% endfor %} + </nav> </div> - <nav class="playlist-videos"> - {% for info in playlist['items'] %} - {# non-lazy load for 5 videos surrounding current video #} - {# for non-js browsers or old such that IntersectionObserver doesn't work #} - {# -10 is sentinel to not load anything if there's no current_index for some reason #} - {% if (playlist.get('current_index', -10) - loop.index0)|abs is lt(5) %} - {{ common_elements.item(info, include_badges=false, lazy_load=false) }} - {% else %} - {{ common_elements.item(info, include_badges=false, lazy_load=true) }} - {% endif %} - {% endfor %} - </nav> - {% if playlist['current_index'] is not none %} - <script> - // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - (function main() { - // from https://stackoverflow.com/a/6969486 - function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string - } - let playability_error = {{ 'true' if playability_error else 'false' }}; - let playlist_id = {{ playlist['id']|tojson }}; - playlist_id = escapeRegExp(playlist_id); - - // read cookies on whether to autoplay thru playlist - // pain in the ass: - // https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie - let cookieValue = document.cookie.replace(new RegExp( - '(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1'); - let autoplayEnabled = 0; - if(cookieValue.length === 0){ - autoplayEnabled = 0; - } else { - autoplayEnabled = Number(cookieValue); - } - - // check the checkbox if autoplay is on - let checkbox = document.querySelector('#autoplay-toggle'); - if(autoplayEnabled){ - checkbox.checked = true; - } - - // listen for checkbox to turn autoplay on and off - checkbox.addEventListener( 'change', function() { - if(this.checked) { - autoplayEnabled = 1; - document.cookie = 'autoplay_' + playlist_id + '=1'; - } else { - autoplayEnabled = 0; - document.cookie = 'autoplay_' + playlist_id + '=0'; - } - }); - - const vid = document.getElementById('js-video-player'); - if(!playability_error){ - if(autoplayEnabled){ - vid.play(); - } - } - - let currentIndex = {{ playlist['current_index']|tojson }}; - {% if playlist['current_index']+1 == playlist['items']|length %} - let nextVideoUrl = null; - {% else %} - let nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }}; - {% endif %} - let nextVideoDelay = 1000; - - // scroll playlist to proper position - let pl = document.querySelector('.playlist-videos'); - // item height + gap == 100 - pl.scrollTop = 100*currentIndex; - - // go to next video when video ends - // https://stackoverflow.com/a/2880950 - if(nextVideoUrl){ - if(playability_error){ - videoEnded(); - } else { - vid.addEventListener('ended', videoEnded, false); - } - function nextVideo(){ - if(autoplayEnabled){ - window.location.href = nextVideoUrl; - } - } - function videoEnded(e) { - window.setTimeout(nextVideo, nextVideoDelay); - } - } - }()); - // @license-end - </script> - {% endif %} - {% if playlist['id'] is not none %} - <script> - // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - (function main() { - // lazy load playlist images - // copied almost verbatim from - // https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/ - // IntersectionObserver isn't supported in pre-quantum - // firefox versions, but the alternative of making it - // manually is a performance drain, so oh well - let observer = new IntersectionObserver(lazyLoad, { - // where in relation to the edge of the viewport, we are observing - rootMargin: "100px", - // how much of the element needs to have intersected - // in order to fire our loading function - threshold: 1.0 - }); - - function lazyLoad(elements) { - elements.forEach(item => { - if (item.intersectionRatio > 0) { - // set the src attribute to trigger a load - item.target.src = item.target.dataset.src; - // stop observing this element. Our work here is done! - observer.unobserve(item.target); - }; - }); - }; - - // Tell our observer to observe all img elements with a "lazy" class - let lazyImages = document.querySelectorAll('img.lazy'); - lazyImages.forEach(img => { - observer.observe(img); - }); - }()); - // @license-end - </script> - {% endif %} - </div> + {% elif settings.related_videos_mode != 0 %} + <div class="related-autoplay"><label for="related-autoplay-toggle">{{ _('AutoNext') }}: </label><input id="related-autoplay-toggle" type="checkbox" class="autoplay-toggle"></div> {% endif %} - <!-- /playlist --> {% if subtitle_sources %} <details id="transcript-details"> @@ -311,7 +237,7 @@ {% if settings.related_videos_mode != 0 %} <details class="related-videos-outer" {{'open' if settings.related_videos_mode == 1 else ''}}> - <summary>Related Videos</summary> + <summary>{{ _('Related Videos') }}</summary> <nav class="related-videos-inner"> {% for info in related %} {{ common_elements.item(info, include_badges=false) }} @@ -325,10 +251,10 @@ <!-- comments --> {% if settings.comments_mode != 0 %} {% if comments_disabled %} - <div class="comments-area-outer comments-disabled">Comments disabled</div> + <div class="comments-area-outer comments-disabled">{{ _('Comments disabled') }}</div> {% else %} <details class="comments-area-outer" {{'open' if settings.comments_mode == 1 else ''}}> - <summary>{{ comment_count|commatize }} comment{{'s' if comment_count != 1 else ''}}</summary> + <summary>{{ comment_count|commatize }} {{ _('Comment') }}{{'s' if comment_count != '1' else ''}}</summary> <div class="comments-area-inner comments-area"> {% if comments_info %} {{ comments.video_comments(comments_info) }} @@ -342,12 +268,62 @@ <script> // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - data = {{ js_data|tojson }}; + let storyboard_url = {{ storyboard_url | tojson }}; + let hls_manifest_url = {{ hls_manifest_url | tojson }}; + let hls_unavailable = {{ hls_unavailable | tojson }}; + let playback_mode = {{ playback_mode | tojson }}; + let pair_sources = {{ pair_sources | tojson }}; + let pair_idx = {{ pair_idx | tojson }}; // @license-end </script> + <script src="/youtube.com/static/js/common.js"></script> <script src="/youtube.com/static/js/transcript-table.js"></script> - {% if settings.use_video_hotkeys %} <script src="/youtube.com/static/js/hotkeys.js"></script> {% endif %} + + {% set hls_should_work = (playback_mode == 'hls' or playback_mode == 'auto') and not hls_unavailable %} + {% set use_dash = not hls_should_work %} + + {% if use_dash %} + <script src="/youtube.com/static/js/av-merge.js"></script> + {% else %} + <script src="/youtube.com/static/js/hls.min.js" + integrity="sha512-CSVqc4a7tn+tizDNt+eDoVn2fXYAwMDpCLrwGlWrOktNfZQ9gp4dKKScElMeRlrIifhliXs0a06BLaUgmMlCUw==" + crossorigin="anonymous"></script> + {% endif %} + + {% if settings.use_video_player == 0 %} + <!-- Native player (no hotkeys) --> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 1 %} + <!-- Native player with hotkeys --> + <script src="/youtube.com/static/js/hotkeys.js"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 2 %} + <!-- plyr --> + <script src="/youtube.com/static/modules/plyr/plyr.min.js" + integrity="sha512-l6ZzdXpfMHRfifqaR79wbYCEWjLDMI9DnROvb+oLkKq6d7MGroGpMbI7HFpicvmAH/2aQO+vJhewq8rhysrImw==" + crossorigin="anonymous"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/plyr.dash.start.js"></script> + {% else %} + <script src="/youtube.com/static/js/plyr.hls.start.js"></script> + {% endif %} + <!-- /plyr --> + {% endif %} + + <!-- Storyboard Preview Thumbnails (native players only; Plyr handles this internally) --> + {% if settings.use_video_player != 2 and settings.native_player_storyboard %} + <script src="/youtube.com/static/js/storyboard-preview.js"></script> + {% endif %} + {% if settings.use_comments_js %} <script src="/youtube.com/static/js/comments.js"></script> {% endif %} {% if settings.use_sponsorblock_js %} <script src="/youtube.com/static/js/sponsorblock.js"></script> {% endif %} {% endblock main %} |
