Greasy Fork is available in English.
Tag tab's title to add more informations: duration, channel, date, age, ...
// ==UserScript==
// @name Youtube - tag title
// @namespace https://github.com/Procyon-b
// @version 0.5.6
// @description Tag tab's title to add more informations: duration, channel, date, age, ...
// @author Achernar
// @match https://www.youtube.com/*
// @noframes
// @run-at document-start
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
"use strict";
// à
var defConf={
format: '%d %s %T %s %c %s_t0(s)',
sep: '✦'
},
cfg=defConf,
format=cfg.format,
loaded;
function tryUntil(F, TO=150, c=-1, fail) {
if (!c--) {
fail && fail();
return;
}
try{F();}catch(e){setTimeout(function(){tryUntil(F,TO,c,fail);}, TO)}
}
function load() {
if (loaded) return;
cfg=GM_getValue('config', null);
if (!cfg || !cfg.format) {
GM_setValue('config', defConf);
cfg=defConf;
}
let sv=false;
for (let k in defConf) {
if ( !(k in cfg) ) {
cfg[k]=defConf[k];
sv=true;
}
}
if (sv) {
GM_setValue('config', cfg);
}
format=cfg.format;
loaded=true;
}
tryUntil(load, 0, 100);
const max=20, delay=1000, initialDelay=100;
var c=max, loop=false;
var vids={};
function getInitialInfo() {
var r, t, t0, c, d, T, ID= (/v=([^&]+)/.exec(location.search)) || (/^\/shorts\/([^\/]+)/.exec(location.pathname));
ID= ID && ID[1];
if (!ID) return;
r=vids[ID];
function fromContext() {
let RE, js=document.querySelector('script:not([src])[type="application/ld+json"]');
if (js) {
try{js=JSON.parse(js.textContent);
}catch(e){js={};}
if ( (RE = /^[^\?]+\?v=(.+)$/.exec(js['@id'])) && (RE[1] == ID) ) {
id=RE[1];
T=js.name;
c=js.author;
d=js.duration;
t0=new Date(js.uploadDate);
return T && c && d && t0;
}
}
}
if (!r) {
if (!yt) return -1;
if (!ytInitialPlayerResponse) return;
var v=ytInitialPlayerResponse.videoDetails;
if (!v) return;
var id=v.videoId;
if (id == ID) {
T=v.title,
c=v.author,
d=ytInitialPlayerResponse.streamingData?.formats?.[0]?.approxDurationMs;
t=ytInitialPlayerResponse.streamingData?.formats?.[0]?.lastModified;
t0=ytInitialPlayerResponse.microformat?.playerMicroformatRenderer?.publishDate;
}
if ( !(T && c && d && t0) ) fromContext();
if ( (ID == id) && d) r=vids[id]={T,c,d,t,t0};
}
if (r && !r.Tt) {
let ytid=ytInitialData?.contents?.twoColumnWatchNextResults?.results?.results?.contents;
let id=findK(ytid, 'videoId');
if (!ytid) ;
else if (id==ID) {
r.Tt=findK(ytid, ['title','text']);
r.Ct=findK(ytid, ['owner','text']);
vids[id]=r;
}
else try{
ytid=ytInitialData?.contents?.twoColumnWatchNextResults?.secondaryResults?.secondaryResults?.results[0].itemSectionRenderer.contents;
for (let i=0, LVM; LVM=ytid[i]?.lockupViewModel; i++) {
let id=LVM.contentId;
if (id == ID) {
r.Tt=findK(LVM, ['title','content']);
r.Ct=findK(LVM, ['metadataRows', '0', 'content'] ).trim();
vids[id]=r;
break;
}
}
}catch(e){}
}
return r;
}
function getNfo(a) {
if ( (location.pathname != '/watch') && !location.pathname.startsWith('/shorts/') ) {
return;
}
if (loop) {
return;
}
c=max;
loopGetNfo(a);
}
function findK(o, k, K) {
if ( k && (typeof k == 'object') && k.length) return findK(o, k.shift(), k);
var r;
if (!k) return;
if (o === null || typeof o !== 'object') return;
if (Object.prototype.hasOwnProperty.call(o, k)) {
r=o[k];
if (K && K.length) {
r=findK(r, K.shift(), K);
}
}
else {
for (let i in o) {
let V=findK(o[i], k, K);
if (V !== undefined) return V;
}
}
return r;
}
function loopGetNfo(a) {
var t, ts, t0, t0s, T, C, D, D2, Tt, Ct;
if (!loop) {
let R=getInitialInfo() || {};
if ( (a == 1) && (R==-1) ) {
setTimeout(function(){
loopGetNfo(a);
}, 100);
return;
}
t=R.t;
t0=R.t0;
C=R.c;
D=R.d;
T=R.T;
Tt=R.Tt;
Ct=R.Ct;
}
if (!T) {
loop=false;
try{
T=document.querySelector('#title h1').innerText;
D=document.querySelector('.ytp-chrome-controls .ytp-time-duration').innerText;
D2=document.querySelector('body > div[id^="watch"][id$="-content"] meta[itemprop="duration"]')?.content;
C=document.querySelector('#upload-info #channel-name').innerText;
}catch(e){
let dl= c==max ? initialDelay : delay;
if ( (c-- > 0) && (a !== 1) ) {
loop=true;
setTimeout(loopGetNfo, dl);
}
else c=max;
return;
}
}
if (!T.trim()) return;
c=max;
var d=dur(!D || (D=='0:00') ? D2 : D);
if (t) try {
let d=new Date(t / 1000);
let diff=Date.now() - t.slice(0,-3);
d=new Date( (t / 1000) - (60000 * d.getTimezoneOffset()) );
t=d.toISOString().split('.')[0].slice(0,-3);
if (diff) t=t+' ('+dur(diff,true)+')';
}catch(e){ t=0; }
if (t0) try {
let d=new Date(t0);
t0=d.getTime();
let diff=Date.now() - t0;
d=new Date( t0 - (60000 * d.getTimezoneOffset()) );
t0=d.toISOString().split('.')[0].slice(0,-3);
if (diff) t0=t0+' ('+dur(diff,true)+')';
}catch(e){ t0=0; }
function tt(m, s0, spc, z, s1, d) {
var s='', T;
if (s0) s+='%s'+(spc?' ':'');
if (z) {
if (!t0) return '';
T=t0;
}
else {
if (!t) return '';
T=t;
}
T=T.replace('T', s1?'%s':'.');
if (d) T=T.split(' ')[1];
return s+T;
}
var NT=trm( format
.replace(/%T/g, trm(Tt || T))
.replace(/%d/g, trm(d))
.replace(/%c/g, trm(Ct || C))
.replace(/%(s)?(_)?t(0)?(\(s\))?(\(d\))?/g, tt)
.replace(/%s/g, cfg.sep)
);
if (document.title != NT) {
document.title=NT;
}
}
function trm(s) {
if (typeof s == 'string') return s.trim().replace(/\s+/g, ' ');
}
function dur(s, full=false) {
function s2dur(s) {
let t=parseInt(s/1000);
S=t%60;
t=parseInt(t/60);
M=t%60;
if (full) {
t=parseInt(t/60);
H=t%24;
d=parseInt(t/24);
}
else H=parseInt(t/60);
}
var re,r, H,M,S,d;
if (re=/^(?:(\d*):)?(\d+):(\d+)$/.exec(s)) {
H=re[1] || 0;
M=re[2] || 0;
S=re[3];
}
else if (s > 0) {
s2dur(s);
}
else {
if ( re=/^PT(?:(\d+)M)?(\d+)S$/.exec(s) ) {
H=parseInt(re[1] || '0');
S=parseInt(re[2] || '0');
s2dur( (H * 60 + S) * 1000);
}
}
if (!full) {
if (S > 44) M++;
if (M == 60) {
H++;
M=0;
}
}
if (H == 0) H='';
if (!full && !H && !M && !S) M='??';
else if (!full && !H && (M == 0) ) M=1;
M=('00'+M).substr(-2);
if (full) {
let m, y;
r='';
y=parseInt(d/365);
if (y) d=d%365;
m=parseInt(d/30);
d=d%30;
if (y || m) H=M='';
else if (d && !H) H='0';
if (y) r+=y+'y';
if (m) r+=m+'m';
r+=(d ? d+'d' : '')+( (H || d) ? H : '')+(!d ? (H?':':'')+M : '');
}
else r=H+':'+M;
return r;
}
var inited=0;
function init() {
if (inited++) return true;
try{load();}catch(e){}
var obs=new MutationObserver(function(mutL){
getNfo();
});
tryUntil( function(){
obs.observe(document.querySelector('html > head > title'), {childList:true, subtree:true});
}, undefined, 50);
getNfo(1);
}
if (document.readyState != 'loading') init();
else {
document.addEventListener('DOMContentLoaded', init);
document.addEventListener('load', init);
window.addEventListener('load', init);
}
})();