Merge branch 'master' of github.com:iv-org/invidious

このコミットが含まれているのは:
守矢諏訪子 2022-05-05 01:26:42 +09:00
コミット ed6c7850ed
33個のファイルの変更900行の追加303行の削除

ファイルの表示

@ -291,7 +291,7 @@ input[type="search"]::-webkit-search-cancel-button {
.flexible { display: flex; }
.flex-left { flex: 1 1 100%; flex-wrap: wrap; }
.flex-right { flex: 1 0 max-content; flex-wrap: nowrap; }
.flex-right { flex: 1 0 auto; flex-wrap: nowrap; }
p.channel-name { margin: 0; }
p.video-data { margin: 0; font-weight: bold; font-size: 80%; }

ファイルの表示

@ -70,6 +70,9 @@
margin-bottom: 2em;
}
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
margin-bottom: 10px;}
ul.vjs-menu-content::-webkit-scrollbar {
display: none;
}

ファイルの表示

@ -1,19 +1,20 @@
var community_data = JSON.parse(document.getElementById('community_data').innerHTML);
'use strict';
var community_data = JSON.parse(document.getElementById('community_data').textContent);
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g, function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
});
}
};
function hide_youtube_replies(event) {
var target = event.target;
sub_text = target.getAttribute('data-inner-text');
inner_text = target.getAttribute('data-sub-text');
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
body = target.parentNode.parentNode.children[1];
var body = target.parentNode.parentNode.children[1];
body.style.display = 'none';
target.innerHTML = sub_text;
@ -25,10 +26,10 @@ function hide_youtube_replies(event) {
function show_youtube_replies(event) {
var target = event.target;
sub_text = target.getAttribute('data-inner-text');
inner_text = target.getAttribute('data-sub-text');
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
body = target.parentNode.parentNode.children[1];
var body = target.parentNode.parentNode.children[1];
body.style.display = '';
target.innerHTML = sub_text;
@ -63,8 +64,8 @@ function get_youtube_replies(target, load_more) {
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
if (load_more) {
body = body.parentNode.parentNode;
body.removeChild(body.lastElementChild);
@ -92,12 +93,12 @@ function get_youtube_replies(target, load_more) {
body.innerHTML = fallback;
}
}
}
};
xhr.ontimeout = function () {
console.log('Pulling comments failed.');
console.warn('Pulling comments failed.');
body.innerHTML = fallback;
}
};
xhr.send();
}

ファイルの表示

