Skip to content

Creating Lyrics Addons ko

ivLis edited this page Jan 22, 2026 · 1 revision

가사 애드온 개발

English

ivLyrics용 커스텀 가사 제공자 애드온을 만드는 방법을 설명합니다.


개요

가사 애드온은 다양한 소스에서 가사를 제공합니다. LyricsAddonManager에 등록되며 사용자가 활성화/비활성화하고 우선순위를 설정할 수 있습니다.


애드온 구조

기본 템플릿

(function() {
    'use strict';

    const addon = {
        // 필수: 고유 식별자
        id: 'my-lyrics-provider',

        // 필수: 표시 이름
        name: 'My Lyrics Provider',

        // 필수: 제작자 이름
        author: 'Your Name',

        // 필수: 설명
        description: {
            en: 'Fetch lyrics from My Service',
            ko: 'My Service에서 가사 가져오기'
        },

        // 필수: 지원하는 가사 유형
        supports: {
            karaoke: false,    // 단어별 싱크
            synced: true,      // 줄별 싱크
            unsynced: true     // 타이밍 없음
        },

        // 선택: ivLyrics Sync 지원
        supportsIvSync: false,

        // 선택: 설정 UI
        getSettingsUI: function() {
            return null;
        },

        // 선택: 애드온 초기화
        init: async function() {
            console.log('My Lyrics Provider initialized');
        },

        // 필수: 가사 가져오기
        getLyrics: async function(info) {
            // info: { uri, title, artist, album, duration }
            // 반환: LyricsResult 또는 null
        }
    };

    function register() {
        if (window.LyricsAddonManager) {
            window.LyricsAddonManager.register(addon);
        } else {
            setTimeout(register, 100);
        }
    }
    register();
})();

필수 필드

id

  • 고유 식별자 문자열
  • 소문자와 하이픈 사용
  • 예시: 'my-lyrics-provider'

name

  • 설정에 표시되는 이름
  • 예시: 'My Lyrics Provider'

author

  • 제작자 이름
  • 예시: 'Your Name'

description

문자열 또는 다국어 객체:

description: {
    en: 'English description',
    ko: '한국어 설명'
}

supports

제공자가 반환할 수 있는 가사 유형 정의:

supports: {
    karaoke: false,    // 단어별 하이라이팅
    synced: true,      // 줄 수준 타임스탬프
    unsynced: true     // 일반 가사
}

가사 결과 형식

일반 가사 (Unsynced)

{
    type: 'unsynced',
    provider: 'my-lyrics-provider',
    lines: [
        { text: '가사 첫 번째 줄' },
        { text: '가사 두 번째 줄' },
        { text: '' },  // 간격을 위한 빈 줄
        { text: '가사 세 번째 줄' }
    ]
}

싱크 가사 (Synced)

{
    type: 'synced',
    provider: 'my-lyrics-provider',
    lines: [
        {
            startTime: 0,       // 밀리초
            endTime: 3500,      // 밀리초 (선택)
            text: '첫 번째 줄'
        },
        {
            startTime: 3500,
            endTime: 7000,
            text: '두 번째 줄'
        }
    ]
}

노래방 가사 (Karaoke)

{
    type: 'karaoke',
    provider: 'my-lyrics-provider',
    lines: [
        {
            startTime: 0,
            endTime: 3500,
            text: '첫 번째 줄 단어들',
            words: [
                { startTime: 0, endTime: 800, text: '첫' },
                { startTime: 800, endTime: 1500, text: '번째' },
                { startTime: 1500, endTime: 2500, text: '줄' },
                { startTime: 2500, endTime: 3500, text: '단어들' }
            ]
        }
    ]
}

getLyrics 메서드

매개변수

async getLyrics(info) {
    // info 객체 내용:
    // - uri: Spotify 트랙 URI (예: 'spotify:track:xxx')
    // - title: 트랙 제목
    // - artist: 아티스트 이름
    // - album: 앨범 이름
    // - duration: 트랙 길이 (밀리초)
}

반환 값

// 가사 성공
return {
    type: 'synced',
    provider: 'my-lyrics-provider',
    lines: [...]
};

