aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--settings.py18
-rw-r--r--youtube/__init__.py17
-rw-r--r--youtube/local_playlist.py1
-rw-r--r--youtube/static/comments.css5
-rw-r--r--youtube/static/dark_theme.css21
-rw-r--r--youtube/static/gray_theme.css9
-rw-r--r--youtube/static/light_theme.css9
-rw-r--r--youtube/static/shared.css491
-rw-r--r--youtube/templates/base.html25
-rw-r--r--youtube/templates/channel.html3
-rw-r--r--youtube/templates/comments.html28
-rw-r--r--youtube/templates/comments_page.html83
-rw-r--r--youtube/templates/common_elements.html140
-rw-r--r--youtube/templates/delete_comment.html8
-rw-r--r--youtube/templates/home.html22
-rw-r--r--youtube/templates/local_playlist.html50
-rw-r--r--youtube/templates/login.html17
-rw-r--r--youtube/templates/playlist.html121
-rw-r--r--youtube/templates/post_comment.html23
-rw-r--r--youtube/templates/search.html34
-rw-r--r--youtube/templates/watch.html439
-rw-r--r--youtube/watch.py25
-rw-r--r--youtube/yt_data_extract.py4
-rw-r--r--youtube_dl/extractor/youtube.py24
24 files changed, 768 insertions, 849 deletions
diff --git a/settings.py b/settings.py
index ae914f3..3e2e1d0 100644
--- a/settings.py
+++ b/settings.py
@@ -92,6 +92,24 @@ For security reasons, enabling this is not recommended.''',
],
}),
+ ('theater_mode', {
+ 'type': bool,
+ 'default': True,
+ 'comment': '',
+ }),
+
+ ('default_resolution', {
+ 'type': int,
+ 'default': 720,
+ 'comment': '',
+ }),
+
+ ('theme', {
+ 'type': int,
+ 'default': 0,
+ 'comment': '',
+ }),
+
('gather_googlevideo_domains', {
'type': bool,
'default': False,
diff --git a/youtube/__init__.py b/youtube/__init__.py
index e620827..38ff7d3 100644
--- a/youtube/__init__.py
+++ b/youtube/__init__.py
@@ -1,7 +1,22 @@
import flask
+import settings
yt_app = flask.Flask(__name__)
yt_app.url_map.strict_slashes = False
@yt_app.route('/')
def homepage():
- return flask.render_template('base.html', title="Youtube local")
+ return flask.render_template('home.html', title="Youtube 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',
+ }
+
diff --git a/youtube/local_playlist.py b/youtube/local_playlist.py
index bb05d1a..4b92315 100644
--- a/youtube/local_playlist.py
+++ b/youtube/local_playlist.py
@@ -82,7 +82,6 @@ def get_local_playlist_videos(name, offset=0, amount=50):
else:
info['thumbnail'] = util.get_thumbnail_url(info['id'])
missing_thumbnails.append(info['id'])
- info['item_size'] = 'small'
info['type'] = 'video'
yt_data_extract.add_extra_html_info(info)
videos.append(info)
diff --git a/youtube/static/comments.css b/youtube/static/comments.css
index 4cec3e1..85f0cc1 100644
--- a/youtube/static/comments.css
+++ b/youtube/static/comments.css
@@ -69,7 +69,7 @@
display:grid;
grid-template-columns: auto auto 100px 1fr;
grid-template-rows: 0fr 0fr 0fr 0fr;
- background-color: #dadada;
+ background-color: var(--interface-color);
justify-content: start;
}
@@ -102,8 +102,6 @@
grid-column: 3;
grid-row: 1;
white-space: nowrap;
- color: black;
-
}
@@ -126,4 +124,5 @@
.more-comments{
justify-self:center;
margin-top:10px;
+ margin-bottom: 10px;
}
diff --git a/youtube/static/dark_theme.css b/youtube/static/dark_theme.css
new file mode 100644
index 0000000..4f302cb
--- /dev/null
+++ b/youtube/static/dark_theme.css
@@ -0,0 +1,21 @@
+body{
+ --interface-color: #333333;
+ --text-color: #cccccc;
+ --background-color: #000000;
+}
+
+a:link {
+ color: #22aaff;
+}
+
+a:visited {
+ color: ##7755ff;
+}
+
+a:not([href]){
+ color: var(--text-color);
+}
+
+.comment .permalink{
+ color: #ffffff;
+}
diff --git a/youtube/static/gray_theme.css b/youtube/static/gray_theme.css
new file mode 100644
index 0000000..69cc849
--- /dev/null
+++ b/youtube/static/gray_theme.css
@@ -0,0 +1,9 @@
+body{
+ --interface-color: #dadada;
+ --text-color: #222222;
+ --background-color: #bcbcbc;
+}
+
+.comment .permalink{
+ color: #000000;
+}
diff --git a/youtube/static/light_theme.css b/youtube/static/light_theme.css
new file mode 100644
index 0000000..05697b9
--- /dev/null
+++ b/youtube/static/light_theme.css
@@ -0,0 +1,9 @@
+body{
+ --interface-color: #ffffff;
+ --text-color: #222222;
+ --background-color: #f8f8f8;
+}
+
+.comment .permalink{
+ color: #000000;
+}
diff --git a/youtube/static/shared.css b/youtube/static/shared.css
index a360972..a79e42b 100644
--- a/youtube/static/shared.css
+++ b/youtube/static/shared.css
@@ -4,93 +4,114 @@ h1, h2, h3, h4, h5, h6, div, button{
}
+address{
+ font-style:normal;
+}
body{
margin:0;
padding: 0;
- color:#222;
+ color:var(--text-color);
- background-color:#cccccc;
+ background-color:var(--background-color);
min-height:100vh;
-
- display:grid;
- grid-template-rows: 50px 1fr;
+ display: flex;
+ flex-direction: column;
}
header{
background-color:#333333;
+ height: 50px;
- grid-row: 1;
-
- display:grid;
- grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr);
- }
-
- main{
- grid-row: 2;
+ display: flex;
+ justify-content: center;
}
-address{
- font-style:normal;
-}
-
-
-
- #site-search{
- grid-column: 2;
- display: grid;
- grid-template-columns: 1fr auto auto;
+ #home-link{
+ align-self: center;
+ margin-left:10px;
+ color: #ffffff;
+ }
- }
- #site-search .search-box{
- align-self:center;
- height:25px;
- border:0;
-
- grid-column: 1;
+ #site-search{
+ max-width: 600px;
+ margin-left:10px;
+ display: flex;
+ flex-grow: 1;
}
- #site-search .search-button{
- grid-column: 2;
- align-self:center;
- height:25px;
- border-style:solid;
- border-width:1px;
- }
- #site-search .dropdown{
- margin-left:5px;
- grid-column: 3;
- align-self:center;
- height:25px;
- }
- #site-search .dropdown button{
+ #site-search .search-box{
+ align-self:center;
+ height:25px;
+ border:0;
+
+ flex-grow: 1;
+ }
+ #site-search .search-button{
align-self:center;
height:25px;
border-style:solid;
border-width:1px;
}
- #site-search .css-sucks{
- width:0px;
- height:0px;
+ #site-search .dropdown{
+ margin-left:5px;
+ align-self:center;
+ height:25px;
}
- #site-search .dropdown-content{
- grid-template-columns: auto auto;
- white-space: nowrap;
+ #site-search .dropdown button{
+ align-self:center;
+ height:25px;
+
+ border-style:solid;
+ border-width:1px;
+ }
+ #site-search .css-sucks{
+ width:0px;
+ height:0px;
}
- #site-search .dropdown-content h3{
- grid-column:1 / span 2;
+ #site-search .dropdown-content{
+ grid-template-columns: auto auto;
+ white-space: nowrap;
}
+ #site-search .dropdown-content h3{
+ grid-column:1 / span 2;
+ }
+
+ #playlist-edit{
+ margin-left: 10px;
+ align-self: center;
+ }
+ #local-playlists{
+ margin-right:5px;
+ color: #ffffff;
+ }
+ #playlist-name-selection{
+ }
+ #playlist-add-button{
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+ #item-selection-reset{
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+ main{
+ flex-grow: 1;
+ padding-bottom: 20px;
+ }
+
.dropdown{
z-index:1;
}
.dropdown-content{
display:none;
- background-color: #e9e9e9;
+ background-color: var(--interface-color);
}
.dropdown:hover .dropdown-content{
/* For some reason, if this is just grid, it will insist on being 0px wide just like its 0px by 0px parent */
@@ -98,281 +119,179 @@ address{
display:inline-grid;
}
-
-
-#header-right{
- grid-column:4;
-
- display:grid;
- grid-template-columns:auto auto auto 1fr;
- grid-template-rows: 1fr;
- width: 540px;
-}
- #playlist-edit{
- display:contents;
- }
- #local-playlists{
- grid-column: 1;
- grid-row:1;
- align-self: center;
- margin-right:5px;
- color: #ffffff;
- }
- #playlist-name-selection{
- grid-column:2;
- grid-row:1;
- justify-self:start;
- align-self: center;
- }
- #playlist-add-button{
- grid-column:3;
- grid-row:1;
- align-self: center;
- padding-left: 10px;
- padding-right: 10px;
- }
- #item-selection-reset{
- grid-column:4;
- grid-row:1;
- align-self: center;
- justify-self:start;
- padding-left: 10px;
- padding-right: 10px;
- }
-
-
-
.item-list{
display: grid;
- grid-auto-rows: 138px;
grid-row-gap: 10px;
}
- .item-list .video-thumbnail-box{
- width:246px;
- }
- .item-list .playlist-thumbnail-box{
- width:246px;
- }
.item-grid{
- display:grid;
- grid-template-columns: repeat(auto-fill, 400px);
- grid-auto-rows: 94px;
- grid-row-gap: 10px;
+ display: flex;
+ flex-wrap: wrap;
}
- .item-grid .video-thumbnail-box{
- width:168px;
+ .item-grid > .playlist-item-box{
+ margin-right: 10px;
}
- .item-grid .playlist-thumbnail-box{
- width:168px;
+ .item-grid > * {
+ margin-bottom: 10px;
+ }
+ .item-grid .horizontal-item-box .item{
+ width:370px;
+ }
+ .item-grid .vertical-item-box .item{
}
-
-
-.medium-item-box{
-
- display:grid;
- grid-template-columns: 1fr 30px;
+.item-box{
+ display: inline-flex;
+ flex-direction: row;
}
-.medium-item{
- background-color:#bcbcbc;
- text-decoration:none;
- font-size: 12px;
- color: #767676;
-
- display: grid;
- align-content: start;
- grid-template-columns: auto 1fr auto;
- grid-template-rows: auto auto auto auto auto 1fr;
+.vertical-item-box{
}
- .medium-item .title{
- grid-column:2 / span 2;
- grid-row:1;
- justify-self:start;
- min-width: 0;
- max-height:3.6em;
- overflow:hidden;
-
- color: #333;
- font-size: 16px;
- font-weight: 500;
- text-decoration:initial;
- }
- .medium-item address{
- display:inline;
+.horizontal-item-box{
+}
+ .item{
+ background-color:var(--interface-color);
+ text-decoration:none;
+ font-size: 12px;
+ color: #767676;
}
- /*.medium-item .views{
- grid-column: 3;
- grid-row: 2;
- justify-self:end;
+
+ .horizontal-item-box .item {
+ flex-grow: 1;
+ display: grid;
+ align-content: start;
+ grid-template-columns: auto 1fr;
+ grid-template-rows: auto auto auto auto 1fr;
}
- .medium-item time{
- grid-column: 2;
- grid-row: 3;
- justify-self:start;
- }*/
- .medium-item .stats{
- grid-column: 2 / span 2;
- grid-row: 2;
- max-height:2.4em;
- overflow:hidden;
+ .vertical-item-box .item{
+ width: 168px;
}
- .medium-item .stats > *::after{
- content: " | ";
+ .thumbnail-box{
+ font-size: 0px; /* prevent newlines and blank space from creating gaps */
+ position: relative;
+ display: block;
}
- .medium-item .stats > *:last-child::after{
- content: "";
+ .horizontal-item-box .thumbnail-box{
+ grid-row: 1 / span 5;
+ margin-right: 4px;
}
-
- .medium-item .description{
- grid-column: 2 / span 2;
- grid-row: 4;
- }
- .medium-item .badges{
- grid-column: 2 / span 2;
- grid-row: 5;
- }
- /* thumbnail size */
- .medium-item img{
- /*height:138px;
- width:246px;*/
- height:100%;
- justify-self:center;
- }
-
-.small-item-box{
- color: #767676;
- font-size: 12px;
+ .no-description .thumbnail-box{
+ width: 168px;
+ height:94px;
+ }
+ .has-description .thumbnail-box{
+ width: 246px;
+ height:138px;
+ }
+ .video-item .thumbnail-info{
+ position: absolute;
+ bottom: 2px;
+ right: 2px;
+ opacity: .8;
+ color: #ffffff;
+ font-size: 12px;
+ background-color: #000000;
+ }
+ .playlist-item .thumbnail-info{
+ position: absolute;
+ right: 0px;
+ bottom: 0px;
+ height: 100%;
+ width: 50%;
+ text-align:center;
+ white-space: pre-line;
+ opacity: .8;
+ color: #cfcfcf;
+ font-size: 12px;
+ background-color: #000000;
+ }
+ .playlist-item .thumbnail-info span{ /* trick to vertically center the text */
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ }
+ .thumbnail-img{ /* center it */
+ margin: auto;
+ display: block;
+ max-height: 100%;
+ }
+ .horizontal-item-box .thumbnail-img{
+ height: 100%;
+ }
- display:grid;
- grid-template-columns: 1fr 30px;
- grid-template-rows: 94px;
-}
+ .item .title{
+ min-width: 0;
+ max-height:3.6em;
+ overflow:hidden;
-.small-item{
- background-color:#bcbcbc;
- align-content: start;
- text-decoration:none;
-
- display: grid;
- grid-template-columns: 168px 1fr;
- grid-column-gap: 5px;
- grid-template-rows: auto auto auto 1fr;
-}
- .small-item .title{
- grid-column:2;
- grid-row:1;
- margin:0;
+ color: var(--text-color);
+ font-size: 16px;
+ font-weight: 500;
+ text-decoration:initial;
+ }
- color: #333;
- font-size: 16px;
- font-weight: 500;
- text-decoration:initial;
- min-width: 0;
- justify-self:start;
+ .stats{
+ list-style: none;
+ padding: 0px;
+ margin: 0px;
+ }
+ .horizontal-stats{
+ max-height:2.4em;
+ overflow:hidden;
+ }
+ .horizontal-stats > li{
+ display: inline;
+ }
- overflow:hidden;
- max-height: 3.3em;
- line-height: 1.1em;
- }
- .small-item address{
- grid-column: 2;
- grid-row: 2;
- justify-self: start;
- }
-
- .small-item .views{
- grid-column: 2;
- grid-row: 3;
- justify-self:start;
- }
- /* thumbnail size */
- .small-item img{
- /*height:94px;
- width:168px;*/
- height:100%;
- justify-self:center;
- }
-
-.item-checkbox{
- justify-self:start;
- align-self:center;
- height:30px;
- width:30px;
-
- grid-column: 2;
-}
+ .horizontal-stats > li::after{
+ content: " | ";
+ }
+ .horizontal-stats > li:last-child::after{
+ content: "";
+ }
-/* ---Thumbnails for videos---- */
-.video-thumbnail-box{
- max-height:100%;
+ .vertical-stats{
+ display: flex;
+ flex-direction: column;
+ }
+ .stats address{
+ display: inline;
+ }
+ .vertical-stats li{
+ max-height: 1.3em;
+ overflow: hidden;
+ }
- grid-column:1;
- grid-row:1 / span 6;
-
- display:grid;
- grid-template-columns: 1fr 0fr;
-}
- .video-thumbnail-img{
- grid-column:1 / span 2;
- grid-row:1;
- }
- .video-duration{
- grid-column: 2;
- grid-row: 1;
- align-self: end;
- opacity: .8;
- color: #ffffff;
- font-size: 12px;
- background-color: #000000;
+ .item-checkbox{
+ justify-self:start;
+ align-self:center;
+ height:30px;
+ width:30px;
+ min-width:30px;
+ margin: 0px;
}
-/* ---Thumbnails for playlists---- */
-.playlist-thumbnail-box{
- max-height:100%;
-
- grid-column:1;
- grid-row:1 / span 6;
-
- display:grid;
- grid-template-columns: 3fr 2fr;
-}
- .playlist-thumbnail-img{
- grid-column:1 / span 2;
- grid-row:1;
- }
- .playlist-thumbnail-info{
- grid-column:2;
- grid-row:1;
-
- display: grid;
- align-items:center;
-
- text-align:center;
- white-space: pre-line;
- opacity: .8;
- color: #cfcfcf;
- background-color: #000000;
- }
.page-button-row{
+ margin-top: 10px;
+ margin-bottom: 10px;
justify-self:center;
+ justify-content: center;
display: grid;
grid-auto-columns: 40px;
grid-auto-flow: column;
height: 40px;
}
.page-button{
- background-color: #e9e9e9;
+ background-color: var(--interface-color);
border-style: outset;
border-width: 2px;
font-weight: bold;
text-align: center;
}
.sort-button{
- background-color: #d0d0d0;
+ background-color: var(--interface-color);
padding: 2px;
justify-self: start;
}
diff --git a/youtube/templates/base.html b/youtube/templates/base.html
index 72e3691..9127efa 100644
--- a/youtube/templates/base.html
+++ b/youtube/templates/base.html
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ page_title }}</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; script-src 'none'; media-src 'self' https://*.googlevideo.com">
+ <link href="{{ theme_path }}" type="text/css" rel="stylesheet">
<link href="/youtube.com/static/shared.css" type="text/css" rel="stylesheet">
<link href="/youtube.com/static/comments.css" type="text/css" rel="stylesheet">
<link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon">
@@ -16,6 +17,7 @@
</head>
<body>
<header>
+ <a href="/youtube.com" id="home-link">Home</a>
<form id="site-search" action="/youtube.com/search">
<input type="search" name="query" class="search-box" value="{{ search_box_value }}">
<button type="submit" value="Search" class="search-button">Search</button>
@@ -91,19 +93,16 @@
</div>
</form>
- <div id="header-right">
- <form id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self">
- <input name="playlist_name" id="playlist-name-selection" list="playlist-options" type="text">
- <datalist id="playlist-options">
- {% for playlist_name in header_playlist_names %}
- <option value="{{ playlist_name }}">{{ playlist_name }}</option>
- {% endfor %}
- </datalist>
- <button type="submit" id="playlist-add-button" name="action" value="add">Add to playlist</button>
- <button type="reset" id="item-selection-reset">Clear selection</button>
- </form>
- <a href="/youtube.com/playlists" id="local-playlists">Local playlists</a>
- </div>
+ <form id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self">
+ <input name="playlist_name" id="playlist-name-selection" list="playlist-options" type="text">
+ <datalist id="playlist-options">
+ {% for playlist_name in header_playlist_names %}
+ <option value="{{ playlist_name }}">{{ playlist_name }}</option>
+ {% endfor %}
+ </datalist>
+ <button type="submit" id="playlist-add-button" name="action" value="add">Add to playlist</button>
+ <button type="reset" id="item-selection-reset">Clear selection</button>
+ </form>
</header>
<main>
{% block main %}
diff --git a/youtube/templates/channel.html b/youtube/templates/channel.html
index 069e33b..ed04988 100644
--- a/youtube/templates/channel.html
+++ b/youtube/templates/channel.html
@@ -31,7 +31,7 @@
grid-auto-flow: column;
justify-content:start;
- background-color: #aaaaaa;
+ background-color: var(--interface-color);
padding: 3px;
padding-left: 6px;
}
@@ -44,7 +44,6 @@
padding-top: 8px;
padding-bottom: 8px;
padding-left: 6px;
- background-color: #bababa;
margin-bottom: 10px;
}
#number-of-results{
diff --git a/youtube/templates/comments.html b/youtube/templates/comments.html
index 82276b8..20cde4e 100644
--- a/youtube/templates/comments.html
+++ b/youtube/templates/comments.html
@@ -29,21 +29,19 @@
{% endmacro %}
{% macro video_comments(comments_info) %}
- <section class="comments-area">
- <div class="comment-links">
- {% for link_text, link_url in comments_info['comment_links'] %}
- <a class="sort-button" href="{{ link_url }}">{{ link_text }}</a>
- {% endfor %}
- </div>
- <div class="comments">
- {% for comment in comments_info['comments'] %}
- {{ render_comment(comment, comments_info['include_avatars']) }}
- {% 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>
- {% endif %}
- </section>
+ <div class="comment-links">
+ {% for link_text, link_url in comments_info['comment_links'] %}
+ <a class="sort-button" href="{{ link_url }}">{{ link_text }}</a>
+ {% endfor %}
+ </div>
+ <div class="comments">
+ {% for comment in comments_info['comments'] %}
+ {{ render_comment(comment, comments_info['include_avatars']) }}
+ {% 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>
+ {% endif %}
{% endmacro %}
{% macro comment_posting_box(info) %}
diff --git a/youtube/templates/comments_page.html b/youtube/templates/comments_page.html
index 68c8537..047404a 100644
--- a/youtube/templates/comments_page.html
+++ b/youtube/templates/comments_page.html
@@ -3,63 +3,46 @@
{% import "comments.html" as comments %}
{% block style %}
- main{
- display:grid;
- grid-template-columns: 3fr 2fr;
+ .comments-area{
+ margin: auto;
+ width:640px;
}
- #left{
- background-color:#bcbcbc;
-
- display: grid;
- grid-column: 1;
- grid-row: 1;
- grid-template-columns: 1fr 640px;
- grid-template-rows: 0fr 0fr 0fr;
- }
- .comments-area{
- grid-column:2;
- }
- .comment{
- width:640px;
- }
{% endblock style %}
{% block main %}
- <div id="left">
- <section class="comments-area">
- {% if not comments_info['is_replies'] %}
- <section class="video-metadata">
- <a class="video-metadata-thumbnail-box" href="{{ comments_info['video_url'] }}" title="{{ comments_info['video_title'] }}">
- <img class="video-metadata-thumbnail-img" src="{{ comments_info['video_thumbnail'] }}" height="180px" width="320px">
- </a>
- <a class="title" href="{{ comments_info['video_url'] }}" title="{{ comments_info['video_title'] }}">{{ comments_info['video_title'] }}</a>
-
- <h2>Comments page {{ comments_info['page_number'] }}</h2>
- <span>Sorted by {{ comments_info['sort_text'] }}</span>
- </section>
- {% endif %}
-
- {{ comments.comment_posting_box(comment_posting_box_info) }}
-
- {% if not comments_info['is_replies'] %}
- <div class="comment-links">
- {% for link_text, link_url in comments_info['comment_links'] %}
- <a class="sort-button" href="{{ link_url }}">{{ link_text }}</a>
- {% endfor %}
- </div>
- {% endif %}
-
- <div class="comments">
- {% for comment in comments_info['comments'] %}
- {{ comments.render_comment(comment, comments_info['include_avatars']) }}
+ <section class="comments-area">
+ {% if not comments_info['is_replies'] %}
+ <section class="video-metadata">
+ <a class="video-metadata-thumbnail-box" href="{{ comments_info['video_url'] }}" title="{{ comments_info['video_title'] }}">
+ <img class="video-metadata-thumbnail-img" src="{{ comments_info['video_thumbnail'] }}" height="180px" width="320px">
+ </a>
+ <a class="title" href="{{ comments_info['video_url'] }}" title="{{ comments_info['video_title'] }}">{{ comments_info['video_title'] }}</a>
+
+ <h2>Comments page {{ comments_info['page_number'] }}</h2>
+ <span>Sorted by {{ comments_info['sort_text'] }}</span>
+ </section>
+ {% endif %}
+
+ {{ comments.comment_posting_box(comment_posting_box_info) }}
+
+ {% if not comments_info['is_replies'] %}
+ <div class="comment-links">
+ {% for link_text, link_url in comments_info['comment_links'] %}
+ <a class="sort-button" href="{{ link_url }}">{{ link_text }}</a>
{% 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>
- {% endif %}
- </section>
- </div>
+ {% endif %}
+
+ <div class="comments">
+ {% for comment in comments_info['comments'] %}
+ {{ comments.render_comment(comment, comments_info['include_avatars']) }}
+ {% 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>
+ {% endif %}
+ </section>
{% endblock main %}
diff --git a/youtube/templates/common_elements.html b/youtube/templates/common_elements.html
index 49e2fad..67655b3 100644
--- a/youtube/templates/common_elements.html
+++ b/youtube/templates/common_elements.html
@@ -14,121 +14,53 @@
{%- endif -%}
{% endmacro %}
-{% macro small_item(info, include_author=true) %}
- <div class="small-item-box">
- <div class="small-item">
- {% if info['type'] == 'video' %}
- <a class="video-thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
- <img class="video-thumbnail-img" src="{{ info['thumbnail'] }}">
- <span class="video-duration">{{ info['duration'] }}</span>
- </a>
- <a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a>
-
- <address>{{ info['author'] }}</address>
- <span class="views">{{ info['views'] }}</span>
-
- {% elif info['type'] == 'playlist' %}
- <a class="playlist-thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
- <img class="playlist-thumbnail-img" src="{{ info['thumbnail'] }}">
- <div class="playlist-thumbnail-info">
- <span>{{ info['size'] }}</span>
+{% macro item(info, description=false, horizontal=true, include_author=true) %}
+ <div class="item-box {{ info['type'] + '-item-box' }} {{'horizontal-item-box' if horizontal else 'vertical-item-box'}} {{'has-description' if description else 'no-description'}}">
+ <div class="item {{ info['type'] + '-item' }}">
+ <a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
+ <img class="thumbnail-img" src="{{ info['thumbnail'] }}">
+ {% if info['type'] != 'channel' %}
+ <div class="thumbnail-info">
+ <span>{{ info['size'] if info['type'] == 'playlist' else info['duration'] }}</span>
</div>
- </a>
- <a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a>
-
- <address>{{ info['author'] }}</address>
- {% else %}
- Error: unsupported item type
- {% endif %}
- </div>
- {% if info['type'] == 'video' %}
- <input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit">
- {% endif %}
- </div>
-{% endmacro %}
-
-{% macro get_stats(info, include_author=true) %}
- {% if include_author %}
- {% if 'author_url' is in(info) %}
- <address>By <a href="{{ info['author_url'] }}">{{ info['author'] }}</a></address>
- {% else %}
- <address><b>{{ info['author'] }}</b></address>
- {% endif %}
- {% endif %}
- {% if 'views' is in(info) %}
- <span class="views">{{ info['views'] }}</span>
- {% endif %}
- {% if 'published' is in(info) %}
- <time>{{ info['published'] }}</time>
- {% endif %}
-{% endmacro %}
-
-
-
-{% macro medium_item(info, include_author=true) %}
- <div class="medium-item-box">
- <div class="medium-item">
- {% if info['type'] == 'video' %}
- <a class="video-thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
- <img class="video-thumbnail-img" src="{{ info['thumbnail'] }}">
- <span class="video-duration">{{ info['duration'] }}</span>
- </a>
-
- <a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a>
-
- <div class="stats">
- {{ get_stats(info, include_author) }}
- </div>
-
+ {% endif %}
+ </a>
+
+ <div class="title"><a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a></div>
+
+ <ul class="stats {{'vertical-stats' if horizontal and not description and include_author else 'horizontal-stats'}}">
+ {% if info['type'] == 'channel' %}
+ <li><span>{{ info['subscriber_count'] }} subscribers</span></li>
+ <li><span>{{ info['size'] }} videos</span></li>
+ {% else %}
+ {% if include_author %}
+ {% if 'author_url' is in(info) %}
+ <li><address title="{{ info['author'] }}">By <a href="{{ info['author_url'] }}">{{ info['author'] }}</a></address></li>
+ {% else %}
+ <li><address title="{{ info['author'] }}"><b>{{ info['author'] }}</b></address></li>
+ {% endif %}
+ {% endif %}
+ {% if 'views' is in(info) %}
+ <li><span class="views">{{ info['views'] }}</span></li>
+ {% endif %}
+ {% if 'published' is in(info) %}
+ <li><time>{{ info['published'] }}</time></li>
+ {% endif %}
+ {% endif %}
+ </ul>
+
+ {% if description %}
<span class="description">{{ text_runs(info.get('description', '')) }}</span>
- <span class="badges">{{ info['badges']|join(' | ') }}</span>
- {% elif info['type'] == 'playlist' %}
- <a class="playlist-thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
- <img class="playlist-thumbnail-img" src="{{ info['thumbnail'] }}">
- <div class="playlist-thumbnail-info">
- <span>{{ info['size'] }}</span>
- </div>
- </a>
-
- <a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a>
-
- <div class="stats">
- {{ get_stats(info, include_author) }}
- </div>
- {% elif info['type'] == 'channel' %}
- <a class="video-thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
- <img class="video-thumbnail-img" src="{{ info['thumbnail'] }}">
- </a>
-
- <a class="title" href="{{ info['url'] }}">{{ info['title'] }}</a>
-
- <span>{{ info['subscriber_count'] }} subscribers</span>
- <span>{{ info['size'] }} videos</span>
-
- <span class="description">{{ text_runs(info.get('description', '')) }}</span>
- {% else %}
- Error: unsupported item type
{% endif %}
+ <span class="badges">{{ info['badges']|join(' | ') }}</span>
</div>
{% if info['type'] == 'video' %}
<input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit">
{% endif %}
</div>
-{% endmacro %}
-
-{% macro item(info, include_author=true) %}
- {% if info['item_size'] == 'small' %}
- {{ small_item(info, include_author) }}
- {% elif info['item_size'] == 'medium' %}
- {{ medium_item(info, include_author) }}
- {% else %}
- Error: Unknown item size
- {% endif %}
{% endmacro %}
-
-
{% macro page_buttons(estimated_pages, url, parameters_dictionary) %}
{% set current_page = parameters_dictionary.get('page', 1)|int %}
{% set parameters_dictionary = parameters_dictionary.to_dict() %}
diff --git a/youtube/templates/delete_comment.html b/youtube/templates/delete_comment.html
index 71555ee..28c8f2a 100644
--- a/youtube/templates/delete_comment.html
+++ b/youtube/templates/delete_comment.html
@@ -2,14 +2,10 @@
{% extends "base.html" %}
{% block style %}
- main{
- display: grid;
- grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr);
- align-content: start;
- }
main > div, main > form{
+ margin: auto;
margin-top:20px;
- grid-column:2;
+ width: 640px;
}
{% endblock style %}
diff --git a/youtube/templates/home.html b/youtube/templates/home.html
new file mode 100644
index 0000000..9890f5e
--- /dev/null
+++ b/youtube/templates/home.html
@@ -0,0 +1,22 @@
+{% set page_title = title %}
+{% extends "base.html" %}
+{% block style %}
+ ul {
+ background-color: var(--interface-color);
+ padding: 20px;
+ width: 400px;
+ margin: auto;
+ margin-top: 20px;
+ }
+ li {
+ margin-bottom: 10px;
+ }
+{% endblock style %}
+{% block main %}
+ <ul>
+ <li><a href="/youtube.com/playlists">Local playlists</a></li>
+ <li><a href="/youtube.com/subscriptions">Subscriptions</a></li>
+ <li><a href="/youtube.com/subscription_manager">Subscription Manager</a></li>
+ <li><a href="/youtube.com/settings">Settings</a></li>
+ </ul>
+{% endblock main %}
diff --git a/youtube/templates/local_playlist.html b/youtube/templates/local_playlist.html
index f8e6f01..7ba0642 100644
--- a/youtube/templates/local_playlist.html
+++ b/youtube/templates/local_playlist.html
@@ -2,59 +2,41 @@
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main{
- display:grid;
- grid-template-columns: 3fr 1fr;
+ main > *{
+ width: 800px;
+ margin: auto;
}
-
-
- #left{
- grid-column: 1;
- grid-row: 1;
-
- display: grid;
- grid-template-columns: 1fr 800px auto;
- grid-template-rows: 0fr 1fr 0fr;
+ .playlist-metadata{
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
}
.playlist-title{
- grid-column:2;
}
#playlist-remove-button{
- grid-column:3;
align-self: center;
white-space: nowrap;
}
#results{
-
- grid-row: 2;
- grid-column: 2 / span 2;
-
-
display: grid;
grid-auto-rows: 0fr;
grid-row-gap: 10px;
-
- }
- .page-button-row{
- grid-row: 3;
- grid-column: 2;
- justify-self: center;
}
{% endblock style %}
{% block main %}
- <div id="left">
+ <div class="playlist-metadata">
<h2 class="playlist-title">{{ playlist_name }}</h2>
<input type="hidden" name="playlist_page" value="{{ playlist_name }}" form="playlist-edit">
<button type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button>
- <div id="results">
- {% for video_info in videos %}
- {{ common_elements.item(video_info) }}
- {% endfor %}
- </div>
- <nav class="page-button-row">
- {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }}
- </nav>
</div>
+ <div id="results">
+ {% for video_info in videos %}
+ {{ common_elements.item(video_info) }}
+ {% endfor %}
+ </div>
+ <nav class="page-button-row">
+ {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }}
+ </nav>
{% endblock main %}
diff --git a/youtube/templates/login.html b/youtube/templates/login.html
index 0f09a62..384f1ac 100644
--- a/youtube/templates/login.html
+++ b/youtube/templates/login.html
@@ -2,16 +2,14 @@
{% extends "base.html" %}
{% block style %}
- main{
- display: grid;
- grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr);
- align-content: start;
- grid-row-gap: 40px;
+ main > * {
+ width: 640px;
+ margin: auto;
}
-
main form{
+ background-color: var(--interface-color);
+ padding: 10px;
margin-top:20px;
- grid-column:2;
display:grid;
justify-items: start;
align-content: start;
@@ -26,10 +24,9 @@
margin-top:20px;
}
#tor-note{
- grid-row:2;
- grid-column:2;
- background-color: #dddddd;
+ background-color: var(--interface-color);
padding: 10px;
+ margin-top: 40px;
}
{% endblock style %}
diff --git a/youtube/templates/playlist.html b/youtube/templates/playlist.html
index 371b51b..ab2640f 100644
--- a/youtube/templates/playlist.html
+++ b/youtube/templates/playlist.html
@@ -2,72 +2,45 @@
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main{
- display:grid;
- grid-template-columns: 3fr 1fr;
+ main > * {
+ width: 800px;
+ margin:auto;
}
-
-
- #left{
- grid-column: 1;
- grid-row: 1;
-
- display: grid;
- grid-template-columns: 1fr 800px;
- grid-template-rows: 0fr 1fr 0fr;
+ .playlist-metadata{
+ display:grid;
+ grid-template-columns: 0fr 1fr;
}
- .playlist-metadata{
+ .playlist-thumbnail{
+ grid-row: 1 / span 5;
+ grid-column:1;
+ justify-self:start;
+ width:250px;
+ margin-right: 10px;
+ }
+ .playlist-title{
+ grid-row: 1;
+ grid-column:2;
+ }
+ .playlist-author{
+ grid-row:2;
grid-column:2;
- grid-row:1;
-
- display:grid;
- grid-template-columns: 0fr 1fr;
}
- .playlist-thumbnail{
- grid-row: 1 / span 5;
- grid-column:1;
- justify-self:start;
- width:250px;
- margin-right: 10px;
- }
- .playlist-title{
- grid-row: 1;
- grid-column:2;
- }
- .playlist-author{
- grid-row:2;
- grid-column:2;
- }
- .playlist-stats{
- grid-row:3;
- grid-column:2;
- }
-
- .playlist-description{
- grid-row:4;
- grid-column:2;
- min-width:0px;
- white-space: pre-line;
- }
- .page-button-row{
- grid-row: 3;
- grid-column: 2;
- justify-self: center;
+ .playlist-stats{
+ grid-row:3;
+ grid-column:2;
+ }
+
+ .playlist-description{
+ grid-row:4;
+ grid-column:2;
+ min-width:0px;
+ white-space: pre-line;
}
-
-
- #right{
- grid-column: 2;
- grid-row: 1;
- }
#results{
-
- grid-row: 2;
- grid-column: 2;
margin-top:10px;
-
+
display: grid;
grid-auto-rows: 0fr;
grid-row-gap: 10px;
@@ -76,27 +49,25 @@
{% endblock style %}
{% block main %}
- <div id="left">
- <div class="playlist-metadata">
- <img class="playlist-thumbnail" src="{{ thumbnail }}">
- <h2 class="playlist-title">{{ title }}</h2>
- <a class="playlist-author" href="{{ author_url }}">{{ author }}</a>
- <div class="playlist-stats">
- <div>{{ views }}</div>
- <div>{{ size }}</div>
- </div>
- <div class="playlist-description">{{ common_elements.text_runs(description) }}</div>
+ <div class="playlist-metadata">
+ <img class="playlist-thumbnail" src="{{ thumbnail }}">
+ <h2 class="playlist-title">{{ title }}</h2>
+ <a class="playlist-author" href="{{ author_url }}">{{ author }}</a>
+ <div class="playlist-stats">
+ <div>{{ views }}</div>
+ <div>{{ size }}</div>
</div>
+ <div class="playlist-description">{{ common_elements.text_runs(description) }}</div>
+ </div>
- <div id="results">
- {% for info in video_list %}
- {{ common_elements.item(info) }}
- {% endfor %}
- </div>
- <nav class="page-button-row">
- {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
- </nav>
+ <div id="results">
+ {% for info in video_list %}
+ {{ common_elements.item(info) }}
+ {% endfor %}
</div>
+ <nav class="page-button-row">
+ {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
+ </nav>
{% endblock main %}
diff --git a/youtube/templates/post_comment.html b/youtube/templates/post_comment.html
index 67c54f1..ba6a22c 100644
--- a/youtube/templates/post_comment.html
+++ b/youtube/templates/post_comment.html
@@ -3,27 +3,18 @@
{% import "comments.html" as comments %}
{% block style %}
- main{
- display: grid;
- grid-template-columns: 3fr 2fr;
- }
- .left{
- display:grid;
- grid-template-columns: 1fr 640px;
- }
- textarea{
- width: 460px;
- height: 85px;
- }
.comment-form{
- grid-column:2;
+ width: 640px;
+ margin: auto;
justify-content:start;
}
+ textarea{
+ width: 460px;
+ height: 85px;
+ }
{% endblock style %}
{% block main %}
- <div class="left">
- {{ comments.comment_posting_box(comment_posting_box_info) }}
- </div>
+ {{ comments.comment_posting_box(comment_posting_box_info) }}
{% endblock %}
diff --git a/youtube/templates/search.html b/youtube/templates/search.html
index 782a85e..63f930e 100644
--- a/youtube/templates/search.html
+++ b/youtube/templates/search.html
@@ -3,31 +3,19 @@
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main{
- display:grid;
- grid-template-columns: minmax(0px, 1fr) 800px minmax(0px,2fr);
- max-width:100vw;
- }
-
-
+ main > * {
+ max-width: 800px;
+ margin: auto;
+ }
+ #result-info{
+ }
#number-of-results{
font-weight:bold;
}
- #result-info{
- grid-row: 1;
- grid-column:2;
- align-self:center;
- }
- .page-button-row{
- grid-column: 2;
- justify-self: center;
- }
-
-
- .item-list{
- grid-row: 2;
- grid-column: 2;
- }
+ .item-list{
+ padding-left: 10px;
+ padding-right: 10px;
+ }
.badge{
background-color:#cccccc;
}
@@ -45,7 +33,7 @@
</div>
<div class="item-list">
{% for info in results %}
- {{ common_elements.item(info) }}
+ {{ common_elements.item(info, description=true) }}
{% endfor %}
</div>
<nav class="page-button-row">
diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html
index 82c1a97..14e953b 100644
--- a/youtube/templates/watch.html
+++ b/youtube/templates/watch.html
@@ -3,96 +3,116 @@
{% import "common_elements.html" as common_elements %}
{% import "comments.html" as comments %}
{% block style %}
- main{
- display:grid;
- grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr);
- background-color:#cccccc;
- }
+ details > summary{
+ background-color: var(--interface-color);
+ border-style: outset;
+ border-width: 2px;
+ font-weight: bold;
+ padding-bottom: 2px;
+ }
+ details > summary:hover{
+ text-decoration: underline;
+ }
+
+ {% if theater_mode %}
+ video{
+ grid-column: 1 / span 5;
+ justify-self: center;
+ max-width: 100%;
+ width: {{ theater_video_target_width }}px;
+ max-height: {{ video_height }}px;
+ margin-bottom: 10px;
+ background-color: var(--background-color);
+ }
+ .related-videos-outer{
+ grid-row: 2 /span 3;
+ width: 400px;
+ }
+ .video-info{
+ width: 640px;
+ }
+ {% else %}
+ video{
+ height: 360px;
+ width: 640px;
+ grid-column: 2;
+ }
+ .related-videos-outer{
+ grid-row: 1 /span 4;
+ }
+ {% endif %}
+
+ main{
+ display:grid;
+ grid-template-columns: 1fr 640px 40px 400px 1fr;
+ grid-template-rows: auto auto auto auto;
+ align-content: start;
+ }
- #left{
- background-color:#bcbcbc;
+ .video-info{
+ grid-column: 2;
+ grid-row: 2;
+ display: grid;
+ grid-template-rows: 0fr 0fr 0fr 20px 0fr 0fr;
+ grid-template-columns: 1fr 1fr;
+ align-content: start;
+ }
+ .video-info > .title{
+ grid-column: 1 / span 2;
+ min-width: 0;
+ }
+ .video-info > .is-unlisted{
+ background-color: var(--interface-color);
+ justify-self:start;
+ padding-left:2px;
+ padding-right:2px;
+ }
+ .video-info > address{
grid-column: 1;
+ grid-row: 3;
+ justify-self: start;
+ }
+ .video-info > .views{
+ grid-column: 2;
+ grid-row: 3;
+ justify-self:end;
+ }
+ .video-info > time{
+ grid-column: 1;
+ grid-row: 4;
+ justify-self:start;
+ }
+ .video-info > .likes-dislikes{
+ grid-column: 2;
+ grid-row: 4;
+ justify-self:end;
+ }
+ .video-info > .download-dropdown{
+ grid-column:1 / span 2;
+ grid-row: 6;
+ }
+ .video-info > .checkbox{
+ justify-self:end;
+ align-self: start;
+ grid-row: 5;
+ grid-column: 2;
+ }
+ .video-info > .description{
+ background-color:var(--interface-color);
+ margin-top:8px;
+ white-space: pre-wrap;
+ min-width: 0;
+ word-wrap: break-word;
+ grid-column: 1 / span 2;
+ grid-row: 7;
+ }
+
+ .music-list{
+ grid-row:8;
+ grid-column: 1 / span 2;
+ background-color: var(--interface-color);
}
- .full-item{
- display: grid;
- grid-column: 2;
- grid-template-rows: 0fr 0fr 0fr 0fr 20px 0fr 0fr;
- grid-template-columns: 1fr 1fr;
- align-content: start;
- background-color:#bcbcbc;
- }
- .full-item > video{
- grid-column: 1 / span 2;
- grid-row: 1;
- }
- .full-item > .title{
- grid-column: 1 / span 2;
- grid-row:2;
- min-width: 0;
- }
- .full-item > .is-unlisted{
- background-color: #d0d0d0;
- justify-self:start;
- padding-left:2px;
- padding-right:2px;
- }
- .full-item > address{
- grid-column: 1;
- grid-row: 4;
- justify-self: start;
- }
- .full-item > .views{
- grid-column: 2;
- grid-row: 4;
- justify-self:end;
- }
- .full-item > time{
- grid-column: 1;
- grid-row: 5;
- justify-self:start;
- }
- .full-item > .likes-dislikes{
- grid-column: 2;
- grid-row: 5;
- justify-self:end;
- }
- .full-item > .download-dropdown{
- grid-column:1;
- grid-row: 6;
- }
- .full-item > .checkbox{
- justify-self:end;
-
- grid-row: 6;
- grid-column: 2;
- }
- .full-item > .description{
- background-color:#d0d0d0;
- margin-top:8px;
- white-space: pre-wrap;
- min-width: 0;
- word-wrap: break-word;
- grid-column: 1 / span 2;
- grid-row: 7;
- }
- .full-item .music-list{
- grid-row:8;
- grid-column: 1 / span 2;
- }
-
- .full-item .comments-area{
- grid-column: 1 / span 2;
- grid-row: 9;
- margin-top:10px;
- }
- .comment{
- width:640px;
- }
-
- .music-list{
- background-color: #d0d0d0;
- }
.music-list table,th,td{
border: 1px solid;
}
@@ -105,126 +125,161 @@
font-weight:bold;
margin-bottom:5px;
}
+ .comments-area-outer{
+ grid-column: 2;
+ grid-row: 3;
+ margin-top:10px;
+ }
+ .comments-area-inner{
+ padding-top: 10px;
+ }
+ .comment{
+ width:640px;
+ }
+ .related-videos-outer{
+ grid-column: 4;
+ max-width: 640px;
+ }
+ .related-videos-inner{
+ padding-top: 10px;
+ display: grid;
+ grid-auto-rows: 94px;
+ grid-row-gap: 10px;
+ }
- #related{
- grid-column: 4;
- display: grid;
- grid-auto-rows: 90px;
- grid-row-gap: 10px;
- }
- #related .medium-item{
- grid-template-columns: 160px 1fr 0fr;
- }
+ /* Put related vids below videos when window is too small */
+ /* 1100px instead of 1080 because W3C is full of idiots who include scrollbar width */
+ @media (max-width:1100px){
+ main{
+ grid-template-columns: 1fr 640px 40px 1fr;
+ }
+ .related-videos-outer{
+ margin-top: 10px;
+ grid-column: 2;
+ grid-row: 3;
+ width: initial;
+ }
+ .comments-area-outer{
+ grid-row: 4;
+ }
+ }
- .download-dropdown{
- z-index:1;
- justify-self:start;
- min-width:0px;
- height:0px;
+ .download-dropdown-content{
+ background-color: var(--interface-color);
+ padding: 10px;
+ list-style: none;
+ margin: 0px;
+ }
+ li.download-format{
+ margin-bottom: 7px;
+ }
+ .format-attributes{
+ list-style: none;
+ padding: 0px;
+ margin: 0px;
+ display: flex;
+ flex-direction: row;
}
-
- .download-dropdown-label{
- background-color: #e9e9e9;
- border-style: outset;
- border-width: 2px;
- font-weight: bold;
+ .format-attributes li{
+ white-space: nowrap;
+ max-height: 1.2em;
}
-
- .download-dropdown-content{
- display:none;
- background-color: #e9e9e9;
+ .format-ext{
+ width: 60px;
}
- .download-dropdown:hover .download-dropdown-content {
- display: grid;
- grid-auto-rows:30px;
- padding-bottom: 50px;
+ .format-res{
+ width:90px;
}
- .download-dropdown-content a{
- white-space: nowrap;
- display:grid;
- grid-template-columns: 60px 90px auto;
- max-height: 1.2em;
- }
{% endblock style %}
{% block main %}
- <div id="left">
- </div>
- <article class="full-item">
-
- <video width="640" height="360" controls autofocus>
-{% 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>
- {% else %}
- <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
- {% endif %}
-{% endfor %}
-
- </video>
-
- <h2 class="title">{{ title }}</h2>
-{% if unlisted %}
- <span class="is-unlisted">Unlisted</span>
-{% endif %}
- <address>Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
- <span class="views">{{ views }} views</span>
-
-
- <time datetime="$upload_date">Published on {{ upload_date }}</time>
- <span class="likes-dislikes">{{ likes }} likes {{ dislikes }} dislikes</span>
- <div class="download-dropdown">
- <button class="download-dropdown-label">Download</button>
- <div class="download-dropdown-content">
-{% for format in download_formats %}
- <a href="{{ format['url'] }}">
- <span>{{ format['ext'] }}</span>
- <span>{{ format['resolution'] }}</span>
- <span>{{ format['note'] }}</span>
- </a>
-{% endfor %}
- </div>
- </div>
- <input class="checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
-
- <span class="description">{{ description }}</span>
- <div class="music-list">
- {% if music_list.__len__() != 0 %}
- <hr>
- <table>
- <caption>Music</caption>
- <tr>
- {% for attribute in music_attributes %}
- <th>{{ attribute }}</th>
- {% endfor %}
- </tr>
- {% for track in music_list %}
- <tr>
- {% for attribute in music_attributes %}
- <td>{{ track.get(attribute.lower(), '') }}</td>
- {% endfor %}
- </tr>
- {% endfor %}
- </table>
- {% endif %}
- </div>
+ <video controls autofocus>
+ {% for video_source in video_sources %}
+ <source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}">
+ {% endfor %}
- {% if comments_info %}
- {{ comments.video_comments(comments_info) }}
- {% endif %}
- </article>
+ {% for source in subtitle_sources %}
+ {% if source['on'] %}
+ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
+ {% else %}
+ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
+ {% endif %}
+ {% endfor %}
+
+ </video>
+
+ <div class="video-info">
+ <h2 class="title">{{ title }}</h2>
+ {% if unlisted %}
+ <span class="is-unlisted">Unlisted</span>
+ {% endif %}
+ <address>Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
+ <span class="views">{{ views }} views</span>
+ <time datetime="$upload_date">Published on {{ upload_date }}</time>
+ <span class="likes-dislikes">{{ likes }} likes {{ dislikes }} dislikes</span>
+ <details class="download-dropdown">
+ <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'] }}">
+ <ol class="format-attributes">
+ <li class="format-ext">{{ format['ext'] }}</li>
+ <li class="format-res">{{ format['resolution'] }}</li>
+ <li class="format-note">{{ format['note'] }}</li>
+ </ol>
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+ </details>
+ <input class="checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
- <nav id="related">
- {% for info in related %}
- {{ common_elements.item(info) }}
+ <span class="description">{{ description }}</span>
+ <div class="music-list">
+ {% if music_list.__len__() != 0 %}
+ <hr>
+ <table>
+ <caption>Music</caption>
+ <tr>
+ {% for attribute in music_attributes %}
+ <th>{{ attribute }}</th>
+ {% endfor %}
+ </tr>
+ {% for track in music_list %}
+ <tr>
+ {% for attribute in music_attributes %}
+ <td>{{ track.get(attribute.lower(), '') }}</td>
+ {% endfor %}
+ </tr>
{% endfor %}
- </nav>
+ </table>
+ {% endif %}
+ </div>
+ </div>
+
+ {% if related_videos_mode != 0 %}
+ <details class="related-videos-outer" {{'open' if related_videos_mode == 1 else ''}}>
+ <summary>Related Videos</summary>
+ <nav class="related-videos-inner">
+ {% for info in related %}
+ {{ common_elements.item(info) }}
+ {% endfor %}
+ </nav>
+ </details>
+ {% endif %}
+ {% if comments_mode != 0 %}
+ <details class="comments-area-outer" {{'open' if comments_mode == 1 else ''}}>
+ <summary>Comments</summary>
+ <section class="comments-area-inner comments-area">
+ {% if comments_info %}
+ {{ comments.video_comments(comments_info) }}
+ {% endif %}
+ </section>
+ </details>
+ {% endif %}
{% endblock main %}
diff --git a/youtube/watch.py b/youtube/watch.py
index 5487dd4..8f83e48 100644
--- a/youtube/watch.py
+++ b/youtube/watch.py
@@ -36,7 +36,6 @@ def watch_page_related_video_info(item):
except KeyError:
result['views'] = ''
result['thumbnail'] = util.get_thumbnail_url(item['id'])
- result['item_size'] = 'small'
result['type'] = 'video'
return result
@@ -47,19 +46,24 @@ def watch_page_related_playlist_info(item):
'id': item['list'],
'first_video_id': item['video_id'],
'thumbnail': util.get_thumbnail_url(item['video_id']),
- 'item_size': 'small',
'type': 'playlist',
}
def get_video_sources(info):
video_sources = []
for format in info['formats']:
- if format['acodec'] != 'none' and format['vcodec'] != 'none':
+ if format['acodec'] != 'none' and format['vcodec'] != 'none' and format['height'] <= settings.default_resolution:
video_sources.append({
'src': format['url'],
'type': 'video/' + format['ext'],
+ 'height': format['height'],
+ 'width': format['width'],
})
+ #### order the videos sources so the preferred resolution is first ###
+
+ video_sources.sort(key=lambda source: source['height'], reverse=True)
+
return video_sources
def get_subtitle_sources(info):
@@ -193,6 +197,12 @@ def get_watch_page():
'note': yt_dl_downloader._format_note(format),
})
+ video_sources = get_video_sources(info)
+ video_height = video_sources[0]['height']
+
+ # 1 second per pixel, or the actual video width
+ theater_video_target_width = max(640, info['duration'], video_sources[0]['width'])
+
return flask.render_template('watch.html',
header_playlist_names = local_playlist.get_playlist_names(),
uploader_channel_url = '/' + info['uploader_url'],
@@ -202,13 +212,20 @@ def get_watch_page():
dislikes = (lambda x: '{:,}'.format(x) if x is not None else "")(info.get("dislike_count", None)),
download_formats = download_formats,
video_info = json.dumps(video_info),
- video_sources = get_video_sources(info),
+ video_sources = video_sources,
subtitle_sources = get_subtitle_sources(info),
related = related_videos,
music_list = info['music_list'],
music_attributes = get_ordered_music_list_attributes(info['music_list']),
comments_info = comments_info,
+ theater_mode = settings.theater_mode,
+ related_videos_mode = settings.related_videos_mode,
+ comments_mode = settings.comments_mode,
+
+ video_height = video_height,
+ theater_video_target_width = theater_video_target_width,
+
title = info['title'],
uploader = info['uploader'],
description = info['description'],
diff --git a/youtube/yt_data_extract.py b/youtube/yt_data_extract.py
index c236c2f..db28e62 100644
--- a/youtube/yt_data_extract.py
+++ b/youtube/yt_data_extract.py
@@ -197,10 +197,6 @@ def renderer_info(renderer, additional_info={}):
info.update(additional_info)
- if type.startswith('compact') or (type.startswith('playlist') and type != 'playlistRenderer'):
- info['item_size'] = 'small'
- else:
- info['item_size'] = 'medium'
if type in ('compactVideoRenderer', 'videoRenderer', 'playlistVideoRenderer', 'gridVideoRenderer'):
info['type'] = 'video'
diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py
index 18b240a..85be28d 100644
--- a/youtube_dl/extractor/youtube.py
+++ b/youtube_dl/extractor/youtube.py
@@ -1703,18 +1703,22 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# Is it unlisted?
unlisted = ('<span id="watch-privacy-icon"' in video_webpage)
- # Related videos
- related_vid_info = self._search_regex(r"""'RELATED_PLAYER_ARGS':\s*(\{.*?\})""", video_webpage, "related_player_args", default='')
- if related_vid_info == '':
- related_vids = []
- else:
- related_vid_info = json.loads(related_vid_info)['rvs']
- if related_vid_info == '':
- related_vids = []
+ # Related videos
+ related_vids = []
+ try:
+ rvs_match = re.search(r'"rvs":"(.*?)[^\\]"', video_webpage)
+ if rvs_match is not None:
+ rvs = json.loads('"' + rvs_match.group(1) + '"') # unescape json string (\u0026 for example)
+ related_vid_parts = (compat_parse_qs(related_item) for related_item in rvs.split(","))
+ related_vids = [{key : value[0] for key,value in vid.items()} for vid in related_vid_parts]
else:
- related_vids = (compat_parse_qs(related_item) for related_item in related_vid_info.split(","))
- related_vids = [{key : value[0] for key,value in vid.items()} for vid in related_vids]
+ print('Failed to extract related videos: no rvs')
+
+ except Exception:
+ print('Error while extracting related videos:')
+ traceback.print_exc()
+
# Music list
# Test case: https://www.youtube.com/watch?v=jbkZdRglnKY