由于个人需要,顺手写了汽某音乐歌词提取脚本
会把网页上的歌词数组转换成LRC/卡拉OK歌词
打开分享链接 - 拓展菜单运行脚本
// ==UserScript==
// @name 歌词提取器
// @namespace https://www.52pojie.cn/home.php?mod=follow&uid=1289557
// @version 114.514
// @description 顺手写的,能用就行
// @AuThor 52pj【xinchenmetx】
// @match 不知道是什么网站QwQ
// @Icon https://s11.ax1x.com/2024/01/24/pFetIiR.png
// @license GPL-3.0
// @run-at document-end
// @grant GM_registerMenuCommand
// ==UserScript==
(function() {
'use strict';
console.log('[歌词提取器] 已加载 - 快捷键: Ctrl+Shift+L');
// 歌词转换
const Converter = {
msToTime(ms) {
if (ms < 0 || ms > 999999999) ms = 0;
const t = ms / 1000;
const m = Math.floor(t / 60).toString().padStart(2, '0');
const s = Math.floor(t % 60).toString().padStart(2, '0');
const c = Math.floor((t % 1) * 100).toString().padStart(2, '0');
return `[${m}:${s}.${c}]`;
},
toLRC(s) {
return s.map(i => {
if (i.type === 'lrc') {
if (i.text.includes('作曲')) return `[ar:${i.text.replace('作曲:', '')}]`;
if (i.text.includes('作词')) return `[au:${i.text.replace('作词:', '')}]`;
if (i.text.includes('贡献者')) return `[by:${i.text.replace('滚动歌词&翻译贡献者:', '')}]`;
}
return `${this.msToTime(i.startMs)}${i.text}`;
}).join('\n');
},
toELRC(s) {
return s.map(i => {
if (i.type === 'lrc') {
if (i.text.includes('作曲')) return `[ar:${i.text.replace('作曲:', '')}]`;
if (i.text.includes('作词')) return `[au:${i.text.replace('作词:', '')}]`;
if (i.text.includes('贡献者')) return `[by:${i.text.replace('滚动歌词&翻译贡献者:', '')}]`;
}
let line = this.msToTime(i.startMs);
if (i.words?.length > 1) {
i.words.forEach(w => line += `<${this.msToTime(w.startMs).slice(1, -1)}>${w.text}`);
} else {
line += i.text;
}
return line;
}).join('\n');
},
toKRC(s) {
return s.map(i => {
const dur = i.endMs - i.startMs;
let words = '';
if (i.words?.length) {
i.words.forEach(w => words += `<${w.startMs - i.startMs},${w.endMs - w.startMs}>${w.text}`);
} else {
words = `<0,${dur}>${i.text}`;
}
return `[${i.startMs},${dur}]${words}`;
}).join('\n');
}
};
// 提取数据
function getLyrics() {
try {
const d = unsafeWindow._ROUTER_DATA?.loaderData?.track_page?.audioWithLyricsOption?.lyrics;
if (d?.sentences) return d;
} catch(e) {}
document.querySelectorAll('script:not([src])').forEach(s => {
const t = s.textContent;
if (t.includes('"lyricType"') && t.includes('"sentences"')) {
const m = t.match(/\{[^}]*"lyricType"[^}]*"sentences"[^}]*\}/);
if (m) return JSON.parse(m[0]);
}
});
return null;
}
// 获取歌曲信息
function getInfo(sentences) {
const info = { title: '未知歌曲', artist: '未知艺术家' };
const t = document.querySelector('title')?.textContent || '';
const m = t.match(/(.+?)\s*[-–]\s*(.+)/);
if (m) { info.title = m[1].trim(); info.artist = m[2].trim(); }
try {
const tr = unsafeWindow._ROUTER_DATA?.loaderData?.track_page?.trackInfo;
if (tr) {
info.title = tr.name || info.title;
info.artist = tr.artist?.name || info.artist;
}
} catch(e) {}
sentences?.forEach(s => {
if (s.type === 'lrc' && s.text.includes('作曲')) info.artist = s.text.replace('作曲:', '');
});
return info;
}
// 复制功能 - 使用多种方法
function doCopy(text) {
return new Promise((resolve, reject) => {
// 1: 现代 Clipboard API
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(resolve).catch(() => {
// 失败则使用方法2
fallbackCopy();
});
} else {
fallbackCopy();
}
// 2: 传统 execCommand
function fallbackCopy() {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none;z-index:-1;';
document.body.appendChild(ta);
ta.focus();
ta.select();
try {
const success = document.execCommand('copy');
document.body.removeChild(ta);
if (success) resolve();
else reject(new Error('execCommand failed'));
} catch (err) {
document.body.removeChild(ta);
reject(err);
}
}
});
}
// 显示提示
function showTip(btn, text) {
const orig = btn.textContent;
const origBg = btn.style.background;
btn.textContent = text;
btn.style.background = '#10b981';
btn.disabled = true;
setTimeout(() => {
btn.textContent = orig;
btn.style.background = origBg;
btn.disabled = false;
}, 1200);
}
// 当前数据存储
let currentData = null;
let currentFormats = null;
// 创建界面
function createUI() {
const data = getLyrics();
if (!data) {
alert('未找到歌词数据!请确保页面正在播放歌曲且歌词已加载。');
return;
}
currentData = data;
const s = data.sentences;
const info = getInfo(s);
currentFormats = [
{ name: 'LRC', desc: '标准格式,通用播放器', content: Converter.toLRC(s) },
{ name: 'ELRC', desc: '增强格式,逐字高亮', content: Converter.toELRC(s) },
{ name: 'KRC', desc: '酷狗格式,精确卡拉OK', content: Converter.toKRC(s) }
];
// 移除旧界面
const old = document.getElementById('lyric-tool');
if (old) old.remove();
// 创建遮罩和容器
const overlay = document.createElement('div');
overlay.id = 'lyric-tool';
const box = document.createElement('div');
box.className = 'lyric-box';
box.onclick = (e) => e.stopPropagation();
box.innerHTML = `
<style>
#lyric-tool {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 999999;
display: flex;
align-items: flex-end;
justify-content: center;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
}
@media(min-width:640px){
#lyric-tool{align-items:center}
}
.lyric-box {
background: #fff;
width: 100%;
max-width: 480px;
max-height: 90vh;
border-radius: 20px 20px 0 0;
overflow: hidden;
display: flex;
flex-direction: column;
animation: slideUp 0.25s ease;
}
@media(min-width:640px){
.lyric-box{border-radius:16px;max-height:85vh}
}
@keyframes slideUp {
from {transform:translateY(100%);opacity:0}
to {transform:translateY(0);opacity:1}
}
.lyric-head {
background: linear-gradient(90deg,#00b894,#00cec9);
color: #fff;
padding: 16px 20px;
position: relative;
flex-shrink: 0;
}
.lyric-head h3 {
margin: 0;
font-size: 18px;
font-weight: 700;
}
.lyric-head p {
margin: 4px 0 0;
font-size: 13px;
opacity: 0.9;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lyric-close {
position: absolute;
top: 12px;
right: 12px;
width: 32px;
height: 32px;
border: none;
background: rgba(255,255,255,0.2);
color: #fff;
border-radius: 50%;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.lyric-close:hover{background:rgba(255,255,255,0.3)}
.lyric-body {
padding: 16px 20px;
overflow-y: auto;
flex: 1;
}
.lyric-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.lyric-meta-item {
font-size: 13px;
color: #666;
}
.lyric-meta-item strong {
display: block;
color: #333;
font-size: 15px;
margin-top: 2px;
}
.lyric-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.lyric-item {
border: 1px solid #e8e8e8;
border-radius: 12px;
overflow: hidden;
}
.lyric-item-hd {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 14px;
background: #fafafa;
}
.lyric-item-title {
display: flex;
align-items: center;
gap: 8px;
}
.lyric-tag {
background: #00b894;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 700;
}
.lyric-tag.elrc{background:#0984e3}
.lyric-tag.krc{background:#e17055}
.lyric-item-desc {
font-size: 12px;
color: #999;
}
.lyric-copy {
border: none;
background: #dfe6e9;
color: #2d3436;
padding: 8px 16px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
min-width: 60px;
}
.lyric-copy:hover{background:#b2bec3}
.lyric-copy:active{transform:scale(0.95)}
.lyric-text {
padding: 12px 14px;
background: #2d3436;
color: #81ecec;
font-family: "SF Mono",Monaco,monospace;
font-size: 11px;
line-height: 1.6;
max-height: 100px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
.lyric-foot {
padding: 12px 20px;
background: #fafafa;
border-top: 1px solid #f0f0f0;
display: flex;
gap: 10px;
flex-shrink: 0;
}
.lyric-btn {
flex: 1;
border: none;
padding: 12px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.lyric-btn:hover{opacity:0.9}
.lyric-btn:active{transform:scale(0.98)}
.lyric-btn-json {
background: #00b894;
color: #fff;
}
.lyric-btn-close {
background: #dfe6e9;
color: #636e72;
}
</style>
<div class="lyric-head">
<h3>🎵 歌词提取器</h3>
<p>${info.title} - ${info.artist}</p>
<button class="lyric-close">×</button>
</div>
<div class="lyric-body">
<div class="lyric-meta">
<div class="lyric-meta-item">总行数<strong>${s.length}行</strong></div>
<div class="lyric-meta-item">逐字支持<strong>${s.some(x=>x.words?.length>1)?'✅':'❌'}</strong></div>
</div>
<div class="lyric-list">
${currentFormats.map((f,i)=>`
<div class="lyric-item">
<div class="lyric-item-hd">
<div class="lyric-item-title">
<span class="lyric-tag ${f.name.toLowerCase()}">${f.name}</span>
<span class="lyric-item-desc">${f.desc}</span>
</div>
<button class="lyric-copy" data-idx="${i}">复制</button>
</div>
<div class="lyric-text">${f.content.substring(0,120)}${f.content.length>120?'...':''}</div>
</div>
`).join('')}
</div>
</div>
<div class="lyric-foot">
<button class="lyric-btn lyric-btn-json" id="btn-json">📋 复制原始JSON</button>
<button class="lyric-btn lyric-btn-close" id="btn-close">关闭</button>
</div>
`;
overlay.appendChild(box);
document.body.appendChild(overlay);
// 绑定事件 - 使用事件委托确保可靠
// 关闭按钮
box.querySelector('.lyric-close').onclick = () => overlay.remove();
box.querySelector('#btn-close').onclick = () => overlay.remove();
overlay.onclick = () => overlay.remove();
// 复制格式按钮
box.querySelectorAll('.lyric-copy').forEach(btn => {
btn.onclick = function(e) {
e.stopPropagation();
const idx = parseInt(this.getAttribute('data-idx'));
const content = currentFormats[idx].content;
doCopy(content).then(() => {
showTip(this, '✓ 已复制');
}).catch(err => {
console.error('复制失败:', err);
showTip(this, '✗ 失败');
});
};
});
// 复制JSON按钮
box.querySelector('#btn-json').onclick = function(e) {
e.stopPropagation();
const jsonStr = JSON.stringify(currentData, null, 2);
doCopy(jsonStr).then(() => {
showTip(this, '✓ JSON已复制');
}).catch(err => {
console.error('复制失败:', err);
showTip(this, '✗ 复制失败');
});
};
}
// 注册菜单
GM_registerMenuCommand('🎵 提取歌词', createUI);
// 快捷键
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.shiftKey && e.key === 'L') {
e.preventDefault();
createUI();
}
});
})();