@ -1,19 +1,21 @@
var video_data = JSON.parse(document.getElementById('video_data').innerHTML);
'use strict';
var video_data = JSON.parse(document.getElementById('video_data').textContent);
function get_playlist(plid, retries) {
if (retries == undefined) retries = 5;
if (retries === undefined) retries = 5;
if (retries <= 0) {
console.log('Failed to pull playlist');
console.warn('Failed to pull playlist');
return;
}
var plid_url;
if (plid.startsWith('RD')) {
var plid_url = '/api/v1/mixes/' + plid +
plid_url = '/api/v1/mixes/' + plid +
'?continuation=' + video_data.id +
'&format=html&hl=' + video_data.preferences.locale;
} else {
var plid_url = '/api/v1/playlists/' + plid +
plid_url = '/api/v1/playlists/' + plid +
'?index=' + video_data.index +
'&continuation' + video_data.id +
'&format=html&hl=' + video_data.preferences.locale;
@ -57,17 +59,17 @@ function get_playlist(plid, retries) {
}
}
}
}
};
xhr.onerror = function () {
console.log('Pulling playlist failed... ' + retries + '/5');
setTimeout(function () { get_playlist(plid, retries - 1) }, 1000);
}
console.warn('Pulling playlist failed... ' + retries + '/5');
setTimeout(function () { get_playlist(plid, retries - 1); }, 1000);
};
xhr.ontimeout = function () {
console.log('Pulling playlist failed... ' + retries + '/5');
console.warn('Pulling playlist failed... ' + retries + '/5');
get_playlist(plid, retries - 1);
}
};
xhr.send();
}
@ -96,7 +98,7 @@ window.addEventListener('load', function (e) {
}
if (video_data.video_series.length !== 0) {
url.searchParams.set('playlist', video_data.video_series.join(','))
url.searchParams.set('playlist', video_data.video_series.join(','));
}
location.assign(url.pathname + url.search);

ファイルの表示

@ -76,7 +76,7 @@
});
n2a(document.querySelectorAll('[data-onrange="update_volume_value"]')).forEach(function (e) {
var cb = function () { update_volume_value(e); }
var cb = function () { update_volume_value(e); };
e.oninput = cb;
e.onchange = cb;
});
@ -102,13 +102,13 @@
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
count.innerText = parseInt(count.innerText) + 1;
row.style.display = '';
}
}
}
};
var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value;
xhr.send('csrf_token=' + csrf_token);
@ -131,20 +131,20 @@
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
count.innerText = parseInt(count.innerText) + 1;
row.style.display = '';
}
}
}
};
var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value;
xhr.send('csrf_token=' + csrf_token);
}
// Handle keypresses
window.addEventListener('keydown', (event) => {
window.addEventListener('keydown', function (event) {
// Ignore modifier keys
if (event.ctrlKey || event.metaKey) return;
@ -152,14 +152,14 @@
let focused_tag = document.activeElement.tagName.toLowerCase();
const allowed = /^(button|checkbox|file|radio|submit)$/;
if (focused_tag === "textarea") return;
if (focused_tag === "input") {
if (focused_tag === 'textarea') return;
if (focused_tag === 'input') {
let focused_type = document.activeElement.type.toLowerCase();
if (!focused_type.match(allowed)) return;
}
// Focus search bar on '/'
if (event.key == "/") {
if (event.key === '/') {
document.getElementById('searchbox').focus();
event.preventDefault();
}

ファイルの表示

@ -1,9 +1,10 @@
var notification_data = JSON.parse(document.getElementById('notification_data').innerHTML);
'use strict';
var notification_data = JSON.parse(document.getElementById('notification_data').textContent);
var notifications, delivered;
function get_subscriptions(callback, retries) {
if (retries == undefined) retries = 5;
if (retries === undefined) retries = 5;
if (retries <= 0) {
return;
@ -17,21 +18,21 @@ function get_subscriptions(callback, retries) {
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
subscriptions = xhr.response;
var subscriptions = xhr.response;
callback(subscriptions);
}
}
}
};
xhr.onerror = function () {
console.log('Pulling subscriptions failed... ' + retries + '/5');
setTimeout(function () { get_subscriptions(callback, retries - 1) }, 1000);
}
console.warn('Pulling subscriptions failed... ' + retries + '/5');
setTimeout(function () { get_subscriptions(callback, retries - 1); }, 1000);
};
xhr.ontimeout = function () {
console.log('Pulling subscriptions failed... ' + retries + '/5');
console.warn('Pulling subscriptions failed... ' + retries + '/5');
get_subscriptions(callback, retries - 1);
}
};
xhr.send();
}
@ -40,7 +41,7 @@ function create_notification_stream(subscriptions) {
notifications = new SSE(
'/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', {
withCredentials: true,
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId }).join(','),
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
delivered = [];
@ -53,7 +54,7 @@ function create_notification_stream(subscriptions) {
}
var notification = JSON.parse(event.data);
console.log('Got notification:', notification);
console.info('Got notification:', notification);
if (start_time < notification.published && !delivered.includes(notification.videoId)) {
if (Notification.permission === 'granted') {
@ -67,7 +68,7 @@ function create_notification_stream(subscriptions) {
system_notification.onclick = function (event) {
window.open('/watch?v=' + event.currentTarget.tag, '_blank');
}
};
}
delivered.push(notification.videoId);
@ -82,16 +83,16 @@ function create_notification_stream(subscriptions) {
'<i class="icon ion-ios-notifications-outline"></i>';
}
}
}
};
notifications.addEventListener('error', handle_notification_error);
notifications.stream();
}
function handle_notification_error(event) {
console.log('Something went wrong with notifications, trying to reconnect...');
console.warn('Something went wrong with notifications, trying to reconnect...');
notifications = { close: function () { } };
setTimeout(function () { get_subscriptions(create_notification_stream) }, 1000);
setTimeout(function () { get_subscriptions(create_notification_stream); }, 1000);
}
window.addEventListener('load', function (e) {

ファイルの表示

@ -1,5 +1,6 @@
var player_data = JSON.parse(document.getElementById('player_data').innerHTML);
var video_data = JSON.parse(document.getElementById('video_data').innerHTML);
'use strict';
var player_data = JSON.parse(document.getElementById('player_data').textContent);
var video_data = JSON.parse(document.getElementById('video_data').textContent);
var options = {
preload: 'auto',
@ -27,7 +28,7 @@ var options = {
overrideNative: true
}
}
}
};
if (player_data.aspect_ratio) {
options.aspectRatio = player_data.aspect_ratio;
@ -38,7 +39,7 @@ embed_url.searchParams.delete('v');
var short_url = location.origin + '/' + video_data.id + embed_url.search;
embed_url = location.origin + '/embed/' + video_data.id + embed_url.search;
var save_player_pos_key = "save_player_pos";
var save_player_pos_key = 'save_player_pos';
videojs.Vhs.xhr.beforeRequest = function(options) {
if (options.uri.indexOf('videoplayback') === -1 && options.uri.indexOf('local=true') === -1) {
@ -111,12 +112,12 @@ var shareOptions = {
description: player_data.description,
image: player_data.thumbnail,
get embedCode() {
return "<iframe id='ivplayer' width='640' height='360' src='" +
addCurrentTimeToURL(embed_url) + "' style='border:none;'></iframe>";
return '<iframe id="ivplayer" width="640" height="360" src="' +
addCurrentTimeToURL(embed_url) + '" style="border:none;"></iframe>';
}
};
const storage = (() => {
const storage = (function () {
try { if (localStorage.length !== -1) return localStorage; }
catch (e) { console.info('No storage available: ' + e); }
@ -137,57 +138,57 @@ if (location.pathname.startsWith('/embed/')) {
// Detection code taken from https://stackoverflow.com/a/20293441
function isMobile() {
try{ document.createEvent("TouchEvent"); return true; }
try{ document.createEvent('TouchEvent'); return true; }
catch(e){ return false; }
}
if (isMobile()) {
player.mobileUi();
buttons = ["playToggle", "volumePanel", "captionsButton"];
var buttons = ['playToggle', 'volumePanel', 'captionsButton'];
if (video_data.params.quality !== 'dash') buttons.push("qualitySelector")
if (video_data.params.quality !== 'dash') buttons.push('qualitySelector');
// Create new control bar object for operation buttons
const ControlBar = videojs.getComponent("controlBar");
const ControlBar = videojs.getComponent('controlBar');
let operations_bar = new ControlBar(player, {
children: [],
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
});
buttons.slice(1).forEach(child => operations_bar.addChild(child))
buttons.slice(1).forEach(function (child) {operations_bar.addChild(child);});
// Remove operation buttons from primary control bar
primary_control_bar = player.getChild("controlBar");
buttons.forEach(child => primary_control_bar.removeChild(child));
var primary_control_bar = player.getChild('controlBar');
buttons.forEach(function (child) {primary_control_bar.removeChild(child);});
operations_bar_element = operations_bar.el();
operations_bar_element.className += " mobile-operations-bar"
player.addChild(operations_bar)
var operations_bar_element = operations_bar.el();
operations_bar_element.className += ' mobile-operations-bar';
player.addChild(operations_bar);
// Playback menu doesn't work when it's initialized outside of the primary control bar
playback_element = document.getElementsByClassName("vjs-playback-rate")[0]
operations_bar_element.append(playback_element)
var playback_element = document.getElementsByClassName('vjs-playback-rate')[0];
operations_bar_element.append(playback_element);
// The share and http source selector element can't be fetched till the players ready.
player.one("playing", () => {
share_element = document.getElementsByClassName("vjs-share-control")[0]
operations_bar_element.append(share_element)
player.one('playing', function () {
var share_element = document.getElementsByClassName('vjs-share-control')[0];
operations_bar_element.append(share_element);
if (video_data.params.quality === 'dash') {
http_source_selector = document.getElementsByClassName("vjs-http-source-selector vjs-menu-button")[0]
operations_bar_element.append(http_source_selector)
}
})
if (video_data.params.quality === 'dash') {
var http_source_selector = document.getElementsByClassName('vjs-http-source-selector vjs-menu-button')[0];
operations_bar_element.append(http_source_selector);
}
});
}
// Enable VR video support
if (!video_data.params.listen && video_data.vr && video_data.params.vr_mode) {
player.crossOrigin("anonymous")
player.crossOrigin('anonymous');
switch (video_data.projection_type) {
case "EQUIRECTANGULAR":
player.vr({projection: "equirectangular"});
default: // Should only be "MESH" but we'll use this as a fallback.
player.vr({projection: "EAC"});
case 'EQUIRECTANGULAR':
player.vr({projection: 'equirectangular'});
default: // Should only be 'MESH' but we'll use this as a fallback.
player.vr({projection: 'EAC'});
}
}
@ -222,27 +223,27 @@ player.playbackRate(video_data.params.speed);
* @returns cookieValue
*/
function getCookieValue(name) {
var value = document.cookie.split(";").filter(item => item.includes(name + "="));
var value = document.cookie.split(';').filter(function (item) {return item.includes(name + '=');});
return (value != null && value.length >= 1)
? value[0].substring((name + "=").length, value[0].length)
return (value.length >= 1)
? value[0].substring((name + '=').length, value[0].length)
: null;
}
/**
* Method for updating the "PREFS" cookie (or creating it if missing)
* Method for updating the 'PREFS' cookie (or creating it if missing)
*
* @param {number} newVolume New volume defined (null if unchanged)
* @param {number} newSpeed New speed defined (null if unchanged)
*/
function updateCookie(newVolume, newSpeed) {
var volumeValue = newVolume != null ? newVolume : video_data.params.volume;
var speedValue = newSpeed != null ? newSpeed : video_data.params.speed;
var volumeValue = newVolume !== null ? newVolume : video_data.params.volume;
var speedValue = newSpeed !== null ? newSpeed : video_data.params.speed;
var cookieValue = getCookieValue('PREFS');
var cookieData;
if (cookieValue != null) {
if (cookieValue !== null) {
var cookieJson = JSON.parse(decodeURIComponent(cookieValue));
cookieJson.volume = volumeValue;
cookieJson.speed = speedValue;
@ -259,7 +260,7 @@ function updateCookie(newVolume, newSpeed) {
var domainUsed = window.location.hostname;
// Fix for a bug in FF where the leading dot in the FQDN is not ignored
if (domainUsed.charAt(0) != '.' && !ipRegex.test(domainUsed) && domainUsed != 'localhost')
if (domainUsed.charAt(0) !== '.' && !ipRegex.test(domainUsed) && domainUsed !== 'localhost')
domainUsed = '.' + window.location.hostname;
document.cookie = 'PREFS=' + cookieData + '; SameSite=Strict; path=/; domain=' +
@ -279,7 +280,7 @@ player.on('volumechange', function () {
player.on('waiting', function () {
if (player.playbackRate() > 1 && player.liveTracker.isLive() && player.liveTracker.atLiveEdge()) {
console.log('Player has caught up to source, resetting playbackRate.')
console.info('Player has caught up to source, resetting playbackRate.');
player.playbackRate(1);
}
});
@ -290,13 +291,13 @@ if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data.
if (video_data.params.save_player_pos) {
const url = new URL(location);
const hasTimeParam = url.searchParams.has("t");
const hasTimeParam = url.searchParams.has('t');
const remeberedTime = get_video_time();
let lastUpdated = 0;
if(!hasTimeParam) set_seconds_after_start(remeberedTime);
const updateTime = () => {
const updateTime = function () {
const raw = player.currentTime();
const time = Math.floor(raw);
@ -306,7 +307,7 @@ if (video_data.params.save_player_pos) {
}
};
player.on("timeupdate", updateTime);
player.on('timeupdate', updateTime);
}
else remove_all_video_times();
@ -316,13 +317,13 @@ if (video_data.params.autoplay) {
player.ready(function () {
new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 1);
setTimeout(function () {resolve(1);}, 1);
}).then(function (result) {
var promise = player.play();
if (promise !== undefined) {
promise.then(_ => {
}).catch(error => {
promise.then(function () {
}).catch(function (error) {
bpb.show();
});
}
@ -333,16 +334,16 @@ if (video_data.params.autoplay) {
if (!video_data.params.listen && video_data.params.quality === 'dash') {
player.httpSourceSelector();
if (video_data.params.quality_dash != "auto") {
player.ready(() => {
player.on("loadedmetadata", () => {
const qualityLevels = Array.from(player.qualityLevels()).sort((a, b) => a.height - b.height);
if (video_data.params.quality_dash !== 'auto') {
player.ready(function () {
player.on('loadedmetadata', function () {
const qualityLevels = Array.from(player.qualityLevels()).sort(function (a, b) {return a.height - b.height;});
let targetQualityLevel;
switch (video_data.params.quality_dash) {
case "best":
case 'best':
targetQualityLevel = qualityLevels.length - 1;
break;
case "worst":
case 'worst':
targetQualityLevel = 0;
break;
default:
@ -356,7 +357,7 @@ if (!video_data.params.listen && video_data.params.quality === 'dash') {
}
}
for (let i = 0; i < qualityLevels.length; i++) {
qualityLevels[i].enabled = (i == targetQualityLevel);
qualityLevels[i].enabled = (i === targetQualityLevel);
}
});
});
@ -390,10 +391,12 @@ if (!video_data.params.listen && video_data.params.annotations) {
}
}
}
}
};
window.addEventListener('__ar_annotation_click', e => {
const { url, target, seconds } = e.detail;
window.addEventListener('__ar_annotation_click', function (e) {
const url = e.detail.url,
target = e.detail.target,
seconds = e.detail.seconds;
var path = new URL(url);
if (path.href.startsWith('https://www.youtube.com/watch?') && seconds) {
@ -463,7 +466,7 @@ function get_video_time() {
return timestamp || 0;
}
catch {
catch (e) {
return 0;
}
}
@ -474,7 +477,7 @@ function set_all_video_times(times) {
try {
storage.setItem(save_player_pos_key, JSON.stringify(times));
} catch (e) {
console.debug('set_all_video_times: ' + e);
console.warn('set_all_video_times: ' + e);
}
} else {
storage.removeItem(save_player_pos_key);
@ -489,7 +492,7 @@ function get_all_video_times() {
try {
return JSON.parse(raw);
} catch (e) {
console.debug('get_all_video_times: ' + e);
console.warn('get_all_video_times: ' + e);
}
}
}
@ -583,7 +586,7 @@ function increase_playback_rate(steps) {
player.playbackRate(options.playbackRates[newIndex]);
}
window.addEventListener('keydown', e => {
window.addEventListener('keydown', function (e) {
if (e.target.tagName.toLowerCase() === 'input') {
// Ignore input when focus is on certain elements, e.g. form fields.
return;
@ -702,7 +705,7 @@ window.addEventListener('keydown', e => {
var volumeHover = false;
var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
if (volumeSelector != null) {
if (volumeSelector !== null) {
volumeSelector.onmouseover = function () { volumeHover = true; };
volumeSelector.onmouseout = function () { volumeHover = false; };
}
@ -722,9 +725,9 @@ window.addEventListener('keydown', e => {
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
event.preventDefault();
if (delta == 1) {
if (delta === 1) {
increase_volume(volumeStep);
} else if (delta == -1) {
} else if (delta === -1) {
increase_volume(-volumeStep);
}
}
@ -733,7 +736,7 @@ window.addEventListener('keydown', e => {
};
player.on('mousewheel', mouseScroll);
player.on("DOMMouseScroll", mouseScroll);
player.on('DOMMouseScroll', mouseScroll);
}());
// Since videojs-share can sometimes be blocked, we defer it until last
@ -743,13 +746,13 @@ if (player.share) {
// show the preferred caption by default
if (player_data.preferred_caption_found) {
player.ready(() => {
player.ready(function () {
player.textTracks()[1].mode = 'showing';
});
}
// Safari audio double duration fix
if (navigator.vendor == "Apple Computer, Inc." && video_data.params.listen) {
if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) {
player.on('loadedmetadata', function () {
player.on('timeupdate', function () {
if (player.remainingTime() < player.duration() / 2 && player.remainingTime() >= 2) {
@ -760,18 +763,18 @@ if (navigator.vendor == "Apple Computer, Inc." && video_data.params.listen) {
}
// Watch on Invidious link
if (window.location.pathname.startsWith("/embed/")) {
if (window.location.pathname.startsWith('/embed/')) {
const Button = videojs.getComponent('Button');
let watch_on_invidious_button = new Button(player);
// Create hyperlink for current instance
redirect_element = document.createElement("a");
redirect_element.setAttribute("href", `//${window.location.host}/watch?v=${window.location.pathname.replace("/embed/","")}`)
redirect_element.appendChild(document.createTextNode("Invidious"))
var redirect_element = document.createElement('a');
redirect_element.setAttribute('href', location.pathname.replace('/embed/', '/watch?v='));
redirect_element.appendChild(document.createTextNode('Invidious'));
watch_on_invidious_button.el().appendChild(redirect_element)
watch_on_invidious_button.addClass("watch-on-invidious")
watch_on_invidious_button.el().appendChild(redirect_element);
watch_on_invidious_button.addClass('watch-on-invidious');
cb = player.getChild('ControlBar')
cb.addChild(watch_on_invidious_button)
};
var cb = player.getChild('ControlBar');
cb.addChild(watch_on_invidious_button);
}

ファイルの表示

@ -1,4 +1,5 @@
var playlist_data = JSON.parse(document.getElementById('playlist_data').innerHTML);
'use strict';
var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent);
function add_playlist_video(target) {
var select = target.parentNode.children[0].children[1];
@ -14,12 +15,12 @@ function add_playlist_video(target) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
option.innerText = '✓' + option.innerText;
}
}
}
};
xhr.send('csrf_token=' + playlist_data.csrf_token);
}
@ -38,12 +39,12 @@ function add_playlist_item(target) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
tile.style.display = '';
}
}
}
};
xhr.send('csrf_token=' + playlist_data.csrf_token);
}
@ -62,12 +63,12 @@ function remove_playlist_item(target) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
tile.style.display = '';
}
}
}
};
xhr.send('csrf_token=' + playlist_data.csrf_token);
}
}

ファイルの表示

@ -1,4 +1,5 @@
var subscribe_data = JSON.parse(document.getElementById('subscribe_data').innerHTML);
'use strict';
var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textContent);
var subscribe_button = document.getElementById('subscribe');
subscribe_button.parentNode['action'] = 'javascript:void(0)';
@ -9,9 +10,11 @@ if (subscribe_button.getAttribute('data-type') === 'subscribe') {
subscribe_button.onclick = unsubscribe;
}
function subscribe(retries = 5) {
function subscribe(retries) {
if (retries === undefined) retries = 5;
if (retries <= 0) {
console.log('Failed to subscribe.');
console.warn('Failed to subscribe.');
return;
}
@ -28,30 +31,33 @@ function subscribe(retries = 5) {
subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
subscribe_button.onclick = subscribe;
subscribe_button.innerHTML = fallback;
}
}
}
};
xhr.onerror = function () {
console.log('Subscribing failed... ' + retries + '/5');
setTimeout(function () { subscribe(retries - 1) }, 1000);
}
console.warn('Subscribing failed... ' + retries + '/5');
setTimeout(function () { subscribe(retries - 1); }, 1000);
};
xhr.ontimeout = function () {
console.log('Subscribing failed... ' + retries + '/5');
console.warn('Subscribing failed... ' + retries + '/5');
subscribe(retries - 1);
}
};
xhr.send('csrf_token=' + subscribe_data.csrf_token);
}
function unsubscribe(retries = 5) {
function unsubscribe(retries) {
if (retries === undefined)
retries = 5;
if (retries <= 0) {
console.log('Failed to subscribe');
console.warn('Failed to subscribe');
return;
}
@ -68,23 +74,23 @@ function unsubscribe(retries = 5) {
subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
subscribe_button.onclick = unsubscribe;
subscribe_button.innerHTML = fallback;
}
}
}
};
xhr.onerror = function () {
console.log('Unsubscribing failed... ' + retries + '/5');
setTimeout(function () { unsubscribe(retries - 1) }, 1000);
}
console.warn('Unsubscribing failed... ' + retries + '/5');
setTimeout(function () { unsubscribe(retries - 1); }, 1000);
};
xhr.ontimeout = function () {
console.log('Unsubscribing failed... ' + retries + '/5');
console.warn('Unsubscribing failed... ' + retries + '/5');
unsubscribe(retries - 1);
}
};
xhr.send('csrf_token=' + subscribe_data.csrf_token);
}

ファイルの表示

@ -1,8 +1,9 @@
'use strict';
var toggle_theme = document.getElementById('toggle_theme');
toggle_theme.href = 'javascript:void(0);';
toggle_theme.addEventListener('click', function () {
var dark_mode = document.body.classList.contains("light-theme");
var dark_mode = document.body.classList.contains('light-theme');
var url = '/toggle_theme?redirect=false';
var xhr = new XMLHttpRequest();
@ -13,7 +14,7 @@ toggle_theme.addEventListener('click', function () {
set_mode(dark_mode);
try {
window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light');
} catch {}
} catch (e) {}
xhr.send();
});
@ -29,7 +30,7 @@ window.addEventListener('DOMContentLoaded', function () {
try {
// Update localStorage if dark mode preference changed on preferences page
window.localStorage.setItem('dark_mode', dark_mode);
} catch {}
} catch (e) {}
update_mode(dark_mode);
});
@ -46,11 +47,11 @@ function scheme_switch (e) {
if (localStorage.getItem('dark_mode')) {
return;
}
} catch {}
} catch (exception) {}
if (e.matches) {
if (e.media.includes("dark")) {
if (e.media.includes('dark')) {
set_mode(true);
} else if (e.media.includes("light")) {
} else if (e.media.includes('light')) {
set_mode(false);
}
}
@ -77,15 +78,13 @@ function update_mode (mode) {
// If preference for dark mode indicated
set_mode(true);
}
else if (mode === 'false' /* for backwards compatibility */ || mode === 'light') {
// If preference for light mode indicated
set_mode(false);
}
else if (mode === 'false' /* for backwards compatibility */ || mode === 'light') {
// If preference for light mode indicated
set_mode(false);
}
else if (document.getElementById('dark_mode_pref').textContent === '' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
// If no preference indicated here and no preference indicated on the preferences page (backend), but the browser tells us that the operating system has a dark theme
set_mode(true);
}
// else do nothing, falling back to the mode defined by the `dark_mode` preference on the preferences page (backend)
}

ファイルの表示

@ -1,31 +1,32 @@
var video_data = JSON.parse(document.getElementById('video_data').innerHTML);
'use strict';
var video_data = JSON.parse(document.getElementById('video_data').textContent);
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g, function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
});
}
};
function toggle_parent(target) {
body = target.parentNode.parentNode.children[1];
var body = target.parentNode.parentNode.children[1];
if (body.style.display === null || body.style.display === '') {
target.innerHTML = '[ + ]';
target.textContent = '[ + ]';
body.style.display = 'none';
} else {
target.innerHTML = '[ - ]';
target.textContent = '[ ]';
body.style.display = '';
}
}
function toggle_comments(event) {
var target = event.target;
body = target.parentNode.parentNode.parentNode.children[1];
var body = target.parentNode.parentNode.parentNode.children[1];
if (body.style.display === null || body.style.display === '') {
target.innerHTML = '[ + ]';
target.textContent = '[ + ]';
body.style.display = 'none';
} else {
target.innerHTML = '[ - ]';
target.textContent = '[ ]';
body.style.display = '';
}
}
@ -43,13 +44,13 @@ function swap_comments(event) {
function hide_youtube_replies(event) {
var target = event.target;
sub_text = target.getAttribute('data-inner-text');
inner_text = target.getAttribute('data-sub-text');
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
body = target.parentNode.parentNode.children[1];
var body = target.parentNode.parentNode.children[1];
body.style.display = 'none';
target.innerHTML = sub_text;
target.textContent = sub_text;
target.onclick = show_youtube_replies;
target.setAttribute('data-inner-text', inner_text);
target.setAttribute('data-sub-text', sub_text);
@ -58,13 +59,13 @@ function hide_youtube_replies(event) {
function show_youtube_replies(event) {
var target = event.target;
sub_text = target.getAttribute('data-inner-text');
inner_text = target.getAttribute('data-sub-text');
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
body = target.parentNode.parentNode.children[1];
var body = target.parentNode.parentNode.children[1];
body.style.display = '';
target.innerHTML = sub_text;
target.textContent = sub_text;
target.onclick = hide_youtube_replies;
target.setAttribute('data-inner-text', inner_text);
target.setAttribute('data-sub-text', sub_text);
@ -116,25 +117,26 @@ function number_with_separator(val) {
}
function get_playlist(plid, retries) {
if (retries == undefined) retries = 5;
playlist = document.getElementById('playlist');
if (retries === undefined) retries = 5;
var playlist = document.getElementById('playlist');
if (retries <= 0) {
console.log('Failed to pull playlist');
console.warn('Failed to pull playlist');
playlist.innerHTML = '';
return;
}
playlist.innerHTML = ' \
<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \
<hr>'
<hr>';
var plid_url;
if (plid.startsWith('RD')) {
var plid_url = '/api/v1/mixes/' + plid +
plid_url = '/api/v1/mixes/' + plid +
'?continuation=' + video_data.id +
'&format=html&hl=' + video_data.preferences.locale;
} else {
var plid_url = '/api/v1/playlists/' + plid +
plid_url = '/api/v1/playlists/' + plid +
'?index=' + video_data.index +
'&continuation=' + video_data.id +
'&format=html&hl=' + video_data.preferences.locale;
@ -146,8 +148,8 @@ function get_playlist(plid, retries) {
xhr.open('GET', plid_url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
playlist.innerHTML = xhr.response.playlistHtml;
var nextVideo = document.getElementById(xhr.response.nextVideo);
nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop;
@ -185,35 +187,35 @@ function get_playlist(plid, retries) {
document.getElementById('continue').style.display = '';
}
}
}
};
xhr.onerror = function () {
playlist = document.getElementById('playlist');
playlist.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
console.log('Pulling playlist timed out... ' + retries + '/5');
setTimeout(function () { get_playlist(plid, retries - 1) }, 1000);
}
console.warn('Pulling playlist timed out... ' + retries + '/5');
setTimeout(function () { get_playlist(plid, retries - 1); }, 1000);
};
xhr.ontimeout = function () {
playlist = document.getElementById('playlist');
playlist.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
console.log('Pulling playlist timed out... ' + retries + '/5');
console.warn('Pulling playlist timed out... ' + retries + '/5');
get_playlist(plid, retries - 1);
}
};
xhr.send();
}
function get_reddit_comments(retries) {
if (retries == undefined) retries = 5;
comments = document.getElementById('comments');
if (retries === undefined) retries = 5;
var comments = document.getElementById('comments');
if (retries <= 0) {
console.log('Failed to pull comments');
console.warn('Failed to pull comments');
comments.innerHTML = '';
return;
}
@ -231,12 +233,12 @@ function get_reddit_comments(retries) {
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
comments.innerHTML = ' \
<div> \
<h3> \
<a href="javascript:void(0)">[ - ]</a> \
<a href="javascript:void(0)">[ ]</a> \
{title} \
</h3> \
<p> \
@ -263,34 +265,34 @@ function get_reddit_comments(retries) {
comments.children[0].children[1].children[0].onclick = swap_comments;
} else {
if (video_data.params.comments[1] === 'youtube') {
console.log('Pulling comments failed... ' + retries + '/5');
setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
console.warn('Pulling comments failed... ' + retries + '/5');
setTimeout(function () { get_youtube_comments(retries - 1); }, 1000);
} else {
comments.innerHTML = fallback;
}
}
}
}
};
xhr.onerror = function () {
console.log('Pulling comments failed... ' + retries + '/5');
setTimeout(function () { get_reddit_comments(retries - 1) }, 1000);
}
console.warn('Pulling comments failed... ' + retries + '/5');
setTimeout(function () { get_reddit_comments(retries - 1); }, 1000);
};
xhr.ontimeout = function () {
console.log('Pulling comments failed... ' + retries + '/5');
console.warn('Pulling comments failed... ' + retries + '/5');
get_reddit_comments(retries - 1);
}
};
xhr.send();
}
function get_youtube_comments(retries) {
if (retries == undefined) retries = 5;
comments = document.getElementById('comments');
if (retries === undefined) retries = 5;
var comments = document.getElementById('comments');
if (retries <= 0) {
console.log('Failed to pull comments');
console.warn('Failed to pull comments');
comments.innerHTML = '';
return;
}
@ -309,12 +311,12 @@ function get_youtube_comments(retries) {
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
comments.innerHTML = ' \
<div> \
<h3> \
<a href="javascript:void(0)">[ - ]</a> \
<a href="javascript:void(0)">[ ]</a> \
{commentsText} \
</h3> \
<b> \
@ -336,27 +338,27 @@ function get_youtube_comments(retries) {
comments.children[0].children[1].children[0].onclick = swap_comments;
} else {
if (video_data.params.comments[1] === 'youtube') {
setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
setTimeout(function () { get_youtube_comments(retries - 1); }, 1000);
} else {
comments.innerHTML = '';
}
}
}
}
};
xhr.onerror = function () {
comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
console.log('Pulling comments failed... ' + retries + '/5');
setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
}
console.warn('Pulling comments failed... ' + retries + '/5');
setTimeout(function () { get_youtube_comments(retries - 1); }, 1000);
};
xhr.ontimeout = function () {
comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
console.log('Pulling comments failed... ' + retries + '/5');
console.warn('Pulling comments failed... ' + retries + '/5');
get_youtube_comments(retries - 1);
}
};
xhr.send();
}
@ -373,7 +375,7 @@ function get_youtube_replies(target, load_more, load_replies) {
'?format=html' +
'&hl=' + video_data.preferences.locale +
'&thin_mode=' + video_data.preferences.thin_mode +
'&continuation=' + continuation
'&continuation=' + continuation;
if (load_replies) {
url += '&action=action_get_comment_replies';
}
@ -383,8 +385,8 @@ function get_youtube_replies(target, load_more, load_replies) {
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
if (load_more) {
body = body.parentNode.parentNode;
body.removeChild(body.lastElementChild);
@ -412,12 +414,12 @@ function get_youtube_replies(target, load_more, load_replies) {
body.innerHTML = fallback;
}
}
}
};
xhr.ontimeout = function () {
console.log('Pulling comments failed.');
console.warn('Pulling comments failed.');
body.innerHTML = fallback;
}
};
xhr.send();
}
@ -461,7 +463,7 @@ window.addEventListener('load', function (e) {
} else if (video_data.params.comments[1] === 'reddit') {
get_reddit_comments();
} else {
comments = document.getElementById('comments');
var comments = document.getElementById('comments');
comments.innerHTML = '';
}
});

ファイルの表示

@ -1,4 +1,5 @@
var watched_data = JSON.parse(document.getElementById('watched_data').innerHTML);
'use strict';
var watched_data = JSON.parse(document.getElementById('watched_data').textContent);
function mark_watched(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
@ -13,12 +14,12 @@ function mark_watched(target) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
tile.style.display = '';
}
}
}
};
xhr.send('csrf_token=' + watched_data.csrf_token);
}
@ -26,7 +27,7 @@ function mark_watched(target) {
function mark_unwatched(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = 'none';
var count = document.getElementById('count')
var count = document.getElementById('count');
count.innerText = count.innerText - 1;
var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' +
@ -38,13 +39,13 @@ function mark_unwatched(target) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
count.innerText = count.innerText - 1 + 2;
tile.style.display = '';
}
}
}
};
xhr.send('csrf_token=' + watched_data.csrf_token);
}
}

