]> git.gir.st - subscriptionfeed.git/blob - app/youtube/templates/watch.html.j2
allow selecting muxed video quality
[subscriptionfeed.git] / app / youtube / templates / watch.html.j2
1 {% extends "base.html.j2" %}
2 {% import 'macros.imp.j2' as macros %}
3
4 {% block title %}{{ title | e }} — {{ author | e }}{% endblock %}
5 {% block more_head %}
6 {# Note: the following line was adopted from invidious and is parsed as a regex by yt-dlp. Do not modify! #}
7 <link rel="alternate" href="https://www.youtube.com/watch?v={{ video_id }}">
8 {% endblock %}
9
10 {% block content %}
11 {% if video_url %}
12 <div class="aspect-ratio main-video" style="--aspect-ratio:{{ aspectr }}">
13 <video controls poster="{{ poster }}">
14 {% set offset = "#t="~request.args.t|timeoffset if request.args.t else "" %}
15 <source src="{{video_url}}{{offset}}">
16 {% set cc_default = False %}
17 {% for cc in subtitles %}
18 <track label="{{ cc.name }}" kind="subtitles" srclang="{{ cc.code }}" src="{{ url_for('youtube.timedtext') }}?{{ cc.query }}" {{ 'default' if cc_default and not loop.counter }}>
19 {% endfor %}
20 {% if '/api/manifest/hls_variant/' in video_url %}{#todo: this is ugly!#}
21 <source src="{{ video_url }}">
22 {% endif %}
23 </video>
24
25 <script>
26 "use strict";
27 var sha256=function a(b){function c(a,b){return a>>>b|a<<32-b}for(var d,e,f=Math.pow,g=f(2,32),h="length",i="",j=[],k=8*b[h],l=a.h=a.h||[],m=a.k=a.k||[],n=m[h],o={},p=2;64>n;p++)if(!o[p]){for(d=0;313>d;d+=p)o[d]=p;l[n]=f(p,.5)*g|0,m[n++]=f(p,1/3)*g|0}for(b+="\x80";b[h]%64-56;)b+="\x00";for(d=0;d<b[h];d++){if(e=b.charCodeAt(d),e>>8)return;j[d>>2]|=e<<(3-d)%4*8}for(j[j[h]]=k/g|0,j[j[h]]=k,e=0;e<j[h];){var q=j.slice(e,e+=16),r=l;for(l=l.slice(0,8),d=0;64>d;d++){var s=q[d-15],t=q[d-2],u=l[0],v=l[4],w=l[7]+(c(v,6)^c(v,11)^c(v,25))+(v&l[5]^~v&l[6])+m[d]+(q[d]=16>d?q[d]:q[d-16]+(c(s,7)^c(s,18)^s>>>3)+q[d-7]+(c(t,17)^c(t,19)^t>>>10)|0),x=(c(u,2)^c(u,13)^c(u,22))+(u&l[1]^u&l[2]^l[1]&l[2]);l=[w+x|0].concat(l),l[4]=l[4]+w|0}for(d=0;8>d;d++)l[d]=l[d]+r[d]|0}for(d=0;8>d;d++)for(e=3;e+1;e--){var y=l[d]>>8*e&255;i+=(16>y?0:"")+y.toString(16)}return i}; /*https://geraintluff.github.io/sha256/sha256.min.js (public domain)*/
28 document.addEventListener('DOMContentLoaded', load_sponsorblock);
29 document.addEventListener('DOMContentLoaded', ()=>{
30 const check = document.querySelector("#skip_sponsors");
31 check.addEventListener("change", () => {if (check.checked) load_sponsorblock()});
32 });
33 function load_sponsorblock(){
34 const video_id = (new URLSearchParams(document.location.search)).get('v');
35 const hash = sha256(video_id).substr(0,4);
36 fetch(`https://sponsor.ajay.app/api/skipSegments/${hash}`)
37 .then(response => response.json())
38 .then(data => {
39 const segments = ((data.find(e => e.videoID == video_id)||{}).segments||[])
40 .filter(e => e.category == "sponsor").map(e => e.segment);
41 document.querySelector('#skip_n').innerText = `; ${segments.length} segments`;
42 for (const [start, end] of segments) {
43 document.querySelector('.main-video video')
44 .addEventListener("timeupdate", function() {
45 if (document.querySelector("#skip_sponsors").checked &&
46 this.currentTime >= start &&
47 this.currentTime < end-1) {
48 this.currentTime = end;
49 }
50 });
51 }
52 });
53 }
54 </script>
55 {% if '/api/manifest/hls_variant/' in video_url %}{#todo: this is ugly!#}
56 <script src="/static/3rd-party/hls.js/hls.min.js"></script>
57 <script>
58 let vid = document.querySelector('video');
59 if (!vid.canPlayType('application/vnd.apple.mpegurl') && Hls.isSupported()) {
60 var hls = new Hls();
61 let source = new URL(document.querySelector("source").src).pathname;
62 hls.loadSource(source);
63 hls.attachMedia(vid);
64 }
65 </script>
66 {% endif %}
67
68 </div>
69 {% else %}{#TODO: this'll break livestreams #}
70 <img src="{{ poster }}" style="width:100%;object-fit:cover;height:calc(100% / {{ aspectr }});">
71 {% endif %}
72
73 {% if video_error %}
74 <div class="video_error">
75 {{ errdetails }} Watch on <a href="{{ invidious_url }}">Invidious</a> or <a href="https://www.youtube.com/watch?v={{ video_id }}">Youtube</a>
76 </div>
77 {% endif %}
78
79 <h1>{{ title | e }}<br>
80 <small><a href="/channel/{{ channel_id }}/">{{ author | e }}</a></small></h1>
81
82 <details><summary>Description</summary>
83 <p style="white-space:pre-wrap">{{ description | e }}</p>
84 </details>
85
86 <details><summary>Metadata</summary>
87 <dl>
88 <dt>Duration
89 <dd>{{ length | format_time }}
90 <dt>Views
91 <dd>{{ '{0:,}'.format(views | int)|replace(",","'") }}
92 <dt>Visibility
93 <dd>{{ 'unlisted' if unlisted else 'public' }}
94 <dt>Resolution
95 {% for v in stream_map.muxed|selectattr("qualityLabel", "ne", "144p")|sort(attribute='height', reverse=True) %}
96 {% set active = v.url == video_url %}
97 {% set itag_url = url_for('.watch', v=video_id, t=request.args.t, itag=v.itag) %}
98 <dd><a href="{{ itag_url }}" class="{{'bold' if active else ''}}">{{v.qualityLabel}}</a>
99 {% else %}
100 <dd>Adaptive {# livestreams have no muxed formats#}
101 {% endfor %}
102 </dl>
103 </details>
104 <script>
105 "use strict";
106 const video_id = (new URLSearchParams(document.location.search)).get('v');
107 document.querySelector('dl').innerHTML += `
108 <dt>Published
109 <dd id="load_published" class="fake-link" style="display:inline">...`;
110 document.querySelector('#load_published').onclick = function(event) {
111 event.target.classList.remove('fake-link');
112 event.target.style.cursor = 'wait';
113 fetch(`/watch?v=${video_id}&show=meta`)
114 .then(response => response.json())
115 .then(data => {
116 let [reg_type, , ...reg_list] = data._.regions.split(' ');
117 event.target.outerHTML = `
118 <dd>${data._.published}
119 <dt>${reg_type.startsWith('n')?'Blocked':'Available'} in
120 <dd>${reg_list.join(', ') || 'all regions'}`;
121 });
122 }
123 </script>
124
125 <details><summary>More Actions</summary>
126 <ul class="more-actions">
127 <li><label><input type=checkbox id=skip_sponsors checked>skip sponsors</label> (with <a href="https://sponsor.ajay.app">SponsorBlock</a><span id=skip_n></span>)
128 <noscript><br>Note: requires javascript</noscript>
129 {# TODO: don't redirect away (204 response?) #}
130 <li>{{ macros.emoji_link("audio", video_id, True) }}
131 <li>{{ macros.emoji_button("pin", video_id, is_pinned, True) }}
132 <li>{{ macros.emoji_button("subscribe", channel_id, is_subscribed, True) }}
133 <li>{{ macros.emoji_link("raw", video_id, True) }}
134 <li>{{ macros.emoji_link("json", video_id, True) }}
135 <li>{{ macros.emoji_link("meta", video_id, True) }}
136 <li>{{ macros.emoji_link("iv", video_id, True) }}
137 <li>{{ macros.emoji_link("yt", video_id, True) }}
138 | <a href="https://www.youtube-nocookie.com/embed/{{ video_id }}">embed</a>
139 </ul>
140 </details>
141
142 <details><summary>Endcards</summary>
143 <div class="cards">
144 {% for card in all_cards %} {# Note: no point in displaying the current channels's channel card #}
145 {{ macros.typed_card(card) if not (card.type == 'CHANNEL' and card.content.channel_id == channel_id) }}
146 {% endfor %}
147 {{ macros.dummycard() }}
148 </div>
149 </details>
150 {% endblock %}
Imprint / Impressum