Apple Music 歌词增强

为网页版 Apple Music 提供翻译歌词,数据来源为网易云音乐

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name Apple Music 歌词增强
// @namespace https://github.com/akashiwest/AML-Enhancer
// @version 1.200
// @description 为网页版 Apple Music 提供翻译歌词,数据来源为网易云音乐
// @author Akashi
// @license GNU GPL 3.0
// @match https://*.music.apple.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    // 定义扩展和缩小时容器的高度
    const expandedHeight = '240px';
    const minimizedHeight = '130px';

    // 全局变量保存主歌词与翻译歌词数据
    let currentLyrics = [];
    let currentTLyrics = [];

    // 歌词时间偏移量(秒)
    let lyricsOffset = 0;

    // 当前歌曲信息
    let currentSongTitle = '';

    // 偏移存储对象 - 在实际环境中可以改为localStorage
    // 格式: { "歌名": 偏移值(秒) }
    let offsetStorage = {};

    // 存储/读取偏移值的函数
    function saveOffset(songTitle, offset) {
        if (!songTitle) return;

       try {
             let storedOffsets = JSON.parse(localStorage.getItem('lyricsOffsets') || '{}');
             storedOffsets[songTitle] = offset;
             localStorage.setItem('lyricsOffsets', JSON.stringify(storedOffsets));
         } catch (e) {
             console.error('保存歌词偏移失败:', e);
         }
    }

    function loadOffset(songTitle) {
        if (!songTitle) return 0;


         try {
             let storedOffsets = JSON.parse(localStorage.getItem('lyricsOffsets') || '{}');
             return storedOffsets[songTitle] || 0;
         } catch (e) {
             console.error('读取歌词偏移失败:', e);
             return 0;
         }
    }

    // 清除指定歌曲的偏移
    function clearOffset(songTitle) {
        if (!songTitle) return;

         try {
             let storedOffsets = JSON.parse(localStorage.getItem('lyricsOffsets') || '{}');
             delete storedOffsets[songTitle];
             localStorage.setItem('lyricsOffsets', JSON.stringify(storedOffsets));
         } catch (e) {
             console.error('清除歌词偏移失败:', e);
         }
    }

    // 创建固定容器(含内部内容容器)用于显示歌词
    function createLyricsDisplay() {
        const lyricsDiv = document.createElement('div');
        lyricsDiv.id = 'lyrics-display';
        // 默认缩小
        lyricsDiv.dataset.isMinimized = 'true';

        Object.assign(lyricsDiv.style, {
            position: 'fixed',
            right: '20px',
            top: '60px',
            width: '850px',
            height: minimizedHeight,
            overflow: 'hidden',
            borderRadius: '20px',
            backdropFilter: 'saturate(200%) blur(25px)',
            background: 'rgba(250,250,250,0.72)',
            zIndex: '9999',
            padding: '20px 30px',
            fontSize: '28px',
            color: '#565656',
            textAlign: 'center',
            boxShadow: '0 5px 30px rgba(0, 0, 0, 0.4)',
            fontWeight: 'bold',
            msOverflowStyle: 'none',
            scrollbarWidth: 'none',
            // 高度切换的动画效果,仅对 height 生效
            transition: 'height 0.3s ease'
        });

        // 内部容器,滚动时带平滑动画
        const lyricsContent = document.createElement('div');
        lyricsContent.id = 'lyrics-content';
        lyricsContent.style.transition = 'transform 0.3s ease-out';
        lyricsContent.style.transform = 'translateY(0)';
        lyricsDiv.appendChild(lyricsContent);

        const defaultGroup = document.createElement('div');
        defaultGroup.className = 'lyric-group';
        defaultGroup.style.height = '70px';
        defaultGroup.style.marginBottom = '10px';

        const defaultText = document.createElement('div');
        defaultText.className = 'main-lyric';
        defaultText.innerText = 'Apple Music 歌词翻译 v1.2';
        defaultText.style.fontSize = '32px';
        defaultText.style.color = '#252525';
        defaultText.style.fontWeight = 'bold';
        defaultText.style.marginTop = '25px';

        defaultGroup.appendChild(defaultText);
        lyricsContent.appendChild(defaultGroup);
        lyricsDiv.appendChild(lyricsContent);

        // 创建右下角按钮容器
        const rightButtonContainer = document.createElement('div');
        Object.assign(rightButtonContainer.style, {
            position: 'absolute',
            bottom: '10px',
            right: '10px',
            display: 'flex',
            gap: '5px',
            alignItems: 'center'
        });

        // 创建左下角按钮容器
        const leftButtonContainer = document.createElement('div');
        Object.assign(leftButtonContainer.style, {
            position: 'absolute',
            bottom: '10px',
            left: '10px',
            display: 'flex',
            gap: '5px',
            alignItems: 'center'
        });

        // 减号按钮
        const minusButton = document.createElement('button');
        minusButton.id = 'minus-button';
        minusButton.innerText = '−';
        Object.assign(minusButton.style, {
            padding: '5px 8px',
            fontSize: '16px',
            fontWeight: 'bold',
            border: 'none',
            borderRadius: '15px',
            background: '#ddd',
            cursor: 'pointer',
            zIndex: '10000',
            opacity: '0.8',
            minWidth: '30px'
        });

        // 偏移显示按钮
        const offsetButton = document.createElement('button');
        offsetButton.id = 'offset-button';
        offsetButton.innerText = '+0.0s';
        Object.assign(offsetButton.style, {
            padding: '5px 10px',
            fontSize: '12px',
            border: 'none',
            borderRadius: '15px',
            background: '#ddd',
            cursor: 'pointer',
            zIndex: '10000',
            opacity: '0.8',
            minWidth: '50px'
        });

        // 加号按钮
        const plusButton = document.createElement('button');
        plusButton.id = 'plus-button';
        plusButton.innerText = '+';
        Object.assign(plusButton.style, {
            padding: '5px 8px',
            fontSize: '16px',
            fontWeight: 'bold',
            border: 'none',
            borderRadius: '15px',
            background: '#ddd',
            cursor: 'pointer',
            zIndex: '10000',
            opacity: '0.8',
            minWidth: '30px'
        });

        // 切换按钮
        const toggleButton = document.createElement('button');
        toggleButton.id = 'toggle-size-button';
        toggleButton.innerText = '放大';
        Object.assign(toggleButton.style, {
            padding: '5px 10px',
            fontSize: '14px',
            border: 'none',
            borderRadius: '15px',
            background: '#ddd',
            cursor: 'pointer',
            zIndex: '10000',
            opacity: '0.8'
        });

        // 信息按钮
        const infoButton = document.createElement('button');
        infoButton.id = 'info-button';
        infoButton.innerText = 'O';
        Object.assign(infoButton.style, {
            padding: '5px 8px',
            fontSize: '14px',
            border: 'none',
            borderRadius: '15px',
            background: '#ddd',
            cursor: 'pointer',
            zIndex: '10000',
            opacity: '0.3'
        });

        // 更新偏移显示
        function updateOffsetDisplay() {
            const offsetValue = lyricsOffset >= 0 ? `+${lyricsOffset.toFixed(1)}s` : `${lyricsOffset.toFixed(1)}s`;
            offsetButton.innerText = offsetValue;

            // 自动保存当前歌曲的偏移值
            if (currentSongTitle) {
                saveOffset(currentSongTitle, lyricsOffset);
            }
        }

        // 减号按钮事件
        minusButton.addEventListener('click', function(e) {
            e.stopPropagation();
            lyricsOffset -= 0.2;
            updateOffsetDisplay();
        });

        // 偏移按钮事件(点击归零并清除存储)
        offsetButton.addEventListener('click', function(e) {
            e.stopPropagation();
            lyricsOffset = 0;
            updateOffsetDisplay();

            // 清除当前歌曲的存储偏移
            if (currentSongTitle) {
                clearOffset(currentSongTitle);
            }
        });

        // 加号按钮事件
        plusButton.addEventListener('click', function(e) {
            e.stopPropagation();
            lyricsOffset += 0.2;
            updateOffsetDisplay();
        });

        // 切换按钮事件
        toggleButton.addEventListener('click', function(e) {
            e.stopPropagation();
            if (lyricsDiv.dataset.isMinimized === 'true') {
                // 扩展
                lyricsDiv.style.height = expandedHeight;
                lyricsDiv.dataset.isMinimized = 'false';
                toggleButton.innerText = '缩小';
            } else {
                // 缩小
                lyricsDiv.style.height = minimizedHeight;
                lyricsDiv.dataset.isMinimized = 'true';
                toggleButton.innerText = '放大';
            }
        });

        // 信息按钮事件
        infoButton.addEventListener('click', function(e) {
            e.stopPropagation();
            window.open('https://imakashi.com/blog/archives/AML_Enhancer.html', '_blank');
        });

        // 将按钮添加到对应的容器
        rightButtonContainer.appendChild(minusButton);
        rightButtonContainer.appendChild(offsetButton);
        rightButtonContainer.appendChild(plusButton);
        rightButtonContainer.appendChild(toggleButton);

        leftButtonContainer.appendChild(infoButton);

        lyricsDiv.appendChild(rightButtonContainer);
        lyricsDiv.appendChild(leftButtonContainer);

        // 拖拽功能
        lyricsDiv.onmousedown = dragMouseDown;

        let pos3 = 0, pos4 = 0;

        function dragMouseDown(e) {
            // 点击按钮不触发拖拽
            if (e.target === toggleButton || e.target === infoButton ||
                e.target === minusButton || e.target === offsetButton || e.target === plusButton) return;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e.preventDefault();
            const pos1 = pos3 - e.clientX;
            const pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            lyricsDiv.style.top = `${lyricsDiv.offsetTop - pos2}px`;
            lyricsDiv.style.left = `${lyricsDiv.offsetLeft - pos1}px`;
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }

        document.body.appendChild(lyricsDiv);
        return lyricsDiv;
    }

    // 根据播放器 title 获取歌曲ID,并调用歌词接口
    function getSongId() {
        const audioPlayer = document.getElementById('apple-music-player');
        if (!audioPlayer) {
            console.log('当前页面未找到音频播放器');
            return;
        }

        let title = audioPlayer.title;
        // 取标题中第一个"-"前面的部分(可根据实际情况调整)
        const secondDashIndex = title.indexOf('-', title.indexOf('-') + 1);
        if (secondDashIndex !== -1) {
            title = title.substring(0, secondDashIndex).trim();
        }

        // 更新当前歌曲标题并加载对应的偏移值
        currentSongTitle = title;
        lyricsOffset = loadOffset(currentSongTitle);

        // 更新显示
        const offsetButton = document.getElementById('offset-button');
        if (offsetButton) {
            const offsetValue = lyricsOffset >= 0 ? `+${lyricsOffset.toFixed(1)}s` : `${lyricsOffset.toFixed(1)}s`;
            offsetButton.innerText = offsetValue;
        }

        showMessage(title);

        const apiUrl = `https://music.163.com/api/search/pc?s=${encodeURIComponent(title)}&offset=0&limit=1&type=1`;

        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            responseType: "json",
            onload: function(response) {
                if (response.status === 200) {
                    const data = response.response;
                    if (data.result && data.result.songs && data.result.songs.length > 0) {
                        const firstSongId = data.result.songs[0].id;
                        getLyrics(firstSongId);
                        console.log(apiUrl, 'ID - ' + firstSongId);
                    } else {
                        showMessage("未找到歌曲");
                    }
                } else {
                    showMessage("请求失败");
                }
            },
            onerror: function() {
                console.error("未知错误");
            }
        });
    }

    // 提示信息
    function showMessage(msg) {
        const lyricsContent = document.getElementById('lyrics-content');
        if (lyricsContent) {
            const messageGroup = document.createElement('div');
            messageGroup.className = 'lyric-group';
            messageGroup.style.height = '70px';
            messageGroup.style.marginBottom = '10px';

            const messageText = document.createElement('div');
            messageText.className = 'main-lyric';
            messageText.innerText = msg;
            messageText.style.fontSize = '32px';
            messageText.style.color = '#252525';
            messageText.style.fontWeight = 'bold';
            messageText.style.filter = 'blur(0) !important';

            // 添加容器样式以确保垂直居中
            messageGroup.style.display = 'flex';
            messageGroup.style.alignItems = 'center';
            messageGroup.style.justifyContent = 'center';
            messageGroup.style.height = '100%';
            messageGroup.style.marginTop = '25px';

            messageGroup.appendChild(messageText);
            lyricsContent.innerHTML = '';
            lyricsContent.appendChild(messageGroup);
        }
    }

    // 获取歌词(同时获取主歌词和翻译歌词)并解析后渲染
    function getLyrics(songId) {
        const apiUrl = `https://music.163.com/api/song/lyric?lv=1&kv=1&tv=-1&id=${songId}`;
        showMessage('歌词正在加载中 ...');

        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            responseType: "json",
            onload: function(response) {
                if (response.status === 200) {
                    const data = response.response;
                    if (!data || (!data.lrc && !data.tlyric)) {
                        showMessage('未找到匹配歌词');
                        currentLyrics = [];
                        currentTLyrics = [];
                        return;
                    }

                    const lyricsLines = data.lrc ? data.lrc.lyric : "";
                    const tlyricsLines = data.tlyric ? data.tlyric.lyric : "";

                    currentLyrics = parseLyrics(lyricsLines);
                    currentTLyrics = parseLyrics(tlyricsLines);

                    if (currentLyrics.length === 0) {
                        showMessage('暂无歌词');
                        return;
                    }

                    renderLyrics();

                    const audioPlayer = document.getElementById('apple-music-player');
                    if (audioPlayer) {
                        audioPlayer.dataset.songId = songId;
                    }
                } else {
                    showMessage('歌词获取失败');
                    currentLyrics = [];
                    currentTLyrics = [];
                }
            },
            onerror: function(err) {
                console.error(err);
                showMessage('歌词获取失败');
                currentLyrics = [];
                currentTLyrics = [];
            }
        });
    }

    // 解析歌词文本(格式:[mm:ss.xxx]歌词内容)
    function parseLyrics(lyricsText) {
        return lyricsText.split('\n').filter(line => line.trim() !== '').map(line => {
            const matches = line.match(/\[(\d{2}):(\d{2})(?:\.(\d{1,3}))?\](.*)/);
            if (matches) {
                const minutes = parseInt(matches[1], 10);
                const seconds = parseInt(matches[2], 10);
                let milliseconds = matches[3] ? parseInt(matches[3], 10) : 0;

                if (milliseconds < 100 && milliseconds >= 10) {
                    milliseconds *= 10;
                }

                const text = matches[4].trim();
                const totalSeconds = minutes * 60 + seconds + milliseconds / 1000;

                return { startTime: totalSeconds, text: text };
            }
        }).filter(Boolean);
    }

    // 渲染歌词:每组歌词显示为两行(主歌词及对应翻译),每组之间有间隙
    function renderLyrics() {
        const lyricsContent = document.getElementById('lyrics-content');
        if (!lyricsContent) return;

        lyricsContent.innerHTML = '';
        const groupHeight = 70; // 每组固定高度(包括两行与间隙)

        currentLyrics.forEach((lyric, index) => {
            const groupDiv = document.createElement('div');
            groupDiv.className = 'lyric-group';
            groupDiv.dataset.index = index;
            groupDiv.style.height = groupHeight + 'px';
            groupDiv.style.marginBottom = '10px';

            // 主歌词行
            const mainDiv = document.createElement('div');
            mainDiv.className = 'main-lyric';
            mainDiv.innerText = lyric.text;
            mainDiv.style.fontSize = '28px';
            mainDiv.style.color = '#565656';

            // 匹配翻译歌词
            let translationText = "";
            if (currentTLyrics && currentTLyrics.length > 0) {
                const tLine = currentTLyrics.find(t => Math.abs(t.startTime - lyric.startTime) < 0.5);
                if (tLine) {
                    translationText = tLine.text;
                }
            }

            const transDiv = document.createElement('div');
            transDiv.className = 'translation-lyric';
            transDiv.innerText = translationText;
            transDiv.style.fontSize = '20px';
            transDiv.style.color = '#888';
            transDiv.style.marginTop = '5px';

            groupDiv.appendChild(mainDiv);
            groupDiv.appendChild(transDiv);
            lyricsContent.appendChild(groupDiv);
        });
    }

    // 当前歌词高亮
    function updateLyricScroll(currentTime) {
        if (currentLyrics.length === 0) return;

        // 应用时间偏移
        const adjustedTime = currentTime + lyricsOffset;

        let currentIndex = 0;
        for (let i = 0; i < currentLyrics.length; i++) {
            if (adjustedTime >= currentLyrics[i].startTime) {
                currentIndex = i;
            } else {
                break;
            }
        }

        const lyricsContent = document.getElementById('lyrics-content');
        if (lyricsContent === null) return;

        const groups = lyricsContent.getElementsByClassName('lyric-group');
        for (let i = 0; i < groups.length; i++) {
            const mainDiv = groups[i].querySelector('.main-lyric');
            const transDiv = groups[i].querySelector('.translation-lyric');
            if (!mainDiv || !transDiv) continue;

            if (i === currentIndex) {
                mainDiv.style.color = '#252525';
                mainDiv.style.fontWeight = 'bold';
                mainDiv.style.fontSize = '32px';
                mainDiv.style.filter = 'blur(0)';
                transDiv.style.filter = 'blur(0)';
                transDiv.style.color = '#353535';
                transDiv.style.fontWeight = 'bold';
                transDiv.style.fontSize = '24px';
            } else {
                mainDiv.style.color = '#565656';
                mainDiv.style.filter = 'blur(3px)';
                mainDiv.style.marginTop = '20px';
                mainDiv.style.fontWeight = 'normal';
                mainDiv.style.fontSize = '28px';
                transDiv.style.filter = 'blur(3px)';
                transDiv.style.color = '#888';
                transDiv.style.fontWeight = 'normal';
                transDiv.style.fontSize = '20px';
            }
        }

        // 计算滚动偏移(groupHeight + 下边距),不知道怎么调的,反正按照现在这样数值设置了看着还可以
        const groupHeight = 90;
        const container = document.getElementById('lyrics-display');
        const containerHeight = container.clientHeight;
        const offset = (currentIndex * groupHeight) - (containerHeight / 2 - groupHeight / 2) + 30;

        const lyricsContentDiv = document.getElementById('lyrics-content');
        lyricsContentDiv.style.transform = `translateY(-${offset}px)`;
    }

    const lyricsDisplay = createLyricsDisplay();

    // 更新滚动
    document.addEventListener('timeupdate', function(event) {
        const audioPlayer = event.target;
        if (audioPlayer.id === 'apple-music-player') {
            const startOffset = parseFloat(audioPlayer.dataset.startOffset) || 0;
            const effectiveTime = audioPlayer.currentTime - startOffset;
            updateLyricScroll(effectiveTime);
        }
    }, true);

    // 每秒检测歌曲标题变化(切歌)
    setInterval(function() {
        const audioPlayer = document.getElementById('apple-music-player');
        if (audioPlayer) {
            let title = audioPlayer.title;
            if (title) {
                const secondDashIndex = title.indexOf('-', title.indexOf('-') + 1);
                if (secondDashIndex !== -1) {
                    title = title.substring(0, secondDashIndex).trim();
                }

                if (title !== audioPlayer.dataset.lastTitle) {
                    audioPlayer.dataset.lastTitle = title;
                    audioPlayer.dataset.startOffset = audioPlayer.currentTime;

                    const lyricsContent = document.getElementById('lyrics-content');
                    if (lyricsContent) {
                        lyricsContent.innerHTML = '';
                    }
                    getSongId();
                }
            }
        }
    }, 1000);

    // 当前曲播放结束时也尝试重新获取歌词(适用于自动切换下一曲)
    const audioPlayer = document.getElementById('apple-music-player');
    if (audioPlayer) {
        audioPlayer.addEventListener('ended', function() {
            setTimeout(() => {
                audioPlayer.dataset.startOffset = audioPlayer.currentTime;
                getSongId();
            }, 500);
        });
    }
})();