ファイルの表示

@ -1,4 +1,4 @@
FROM crystallang/crystal:1.4.0-alpine AS builder
FROM crystallang/crystal:1.4.1-alpine AS builder
RUN apk add --no-cache sqlite-static yaml-static
ARG release

ファイルの表示

@ -1,5 +1,5 @@
FROM alpine:edge AS builder
RUN apk add --no-cache 'crystal=1.4.0-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev
RUN apk add --no-cache 'crystal=1.4.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev
ARG release

ファイルの表示

@ -68,7 +68,7 @@
"preferences_watch_history_label": "Enable watch history: ",
"preferences_speed_label": "Default speed: ",
"preferences_quality_label": "Preferred video quality: ",
"preferences_quality_option_dash": "DASH (adaptative quality)",
"preferences_quality_option_dash": "DASH (adaptive quality)",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Medium",
"preferences_quality_option_small": "Small",

474
locales/hi.json ノーマルファイル
ファイルの表示

@ -0,0 +1,474 @@
{
"last": "आखिरी",
"Yes": "हाँ",
"No": "नहीं",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML के रूप में सदस्यताएँ निर्यात करें (NewPipe और FreeTube के लिए)",
"Log in/register": "लॉग-इन/पंजीकृत करें",
"Log in with Google": "Google के साथ लॉग-इन करें",
"preferences_autoplay_label": "अपने आप चलाने की सुविधा: ",
"preferences_dark_mode_label": "थीम: ",
"preferences_default_home_label": "डिफ़ॉल्ट मुखपृष्ठ: ",
"Could not fetch comments": "टिप्पणियाँ प्राप्त न की जा सकीं",
"comments_points_count": "{{count}} पॉइंट",
"comments_points_count_plural": "{{count}} पॉइंट्स",
"Subscription manager": "सदस्यता प्रबंधन",
"License: ": "लाइसेंस: ",
"Wilson score: ": "Wilson स्कोर: ",
"Wrong answer": "गलत जवाब",
"Erroneous CAPTCHA": "गलत CAPTCHA",
"Please log in": "कृपया लॉग-इन करें",
"Bosnian": "बोस्नियाई",
"Bulgarian": "बुल्गारियाई",
"Burmese": "बर्मी",
"Chinese (Traditional)": "चीनी (पारंपरिक)",
"Kurdish": "कुर्द",
"Punjabi": "पंजाबी",
"Sinhala": "सिंहली",
"Slovak": "स्लोवाक",
"generic_count_days": "{{count}} दिन",
"generic_count_days_plural": "{{count}} दिन",
"generic_count_hours": "{{count}} घंटे",
"generic_count_hours_plural": "{{count}} घंटे",
"generic_count_minutes": "{{count}} मिनट",
"generic_count_minutes_plural": "{{count}} मिनट",
"generic_count_seconds": "{{count}} सेकंड",
"generic_count_seconds_plural": "{{count}} सेकंड",
"generic_playlists_count": "{{count}} प्लेलिस्ट",
"generic_playlists_count_plural": "{{count}} प्लेलिस्ट्स",
"crash_page_report_issue": "अगर इनमें से कुछ भी काम नहीं करता, कृपया <a href=\"`x`\">GitHub पर एक नया मुद्दा खोल दें</a> (अंग्रेज़ी में) और अपने संदेश में यह टेक्स्ट दर्ज करें (इसे अनुवादित न करें!):",
"generic_views_count": "{{count}} बार देखा गया",
"generic_views_count_plural": "{{count}} बार देखा गया",
"generic_videos_count": "{{count}} वीडियो",
"generic_videos_count_plural": "{{count}} वीडियो",
"generic_subscribers_count": "{{count}} सदस्य",
"generic_subscribers_count_plural": "{{count}} सदस्य",
"generic_subscriptions_count": "{{count}} सदस्यता",
"generic_subscriptions_count_plural": "{{count}} सदस्यताएँ",
"LIVE": "लाइव",
"Shared `x` ago": "`x` पहले बाँटा गया",
"Unsubscribe": "सदस्यता छोड़ें",
"Subscribe": "सदस्यता लें",
"View channel on YouTube": "चैनल YouTube पर देखें",
"View playlist on YouTube": "प्लेलिस्ट YouTube पर देखें",
"newest": "सबसे नया",
"oldest": "सबसे पुराना",
"popular": "सर्वाधिक लोकप्रिय",
"Next page": "अगला पृष्ठ",
"Previous page": "पिछला पृष्ठ",
"Clear watch history?": "देखने का इतिहास मिटाएँ?",
"New password": "नया पासवर्ड",
"New passwords must match": "पासवर्ड्स को मेल खाना होगा",
"Cannot change password for Google accounts": "Google खातों के लिए पासवर्ड नहीं बदल सकते",
"Authorize token?": "टोकन को प्रमाणित करें?",
"Authorize token for `x`?": "`x` के लिए टोकन को प्रमाणित करें?",
"Import and Export Data": "डेटा को आयात और निर्यात करें",
"Import": "आयात करें",
"Import Invidious data": "Invidious JSON डेटा आयात करें",
"Import YouTube subscriptions": "YouTube/OPML सदस्यताएँ आयात करें",
"Import FreeTube subscriptions (.db)": "FreeTube सदस्यताएँ आयात करें (.db)",
"Import NewPipe subscriptions (.json)": "NewPipe सदस्यताएँ आयात करें (.json)",
"Import NewPipe data (.zip)": "NewPipe डेटा आयात करें (.zip)",
"Export": "निर्यात करें",
"Export subscriptions as OPML": "OPML के रूप में सदस्यताएँ निर्यात करें",
"Export data as JSON": "Invidious डेटा को JSON के रूप में निर्यात करें",
"Delete account?": "खाता हटाएँ?",
"History": "देखे गए वीडियो",
"An alternative front-end to YouTube": "YouTube का एक वैकल्पिक फ्रंट-एंड",
"JavaScript license information": "जावास्क्रिप्ट लाइसेंस की जानकारी",
"source": "स्रोत",
"Log in": "लॉग-इन करें",
"User ID": "सदस्य ID",
"Password": "पासवर्ड",
"Register": "पंजीकृत करें",
"E-mail": "ईमेल",
"Google verification code": "Google प्रमाणीकरण कोड",
"Time (h:mm:ss):": "समय (घं:मिमि:सेसे):",
"Text CAPTCHA": "टेक्स्ट CAPTCHA",
"Image CAPTCHA": "चित्र CAPTCHA",
"Sign In": "साइन इन करें",
"Preferences": "प्राथमिकताएँ",
"preferences_category_player": "प्लेयर की प्राथमिकताएँ",
"preferences_video_loop_label": "हमेशा लूप करें: ",
"preferences_continue_label": "डिफ़ॉल्ट से अगला चलाएँ: ",
"preferences_continue_autoplay_label": "अगला वीडियो अपने आप चलाएँ: ",
"preferences_listen_label": "डिफ़ॉल्ट से सुनें: ",
"preferences_local_label": "प्रॉक्सी वीडियो: ",
"preferences_watch_history_label": "देखने का इतिहास सक्षम करें: ",
"preferences_speed_label": "वीडियो चलाने की डिफ़ॉल्ट रफ़्तार: ",
"preferences_quality_label": "वीडियो की प्राथमिक क्वालिटी: ",
"preferences_quality_option_dash": "DASH (अनुकूली गुणवत्ता)",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "मध्यम",
"preferences_quality_option_small": "छोटा",
"preferences_quality_dash_label": "प्राथमिक DASH वीडियो क्वालिटी: ",
"preferences_quality_dash_option_720p": "720p",
"preferences_quality_dash_option_auto": "अपने-आप",
"preferences_quality_dash_option_best": "सबसे अच्छा",
"preferences_quality_dash_option_worst": "सबसे खराब",
"preferences_quality_dash_option_4320p": "4320p",
"preferences_quality_dash_option_2160p": "2160p",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_480p": "480p",
"preferences_quality_dash_option_360p": "360p",
"preferences_quality_dash_option_240p": "240p",
"preferences_quality_dash_option_144p": "144p",
"preferences_comments_label": "डिफ़ॉल्ट टिप्पणियाँ: ",
"preferences_volume_label": "प्लेयर का वॉल्यूम: ",
"youtube": "YouTube",
"reddit": "Reddit",
"invidious": "Invidious",
"preferences_captions_label": "डिफ़ॉल्ट कैप्शन: ",
"Fallback captions: ": "वैकल्पिक कैप्शन: ",
"preferences_related_videos_label": "संबंधित वीडियो दिखाएँ: ",
"preferences_annotations_label": "डिफ़ॉल्ट से टिप्पणियाँ दिखाएँ: ",
"preferences_extend_desc_label": "अपने आप वीडियो के विवरण का विस्तार करें: ",
"preferences_vr_mode_label": "उत्तरदायी 360 डिग्री वीडियो (WebGL की ज़रूरत है): ",
"preferences_category_visual": "यथादृश्य प्राथमिकताएँ",
"preferences_region_label": "सामग्री का राष्ट्र: ",
"preferences_player_style_label": "प्लेयर का स्टाइल: ",
"Dark mode: ": "डार्क मोड: ",
"dark": "डार्क",
"light": "लाइट",
"preferences_thin_mode_label": "हल्का मोड: ",
"preferences_category_misc": "विविध प्राथमिकताएँ",
"preferences_automatic_instance_redirect_label": "अपने आप अनुप्रेषित करें (redirect.invidious.io पर फ़ॉलबैक करें): ",
"preferences_category_subscription": "सदस्यताओं की प्राथमिकताएँ",
"preferences_annotations_subscribed_label": "सदस्यता लिए गए चैनलों पर डिफ़ॉल्ट से टिप्पणियाँ दिखाएँ? ",
"Redirect homepage to feed: ": "फ़ीड पर मुखपृष्ठ को अनुप्रेषित करें: ",
"preferences_max_results_label": "फ़ीड में दिखाए जाने वाले वीडियों की संख्या: ",
"preferences_sort_label": "वीडियों को इस मानदंड पर छाँटें: ",
"published": "प्रकाशित",
"published - reverse": "प्रकाशित - उल्टा",
"Only show latest video from channel: ": "चैनल से सिर्फ नवीनतम वीडियो ही दिखाएँ: ",
"alphabetically": "वर्णक्रमानुसार",
"Only show latest unwatched video from channel: ": "चैनल से सिर्फ न देखा गया नवीनतम वीडियो ही दिखाएँ: ",
"alphabetically - reverse": "वर्णक्रमानुसार - उल्टा",
"channel name": "चैनल का नाम",
"channel name - reverse": "चैनल का नाम - उल्टा",
"preferences_unseen_only_label": "सिर्फ न देखे गए वीडियो ही दिखाएँ: ",
"preferences_notifications_only_label": "सिर्फ सूचनाएँ दिखाएँ (अगर हो तो): ",
"Enable web notifications": "वेब सूचनाएँ सक्षम करें",
"`x` uploaded a video": "`x` ने वीडियो अपलोड किया",
"`x` is live": "`x` लाइव हैं",
"preferences_category_data": "डेटा की प्राथमिकताएँ",
"Clear watch history": "देखने का इतिहास साफ़ करें",
"Import/export data": "डेटा को आयात/निर्यात करें",
"Change password": "पासवर्ड बदलें",
"Manage subscriptions": "सदस्यताएँ प्रबंधित करें",
"Manage tokens": "टोकन प्रबंधित करें",
"Watch history": "देखने का इतिहास",
"Delete account": "खाता हटाएँ",
"preferences_category_admin": "प्रबंधक प्राथमिकताएँ",
"preferences_feed_menu_label": "फ़ीड मेन्यू: ",
"preferences_show_nick_label": "ऊपर उपनाम दिखाएँ: ",
"Top enabled: ": "ऊपर का हिस्सा सक्षम है: ",
"CAPTCHA enabled: ": "CAPTCHA सक्षम है: ",
"Login enabled: ": "लॉग-इन सक्षम है: ",
"Registration enabled: ": "पंजीकरण सक्षम है: ",
"Report statistics: ": "सांख्यिकी रिपोर्ट करें: ",
"Released under the AGPLv3 on Github.": "GitHub पर AGPLv3 के अंतर्गत प्रकाशित।",
"Save preferences": "प्राथमिकताएँ सहेजें",
"Token manager": "टोकन प्रबंधन",
"Token": "टोकन",
"tokens_count": "{{count}} टोकन",
"tokens_count_plural": "{{count}} टोकन",
"Import/export": "आयात/निर्यात करें",
"unsubscribe": "सदस्यता छोड़ें",
"revoke": "हटाएँ",
"Subscriptions": "सदस्यताएँ",
"subscriptions_unseen_notifs_count": "{{count}} अपठित सूचना",
"subscriptions_unseen_notifs_count_plural": "{{count}} अपठित सूचना",
"search": "खोजें",
"Log out": "लॉग-आउट करें",
"Source available here.": "स्रोत यहाँ उपलब्ध है।",
"View JavaScript license information.": "जावास्क्रिप्ट लाइसेंस की जानकारी देखें।",
"View privacy policy.": "निजता नीति देखें।",
"Trending": "रुझान में",
"Public": "सार्वजनिक",
"Unlisted": "सबके लिए उपलब्ध नहीं",
"Private": "निजी",
"View all playlists": "सभी प्लेलिस्ट देखें",
"Create playlist": "प्लेलिस्ट बनाएँ",
"Updated `x` ago": "`x` पहले अपडेट किया गया",
"Delete playlist `x`?": "प्लेलिस्ट `x` हटाएँ?",
"Delete playlist": "प्लेलिस्ट हटाएँ",
"Title": "शीर्षक",
"Playlist privacy": "प्लेलिस्ट की निजता",
"Editing playlist `x`": "प्लेलिस्ट `x` को संपादित किया जा रहा है",
"Show more": "अधिक देखें",
"Show less": "कम देखें",
"Watch on YouTube": "YouTube पर देखें",
"Switch Invidious Instance": "Invidious उदाहरण बदलें",
"search_message_no_results": "कोई परिणाम नहीं मिला।",
"search_message_change_filters_or_query": "अपने खोज क्वेरी को और चौड़ा करें और/या फ़िल्टर बदलें।",
"search_message_use_another_instance": " आप <a href=\"`x`\">दूसरे उदाहरण पर भी खोज सकते हैं</a>।",
"Hide annotations": "टिप्पणियाँ छिपाएँ",
"Show annotations": "टिप्पणियाँ दिखाएँ",
"Genre: ": "श्रेणी: ",
"Family friendly? ": "परिवार के लिए ठीक है? ",
"Engagement: ": "सगाई: ",
"Whitelisted regions: ": "स्वीकृत क्षेत्र: ",
"Blacklisted regions: ": "अस्वीकृत क्षेत्र: ",
"Shared `x`": "`x` बाँटा गया",
"Premieres in `x`": "`x` बाद प्रीमियर होगा",
"Premieres `x`": "`x` को प्रीमिर होगा",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "नमस्कार! ऐसा लगता है कि आपका जावास्क्रिप्ट अक्षम है। टिप्पणियाँ देखने के लिए यहाँ क्लिक करें, लेकिन याद रखें कि इन्हें लोड होने में थोड़ा ज़्यादा समय लग सकता है।",
"View YouTube comments": "YouTube टिप्पणियाँ देखें",
"View more comments on Reddit": "Reddit पर अधिक टिप्पणियाँ देखें",
"View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` टिप्पणी देखें",
"": "`x` टिप्पणियाँ देखें"
},
"View Reddit comments": "Reddit पर टिप्पणियाँ",
"Hide replies": "जवाब छिपाएँ",
"Show replies": "जवाब दिखाएँ",
"Incorrect password": "गलत पासवर्ड",
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "लॉग-इन नहीं किया जा सका, सुनिश्चित करें कि दो-कारक प्रमाणीकरण (Authenticator या SMS) सक्षम है।",
"Invalid TFA code": "अमान्य TFA कोड",
"Login failed. This may be because two-factor authentication is not turned on for your account.": "लॉग-इन नाकाम रहा। ऐसा इसलिए हो सकता है कि दो-कारक प्रमाणीकरण आपके खाते पर सक्षम नहीं है।",
"Quota exceeded, try again in a few hours": "कोटा पार हो चुका है, कृपया कुछ घंटों में फिर कोशिश करें",
"CAPTCHA is a required field": "CAPTCHA एक ज़रूरी फ़ील्ड है",
"User ID is a required field": "सदस्य ID एक ज़रूरी फ़ील्ड है",
"Password is a required field": "पासवर्ड एक ज़रूरी फ़ील्ड है",
"Wrong username or password": "गलत सदस्यनाम या पासवर्ड",
"Please sign in using 'Log in with Google'": "कृपया 'Google के साथ लॉग-इन करें' के साथ साइन-इन करें",
"Password cannot be empty": "पासवर्ड खाली नहीं हो सकता",
"Password cannot be longer than 55 characters": "पासवर्ड में अधिकतम 55 अक्षर हो सकते हैं",
"Invidious Private Feed for `x`": "`x` के लिए Invidious निजी फ़ीड",
"channel:`x`": "चैनल:`x`",
"Deleted or invalid channel": "हटाया गया या अमान्य चैनल",
"This channel does not exist.": "यह चैनल मौजूद नहीं है।",
"Could not get channel info.": "चैनल की जानकारी प्राप्त न की जा सकी।",
"comments_view_x_replies": "{{count}} टिप्पणी देखें",
"comments_view_x_replies_plural": "{{count}} टिप्पणियाँ देखें",
"`x` ago": "`x` पहले",
"Load more": "अधिक लोड करें",
"Could not create mix.": "मिक्स न बनाया जा सका।",
"Empty playlist": "खाली प्लेलिस्ट",
"Not a playlist.": "यह प्लेलिस्ट नहीं है।",
"Playlist does not exist.": "प्लेलिस्ट मौजूद नहीं है।",
"Could not pull trending pages.": "रुझान के पृष्ठ प्राप्त न किए जा सके।",
"Hidden field \"challenge\" is a required field": "छिपाया गया फ़ील्ड \"चुनौती\" एक आवश्यक फ़ील्ड है",
"Hidden field \"token\" is a required field": "छिपाया गया फ़ील्ड \"टोकन\" एक आवश्यक फ़ील्ड है",
"Erroneous challenge": "त्रुटिपूर्ण चुनौती",
"Erroneous token": "त्रुटिपूर्ण टोकन",
"No such user": "यह सदस्य मौजूद नहीं हैं",
"Token is expired, please try again": "टोकन की समय-सीमा समाप्त हो चुकी है, कृपया दोबारा कोशिश करें",
"English": "अंग्रेज़ी",
"English (United Kingdom)": "अंग्रेज़ी (यूनाइटेड किंग्डम)",
"English (United States)": "अंग्रेज़ी (संयुक्त राष्ट्र)",
"English (auto-generated)": "अंग्रेज़ी (अपने-आप जनरेट हुआ)",
"Afrikaans": "अफ़्रीकी",
"Albanian": "अल्बानियाई",
"Amharic": "अम्हेरी",
"Arabic": "अरबी",
"Armenian": "आर्मेनियाई",
"Belarusian": "बेलारूसी",
"Azerbaijani": "अज़रबैजानी",
"Bangla": "बंगाली",
"Basque": "बास्क",
"Cantonese (Hong Kong)": "कैंटोनीज़ (हाँग काँग)",
"Catalan": "कातालान",
"Cebuano": "सेबुआनो",
"Chinese": "चीनी",
"Chinese (China)": "चीनी (चीन)",
"Chinese (Hong Kong)": "चीनी (हाँग काँग)",
"Chinese (Simplified)": "चीनी (सरलीकृत)",
"Chinese (Taiwan)": "चीनी (ताइवान)",
"Corsican": "कोर्सिकन",
"Croatian": "क्रोएशियाई",
"Czech": "चेक",
"Danish": "डेनिश",
"Dutch": "डच",
"Dutch (auto-generated)": "डच (अपने-आप जनरेट हुआ)",
"Esperanto": "एस्पेरांतो",
"Estonian": "एस्टोनियाई",
"Filipino": "फ़िलिपीनो",
"Finnish": "फ़िनिश",
"French": "फ़्रेंच",
"French (auto-generated)": "फ़्रेंच (अपने-आप जनरेट हुआ)",
"Galician": "गैलिशियन",
"Georgian": "जॉर्जियाई",
"German": "जर्मन",
"German (auto-generated)": "जर्मन (अपने-आप जनरेट हुआ)",
"Greek": "यूनानी",
"Gujarati": "गुजराती",
"Haitian Creole": "हैती क्रियोल",
"Hausa": "हौसा",
"Hawaiian": "हवाई",
"Hebrew": "हीब्रू",
"Hindi": "हिन्दी",
"Hmong": "हमोंग",
"Hungarian": "हंगेरी",
"Icelandic": "आइसलैंडिक",
"Igbo": "इग्बो",
"Indonesian": "इंडोनेशियाई",
"Indonesian (auto-generated)": "इंडोनेशियाई (अपने-आप जनरेट हुआ)",
"Interlingue": "इंटरलिंगुआ",
"Irish": "आयरिश",
"Italian": "इतालवी",
"Italian (auto-generated)": "इतालवी (अपने-आप जनरेट हुआ)",
"Japanese": "जापानी",
"Japanese (auto-generated)": "जापानी (अपने-आप जनरेट हुआ)",
"Javanese": "जावानीज़",
"Kannada": "कन्नड़",
"Kazakh": "कज़ाख़",
"Khmer": "खमेर",
"Korean": "कोरियाई",
"Korean (auto-generated)": "कोरियाई (अपने-आप जनरेट हुआ)",
"Kyrgyz": "किर्गीज़",
"Lao": "लाओ",
"Latin": "लैटिन",
"Latvian": "लातवियाई",
"Lithuanian": "लिथुएनियाई",
"Luxembourgish": "लग्ज़मबर्गी",
"Macedonian": "मकादूनियाई",
"Malagasy": "मालागासी",
"Malay": "मलय",
"Malayalam": "मलयालम",
"Maltese": "माल्टीज़",
"Maori": "माओरी",
"Marathi": "मराठी",
"Mongolian": "मंगोलियाई",
"Nepali": "नेपाली",
"Norwegian Bokmål": "नॉर्वेजियाई",
"Nyanja": "न्यानजा",
"Pashto": "पश्तो",
"Persian": "फ़ारसी",
"Polish": "पोलिश",
"Portuguese": "पुर्तगाली",
"Portuguese (auto-generated)": "पुर्तगाली (अपने-आप जनरेट हुआ)",
"Portuguese (Brazil)": "पुर्तगाली (ब्राज़ील)",
"Romanian": "रोमेनियाई",
"Russian": "रूसी",
"Russian (auto-generated)": "रूसी (अपने-आप जनरेट हुआ)",
"Samoan": "सामोन",
"Scottish Gaelic": "स्कॉटिश गाएलिक",
"Serbian": "सर्बियाई",
"Shona": "शोणा",
"Sindhi": "सिंधी",
"Slovenian": "स्लोवेनियाई",
"Somali": "सोमाली",
"Southern Sotho": "दक्षिणी सोथो",
"Spanish": "स्पेनी",
"Spanish (auto-generated)": "स्पेनी (अपने-आप जनरेट हुआ)",
"Spanish (Latin America)": "स्पेनी (लातिन अमेरिकी)",
"Spanish (Mexico)": "स्पेनी (मेक्सिको)",
"Spanish (Spain)": "स्पेनी (स्पेन)",
"Sundanese": "सुंडानी",
"Swahili": "स्वाहिली",
"Swedish": "स्वीडिश",
"Tajik": "ताजीक",
"Tamil": "तमिल",
"Telugu": "तेलुगु",
"Thai": "थाई",
"Turkish": "तुर्की",
"Turkish (auto-generated)": "तुर्की (अपने-आप जनरेट हुआ)",
"Ukrainian": "यूक्रेनी",
"Urdu": "उर्दू",
"Uzbek": "उज़्बेक",
"Vietnamese": "वियतनामी",
"Vietnamese (auto-generated)": "वियतनामी (अपने-आप जनरेट हुआ)",
"Welsh": "Welsh",
"Western Frisian": "पश्चिमी फ़्रिसियाई",
"Xhosa": "खोसा",
"Yiddish": "यहूदी",
"generic_count_years": "{{count}} वर्ष",
"generic_count_years_plural": "{{count}} वर्ष",
"Yoruba": "योरुबा",
"generic_count_months": "{{count}} महीने",
"generic_count_months_plural": "{{count}} महीने",
"Zulu": "ज़ूलू",
"generic_count_weeks": "{{count}} हफ़्ते",
"generic_count_weeks_plural": "{{count}} हफ़्ते",
"Fallback comments: ": "फ़ॉलबैक टिप्पणियाँ: ",
"Popular": "प्रसिद्ध",
"Search": "खोजें",
"Top": "ऊपर",
"About": "जानकारी",
"Rating: ": "रेटिंग: ",
"preferences_locale_label": "भाषा: ",
"View as playlist": "प्लेलिस्ट के रूप में देखें",
"Default": "डिफ़ॉल्ट",
"Download": "डाउनलोड करें",
"Download as: ": "इस रूप में डाउनलोड करें: ",
"%A %B %-d, %Y": "%A %B %-d, %Y",
"Music": "संगीत",
"Gaming": "गेमिंग",
"News": "समाचार",
"Movies": "फ़िल्में",
"(edited)": "(संपादित)",
"YouTube comment permalink": "YouTube पर टिप्पणी की स्थायी कड़ी",
"permalink": "स्थायी कड़ी",
"Videos": "वीडियो",
"`x` marked it with a ❤": "`x` ने इसे एक ❤ से चिह्नित किया",
"Audio mode": "ऑडियो मोड",
"Playlists": "प्लेलिस्ट्स",
"Video mode": "वीडियो मोड",
"Community": "समुदाय",
"search_filters_title": "फ़िल्टर",
"search_filters_date_label": "अपलोड करने का समय",
"search_filters_date_option_none": "कोई भी समय",
"search_filters_date_option_week": "इस हफ़्ते",
"search_filters_date_option_month": "इस महीने",
"search_filters_date_option_hour": "पिछला घंटा",
"search_filters_date_option_today": "आज",
"search_filters_date_option_year": "इस साल",
"search_filters_type_label": "प्रकार",
"search_filters_type_option_all": "कोई भी प्रकार",
"search_filters_type_option_video": "वीडियो",
"search_filters_type_option_channel": "चैनल",
"search_filters_sort_option_relevance": "प्रासंगिकता",
"search_filters_type_option_playlist": "प्लेलिस्ट",
"search_filters_type_option_movie": "फ़िल्म",
"search_filters_type_option_show": "शो",
"search_filters_duration_label": "अवधि",
"search_filters_duration_option_none": "कोई भी अवधि",
"search_filters_duration_option_short": "4 मिनट से कम",
"search_filters_duration_option_medium": "4 से 20 मिनट तक",
"search_filters_duration_option_long": "20 मिनट से ज़्यादा",
"search_filters_features_label": "सुविधाएँ",
"search_filters_features_option_live": "लाइव",
"search_filters_sort_option_rating": "रेटिंग",
"search_filters_features_option_four_k": "4K",
"search_filters_features_option_hd": "HD",
"search_filters_features_option_subtitles": "उपशीर्षक/कैप्शन",
"search_filters_features_option_c_commons": "क्रिएटिव कॉमन्स",
"search_filters_features_option_three_sixty": "360°",
"search_filters_features_option_vr180": "VR180",
"search_filters_features_option_three_d": "3D",
"search_filters_features_option_hdr": "HDR",
"search_filters_features_option_location": "जगह",
"search_filters_features_option_purchased": "खरीदा गया",
"search_filters_sort_label": "इस क्रम से लगाएँ",
"search_filters_sort_option_date": "अपलोड की ताऱीख",
"search_filters_sort_option_views": "देखे जाने की संख्या",
"search_filters_apply_button": "चयनित फ़िल्टर लागू करें",
"footer_documentation": "प्रलेख",
"footer_source_code": "स्रोत कोड",
"footer_original_source_code": "मूल स्रोत कोड",
"footer_modfied_source_code": "बदला गया स्रोत कोड",
"Current version: ": "वर्तमान संस्करण: ",
"next_steps_error_message": "इसके बाद आपके ये आज़माने चाहिए: ",
"next_steps_error_message_refresh": "साफ़ करें",
"next_steps_error_message_go_to_youtube": "YouTube पर जाएँ",
"footer_donate_page": "दान करें",
"adminprefs_modified_source_code_url_label": "बदले गए स्रोत कोड के रिपॉज़िटरी का URL",
"none": "कुछ नहीं",
"videoinfo_started_streaming_x_ago": "`x` पहले स्ट्रीम करना शुरू किया",
"videoinfo_watch_on_youTube": "YouTube पर देखें",
"Video unavailable": "वीडियो उपलब्ध नहीं है",
"preferences_save_player_pos_label": "यहाँ से चलाना शुरू करें: ",
"crash_page_you_found_a_bug": "शायद आपको Invidious में कोई बग नज़र आ गया है!",
"videoinfo_youTube_embed_link": "एम्बेड करें",
"videoinfo_invidious_embed_link": "एम्बोड करने की कड़ी",
"download_subtitles": "उपशीर्षक - `x` (.vtt)",
"user_created_playlists": "बनाए गए `x` प्लेलिस्ट्स",
"user_saved_playlists": "सहेजे गए `x` प्लेलिस्ट्स",
"crash_page_before_reporting": "बग रिपोर्ट करने से पहले:",
"crash_page_switch_instance": "<a href=\"`x`\">किसी दूसरे उदाहरण का इस्तेमाल करें</a>",
"crash_page_read_the_faq": "<a href=\"`x`\">अक्सर पूछे जाने वाले प्रश्न (FAQ)</a> पढ़ें",
"crash_page_refresh": "<a href=\"`x`\">पृष्ठ को एक बार साफ़ करें</a>",
"crash_page_search_issue": "<a href=\"`x`\">GitHub पर मौजूदा मुद्दे</a> ढूँढ़ें"
}