// 가사를 찾지 못함
return null;

// 오류 (null 반환, 선택적으로 오류 로그)
console.error('가사 가져오기 실패:', error);
return null;

예제: LRCLIB 스타일 제공자

(function() {
    'use strict';

    const addon = {
        id: 'my-lrc-provider',
        name: 'My LRC Provider',
        author: 'Developer',
        description: 'LRC 형식 가사 가져오기',
        
        supports: {
            karaoke: false,
            synced: true,
            unsynced: true
        },

        getLyrics: async function(info) {
            try {
                // 검색 URL 생성
                const url = new URL('https://api.example.com/lyrics');
                url.searchParams.set('track', info.title);
                url.searchParams.set('artist', info.artist);

                const response = await fetch(url.toString());
                
                if (!response.ok) {
                    return null;
                }

                const data = await response.json();

                if (!data.lyrics) {
                    return null;
                }

                // LRC 형식 파싱
                if (data.syncedLyrics) {
                    return this.parseSyncedLyrics(data.syncedLyrics);
                } else if (data.plainLyrics) {
                    return this.parsePlainLyrics(data.plainLyrics);
                }

                return null;
            } catch (error) {
                console.error('[MyLRCProvider] 오류:', error);
                return null;
            }
        },

        parseSyncedLyrics: function(lrcText) {
            const lines = [];
            const regex = /\[(\d{2}):(\d{2})\.(\d{2,3})\](.*)/g;
            let match;

            while ((match = regex.exec(lrcText)) !== null) {
                const minutes = parseInt(match[1]);
                const seconds = parseInt(match[2]);
                const ms = parseInt(match[3].padEnd(3, '0'));
                const text = match[4].trim();

                const startTime = (minutes * 60 + seconds) * 1000 + ms;

                lines.push({ startTime, text });
            }

            // 종료 시간 계산
            for (let i = 0; i < lines.length - 1; i++) {
                lines[i].endTime = lines[i + 1].startTime;
            }
            if (lines.length > 0) {
                lines[lines.length - 1].endTime = 
                    lines[lines.length - 1].startTime + 5000;
            }

            return {
                type: 'synced',
                provider: 'my-lrc-provider',
                lines
            };
        },

        parsePlainLyrics: function(text) {
            const lines = text.split('\n').map(line => ({
                text: line.trim()
            }));

            return {
                type: 'unsynced',
                provider: 'my-lrc-provider',
                lines
            };
        }
    };

    function register() {
        if (window.LyricsAddonManager) {
            window.LyricsAddonManager.register(addon);
        } else {
            setTimeout(register, 100);
        }
    }
    register();
})();

설정

애드온별 설정 가져오기/저장:

// 설정 가져오기
const value = window.LyricsAddonManager.getAddonSetting(
    'my-lyrics-provider',
    'settingKey',
    'defaultValue'
);

// 설정 저장
window.LyricsAddonManager.setAddonSetting(
    'my-lyrics-provider',
    'settingKey',
    'newValue'
);

파일 배치

manifest.json에 애드온 추가:

{
    "subfiles_extension": [
        "Addon_Lyrics_MyProvider.js"
    ]
}

테스트

  1. 애드온 파일 생성
  2. manifest.json에 추가
  3. spicetify apply 실행
  4. 설정 > Lyrics Providers 열기
  5. 제공자 활성화 및 우선순위 조정
  6. 노래를 재생하여 테스트

모범 사례

  1. 검색 정확도: 제목/아티스트에 퍼지 매칭 사용
  2. 폴백: 여러 검색 전략 시도
  3. 오류 처리: 오류 시 우아하게 null 반환
  4. 성능: 가능하면 결과 캐싱
  5. 속도 제한: API 제한 준수

제공자 우선순위

사용자가 제공자 순서를 재정렬할 수 있습니다. 매니저는 가사를 반환할 때까지 순서대로 제공자를 시도합니다:

사용자 우선순위: [Spotify, MyProvider, LRCLIB]

1. Spotify 시도 → 가사 없음 → 계속
2. MyProvider 시도 → 가사 반환 → 중지

관련 문서

Clone this wiki locally