maple3142 / browser-extensions Goto Github PK
View Code? Open in Web Editor NEW我的一些 userjs & usercss
我的一些 userjs & usercss
So far this wasn't an issue for me since I've always been using the manual link downloads, but the manual link downloads are now throttled since a few days while the one click download isn't, which is why I'm proposing this enhancement.
It would be nice to be able to get the higher quality codecs (when available) when the source video is 1080p (or lower) when using the one click download. The one click download only downloads AVC (x264) video and MP3 audio even when VP9 or AV1 video and OPUS audio is available. I tried editing the script by changing
x.mimeType.includes('video/mp4')
to
x.mimeType.includes('video/webm')
for both video and audio, and while the video is correctly downloaded in VP9, the audio is being downloaded in the lowest OPUS quality. This is (probably) because the script takes the first OPUS codec from the itag
list , which is sorted in ascending order, and the highest quality OPUS audio is the highest ID from the list (in this case, itag=251
).
My coding knowledge is rather limited so I'm not able to go much further by myself, but maybe the script can be made to take the highest itag
from the audio and video list respectively (since this is also the case for video side, where the itag
s are in ascending order of AVC<VP9<AV1 within the same resolution and framerate), or to sort by descending order instead.
Note that the video is downloaded in VP9 when the source is >1080p (since such sources don't have AVC encoding)
hi: {
togglelinks: 'लिंक टॉगल करें',
stream: 'स्ट्रीमिंग (Stream)',
adaptive: 'अनुकूली (Adaptive)',
videoid: 'वीडियो आईडी: {{id}}'
}
When choosing any mp4 option in the "adaptive" tab the video always lacks any sort of audio. The 360p option under "stream" works, but it's only 360p. How can I download 1080p with audio?
[deleted]
Would it be possible to use the following variables in the page source to add ID3 metadata?
"artist":"____",
"album_title":"____",
"upc_or_ean"::"____",
"isrc"::"____",
Hey there,
I think YT changed some code cause the script is no longer working as of today.
es: {
togglelinks: 'Mostrar/Ocultar Links',
stream: 'Stream',
adaptive: 'Adaptable',
videoid: 'Id del Video: ',
thumbnail: 'Miniatura',
inbrowser_adaptive_merger: 'Acoplar Audio a Video '
},
kr: {
togglelinks: '링크 보이기/숨기기',
stream: '스트리밍',
adaptive: '조정 가능한',
videoid: 'Video Id: {{id}}'
},
Hindi Translation is below.
hi: {
togglelinks: 'लिंक दिखाएँ/छिपाएँ'
stream: 'स्ट्रीम'',
adaptive: 'अडैप्टिव (कोई आवाज नहीं)',
videoid: 'वीडियो आईडी: ',
inbrowser_adaptive_merger: 'ऑनलाइन एडैप्टिव वीडियो और ऑडियो मर्जर (FFmpeg)',
dlmp4: 'हाई-रिज़ॉल्यूशन mp4 एक क्लिक में डाउनलोड करें',
get_video_failed: 'अज्ञात कारण से वीडियो जानकारी प्राप्त करने में विफल, पेज रीफ्रेश करें काम कर सकता है।',
live_stream_disabled_message: 'स्थानीय YouTube डाउनलोडर लाइव स्ट्रीम के लिए उपलब्ध नहीं है''
}
好久不見~
Error occurred at:
Some debug info:
Variable | Value |
---|---|
fnname | Xua |
argname | a |
fnbody | a=a.split("");MC["if"](a,71);MC.pn(a,3);MC.QO(a,27);MC.pn(a,2);MC["if"](a,36);MC.QO(a,14);MC.pn(a,3);MC.QO(a,68);MC.pn(a,3);return a.join("") |
helpername | MC["if"](a,71);MC |
How come the minifier use if
as a property name XD
So I managed to solve that by change helpernameresult
to /;([a-zA-Z0-9$_]+?)\..+?\(/.exec(fnbody)
.
And for safety let's also add _
to fnnameresult
, i.e. /=([a-zA-Z0-9$_]+?)\(decodeURIComponent/.exec(data)
.
Hello, thank you for your scripts!
Local SoundCloud Downloader is not working on Firefox 72, Tampermonkey v4.10.
The script loads correctly, but following error shows up in developer console if I click on the download button:
TypeError: s.WritableStream is not a constructor
Than nothing happens
Hi
I don't know how to use github properly but i want to help in the translation
There is the Brazilian Portuguese version from English Version
pt-br: {
togglelinks: 'Mostrar/Esconder Links',
stream: 'Stream',
adaptive: 'Adaptativo (Sem Som)',
videoid: 'ID do Vídeo: ',
inbrowser_adaptive_merger: 'Video Adaptativo Online & Mesclagem de Áudio (FFmpeg)',
dlmp4: 'Baixe MP4 em Alta Resolução Com um Clique!',
get_video_failed: 'Falha ao Pegar Dados do Vídeo por Erro Desconhecido, Tente Recarregar a Página.',
live_stream_disabled_message: 'Local YouTube Downloader Não Funciona Para Streams ao Vivo.'
pull request again?
Here it is:
// ==UserScript==
// @name Local YouTube Downloader
// @name:zh-TW 本地 YouTube 下載器
// @name:zh-CN 本地 YouTube 下载器
// @namespace https://blog.maple3142.net/
// @Version 0.5.8
// @description Get youtube raw link without external service.
// @description:zh-TW 不需要透過第三方的服務就能下載 YouTube 影片。
// @description:zh-CN 不需要透过第三方的服务就能下载 YouTube 影片。
// @author maple3142
// @match https://.youtube.com/
// @connect youtube.com
// @require https://cdnjs.cloudflare.com/ajax/libs/hyperapp/1.2.6/hyperapp.js
// @run-at document-start
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
;(function() {
'use strict'
const DEBUG = false
const create$p = console =>
Object.keys(console)
.map(k => [k, (...args) => (DEBUG ? console[k]('YTDL: ' + args[0], ...args.slice(1)) : void 0)])
.reduce((acc, [k, fn]) => ((acc[k] = fn), acc), {})
const $p = create$p(console)
const LANG_FALLBACK = 'en'
const LOCALE = {
en: {
togglelinks: 'Show/Hide Links',
stream: 'Stream',
adaptive: 'Adaptive',
videoid: 'Video Id: {{id}}'
},
pl:
togglelinks: 'Pokaż/ukryj linki',
stream: 'Strumień',
adaptive: 'Adaptacyjny',
videoid: 'Id filmu: {{id}}'
},
ru:
togglelinks: 'Показать/скрыть ссылки',
stream: 'Ручей',
adaptive: 'Адаптивный',
videoid: 'Id видео: {{id}}',
},
hu:
togglelinks: 'Linkek megjelenítése/elrejtése',
stream: 'Folyam',
adaptive: 'Alkalmazkodó',
videoid: 'Videóazonosító: {{id}}',
},
'zh-tw': {
togglelinks: '顯示/隱藏連結',
stream: '串流 Stream',
adaptive: '自適應 Adaptive',
videoid: '影片 Id: {{id}}'
},
zh: {
togglelinks: '显示/隐藏连结',
stream: '串流 Stream',
adaptive: '自适应 Adaptive',
videoid: '影片 Id: {{id}}'
}
}
const findLang = l => {
// language resolution logic: zh-tw --(if not exists)--> zh --(if not exists)--> LANG_FALLBACK(en)
l = l.toLowerCase()
if (l in LOCALE) return l
else if (l.length > 2) return findLang(l.split('-')[0])
else return LANG_FALLBACK
}
const format = s => d => s.replace(/{{(\w+?)}}/g, (m, g1) => d[g1])
const $ = (s, x = document) => x.querySelector(s)
const $el = (tag, opts) => {
const el = document.createElement(tag)
Object.assign(el, opts)
return el
}
const gmxhr = o => new Promise((res, rej) => GM_xmlhttpRequest({ ...o, onload: res, onerror: rej }))
const xhrhead = url =>
new Promise((res, rej) => {
const xhr = new XMLHttpRequest()
xhr.open('HEAD', url)
xhr.onreadystatechange = () => {
if (xhr.readyState === xhr.DONE) {
res(xhr.responseText)
}
}
xhr.onerror = rej
xhr.send()
})
const getytplayer = async () => {
if (typeof ytplayer !== 'undefined' && ytplayer.config) return ytplayer
$p.log('No ytplayer is founded')
const html = await gmxhr({
method: 'GET',
url: 'https://www.youtube.com' + location.pathname + location.search
}).then(r => r.responseText)
const d = /<script >(var ytplayer[\s\S]*?)ytplayer\.load/.exec(html)
let config = eval(d[1])
unsafeWindow.ytplayer = {
config
}
$p.log('ytplayer fetched: %o', unsafeWindow.ytplayer)
return ytplayer
}
const parsedecsig = data => {
const fnname = /\"signature\"\),.+?\.set\(.+?,(.+?)\(/.exec(data)[1]
const [_, argname, fnbody] = new RegExp(fnname + '=function\\((.+?)\\){(.+?)}').exec(data)
const helpername = /;(.+?)\..+?\(/.exec(fnbody)[1]
const helper = new RegExp('var ' + helpername + '={[\\s\\S]+?};').exec(data)[0]
return new Function([argname], helper + ';' + fnbody)
}
const getdecsig = path => xhrhead('https://www.youtube.com' + path).then(parsedecsig)
const parseQuery = s =>
Object.assign(
...s
.split('&')
.map(x => x.split('='))
.map(p => ({ [p[0]]: decodeURIComponent(p[1]) }))
)
const getVideo = async (id, decsig) => {
return fetch(`https://www.youtube.com/get_video_info?video_id=${id}&el=detailpage`)
.then(r => r.text())
.then(async data => {
const obj = parseQuery(data)
$p.log(`video ${id} data: %o`, obj)
if (obj.status === 'fail') {
throw obj
}
let stream = []
if (obj.url_encoded_fmt_stream_map) {
stream = obj.url_encoded_fmt_stream_map.split(',').map(parseQuery)
if (stream[0].sp && stream[0].sp.includes('signature')) {
stream = stream
.map(x => ({ ...x, s: decsig(x.s) }))
.map(x => ({ ...x, url: x.url + `&signature=${x.s}` }))
}
}
let adaptive = []
if (obj.adaptive_fmts) {
adaptive = obj.adaptive_fmts.split(',').map(parseQuery)
if (adaptive[0].sp && adaptive[0].sp.includes('signature')) {
adaptive = adaptive
.map(x => ({ ...x, s: decsig(x.s) }))
.map(x => ({ ...x, url: x.url + `&signature=${x.s}` }))
}
}
$p.log(`video ${id} result: %o`, { stream, adaptive })
return { stream, adaptive }
})
}
const ytdlWorkerCode = `
const DEBUG=${DEBUG}
const
const parseQuery=${parseQuery.toString()}
const xhrhead=${xhrhead.toString()}
const parsedecsig=${parsedecsig.toString()}
const getdecsig=${getdecsig.toString()}
const getVideo=${getVideo.toString()}
self.onmessage=async e=>{
const decsig=await getdecsig(e.data.path)
const result=await getVideo(e.data.id,decsig)
postMessage(result)
} const ytdlWorker = new Worker(URL.createObjectURL(new Blob([ytdlWorkerCode]))) const workerGetVideo = (id, path) => { $p.log(
workerGetVideo start: ${id} ${path}`)
return new Promise((res, rej) => {
const callback = e => {
ytdlWorker.removeEventListener('message', callback)
$p.log('workerGetVideo end: %o', e.data)
res(e.data)
}
ytdlWorker.addEventListener('message', callback)
ytdlWorker.postMessage({ id, path })
})
}
const { app, h } = hyperapp
const state = {
hide: true,
id: '',
stream: [],
adaptive: [],
lang: findLang(navigator.language),
strings: LOCALE[findLang(navigator.language)]
}
$p.log(`default language: ${state.lang}`)
const actions = {
toggleHide: () => state => ({ hide: !state.hide }),
setState: newstate => state => newstate,
setLang: lang => state => {
const target = findLang(lang)
$p.log(`language change to: ${target}`)
return {
lang: target,
strings: LOCALE[target]
}
},
getState: () => state => state
}
const view = (state, actions) =>
h('div', { id: 'ytdl-box' }, [
h(
'div',
{ onclick: () => actions.toggleHide(), id: 'ytdl-box-toggle', className: 't-center' },
state.strings.togglelinks
),
h('div', { className: state.hide ? 'hide' : '' }, [
h('div', { className: 't-center fs-14px' }, format(state.strings.videoid, state)),
h('div', { className: 'd-flex' }, [
h(
'div',
{ className: 'f-1 of-h' },
[h('div', { className: 't-center fs-14px' }, state.strings.stream)].concat(
state.stream.map(x =>
h(
'a',
{ href: x.url, title: x.type, target: '_blank', className: 'ytdl-link-btn' },
x.quality || x.type
)
)
)
),
h(
'div',
{ className: 'f-1 of-h' },
[h('div', { className: 't-center fs-14px' }, state.strings.adaptive)].concat(
state.adaptive.map(x =>
h(
'a',
{ href: x.url, title: x.type, target: '_blank', className: 'ytdl-link-btn' },
(x.quality_label ? x.quality_label + ':' : '') + x.type
)
)
)
)
])
])
])
const container = $el('div')
const $app = app(state, actions, view, container)
if (DEBUG) unsafeWindow.$app = $app
const load = async id => {
const ytplayer = await getytplayer()
return workerGetVideo(id, ytplayer.config.assets.js)
.then(data => {
$p.log('video loaded: %s', id)
$app.setState({
id,
stream: data.stream,
adaptive: data.adaptive
})
if (ytplayer.config.args.host_language) $app.setLang(ytplayer.config.args.host_language)
})
.catch(err => $p.error('load', err))
}
let prevurl = null
setInterval(() => {
const el = $('#info-contents') || $('#watch-header') || $('ytm-item-section-renderer>lazy-list')
if (el && !el.contains(container)) el.appendChild(container)
if (location.href !== prevurl && location.pathname === '/watch') {
prevurl = location.href
$app.setState({
hide: true
})
const id = new URLSearchParams(location.search).get('v')
$p.log(`start loading new video: ${id}`)
load(id)
}
}, 1000)
GM_addStyle(`
.hide{
display: none;
}
.t-center{
text-align: center;
}
.d-flex{
display: flex;
}
.f-1{
flex: 1;
}
.fs-14px{
font-size: 14px;
}
.of-h{
overflow: hidden;
}
#ytdl-box{
border-bottom: 1px solid var(--yt-border-color);
}
#ytdl-box-toggle{
margin: 3px;
user-select: none;
-moz-user-select: -moz-none;
}
#ytdl-box-toggle:hover{
color: blue;
}
.ytdl-link-btn{
display: block;
border: 1px solid !important;
border-radius: 3px;
text-decoration: none !important;
outline: 0;
text-align: center;
padding: 2px;
margin: 5px;
color: black;
}
a.ytdl-link-btn{
text-decoration: none;
}
a.ytdl-link-btn:hover{
color: blue;
}
`)
})()
Here is a Czech translation for Local YouTube Downloader:
// @description:cs Stahujte YouTube videa bez externích služeb.
cs: {
togglelinks: 'Zobrazit/Skrýt odkazy',
stream: 'Stream',
adaptive: 'Adaptivní',
videoid: 'ID videa: ',
inbrowser_adaptive_merger: 'Online nástroj pro sloučení videa a audia (FFmpeg)',
dlmp4: 'Stáhnout video mp4 jedním kliknutím ve vysokém rozlišení',
get_video_failed: 'Nepodařilo se nahrát informace o videu. Zkuste obnovit stránku (F5).',
live_stream_disabled_message: 'Local YouTube Downloader není dostupný pro živé vysílání'
}
// @description:fr Obtenez un lien brut YouTube sans service externe.
fr: {
togglelinks: 'Afficher/Masquer les liens',
stream: 'Stream',
adaptive: 'Adaptative',
videoid: 'ID vidéo: ',
inbrowser_adaptive_merger:
'Fusionner vidéos et audios adaptatifs dans le navigateur (FFmpeg)',
dlmp4: 'Téléchargez la plus haute résolution mp4 en un clic',
get_video_failed:
'Il semble qu'une extension de blocage de pubs soit installée, ce qui bloque %s.\nVeuillez ajouter la règle suivante au jeu de règles, ou cela empêchera Local YouTube Downloader de fonctionner.\n\nPS: Si votre bloqueur refuse d'ajouter cette règle, vous devez le désinstaller et utiliser plutôt "uBlock Origin".\nSi vous ne comprenez toujours pas ce que je dis, désinstallez ou désactivez simplement votre bloqueur de pubs ...'
}
If you want to parse JS literal stuff, you can transform it into JSON using a regex and then parse that JSON.
Not sure where to get the logs, the console is empty both in the original Youtube tab and in the popup window.
I have some suggestions "on the Local YouTube video downloader", like adding a green Download button next to the subscribe channel button, language change and making a better Download UI. The UI could look like this, the download button instead of save to album or next to the subscribe button with an option to choose between audio and the video format with the quality.
My professional Paint skills
pl: {
togglelinks: 'Pokaż/Ukryj Linki',
stream: 'Stream',
adaptive: 'Adaptywne',
videoid: 'ID filmu: ',
inbrowser_adaptive_merger:
'Połącz audio i wideo adaptywne w przeglądarce (FFmpeg)',
dlmp4: 'Pobierz .mp4 w najwyższej jakości'
}
Turkish translation is below:
tr: {
togglelinks: 'Linkleri Göster/Gizle',
stream: 'Yayın',
adaptive: 'Adaptif (Sessiz)',
videoid: 'Video ID: ',
inbrowser_adaptive_merger: 'Online Adaptif Video & Ses Birleştirici (FFmpeg)',
dlmp4: 'En yüksek çözünürlükte MP4 indir',
get_video_failed: 'Bilinmeyen bir nedenden dolayı video bilgileri alınamadı, sayfayı yenilemek işe yarayabilir.',
live_stream_disabled_message: 'Yerel Youtube Yükleyici canlı yayınlarda çalışmıyor'
},
Other Translations:
// @name:tr Yerel Youtube Yükleyici
// @description:tr Harici bir programa gerek kalmadan Youtube videoları indir.
not sure if "iw" or "he"
xx : {
togglelinks: 'הצג/הסתר קישורים',
stream: 'סטרים',
adaptive: 'אדפטיבי',
videoid: 'מזהה סרטון: {{id}}'
},
It's a long shot I know, but there is already an extension out there doing a pretty great job at skipping ADs and other such stuff that video creators include in their videos. Being able to download videos from YouTube is great, but wouldn't you say it could be better if you could download some without the sponsors included?
I suggest looking into maybe using some SponsorBlock APIs (https://github.com/ajayyy/SponsorBlock) as it would prove most useful for what I imagine most of the user base is for the script you provide.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.