ファイルの表示

@ -31,15 +31,15 @@
"No": "Nem",
"Import and Export Data": "Adatok importálása és exportálása",
"Import": "Importálás",
"Import Invidious data": "Az Invidious adatainak importálása",
"Import YouTube subscriptions": "YouTube-feliratkozások importálása",
"Import Invidious data": "Az Invidious JSON-adatainak importálása",
"Import YouTube subscriptions": "YouTube- vagy OPML-feliratkozások importálása",
"Import FreeTube subscriptions (.db)": "FreeTube-feliratkozások importálása (.db)",
"Import NewPipe subscriptions (.json)": "NewPipe-feliratkozások importálása (.json)",
"Import NewPipe data (.zip)": "NewPipe adatainak importálása (.zip)",
"Export": "Exportálás",
"Export subscriptions as OPML": "Feliratkozások exportálása OPML-ként",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Feliratkozások exportálása OPML-ként (NewPipe-hoz és FreeTube-hoz)",
"Export data as JSON": "Adat exportálása JSON-ként",
"Export data as JSON": "Az Invidious JSON-adatainak exportálása",
"Delete account?": "Törlésre kerüljön a fiók?",
"History": "Megnézett videók naplója",
"An alternative front-end to YouTube": "Ez az oldal egyike a YouTube alternatív kezelőfelületeinek",
@ -159,7 +159,7 @@
"Engagement: ": "Visszajelzési mutató: ",
"Whitelisted regions: ": "Engedélyezett régiók: ",
"Blacklisted regions: ": "Tiltott régiók: ",
"Shared `x`": "`x` napon osztották meg",
"Shared `x`": "`x` dátummal osztották meg",
"Premieres in `x`": "`x` később lesz a premierje",
"Premieres `x`": "`x` lesz a premierje",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Helló! Úgy tűnik a JavaScript ki van kapcsolva a böngészőben. Ide kattintva lehet olvasni a hozzászólásokat, de a betöltésük így kicsit több időbe telik.",
@ -366,13 +366,13 @@
"invidious": "Invidious",
"videoinfo_started_streaming_x_ago": "`x` ezelőtt kezdte streamelni",
"search_filters_sort_option_views": "Mennyien látták",
"search_filters_features_option_purchased": "Megvásárolva",
"search_filters_features_option_three_sixty": "360°-os",
"search_filters_features_option_purchased": "Megvásárolt",
"search_filters_features_option_three_sixty": "360°-os virtuális valóság",
"footer_original_source_code": "Eredeti forráskód",
"none": "egyik sem",
"videoinfo_watch_on_youTube": "YouTube-on megnézni",
"videoinfo_youTube_embed_link": "beágyazva",
"videoinfo_invidious_embed_link": "Beágyazás linkje",
"videoinfo_invidious_embed_link": "Beágyazott hivatkozás",
"download_subtitles": "Felirat `x` (.vtt)",
"user_created_playlists": "`x` létrehozott lejátszási lista",
"user_saved_playlists": "`x` mentett lejátszási lista",
@ -459,5 +459,16 @@
"Dutch (auto-generated)": "holland (automatikusan generált)",
"French (auto-generated)": "francia (automatikusan generált)",
"Vietnamese (auto-generated)": "vietnámi (automatikusan generált)",
"search_filters_title": "Szűrők"
"search_filters_title": "Szűrők",
"preferences_watch_history_label": "Megnézett videók naplózása: ",
"search_message_no_results": "Nincs találat.",
"search_message_change_filters_or_query": "Próbálj meg bővebben rákeresni vagy a szűrőkön állítani.",
"search_message_use_another_instance": " Megpróbálhatod <a href=\"`x`\">egy másik</a> Invidious-oldalon is a keresést.",
"search_filters_date_label": "Feltöltés ideje",
"search_filters_date_option_none": "Mindegy mikor",
"search_filters_type_option_all": "Bármilyen",
"search_filters_duration_option_none": "Mindegy",
"search_filters_duration_option_medium": "Átlagos (4 és 20 perc között)",
"search_filters_features_option_vr180": "180°-os virtuális valóság",
"search_filters_apply_button": "Keresés a megadott szűrőkkel"
}

