Download YouTube videos without external service.
< Feedback on Local YouTube Downloader
請提供瀏覽器與腳本管理器的版本與名稱
显示/隐藏下载链接 无法点开
@"xie xltz" 说道: 显示/隐藏下载链接 无法点开 請提供詳細資訊,我這邊測試是沒問題的 如果不是使用Chrome或FireFox,請換瀏覽器
@"3142 maple" 说道: 請提供瀏覽器與腳本管理器的版本與名稱
chrome 72.0.3626.81
能在影片頁面打開瀏覽器的開發人員工具,把 console 頁面中的完整內容截圖給我嗎?
@"3142 maple" 说道: 能在影片頁面打開瀏覽器的開發人員工具,把 console 頁面中的完整內容截圖給我嗎?
内容太长已私信~
@"3142 maple" 说道: 能在影片頁面打開瀏覽器的開發人員工具,把 console 頁面中的完整內容截圖給我嗎?
[Deprecation] HTML Imports is deprecated and will be removed in M73, around March 2019. Please use ES modules instead. See https://www.chromestatus.com/features/5144752345317376 for more details.
desktop_polymer.js:23 [Deprecation] document.registerElement is deprecated and will be removed in M73, around March 2019. Please use window.customElements.define instead. See https://www.chromestatus.com/features/4642138092470272 for more details.
(anonymous) @ desktop_polymer.js:23
desktop_polymer.js:1902 <link rel=preload> must have a valid `as` value
e @ desktop_polymer.js:1902
VM28:1 Active resource loading counts reached a per-frame limit while the tab was in background. Network requests will be delayed until a previous loading finishes, or the tab is brought to the foreground. See https://www.chromestatus.com/feature/5527160148197376 for more details
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: default language: zh
/video_masthead?video_id=L4TuuNgxkWI&textLine1=Redmi+Note+7&textLine2=Redmi+Note+7+ju%C5%BC+w+sprzeda%C5%BCy+za+899+z%C5%82%21&autocrop=0&autoplay_start_time=0&autoplay_duration=30000&channel_banner=1&video_wall=0&subscribe_button=1&subscriber_count=1&site_cta=1&cta_label=Kup+teraz%21&layout=material&line_item_id=4976602468&creative_id=138265088714:1 Active resource loading counts reached a per-frame limit while the tab was in background. Network requests will be delayed until a previous loading finishes, or the tab is brought to the foreground. See https://www.chromestatus.com/feature/5527160148197376 for more details
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:9054 Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:9063 You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
/embed/?controls=0&enablejsapi=1&iv_load_policy=3&modestbranding=1&mute=1&nologo=1&rel=0&showinfo=0&wmode=transparent&widget_referrer=https%3A%2F%2Fpubads.g.doubleclick.net%2Fgampad%2Fads%3Fsz%3D850x250%26gdfp_req%3D1%26ad_rule%3D0%26iu%3D%2F4061%2Fcom.ythome%26impl%3Difr%26loeid%3D23761460%26scp%3Dssl%253D1%2526dc_yt%253D1%2526kauth%253D1%2526klg%253Den-gb%2526kmyd%253Dvideo-masthead%2526ytdevice%253D1%2526ytexp%253D23801216%2C23797561%2C23757411%2C9471239%26d_&origin=https%3A%2F%2Fwww.youtube.com&widgetid=1:1 Active resource loading counts reached a per-frame limit while the tab was in background. Network requests will be delayed until a previous loading finishes, or the tab is brought to the foreground. See https://www.chromestatus.com/feature/5527160148197376 for more details
(unknown) [LOOPER FOR YOUTUBE] Debug Mode: false
(unknown) [LOOPER FOR YOUTUBE] Browser is in Incognito window: false
network.js:15 [Deprecation] chrome.loadTimes() is deprecated, instead use standardized API: nextHopProtocol in Navigation Timing 2. https://www.chromestatus.com/features/5637885046816768.
g.onreadystatechange @ network.js:15
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: start loading new video: ovB0R2RjQtQ
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: No ytplayer is founded
(unknown) [LOOPER FOR YOUTUBE] Debug Mode: false
(unknown) [LOOPER FOR YOUTUBE] Browser is in Incognito window: false
(unknown) [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
_ax.open._ax.o
/get_video_info?html5=1&video_id=PMZUnvnCuzc&cpn=L8UmgKodnqWVw7gS&eurl&ps=desktop-polymer&el=adunit&hl=en_GB&aqi=FJaYXOjFHdeklgT0n6OYDw&sts=17976&lact=2073&c=WEB&cver=2.20190321&cplayer=UNIPLAYER&cbr=Chrome&cbrver=72.0.3626.81&cos=Windows&cosver=6.1&adformat=15_2_1&encoded_ad_playback_context=CA8QAhgBKgtvdkIwUjJSalF0UUIWRkphWVhPakZIZGVrbGdUMG42T1lEd2ABdW9Oez8%253D&iv_load_policy=1&autoplay=1&width=727&height=409&content_v=ovB0R2RjQtQ:1 Failed to load resource: net::ERR_BLOCKED_BY_CLIENT
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: ytplayer fetched: Object
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: workerGetVideo start: ovB0R2RjQtQ /yts/jsbin/player_ias-vflGPko2h/en_GB/base.js
blob:https://www.youtube.com/4c9504fb-9155-404c-b2a2-1c44530102e7:6 YTDL: parsedecsig result: a => { var av={Bh:function(a){a.reverse()},
fK:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c},
Zs:function(a,b){a.splice(0,b)}};
a=a.split("");av.fK(a,23);av.Zs(a,1);av.Bh(a,20);av.Zs(a,2);av.Bh(a,10);av.Zs(a,1);av.Bh(a,10);av.fK(a,23);av.Zs(a,2);return a.join("")}
blob:https://www.youtube.com/4c9504fb-9155-404c-b2a2-1c44530102e7:6 YTDL: parsedecsig result: a => { var av={Bh:function(a){a.reverse()},
fK:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c},
Zs:function(a,b){a.splice(0,b)}};
a=a.split("");av.fK(a,23);av.Zs(a,1);av.Bh(a,20);av.Zs(a,2);av.Bh(a,10);av.Zs(a,1);av.Bh(a,10);av.fK(a,23);av.Zs(a,2);return a.join("")}
blob:https://www.youtube.com/4c9504fb-9155-404c-b2a2-1c44530102e7:6 YTDL: video ovB0R2RjQtQ data: Object
blob:https://www.youtube.com/4c9504fb-9155-404c-b2a2-1c44530102e7:6 YTDL: video ovB0R2RjQtQ data: %o
blob:https://www.youtube.com/4c9504fb-9155-404c-b2a2-1c44530102e7:6 YTDL: video ovB0R2RjQtQ result: Object
blob:https://www.youtube.com/4c9504fb-9155-404c-b2a2-1c44530102e7:6 YTDL: video ovB0R2RjQtQ result: %o
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: workerGetVideo end: Object
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:11975 YTDL: video loaded: ovB0R2RjQtQ
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9881c696-8471-4ccf-b11d-1381cacf552a:639 [Vue warn]: Error in render: "TypeError: Cannot read property 'togglelinks' of undefined"
把腳本移除重新安裝看看,我看錯誤訊息應該有你有不小心改到了什麼
chrome 和 脚本都是最新版的, 显示和隐藏下载链接还是打不开
我用 chrome 也都沒問題
@"3142 maple" 说道: 把腳本移除重新安裝看看,我看錯誤訊息應該有你有不小心改到了什麼 我试过重新安装,也试过移除后安装,发现错误信息还是在,blocked by client啥的。我现在试试重装chrome
@"3142 maple" 说道: 我用 chrome 也都沒問題
我截图上传不了,一直显示file failed to upload.
“chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:639 [Vue warn]: Error in render: "TypeError: Cannot read property 'togglelinks' of undefined"
(found in ) warn @ chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:639 chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:1902 TypeError: Cannot read property 'togglelinks' of undefined at Proxy.eval (eval at createFunction (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2), :3:156) at Vue._render (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:3550) at Vue.updateComponent (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:4066) at Watcher.get (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:4477) at Watcher.run (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:4552) at flushSchedulerQueue (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:4310) at Array.eval (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:1994) at flushCallbacks (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?id=9e36a4dc-6b43-4528-9690-1660d79303a2:1920)”
你能提供你安裝的這個腳本的完整內容嗎?好像和我的不太一樣
@"3142 maple" 说道: 你能提供你安裝的這個腳本的完整內容嗎?好像和我的不太一樣
// ==UserScript== // @name Local YouTube Downloader // @name:zh-TW 本地 YouTube 下載器 // @name:zh-CN 本地 YouTube 下载器 // @namespace https://blog.maple3142.net/ // @version 0.7.0 // @description Get youtube raw link without external service. // @description:zh-TW 不需要透過第三方的服務就能下載 YouTube 影片。 // @description:zh-CN 不需要透过第三方的服务就能下载 YouTube 影片。 // @author maple3142 // @match https://.youtube.com/ // @require https://unpkg.com/vue@2.6.10/dist/vue.js // @require https://unpkg.com/xfetch-js@0.3.4/xfetch.min.js // @compatible firefox >=52 // @compatible chrome >=55 // @license MIT // ==/UserScript==
;(function() { 'use strict' const DEBUG = true 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: ',
inbrowser_adaptive_merger: 'In browser adaptive video & audio merger'
},
'zh-tw': {
togglelinks: '顯示 / 隱藏連結',
stream: '串流 Stream',
adaptive: '自適應 Adaptive',
videoid: '影片 ID: ',
inbrowser_adaptive_merger: '瀏覽器版自適應影片及聲音合成器'
},
zh: {
togglelinks: '显示 / 隐藏 下载链接',
stream: '串流 Stream',
adaptive: '自适应 Adaptive',
videoid: '视频 ID: ',
inbrowser_adaptive_merger: '浏览器版自适应视频及声音合成器'
}
}
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 $ = (s, x = document) => x.querySelector(s)
const $el = (tag, opts) => {
const el = document.createElement(tag)
Object.assign(el, opts)
return el
}
const xhrget = url =>
// not sure why `fetch` doesn't work here
new Promise((res, rej) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', 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 xf.get(location.href).text()
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 => {
try {
if (data.startsWith('var script')) {
// they inject the script via script tag
const obj = {}
const document = { createElement: () => obj, head: { appendChild: () => {} } }
eval(data)
data = obj.innerHTML
}
@"3142 maple" 说道: 你能提供你安裝的這個腳本的完整內容嗎?好像和我的不太一樣
const fnnameresult = /yt.akamaized.net.*encodeURIComponent((\w+)/.exec(data)
const fnname = fnnameresult[1]
const argnamefnbodyresult = new RegExp(fnname + '=function\((.+?)\){(.+?)}').exec(data)
const [, argname, fnbody] = argnamefnbodyresult
const helpernameresult = /;(.+?)..+?(/.exec(fnbody)
const helpername = helpernameresult[1]
const helperresult = new RegExp('var ' + helpername + '={[\s\S]+?};').exec(data)
const helper = helperresult[0]
$p.log(parsedecsig result: ${argname} => { ${helper}\n${fnbody}}
)
return new Function([argname], helper + '\n' + fnbody)
} catch (e) {
$p.error('parsedecsig error: %o', e)
$p.info('script content: %s', data)
$p.info(
'If you encounter this error, please copy the full "script content" to https://pastebin.com/ for me.'
)
}
}
const getdecsig = path => xhrget('https://www.youtube.com' + path).then(parsedecsig)
const parseQuery = s => [...new URLSearchParams(s).entries()].reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
const getVideo = async (id, decsig) => {
return xf
.get(`https://www.youtube.com/getvideoinfo?videoid=${id}&el=detailpage)
.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, meta: obj }
})
}
const workerMessageHandler = async e => {
const decsig = await getdecsig(e.data.path)
const result = await getVideo(e.data.id, decsig)
self.postMessage(result)
}
const ytdlWorkerCode = `
importScripts('https://unpkg.com/xfetch-js@0.3.4/xfetch.min.js')
const DEBUG=${DEBUG}
const $p=(${create$p.toString()})(console)
const parseQuery=${parseQuery.toString()}
const xhrget=${xhrget.toString()}
const parsedecsig=${parsedecsig.toString()}
const getdecsig=${getdecsig.toString()}
const getVideo=${getVideo.toString()}
self.onmessage=${workerMessageHandler.toString()}
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 })
})
}
@"3142 maple" 说道: 你能提供你安裝的這個腳本的完整內容嗎?好像和我的不太一樣
const template = `
.slice(1)
const app = new Vue({
data() {
return {
hide: true,
id: '',
stream: [],
adaptive: [],
dark: false,
lang: findLang(navigator.language)
}
},
computed: {
strings() {
return LOCALE[this.lang.toLowerCase()]
}
},
template
})
$p.log(
default language: ${app.lang}`)
// attach element
const shadowHost = $el('div')
const shadow = shadowHost.attachShadow ? shadowHost.attachShadow({ mode: 'closed' }) : shadowHost // no shadow dom
const container = $el('div')
shadow.appendChild(container)
app.$mount(container)
if (DEBUG) unsafeWindow.$app = app
const load = async id => {
const ytplayer = await getytplayer()
return workerGetVideo(id, ytplayer.config.assets.js)
.then(async data => {
$p.log('video loaded: %s', id)
app.id = id
app.stream = data.stream
app.adaptive = data.adaptive
app.meta = data.meta
if (ytplayer.config.args.host_language) app.lang = 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(shadowHost)) el.appendChild(shadowHost)
if (location.href !== prevurl && location.pathname === '/watch') {
prevurl = location.href
app.hide = true
const id = parseQuery(location.search).v
$p.log(`start loading new video: ${id}`)
load(id)
}
}, 1000)
@"3142 maple" 说道: 你能提供你安裝的這個腳本的完整內容嗎?好像和我的不太一樣
// listen to dark mode toggle
const $html = $('html')
new MutationObserver(() => {
app.dark = $html.getAttribute('dark') === 'true'
}).observe($html, { attributes: true })
app.dark = $html.getAttribute('dark') === 'true'
const css = `
.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; } .box{ border-bottom: 1px solid var(--yt-border-color); font-family: Arial; } .box-toggle{ margin: 3px; user-select: none; -moz-user-select: -moz-none; } .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; } .box.dark{ color: var(--ytd-video-primary-info-renderer-title-color, var(--yt-primary-text-color)); } .box.dark .ytdl-link-btn{ color: var(--ytd-video-primary-info-renderer-title-color, var(--yt-primary-text-color)); } .box.dark .ytdl-link-btn:hover{ color: rgba(200, 200, 255, 0.8); } .box.dark .box-toggle:hover{ color: rgba(200, 200, 255, 0.8); } ` shadow.appendChild($el('style', { textContent: css })) })()
全部内容如上。麻烦您了,我还是无法上传截图╮(╯▽╰)╭
看來是 youtube ui 語言導致的問題,使用英文和繁中能正常 但使用簡體正好會遇到 bug,已經在 0.7.1 修復了
@"3142 maple" 说道: 看來是 youtube ui 語言導致的問題,使用英文和繁中能正常 但使用簡體正好會遇到 bug,已經在 0.7.1 修復了
非常感谢,我youtube语言是英文,但chrome是中文简体,现在把chrome改为英文后可以正常使用脚本了。这两天麻烦您了!
显示/隐藏下载链接 无法点开
运行正常,显示启用状态,但发现页面里“显示/隐藏下载链接 ”点不开,不知道什么原因。