// ==UserScript==
// @name 获取哔哩哔哩视频音频
// @description 下载音频
// @license The Unlicense
// @version 1.0.0
// @author girl-dream
// @namespace https://github.com/girl-dream/
// @match https://www.bilibili.com/video/*
// @icon https://www.bilibili.com/favicon.ico
// @run-at document-end
// ==/UserScript==
(async () => {
const data = __playinfo__.data.dash
if (!data) {
throw new Error("未获取音频信息")
}
const title = document.querySelector('.video-title').textContent.trim().replaceAll(/[\\/:*?"<>|]/g, '_')
const isElementLoaded = async (selector, root = document) => {
const getElement = () => root.querySelector(selector)
return new Promise((resolve) => {
const element = getElement()
if (element) return resolve(element)
const observer = new MutationObserver(() => {
const element2 = getElement()
if (!element2) return
resolve(element2)
observer.disconnect()
});
observer.observe(root === document ? root.documentElement : root, {
childList: true,
subtree: true
})
})
}
const pop = document.createElement('div')
pop.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999999;`
const content = document.createElement('div')
content.style.cssText = `
background: var(--bg1_float);
padding: 20px;
border-radius: 8px;
width: 200px;
max-width: 90%;
max-height: 80vh;
overflow-y: auto;
color:var(--text1);`
content.innerHTML = `
选择音频类型
`
content.querySelector('#cancel-btn').onclick = () => {
pop.remove()
}
const select = content.querySelector('select')
content.querySelector('#confirm-btn').onclick = () => {
const url = select.options[select.selectedIndex].value
download(url)
pop.remove()
}
if (Array.isArray(data.audio)) {
data.audio.forEach(e => {
const op = document.createElement('option')
op.value = e.id
switch (e.id) {
case 30232:
op.textContent = 132 + 'K'
break
case 30280:
op.textContent = 192 + 'K'
break
case 30216:
op.textContent = 64 + 'K'
break
}
op.value = e.backupUrl.find(e => (new URL(e)).port === '')
select.append(op)
})
}
if (Array.isArray(data.dolby?.audio)) {
const op = document.createElement('option')
op.value = data.dolby.audio[0].backupUrl.find(e => (new URL(e)).port === '')
op.textContent = '杜比音效'
select.append(op)
}
if (data.flac != null) {
const op = document.createElement('option')
op.value = data.flac.audio.backupUrl.find(e => (new URL(e)).port === '')
op.textContent = 'Hi-Res'
select.append(op)
}
pop.append(content)
const toolbarLeft = document.querySelector('.video-toolbar-left-main')
const progressDiv = document.createElement('div')
progressDiv.style.color = 'var(--text2)'
const div = document.createElement('div')
div.className = 'video-toolbar-right-item'
div.title = '下载音频'
div.innerHTML = ''
div.style.cssText = `
width: 35px;
height: 35px;
margin-right: 18px;`
div.addEventListener('click', () => {
document.body.append(pop)
})
await Promise.all([isElementLoaded('.nav-search-input[maxlength]'), isElementLoaded('.view-icon[width]')])
toolbarLeft.append(div, progressDiv)
function download(audioUrl) {
const xhr = new XMLHttpRequest()
xhr.open('GET', audioUrl, true)
xhr.responseType = 'blob'
xhr.onload = function () {
if (xhr.status === 200) {
const blob = new Blob([xhr.response])
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = title + '.m4s'
a.click()
URL.revokeObjectURL(url)
progressDiv.textContent = null
} else {
console.error('下载失败,状态码:' + xhr.status)
}
}
xhr.onerror = function (error) {
console.error('请求失败:', error)
}
xhr.onprogress = function (progress) {
progressDiv.textContent = `下载进度: ${(progress.loaded / progress.total * 100).toFixed(2)}%`
}
xhr.send()
}
})();