Merge branch 'layout'

This commit is contained in:
James Taylor 2019-09-06 14:23:20 -07:00
commit ca49ab170f
23 changed files with 770 additions and 855 deletions

View File

@ -66,6 +66,24 @@ For security reasons, enabling this is not recommended.''',
1 to sort by newest''', 1 to sort by newest''',
}), }),
('theater_mode', {
'type': bool,
'default': True,
'comment': '',
}),
('default_resolution', {
'type': int,
'default': 720,
'comment': '',
}),
('theme', {
'type': int,
'default': 0,
'comment': '',
}),
('gather_googlevideo_domains', { ('gather_googlevideo_domains', {
'type': bool, 'type': bool,
'default': False, 'default': False,

View File

@ -1,7 +1,22 @@
import flask import flask
import settings
yt_app = flask.Flask(__name__) yt_app = flask.Flask(__name__)
yt_app.url_map.strict_slashes = False yt_app.url_map.strict_slashes = False
@yt_app.route('/') @yt_app.route('/')
def homepage(): 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',
}

View File

@ -82,7 +82,6 @@ def get_local_playlist_videos(name, offset=0, amount=50):
else: else:
info['thumbnail'] = util.get_thumbnail_url(info['id']) info['thumbnail'] = util.get_thumbnail_url(info['id'])
missing_thumbnails.append(info['id']) missing_thumbnails.append(info['id'])
info['item_size'] = 'small'
info['type'] = 'video' info['type'] = 'video'
yt_data_extract.add_extra_html_info(info) yt_data_extract.add_extra_html_info(info)
videos.append(info) videos.append(info)

View File

@ -69,7 +69,7 @@
display:grid; display:grid;
grid-template-columns: auto auto 100px 1fr; grid-template-columns: auto auto 100px 1fr;
grid-template-rows: 0fr 0fr 0fr 0fr; grid-template-rows: 0fr 0fr 0fr 0fr;
background-color: #dadada; background-color: var(--interface-color);
justify-content: start; justify-content: start;
} }
@ -102,8 +102,6 @@
grid-column: 3; grid-column: 3;
grid-row: 1; grid-row: 1;
white-space: nowrap; white-space: nowrap;
color: black;
} }
@ -126,4 +124,5 @@
.more-comments{ .more-comments{
justify-self:center; justify-self:center;
margin-top:10px; margin-top:10px;
margin-bottom: 10px;
} }

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
body{
--interface-color: #dadada;
--text-color: #222222;
--background-color: #bcbcbc;
}
.comment .permalink{
color: #000000;
}

View File

@ -0,0 +1,9 @@
body{
--interface-color: #ffffff;
--text-color: #222222;
--background-color: #f8f8f8;
}
.comment .permalink{
color: #000000;
}

View File

@ -4,45 +4,43 @@ h1, h2, h3, h4, h5, h6, div, button{
} }
body{
margin:0;
padding: 0;
color:#222;
background-color:#cccccc;
min-height:100vh;
display:grid;
grid-template-rows: 50px 1fr;
}
header{
background-color:#333333;
grid-row: 1;
display:grid;
grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr);
}
main{
grid-row: 2;
}
address{ address{
font-style:normal; font-style:normal;
} }
body{
margin:0;
padding: 0;
color:var(--text-color);
background-color:var(--background-color);
min-height:100vh;
display: flex;
flex-direction: column;
}
header{
background-color:#333333;
height: 50px;
display: flex;
justify-content: center;
}
#home-link{
align-self: center;
margin-left:10px;
color: #ffffff;
}
#site-search{ #site-search{
grid-column: 2; max-width: 600px;
display: grid; margin-left:10px;
grid-template-columns: 1fr auto auto; display: flex;
flex-grow: 1;
} }
#site-search .search-box{ #site-search .search-box{
@ -50,10 +48,9 @@ address{
height:25px; height:25px;
border:0; border:0;
grid-column: 1; flex-grow: 1;
} }
#site-search .search-button{ #site-search .search-button{
grid-column: 2;
align-self:center; align-self:center;
height:25px; height:25px;
@ -62,7 +59,6 @@ address{
} }
#site-search .dropdown{ #site-search .dropdown{
margin-left:5px; margin-left:5px;
grid-column: 3;
align-self:center; align-self:center;
height:25px; height:25px;
} }
@ -85,12 +81,37 @@ address{
grid-column:1 / span 2; 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{ .dropdown{
z-index:1; z-index:1;
} }
.dropdown-content{ .dropdown-content{
display:none; display:none;
background-color: #e9e9e9; background-color: var(--interface-color);
} }
.dropdown:hover .dropdown-content{ .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 */ /* 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; 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{ .item-list{
display: grid; display: grid;
grid-auto-rows: 138px;
grid-row-gap: 10px; grid-row-gap: 10px;
} }
.item-list .video-thumbnail-box{
width:246px;
}
.item-list .playlist-thumbnail-box{
width:246px;
}
.item-grid{ .item-grid{
display:grid; display: flex;
grid-template-columns: repeat(auto-fill, 400px); flex-wrap: wrap;
grid-auto-rows: 94px;
grid-row-gap: 10px;
} }
.item-grid .video-thumbnail-box{ .item-grid > .playlist-item-box{
width:168px; margin-right: 10px;
} }
.item-grid .playlist-thumbnail-box{ .item-grid > * {
width:168px; margin-bottom: 10px;
}
.item-grid .horizontal-item-box .item{
width:370px;
}
.item-grid .vertical-item-box .item{
} }
.item-box{
display: inline-flex;
.medium-item-box{ flex-direction: row;
display:grid;
grid-template-columns: 1fr 30px;
} }
.medium-item{ .vertical-item-box{
background-color:#bcbcbc; }
.horizontal-item-box{
}
.item{
background-color:var(--interface-color);
text-decoration:none; text-decoration:none;
font-size: 12px; font-size: 12px;
color: #767676; color: #767676;
}
.horizontal-item-box .item {
flex-grow: 1;
display: grid; display: grid;
align-content: start; align-content: start;
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr;
grid-template-rows: auto auto auto auto auto 1fr; grid-template-rows: auto auto auto auto 1fr;
}
.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{ .vertical-item-box .item{
display:inline; width: 168px;
} }
/*.medium-item .views{ .thumbnail-box{
grid-column: 3; font-size: 0px; /* prevent newlines and blank space from creating gaps */
grid-row: 2; position: relative;
justify-self:end; display: block;
} }
.medium-item time{ .horizontal-item-box .thumbnail-box{
grid-column: 2; grid-row: 1 / span 5;
grid-row: 3; margin-right: 4px;
justify-self:start;
}*/
.medium-item .stats{
grid-column: 2 / span 2;
grid-row: 2;
max-height:2.4em;
overflow:hidden;
} }
.medium-item .stats > *::after{ .no-description .thumbnail-box{
content: " | "; width: 168px;
height:94px;
} }
.medium-item .stats > *:last-child::after{ .has-description .thumbnail-box{
content: ""; width: 246px;
height:138px;
} }
.video-item .thumbnail-info{
.medium-item .description{ position: absolute;
grid-column: 2 / span 2; bottom: 2px;
grid-row: 4; right: 2px;
}
.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;
display:grid;
grid-template-columns: 1fr 30px;
grid-template-rows: 94px;
}
.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: #333;
font-size: 16px;
font-weight: 500;
text-decoration:initial;
min-width: 0;
justify-self:start;
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;
}
/* ---Thumbnails for videos---- */
.video-thumbnail-box{
max-height:100%;
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; opacity: .8;
color: #ffffff; color: #ffffff;
font-size: 12px; font-size: 12px;
background-color: #000000; background-color: #000000;
} }
.playlist-item .thumbnail-info{
/* ---Thumbnails for playlists---- */ position: absolute;
.playlist-thumbnail-box{ right: 0px;
max-height:100%; bottom: 0px;
height: 100%;
grid-column:1; width: 50%;
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; text-align:center;
white-space: pre-line; white-space: pre-line;
opacity: .8; opacity: .8;
color: #cfcfcf; color: #cfcfcf;
font-size: 12px;
background-color: #000000; 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%;
}
.item .title{
min-width: 0;
max-height:3.6em;
overflow:hidden;
color: var(--text-color);
font-size: 16px;
font-weight: 500;
text-decoration:initial;
}
.stats{
list-style: none;
padding: 0px;
margin: 0px;
}
.horizontal-stats{
max-height:2.4em;
overflow:hidden;
}
.horizontal-stats > li{
display: inline;
}
.horizontal-stats > li::after{
content: " | ";
}
.horizontal-stats > li:last-child::after{
content: "";
}
.vertical-stats{
display: flex;
flex-direction: column;
}
.stats address{
display: inline;
}
.vertical-stats li{
max-height: 1.3em;
overflow: hidden;
}
.item-checkbox{
justify-self:start;
align-self:center;
height:30px;
width:30px;
min-width:30px;
margin: 0px;
}
.page-button-row{ .page-button-row{
margin-top: 10px;
margin-bottom: 10px;
justify-self:center; justify-self:center;
justify-content: center;
display: grid; display: grid;
grid-auto-columns: 40px; grid-auto-columns: 40px;
grid-auto-flow: column; grid-auto-flow: column;
height: 40px; height: 40px;
} }
.page-button{ .page-button{
background-color: #e9e9e9; background-color: var(--interface-color);
border-style: outset; border-style: outset;
border-width: 2px; border-width: 2px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
} }
.sort-button{ .sort-button{
background-color: #d0d0d0; background-color: var(--interface-color);
padding: 2px; padding: 2px;
justify-self: start; justify-self: start;
} }

View File

@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{ page_title }}</title> <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"> <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/shared.css" type="text/css" rel="stylesheet">
<link href="/youtube.com/static/comments.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"> <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon">
@ -16,6 +17,7 @@
</head> </head>
<body> <body>
<header> <header>
<a href="/youtube.com" id="home-link">Home</a>
<form id="site-search" action="/youtube.com/search"> <form id="site-search" action="/youtube.com/search">
<input type="search" name="query" class="search-box" value="{{ search_box_value }}"> <input type="search" name="query" class="search-box" value="{{ search_box_value }}">
<button type="submit" value="Search" class="search-button">Search</button> <button type="submit" value="Search" class="search-button">Search</button>
@ -91,7 +93,6 @@
</div> </div>
</form> </form>
<div id="header-right">
<form id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self"> <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"> <input name="playlist_name" id="playlist-name-selection" list="playlist-options" type="text">
<datalist id="playlist-options"> <datalist id="playlist-options">
@ -102,8 +103,6 @@
<button type="submit" id="playlist-add-button" name="action" value="add">Add to playlist</button> <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> <button type="reset" id="item-selection-reset">Clear selection</button>
</form> </form>
<a href="/youtube.com/playlists" id="local-playlists">Local playlists</a>
</div>
</header> </header>
<main> <main>
{% block main %} {% block main %}

View File

@ -31,7 +31,7 @@
grid-auto-flow: column; grid-auto-flow: column;
justify-content:start; justify-content:start;
background-color: #aaaaaa; background-color: var(--interface-color);
padding: 3px; padding: 3px;
padding-left: 6px; padding-left: 6px;
} }
@ -44,7 +44,6 @@
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
padding-left: 6px; padding-left: 6px;
background-color: #bababa;
margin-bottom: 10px; margin-bottom: 10px;
} }
#number-of-results{ #number-of-results{

View File

@ -29,7 +29,6 @@
{% endmacro %} {% endmacro %}
{% macro video_comments(comments_info) %} {% macro video_comments(comments_info) %}
<section class="comments-area">
<div class="comment-links"> <div class="comment-links">
{% for link_text, link_url in comments_info['comment_links'] %} {% for link_text, link_url in comments_info['comment_links'] %}
<a class="sort-button" href="{{ link_url }}">{{ link_text }}</a> <a class="sort-button" href="{{ link_url }}">{{ link_text }}</a>
@ -43,7 +42,6 @@
{% if 'more_comments_url' is in comments_info %} {% 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 %}
</section>
{% endmacro %} {% endmacro %}
{% macro comment_posting_box(info) %} {% macro comment_posting_box(info) %}

View File

@ -3,30 +3,14 @@
{% import "comments.html" as comments %} {% import "comments.html" as comments %}
{% block style %} {% block style %}
main{
display:grid;
grid-template-columns: 3fr 2fr;
}
#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{ .comments-area{
grid-column:2; margin: auto;
}
.comment{
width:640px; width:640px;
} }
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div id="left">
<section class="comments-area"> <section class="comments-area">
{% if not comments_info['is_replies'] %} {% if not comments_info['is_replies'] %}
<section class="video-metadata"> <section class="video-metadata">
@ -59,7 +43,6 @@
<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 %}
</section> </section>
</div>
{% endblock main %} {% endblock main %}

View File

@ -14,121 +14,53 @@
{%- endif -%} {%- endif -%}
{% endmacro %} {% endmacro %}
{% macro small_item(info, include_author=true) %} {% macro item(info, description=false, horizontal=true, include_author=true) %}
<div class="small-item-box"> <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="small-item"> <div class="item {{ info['type'] + '-item' }}">
{% if info['type'] == 'video' %} <a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
<a class="video-thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}"> <img class="thumbnail-img" src="{{ info['thumbnail'] }}">
<img class="video-thumbnail-img" src="{{ info['thumbnail'] }}"> {% if info['type'] != 'channel' %}
<span class="video-duration">{{ info['duration'] }}</span> <div class="thumbnail-info">
</a> <span>{{ info['size'] if info['type'] == 'playlist' else info['duration'] }}</span>
<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>
</div> </div>
{% endif %}
</a> </a>
<a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a>
<address>{{ info['author'] }}</address> <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 %} {% 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 include_author %}
{% if 'author_url' is in(info) %} {% if 'author_url' is in(info) %}
<address>By <a href="{{ info['author_url'] }}">{{ info['author'] }}</a></address> <li><address title="{{ info['author'] }}">By <a href="{{ info['author_url'] }}">{{ info['author'] }}</a></address></li>
{% else %} {% else %}
<address><b>{{ info['author'] }}</b></address> <li><address title="{{ info['author'] }}"><b>{{ info['author'] }}</b></address></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if 'views' is in(info) %} {% if 'views' is in(info) %}
<span class="views">{{ info['views'] }}</span> <li><span class="views">{{ info['views'] }}</span></li>
{% endif %} {% endif %}
{% if 'published' is in(info) %} {% if 'published' is in(info) %}
<time>{{ info['published'] }}</time> <li><time>{{ info['published'] }}</time></li>
{% endif %} {% endif %}
{% endmacro %} {% endif %}
</ul>
{% 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>
{% if description %}
<span class="description">{{ text_runs(info.get('description', '')) }}</span> <span class="description">{{ text_runs(info.get('description', '')) }}</span>
{% endif %}
<span class="badges">{{ info['badges']|join(' | ') }}</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 %}
</div> </div>
{% if info['type'] == 'video' %} {% if info['type'] == 'video' %}
<input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit"> <input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit">
{% endif %} {% endif %}
</div> </div>
{% endmacro %} {% 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) %} {% macro page_buttons(estimated_pages, url, parameters_dictionary) %}
{% set current_page = parameters_dictionary.get('page', 1)|int %} {% set current_page = parameters_dictionary.get('page', 1)|int %}
{% set parameters_dictionary = parameters_dictionary.to_dict() %} {% set parameters_dictionary = parameters_dictionary.to_dict() %}

View File

@ -2,14 +2,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block style %} {% block style %}
main{
display: grid;
grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr);
align-content: start;
}
main > div, main > form{ main > div, main > form{
margin: auto;
margin-top:20px; margin-top:20px;
grid-column:2; width: 640px;
} }
{% endblock style %} {% endblock style %}

View File

@ -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 %}

View File

@ -2,52 +2,35 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main{ main > *{
display:grid; width: 800px;
grid-template-columns: 3fr 1fr; margin: auto;
} }
.playlist-metadata{
display: flex;
#left{ flex-direction: row;
grid-column: 1; justify-content: space-between;
grid-row: 1;
display: grid;
grid-template-columns: 1fr 800px auto;
grid-template-rows: 0fr 1fr 0fr;
} }
.playlist-title{ .playlist-title{
grid-column:2;
} }
#playlist-remove-button{ #playlist-remove-button{
grid-column:3;
align-self: center; align-self: center;
white-space: nowrap; white-space: nowrap;
} }
#results{ #results{
grid-row: 2;
grid-column: 2 / span 2;
display: grid; display: grid;
grid-auto-rows: 0fr; grid-auto-rows: 0fr;
grid-row-gap: 10px; grid-row-gap: 10px;
}
.page-button-row{
grid-row: 3;
grid-column: 2;
justify-self: center;
} }
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div id="left"> <div class="playlist-metadata">
<h2 class="playlist-title">{{ playlist_name }}</h2> <h2 class="playlist-title">{{ playlist_name }}</h2>
<input type="hidden" name="playlist_page" value="{{ playlist_name }}" form="playlist-edit"> <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> <button type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button>
</div>
<div id="results"> <div id="results">
{% for video_info in videos %} {% for video_info in videos %}
{{ common_elements.item(video_info) }} {{ common_elements.item(video_info) }}
@ -56,5 +39,4 @@
<nav class="page-button-row"> <nav class="page-button-row">
{{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }} {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }}
</nav> </nav>
</div>
{% endblock main %} {% endblock main %}

View File

@ -2,16 +2,14 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block style %} {% block style %}
main{ main > * {
display: grid; width: 640px;
grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr); margin: auto;
align-content: start;
grid-row-gap: 40px;
} }
main form{ main form{
background-color: var(--interface-color);
padding: 10px;
margin-top:20px; margin-top:20px;
grid-column:2;
display:grid; display:grid;
justify-items: start; justify-items: start;
align-content: start; align-content: start;
@ -26,10 +24,9 @@
margin-top:20px; margin-top:20px;
} }
#tor-note{ #tor-note{
grid-row:2; background-color: var(--interface-color);
grid-column:2;
background-color: #dddddd;
padding: 10px; padding: 10px;
margin-top: 40px;
} }
{% endblock style %} {% endblock style %}

View File

@ -2,25 +2,12 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main{ main > * {
display:grid; width: 800px;
grid-template-columns: 3fr 1fr; margin:auto;
} }
#left{
grid-column: 1;
grid-row: 1;
display: grid;
grid-template-columns: 1fr 800px;
grid-template-rows: 0fr 1fr 0fr;
}
.playlist-metadata{ .playlist-metadata{
grid-column:2;
grid-row:1;
display:grid; display:grid;
grid-template-columns: 0fr 1fr; grid-template-columns: 0fr 1fr;
} }
@ -50,22 +37,8 @@
min-width:0px; min-width:0px;
white-space: pre-line; white-space: pre-line;
} }
.page-button-row{
grid-row: 3;
grid-column: 2;
justify-self: center;
}
#right{
grid-column: 2;
grid-row: 1;
}
#results{ #results{
grid-row: 2;
grid-column: 2;
margin-top:10px; margin-top:10px;
display: grid; display: grid;
@ -76,7 +49,6 @@
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div id="left">
<div class="playlist-metadata"> <div class="playlist-metadata">
<img class="playlist-thumbnail" src="{{ thumbnail }}"> <img class="playlist-thumbnail" src="{{ thumbnail }}">
<h2 class="playlist-title">{{ title }}</h2> <h2 class="playlist-title">{{ title }}</h2>
@ -96,7 +68,6 @@
<nav class="page-button-row"> <nav class="page-button-row">
{{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }} {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
</nav> </nav>
</div>
{% endblock main %} {% endblock main %}

View File

@ -3,27 +3,18 @@
{% import "comments.html" as comments %} {% import "comments.html" as comments %}
{% block style %} {% block style %}
main{ .comment-form{
display: grid; width: 640px;
grid-template-columns: 3fr 2fr; margin: auto;
} justify-content:start;
.left{
display:grid;
grid-template-columns: 1fr 640px;
} }
textarea{ textarea{
width: 460px; width: 460px;
height: 85px; height: 85px;
} }
.comment-form{
grid-column:2;
justify-content:start;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div class="left">
{{ comments.comment_posting_box(comment_posting_box_info) }} {{ comments.comment_posting_box(comment_posting_box_info) }}
</div>
{% endblock %} {% endblock %}

View File

@ -3,30 +3,18 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% block style %} {% block style %}
main{ main > * {
display:grid; max-width: 800px;
grid-template-columns: minmax(0px, 1fr) 800px minmax(0px,2fr); margin: auto;
max-width:100vw; }
#result-info{
} }
#number-of-results{ #number-of-results{
font-weight:bold; 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{ .item-list{
grid-row: 2; padding-left: 10px;
grid-column: 2; padding-right: 10px;
} }
.badge{ .badge{
background-color:#cccccc; background-color:#cccccc;
@ -45,7 +33,7 @@
</div> </div>
<div class="item-list"> <div class="item-list">
{% for info in results %} {% for info in results %}
{{ common_elements.item(info) }} {{ common_elements.item(info, description=true) }}
{% endfor %} {% endfor %}
</div> </div>
<nav class="page-button-row"> <nav class="page-button-row">

View File

@ -3,72 +3,103 @@
{% import "common_elements.html" as common_elements %} {% import "common_elements.html" as common_elements %}
{% import "comments.html" as comments %} {% import "comments.html" as comments %}
{% block style %} {% block style %}
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{ main{
display:grid; display:grid;
grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr); grid-template-columns: 1fr 640px 40px 400px 1fr;
background-color:#cccccc; grid-template-rows: auto auto auto auto;
align-content: start;
} }
#left{ .video-info{
background-color:#bcbcbc;
grid-column: 1;
}
.full-item{
display: grid;
grid-column: 2; grid-column: 2;
grid-template-rows: 0fr 0fr 0fr 0fr 20px 0fr 0fr; grid-row: 2;
display: grid;
grid-template-rows: 0fr 0fr 0fr 20px 0fr 0fr;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
align-content: start; align-content: start;
background-color:#bcbcbc;
} }
.full-item > video{ .video-info > .title{
grid-column: 1 / span 2; grid-column: 1 / span 2;
grid-row: 1;
}
.full-item > .title{
grid-column: 1 / span 2;
grid-row:2;
min-width: 0; min-width: 0;
} }
.full-item > .is-unlisted{ .video-info > .is-unlisted{
background-color: #d0d0d0; background-color: var(--interface-color);
justify-self:start; justify-self:start;
padding-left:2px; padding-left:2px;
padding-right:2px; padding-right:2px;
} }
.full-item > address{ .video-info > address{
grid-column: 1; grid-column: 1;
grid-row: 4; grid-row: 3;
justify-self: start; justify-self: start;
} }
.full-item > .views{ .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-column: 2;
grid-row: 4; grid-row: 4;
justify-self:end; justify-self:end;
} }
.full-item > time{ .video-info > .download-dropdown{
grid-column: 1; grid-column:1 / span 2;
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; grid-row: 6;
} }
.full-item > .checkbox{ .video-info > .checkbox{
justify-self:end; justify-self:end;
align-self: start;
grid-row: 6; grid-row: 5;
grid-column: 2; grid-column: 2;
} }
.full-item > .description{ .video-info > .description{
background-color:#d0d0d0; background-color:var(--interface-color);
margin-top:8px; margin-top:8px;
white-space: pre-wrap; white-space: pre-wrap;
min-width: 0; min-width: 0;
@ -76,22 +107,11 @@
grid-column: 1 / span 2; grid-column: 1 / span 2;
grid-row: 7; 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{ .music-list{
background-color: #d0d0d0; grid-row:8;
grid-column: 1 / span 2;
background-color: var(--interface-color);
} }
.music-list table,th,td{ .music-list table,th,td{
border: 1px solid; border: 1px solid;
@ -105,92 +125,119 @@
font-weight:bold; font-weight:bold;
margin-bottom:5px; margin-bottom:5px;
} }
.comments-area-outer{
#related{ grid-column: 2;
grid-row: 3;
margin-top:10px;
}
.comments-area-inner{
padding-top: 10px;
}
.comment{
width:640px;
}
.related-videos-outer{
grid-column: 4; grid-column: 4;
max-width: 640px;
}
.related-videos-inner{
padding-top: 10px;
display: grid; display: grid;
grid-auto-rows: 90px; grid-auto-rows: 94px;
grid-row-gap: 10px; grid-row-gap: 10px;
} }
#related .medium-item{
grid-template-columns: 160px 1fr 0fr;
}
.download-dropdown{ /* Put related vids below videos when window is too small */
z-index:1; /* 1100px instead of 1080 because W3C is full of idiots who include scrollbar width */
justify-self:start; @media (max-width:1100px){
min-width:0px; main{
height:0px; 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-label{
background-color: #e9e9e9;
border-style: outset;
border-width: 2px;
font-weight: bold;
} }
.download-dropdown-content{ .download-dropdown-content{
display:none; background-color: var(--interface-color);
background-color: #e9e9e9; padding: 10px;
list-style: none;
margin: 0px;
} }
.download-dropdown:hover .download-dropdown-content { li.download-format{
display: grid; margin-bottom: 7px;
grid-auto-rows:30px;
padding-bottom: 50px;
} }
.download-dropdown-content a{ .format-attributes{
list-style: none;
padding: 0px;
margin: 0px;
display: flex;
flex-direction: row;
}
.format-attributes li{
white-space: nowrap; white-space: nowrap;
display:grid;
grid-template-columns: 60px 90px auto;
max-height: 1.2em; max-height: 1.2em;
} }
.format-ext{
width: 60px;
}
.format-res{
width:90px;
}
{% endblock style %} {% endblock style %}
{% block main %} {% block main %}
<div id="left"> <video controls autofocus>
</div> {% for video_source in video_sources %}
<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'] }}"> <source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}">
{% endfor %} {% endfor %}
{% for source in subtitle_sources %} {% for source in subtitle_sources %}
{% if source['on'] %} {% if source['on'] %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default> <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
{% else %} {% else %}
<track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}"> <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</video> </video>
<div class="video-info">
<h2 class="title">{{ title }}</h2> <h2 class="title">{{ title }}</h2>
{% if unlisted %} {% if unlisted %}
<span class="is-unlisted">Unlisted</span> <span class="is-unlisted">Unlisted</span>
{% endif %} {% endif %}
<address>Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address> <address>Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
<span class="views">{{ views }} views</span> <span class="views">{{ views }} views</span>
<time datetime="$upload_date">Published on {{ upload_date }}</time> <time datetime="$upload_date">Published on {{ upload_date }}</time>
<span class="likes-dislikes">{{ likes }} likes {{ dislikes }} dislikes</span> <span class="likes-dislikes">{{ likes }} likes {{ dislikes }} dislikes</span>
<div class="download-dropdown"> <details class="download-dropdown">
<button class="download-dropdown-label">Download</button> <summary class="download-dropdown-label">Download</summary>
<div class="download-dropdown-content"> <ul class="download-dropdown-content">
{% for format in download_formats %} {% for format in download_formats %}
<a href="{{ format['url'] }}"> <li class="download-format">
<span>{{ format['ext'] }}</span> <a class="download-link" href="{{ format['url'] }}">
<span>{{ format['resolution'] }}</span> <ol class="format-attributes">
<span>{{ format['note'] }}</span> <li class="format-ext">{{ format['ext'] }}</li>
<li class="format-res">{{ format['resolution'] }}</li>
<li class="format-note">{{ format['note'] }}</li>
</ol>
</a> </a>
{% endfor %} </li>
</div> {% endfor %}
</div> </ul>
</details>
<input class="checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox"> <input class="checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
<span class="description">{{ description }}</span> <span class="description">{{ description }}</span>
<div class="music-list"> <div class="music-list">
{% if music_list.__len__() != 0 %} {% if music_list.__len__() != 0 %}
@ -212,19 +259,27 @@
</table> </table>
{% endif %} {% endif %}
</div> </div>
</div>
{% if comments_info %} {% if related_videos_mode != 0 %}
{{ comments.video_comments(comments_info) }} <details class="related-videos-outer" {{'open' if related_videos_mode == 1 else ''}}>
{% endif %} <summary>Related Videos</summary>
</article> <nav class="related-videos-inner">
<nav id="related">
{% for info in related %} {% for info in related %}
{{ common_elements.item(info) }} {{ common_elements.item(info) }}
{% endfor %} {% endfor %}
</nav> </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 %} {% endblock main %}

View File

@ -36,7 +36,6 @@ def watch_page_related_video_info(item):
except KeyError: except KeyError:
result['views'] = '' result['views'] = ''
result['thumbnail'] = util.get_thumbnail_url(item['id']) result['thumbnail'] = util.get_thumbnail_url(item['id'])
result['item_size'] = 'small'
result['type'] = 'video' result['type'] = 'video'
return result return result
@ -47,19 +46,24 @@ def watch_page_related_playlist_info(item):
'id': item['list'], 'id': item['list'],
'first_video_id': item['video_id'], 'first_video_id': item['video_id'],
'thumbnail': util.get_thumbnail_url(item['video_id']), 'thumbnail': util.get_thumbnail_url(item['video_id']),
'item_size': 'small',
'type': 'playlist', 'type': 'playlist',
} }
def get_video_sources(info): def get_video_sources(info):
video_sources = [] video_sources = []
for format in info['formats']: 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({ video_sources.append({
'src': format['url'], 'src': format['url'],
'type': 'video/' + format['ext'], '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 return video_sources
def get_subtitle_sources(info): def get_subtitle_sources(info):
@ -193,6 +197,12 @@ def get_watch_page():
'note': yt_dl_downloader._format_note(format), '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', return flask.render_template('watch.html',
header_playlist_names = local_playlist.get_playlist_names(), header_playlist_names = local_playlist.get_playlist_names(),
uploader_channel_url = '/' + info['uploader_url'], 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)), dislikes = (lambda x: '{:,}'.format(x) if x is not None else "")(info.get("dislike_count", None)),
download_formats = download_formats, download_formats = download_formats,
video_info = json.dumps(video_info), video_info = json.dumps(video_info),
video_sources = get_video_sources(info), video_sources = video_sources,
subtitle_sources = get_subtitle_sources(info), subtitle_sources = get_subtitle_sources(info),
related = related_videos, related = related_videos,
music_list = info['music_list'], music_list = info['music_list'],
music_attributes = get_ordered_music_list_attributes(info['music_list']), music_attributes = get_ordered_music_list_attributes(info['music_list']),
comments_info = comments_info, 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'], title = info['title'],
uploader = info['uploader'], uploader = info['uploader'],
description = info['description'], description = info['description'],

View File

@ -197,10 +197,6 @@ def renderer_info(renderer, additional_info={}):
info.update(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'): if type in ('compactVideoRenderer', 'videoRenderer', 'playlistVideoRenderer', 'gridVideoRenderer'):
info['type'] = 'video' info['type'] = 'video'