ファイルの表示

@ -21,15 +21,15 @@
"No": "Não",
"Import and Export Data": "Importar e Exportar Dados",
"Import": "Importar",
"Import Invidious data": "Importar dados do Invidious",
"Import YouTube subscriptions": "Importar inscrições do YouTube",
"Import Invidious data": "Importar dados em JSON do Invidious",
"Import YouTube subscriptions": "Importar inscrições do YouTube/OPML",
"Import FreeTube subscriptions (.db)": "Importar inscrições do FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importar inscrições do NewPipe (.json)",
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
"Export": "Exportar",
"Export subscriptions as OPML": "Exportar inscrições como OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar inscrições como OPML (para NewPipe e FreeTube)",
"Export data as JSON": "Exportar dados como JSON",
"Export data as JSON": "Exportar dados Invidious como JSON",
"Delete account?": "Excluir conta?",
"History": "Histórico",
"An alternative front-end to YouTube": "Uma interface alternativa para o YouTube",
@ -66,7 +66,7 @@
"preferences_related_videos_label": "Mostrar vídeos relacionados: ",
"preferences_annotations_label": "Sempre mostrar anotações: ",
"preferences_extend_desc_label": "Estenda automaticamente a descrição do vídeo: ",
"preferences_vr_mode_label": "Vídeos interativos de 360 graus: ",
"preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ",
"preferences_category_visual": "Preferências visuais",
"preferences_player_style_label": "Estilo do tocador: ",
"Dark mode: ": "Modo escuro: ",
@ -410,7 +410,7 @@
"crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas Frequentes (FAQ)</a>",
"generic_views_count": "{{count}} visualização",
"generic_views_count_plural": "{{count}} visualizações",
"preferences_quality_option_dash": "DASH (qualidade adaptiva)",
"preferences_quality_option_dash": "DASH (qualidade adaptável)",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_small": "Pequeno",
"preferences_quality_dash_option_auto": "Auto",
@ -436,5 +436,10 @@
"user_saved_playlists": "`x` listas de reprodução salvas",
"Video unavailable": "Vídeo indisponível",
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`",
"search_filters_title": "Filtro"
"search_filters_title": "Filtro",
"preferences_watch_history_label": "Ative o histórico de exibição: ",
"search_message_no_results": "Nenhum resultado encontrado.",
"search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.",
"English (United Kingdom)": "Inglês (Reino Unido)",
"English (United States)": "Inglês (Estados Unidos)"
}

ファイルの表示

@ -5,8 +5,8 @@
"Subscribe": "Подписаться",
"View channel on YouTube": "Смотреть канал на YouTube",
"View playlist on YouTube": "Посмотреть плейлист на YouTube",
"newest": "самые свежие",
"oldest": "самые старые",
"newest": "сначала новые",
"oldest": "сначала старые",
"popular": "популярные",
"last": "недавние",
"Next page": "Следующая страница",
@ -74,8 +74,8 @@
"dark": "темная",
"light": "светлая",
"preferences_thin_mode_label": "Облегчённое оформление: ",
"preferences_category_misc": "Прочие предпочтения",
"preferences_automatic_instance_redirect_label": "Автоматическое перенаправление на зеркало сайта (резервный вариант redirect.invidious.io): ",
"preferences_category_misc": "Прочие настройки",
"preferences_automatic_instance_redirect_label": "Автоматическое перенаправление на зеркало сайта (переход на redirect.invidious.io): ",
"preferences_category_subscription": "Настройки подписок",
"preferences_annotations_subscribed_label": "Всегда показывать аннотации в видео каналов, на которые вы подписаны? ",
"Redirect homepage to feed: ": "Отображать видео с каналов, на которые вы подписаны, как главную страницу: ",
@ -476,5 +476,15 @@
"preferences_save_player_pos_label": "Запоминать позицию: ",
"preferences_region_label": "Страна: ",
"preferences_watch_history_label": "Включить историю просмотров ",
"search_filters_title": "Фильтр"
"search_filters_title": "Фильтр",
"search_filters_duration_option_none": "Любой длины",
"search_filters_type_option_all": "Любого типа",
"search_filters_date_option_none": "Любой даты",
"search_filters_date_label": "Дата загрузки",
"search_message_no_results": "Ничего не найдено.",
"search_message_use_another_instance": " Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.",
"search_filters_features_option_vr180": "VR180",
"search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос и изменить фильтры.",
"search_filters_duration_option_medium": "Средние (4 - 20 минут)",
"search_filters_apply_button": "Применить фильтры"
}

ファイルの表示

@ -12,7 +12,8 @@ record AboutChannel,
joined : Time,
is_family_friendly : Bool,
allowed_regions : Array(String),
tabs : Array(String)
tabs : Array(String),
verified : Bool
record AboutRelatedChannel,
ucid : String,
@ -70,6 +71,9 @@ def get_about_info(ucid, locale) : AboutChannel
# if banner.includes? "channels/c4/default_banner"
# banner = nil
# end
# author_verified_badges = initdata["header"]?.try &.["c4TabbedHeaderRenderer"]?.try &.["badges"]?
author_verified_badge = initdata["header"].dig?("c4TabbedHeaderRenderer", "badges", 0, "metadataBadgeRenderer", "tooltip")
author_verified = (author_verified_badge && author_verified_badge == "Verified")
description = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?.try &.as_s? || ""
description_html = HTML.escape(description)
@ -128,6 +132,7 @@ def get_about_info(ucid, locale) : AboutChannel
is_family_friendly: is_family_friendly,
allowed_regions: allowed_regions,
tabs: tabs,
verified: author_verified || false,
)
end

ファイルの表示

@ -146,6 +146,8 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
content_html = node_comment["contentText"]?.try { |t| parse_content(t) } || ""
author = node_comment["authorText"]?.try &.["simpleText"]? || ""
json.field "verified", (node_comment["authorCommentBadge"]? != nil)
json.field "author", author
json.field "authorThumbnails" do
json.array do
@ -329,7 +331,11 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
end
author_name = HTML.escape(child["author"].as_s)
if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool
author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark-circle\"></i>"
elsif child["verified"]?.try &.as_bool
author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark\"></i>"
end
html << <<-END_HTML
<div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">

ファイルの表示

@ -46,7 +46,7 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
TEXT
issue_template += github_details("Backtrace", HTML.escape(exception.inspect_with_backtrace))
issue_template += github_details("Backtrace", exception.inspect_with_backtrace)
# URLs for the error message below
url_faq = "https://github.com/iv-org/documentation/blob/master/docs/faq.md"

ファイルの表示

@ -14,6 +14,7 @@ LOCALES_LIST = {
"fi" => "Suomi", # Finnish
"fr" => "Français", # French
"he" => "עברית", # Hebrew
"hi" => "हिन्दी", # Hindi
"hr" => "Hrvatski", # Croatian
"hu-HU" => "Magyar Nyelv", # Hungarian
"id" => "Bahasa Indonesia", # Indonesian

ファイルの表示

@ -12,6 +12,7 @@ struct SearchVideo
property live_now : Bool
property premium : Bool
property premiere_timestamp : Time?
property author_verified : Bool
def to_xml(auto_generated, query_params, xml : XML::Builder)
query_params["v"] = self.id
@ -129,6 +130,7 @@ struct SearchPlaylist
property video_count : Int32
property videos : Array(SearchPlaylistVideo)
property thumbnail : String?
property author_verified : Bool
def to_json(locale : String?, json : JSON::Builder)
json.object do
@ -141,6 +143,8 @@ struct SearchPlaylist
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "authorVerified", self.author_verified
json.field "videoCount", self.video_count
json.field "videos" do
json.array do
@ -182,6 +186,7 @@ struct SearchChannel
property video_count : Int32
property description_html : String
property auto_generated : Bool
property author_verified : Bool
def to_json(locale : String?, json : JSON::Builder)
json.object do
@ -189,7 +194,7 @@ struct SearchChannel
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "authorVerified", self.author_verified
json.field "authorThumbnails" do
json.array do
qualities = {32, 48, 76, 100, 176, 512}

ファイルの表示

@ -62,6 +62,9 @@ module Invidious::Routes::API::Manifest
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true) do
mime_streams.each do |fmt|
# OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415)
next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange"))
codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"')
bandwidth = fmt["bitrate"].as_i
itag = fmt["itag"].as_i
@ -90,6 +93,9 @@ module Invidious::Routes::API::Manifest
heights = [] of Int32
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, scanType: "progressive") do
mime_streams.each do |fmt|
# OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415)
next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange"))
codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"')
bandwidth = fmt["bitrate"].as_i
itag = fmt["itag"].as_i

ファイルの表示

@ -182,6 +182,7 @@ module Invidious::Routes::Feeds
paid: false,
premium: false,
premiere_timestamp: nil,
author_verified: false, # ¯\_(ツ)_/¯
})
end

ファイルの表示

@ -374,18 +374,25 @@ struct Video
json.array do
self.adaptive_fmts.each do |fmt|
json.object do
json.field "index", "#{fmt["indexRange"]["start"]}-#{fmt["indexRange"]["end"]}"
json.field "bitrate", fmt["bitrate"].as_i.to_s
json.field "init", "#{fmt["initRange"]["start"]}-#{fmt["initRange"]["end"]}"
# Only available on regular videos, not livestreams/OTF streams
if init_range = fmt["initRange"]?
json.field "init", "#{init_range["start"]}-#{init_range["end"]}"
end
if index_range = fmt["indexRange"]?
json.field "index", "#{index_range["start"]}-#{index_range["end"]}"
end
# Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only)
json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]?
json.field "url", fmt["url"]
json.field "itag", fmt["itag"].as_i.to_s
json.field "type", fmt["mimeType"]
json.field "clen", fmt["contentLength"]
json.field "clen", fmt["contentLength"]? || "-1"
json.field "lmt", fmt["lastModified"]
json.field "projectionType", fmt["projectionType"]
fmt_info = itag_to_metadata?(fmt["itag"])
if fmt_info
if fmt_info = itag_to_metadata?(fmt["itag"])
fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30
json.field "fps", fps
json.field "container", fmt_info["ext"]
@ -405,6 +412,19 @@ struct Video
end
end
end
# Livestream chunk infos
json.field "targetDurationSec", fmt["targetDurationSec"].as_i if fmt.has_key?("targetDurationSec")
json.field "maxDvrDurationSec", fmt["maxDvrDurationSec"].as_i if fmt.has_key?("maxDvrDurationSec")
# Audio-related data
json.field "audioQuality", fmt["audioQuality"] if fmt.has_key?("audioQuality")
json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate")
json.field "audioChannels", fmt["audioChannels"] if fmt.has_key?("audioChannels")
# Extra misc stuff
json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo")
json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack")
end
end
end
@ -593,6 +613,10 @@ struct Video
info["authorThumbnail"]?.try &.as_s || ""
end
def author_verified : Bool
info["authorVerified"]?.try &.as_bool || false
end
def sub_count_text : String
info["subCountText"]?.try &.as_s || "-"
end
@ -612,6 +636,7 @@ struct Video
fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}")
fmt["url"] = JSON::Any.new("#{fmt["url"]}&region=#{self.info["region"]}") if self.info["region"]?
end
fmt_stream.sort_by! { |f| f["width"]?.try &.as_i || 0 }
@fmt_stream = fmt_stream
return @fmt_stream.as(Array(Hash(String, JSON::Any)))
@ -631,9 +656,7 @@ struct Video
fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}")
fmt["url"] = JSON::Any.new("#{fmt["url"]}&region=#{self.info["region"]}") if self.info["region"]?
end
# See https://github.com/TeamNewPipe/NewPipe/issues/2415
# Some streams are segmented by URL `sq/` rather than index, for now we just filter them out
fmt_stream.reject! { |f| !f["indexRange"]? }
fmt_stream.sort_by! { |f| f["width"]?.try &.as_i || 0 }
@adaptive_fmts = fmt_stream
return @adaptive_fmts.as(Array(Hash(String, JSON::Any)))
@ -845,6 +868,12 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
.try &.dig?("runs", 0)
author = channel_info.try &.dig?("text")
author_verified_badge = related["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0).to_s
ucid = channel_info.try { |ci| HelperExtractors.get_browse_id(ci) }
# "4,088,033 views", only available on compact renderer
@ -868,6 +897,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
"length_seconds" => JSON::Any.new(length || "0"),
"view_count" => JSON::Any.new(view_count || "0"),
"short_view_count" => JSON::Any.new(short_view_count || "0"),
"author_verified" => JSON::Any.new(author_verified),
}
end
@ -1062,6 +1092,10 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_
author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer")
author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url")
author_verified_badge = author_info.try &.dig?("badges", 0, "metadataBadgeRenderer", "tooltip")
author_verified = (!author_verified_badge.nil? && author_verified_badge == "Verified")
params["authorVerified"] = JSON::Any.new(author_verified)
params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "")
params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]?

ファイルの表示

@ -20,7 +20,7 @@
<div class="pure-u-2-3">
<div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
<span><%= author %></span>
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div>
</div>
<div class="pure-u-1-3">

ファイルの表示

@ -19,7 +19,7 @@
<div class="pure-u-2-3">
<div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
<span><%= author %></span>
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div>
</div>
<div class="pure-u-1-3" style="text-align:right">

ファイルの表示

@ -8,7 +8,7 @@
<img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>"/>
</center>
<% end %>
<p dir="auto"><%= HTML.escape(item.author) %></p>
<p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
</a>
<p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p>
<% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
@ -30,7 +30,7 @@
<p dir="auto"><%= HTML.escape(item.title) %></p>
</a>
<a href="/channel/<%= item.ucid %>">
<p dir="auto"><b><%= HTML.escape(item.author) %></b></p>
<p dir="auto"><b><%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b></p>
</a>
<% when MixVideo %>
<a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>">
@ -142,7 +142,7 @@
<div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %></p>
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %><% if !item.is_a?(ChannelVideo) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
</a></div>
<% endpoint_params = "?v=#{item.id}" %>

ファイルの表示

@ -19,7 +19,7 @@
<div class="pure-u-2-3">
<div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
<span><%= author %></span>
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div>
</div>
<div class="pure-u-1-3" style="text-align:right">

ファイルの表示

@ -207,7 +207,7 @@ we're going to need to do it here in order to allow for translations.
<% if !video.author_thumbnail.empty? %>
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>">
<% end %>
<span id="channel-name"><%= author %></span>
<span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></span>
</div>
</a>
@ -281,9 +281,9 @@ we're going to need to do it here in order to allow for translations.
<h5 class="pure-g">
<div class="pure-u-14-24">
<% if rv["ucid"]? %>
<b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %></a></b>
<b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
<% else %>
<b style="width:100%"><%= rv["author"]? %></b>
<b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
<% end %>
</div>

ファイルの表示

@ -102,7 +102,11 @@ private module Parsers
premium = false
premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) }
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
item_contents["badges"]?.try &.as_a.each do |badge|
b = badge["metadataBadgeRenderer"]
case b["label"].as_s
@ -129,6 +133,7 @@ private module Parsers
live_now: live_now,
premium: premium,
premiere_timestamp: premiere_timestamp,
author_verified: author_verified || false,
})
end
@ -156,7 +161,11 @@ private module Parsers
private def self.parse(item_contents, author_fallback)
author = extract_text(item_contents["title"]) || author_fallback.name
author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
author_thumbnail = HelperExtractors.get_thumbnails(item_contents)
# When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube.
# Always simpleText
@ -179,6 +188,7 @@ private module Parsers
video_count: video_count,
description_html: description_html,
auto_generated: auto_generated,
author_verified: author_verified || false,
})
end
@ -206,18 +216,23 @@ private module Parsers
private def self.parse(item_contents, author_fallback)
title = extract_text(item_contents["title"]) || ""
plid = item_contents["playlistId"]?.try &.as_s || ""
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
video_count = HelperExtractors.get_video_count(item_contents)
playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents)
SearchPlaylist.new({
title: title,
id: plid,
author: author_fallback.name,
ucid: author_fallback.id,
video_count: video_count,
videos: [] of SearchPlaylistVideo,
thumbnail: playlist_thumbnail,
title: title,
id: plid,
author: author_fallback.name,
ucid: author_fallback.id,
video_count: video_count,
videos: [] of SearchPlaylistVideo,
thumbnail: playlist_thumbnail,
author_verified: author_verified || false,
})
end
@ -251,7 +266,11 @@ private module Parsers
author_info = item_contents.dig?("shortBylineText", "runs", 0)
author = author_info.try &.["text"].as_s || author_fallback.name
author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
videos = item_contents["videos"]?.try &.as_a.map do |v|
v = v["childVideoRenderer"]
v_title = v.dig?("title", "simpleText").try &.as_s || ""
@ -267,13 +286,14 @@ private module Parsers
# TODO: item_contents["publishedTimeText"]?
SearchPlaylist.new({
title: title,
id: plid,
author: author,
ucid: author_id,
video_count: video_count,
videos: videos,
thumbnail: playlist_thumbnail,
title: title,
id: plid,
author: author,
ucid: author_id,
video_count: video_count,
videos: videos,
thumbnail: playlist_thumbnail,
author_verified: author_verified || false,
})
end