TypeScript로 Spotify에서 YouTube Music 재생목록 전송 자동화
Source: Dev.to
번역할 전체 텍스트를 제공해 주시면, 요청하신 대로 마크다운 형식과 코드 블록, URL은 그대로 유지하면서 한국어로 번역해 드리겠습니다.
📋 Overview
음악 스트리밍 서비스 간 전환이 신중하게 큐레이션한 재생 목록을 잃는 것을 의미해서는 안 됩니다. 영구적으로 마이그레이션하든 두 플랫폼 모두에서 재생 목록을 유지하든, 작은 TypeScript 스크립트가 여러분을 대신해 무거운 작업을 수행할 수 있습니다.
🛠 전제 조건
| Requirement | Details |
|---|---|
| Node.js | v18 또는 그 이상 |
| Spotify Developer | 앱이 생성된 계정 (클라이언트 ID, 클라이언트 시크릿, 리디렉션 URI) |
| YouTube Music | 일반 계정 (인증 쿠키가 필요합니다) |
| TypeScript | 언어에 대한 기본 지식 |
| Environment variables | 비밀 정보를 위한 .env 파일 |
🚀 프로젝트 설정
# Create a new folder and initialise the project
mkdir spotify-to-ytmusic
cd spotify-to-ytmusic
npm init -y
# Development dependencies
npm install typescript ts-node @types/node --save-dev
# Runtime dependencies
npm install @spotify/web-api-ts-sdk ytmusic-api youtube-music-ts-api dotenv
# Initialise a tsconfig.json
npx tsc --init
.env
프로젝트 루트에 .env 파일을 만들고 아래 내용을 붙여넣으세요 (플레이스홀더를 자신의 값으로 교체하세요):
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:3000/callback
🎧 Spotify – 재생목록 트랙 가져오기
// src/spotify.ts
import { SpotifyApi } from "@spotify/web-api-ts-sdk";
export interface Track {
name: string;
artist: string;
album: string;
}
/**
* Spotify 재생목록에서 모든 트랙을 가져옵니다.
*/
export async function getSpotifyPlaylistTracks(
playlistId: string
): Promise<Track[]> {
const sdk = SpotifyApi.withClientCredentials(
process.env.SPOTIFY_CLIENT_ID!,
process.env.SPOTIFY_CLIENT_SECRET!
);
const tracks: Track[] = [];
let offset = 0;
const limit = 50;
while (true) {
const response = await sdk.playlists.getPlaylistItems(
playlistId,
undefined,
undefined,
limit,
offset
);
for (const item of response.items) {
if (item.track && "name" in item.track) {
tracks.push({
name: item.track.name,
artist: item.track.artists[0]?.name ?? "Unknown",
album: item.track.album?.name ?? "Unknown",
});
}
}
if (response.items.length < limit) {
break;
}
offset += limit;
}
return tracks;
}
🔎 YouTube Music 검색
// src/ytmusic.ts
import YTMusic from "ytmusic-api";
export interface YTMusicTrack {
videoId: string;
name: string;
artist: string;
}
/**
* Searches YouTube Music for a track and returns the first match.
*/
export async function searchYouTubeMusic(
ytmusic: YTMusic,
track: { name: string; artist: string }
): Promise<YTMusicTrack | null> {
const query = `${track.name} ${track.artist}`;
try {
const results = await ytmusic.searchSongs(query);
if (results.length > 0) {
const match = results[0];
return {
videoId: match.videoId,
name: match.name,
artist: match.artist?.name ?? "Unknown",
};
}
} catch (error) {
console.error(`Failed to find: ${query}`, error);
}
return null;
}
🔄 전송 함수 – 모든 것을 연결하기
// src/transfer.ts
import YTMusic from "ytmusic-api";
import { getSpotifyPlaylistTracks, Track } from "./spotify";
import { searchYouTubeMusic, YTMusicTrack } from "./ytmusic";
export async function transferPlaylist(
spotifyPlaylistId: string,
newPlaylistName: string
): Promise<void> {
console.log("🎵 Starting playlist transfer...");
// Initialise YouTube Music API
const ytmusic = new YTMusic();
await ytmusic.initialize();
// 1️⃣ Fetch Spotify tracks
console.log("📥 Fetching Spotify playlist tracks...");
const spotifyTracks = await getSpotifyPlaylistTracks(spotifyPlaylistId);
console.log(`Found ${spotifyTracks.length} tracks`);
// 2️⃣ Search for each track on YouTube Music
console.log("🔍 Searching for matches on YouTube Music...");
const matchedTracks: YTMusicTrack[] = [];
const notFound: string[] = [];
for (const track of spotifyTracks) {
const match = await searchYouTubeMusic(ytmusic, track);
if (match) {
matchedTracks.push(match);
console.log(`✓ Found: ${track.name} – ${track.artist}`);
} else {
notFound.push(`${track.name} – ${track.artist}`);
console.log(`✗ Not found: ${track.name} – ${track.artist}`);
}
// Simple rate‑limiting to avoid throttling
await new Promise((r) => setTimeout(r, 500));
}
// 3️⃣ Summary
console.log("\n📊 Transfer Summary:");
console.log(`✓ Matched: ${matchedTracks.length}`);
console.log(`✗ Not found: ${notFound.length}`);
if (notFound.length) {
console.log("\nTracks not found:");
notFound.forEach((t) => console.log(` - ${t}`));
}
// 4️⃣ (Optional) Create a YouTube Music playlist and add the matches
// await createYTMusicPlaylist(...);
}
Source: …
📂 YouTube Music 재생목록 만들기 (쿠키 기반 인증)
// src/ytmusic-create.ts
import YouTubeMusic from "youtube-music-ts-api";
export async function createYTMusicPlaylist(
cookieString: string,
playlistName: string,
videoIds: string[]
): Promise<void> {
const ytm = new YouTubeMusic();
const ytma = await ytm.authenticate(cookieString);
// Create the new playlist
const playlist = await ytma.createPlaylist(
playlistName,
"Imported from Spotify",
"PRIVATE"
);
console.log(`Created playlist: ${playlist.name}`);
// Add tracks – you need full track objects, not just IDs.
// Example (pseudo‑code):
// for (const id of videoIds) {
// const track = await ytma.getTrack(id);
// await ytma.addTrackToPlaylist(playlist.id, track);
// }
}
인증 쿠키를 얻는 방법
- 브라우저에서 YouTube Music을 엽니다.
- F12 → Network 탭을 누릅니다.
- 아무 작업(예: 노래 재생)을 수행합니다.
music.youtube.com에 대한 요청을 찾아 Cookie 헤더 값을 복사합니다.- 해당 문자열을 스크립트에 붙여넣거나 환경 변수에 저장합니다.
📂 전체 index.ts 예시
// src/index.ts
import "dotenv/config";
import { transferPlaylist } from "./transfer";
async function main() {
// Spotify playlist ID (taken from the URL)
const spotifyPlaylistId = "37i9dQZF1DXcBWIGoYBM5M"; // Today's Top Hits
// Desired name for the new YouTube Music playlist
const ytPlaylistName = "Today's Top Hits (from Spotify)";
await transferPlaylist(spotifyPlaylistId, ytPlaylistName);
}
main().catch((err) => {
console.error("❌ Unexpected error:", err);
process.exit(1);
});
Note: 위 스니펫은 검색 및 매칭 단계만 수행합니다.
실제로 YouTube Music 재생목록을 만들고 트랙을 추가하려면 매칭 단계가 끝난 후createYTMusicPlaylist(이전 섹션 참고)를 호출하고, 쿠키 문자열과matchedTracks에서 얻은videoId배열을 전달하세요.
📦 프로젝트 실행
# Compile & run with ts-node (development)
npx ts-node src/index.ts
# Or compile first, then run the JavaScript output
npm run build # assuming you added a "build" script that runs tsc
node dist/index.js
🧩 다음은?
- 오류 처리 – 실패한 검색을 재시도하고, 퍼지 매칭으로 대체합니다.
- 배치 처리 – 새 플레이리스트에 트랙을 대량으로 추가해 속도를 높입니다.
- Spotify용 OAuth – 개인 플레이리스트를 읽거나 사용자의 라이브러리에 쓰기 위해 Authorization Code 흐름을 사용합니다.
- CLI 래퍼 – 스크립트를 간단한 명령줄 도구(
npx spotify-to-ytmusic …)로 노출합니다.
🎉 즐거운 코딩 되세요!
이제 몇 가지 명령만으로 Spotify 플레이리스트를 YouTube Music에 복사할 수 있는 완전한 TypeScript 기반 파이프라인이 준비되었습니다. 코드를 자유롭게 수정하고, 로깅을 추가하거나, 더 큰 자동화 스위트에 통합해 보세요.
import { YTMusic } from "ytmusic-api";
import SpotifyWebApi from "spotify-web-api-node";
async function main() {
// -------------------------------------------------
// 1️⃣ Get Spotify playlist tracks
// -------------------------------------------------
const spotify = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID!,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
redirectUri: "http://localhost:8888/callback",
});
// Get an access token (you can also use a refresh token)
const { body: tokenData } = await spotify.clientCredentialsGrant();
spotify.setAccessToken(tokenData.access_token);
// Replace with your playlist ID
const playlistId = "YOUR_SPOTIFY_PLAYLIST_ID";
const data = await spotify.getPlaylistTracks(playlistId, {
fields: "items(track(name,artists(name)))",
limit: 100,
});
const tracks = data.body.items.map((item) => ({
name: item.track!.name,
artist: (item.track as any).artists[0]?.name || "Unknown",
}));
console.log(`Found ${tracks.length} tracks`);
// -------------------------------------------------
// 2️⃣ Initialise YouTube Music client
// -------------------------------------------------
const ytmusic = new YTMusic();
await ytmusic.initialize();
// -------------------------------------------------
// 3️⃣ Search & match each track (first 5 as demo)
// -------------------------------------------------
for (const track of tracks.slice(0, 5)) {
const query = `${track.name} ${track.artist}`;
const results = await ytmusic.searchSongs(query);
if (results.length > 0) {
console.log(`✓ ${track.name} → ${results[0].name}`);
} else {
console.log(`✗ ${track.name} – Not found`);
}
// Respect rate limits
await new Promise((r) => setTimeout(r, 300));
}
}
main().catch(console.error);
이제 완전한 TypeScript 기반 파이프라인을 통해 Spotify 플레이리스트를 YouTube Music에 손쉽게 복사할 수 있습니다. 코드를 자유롭게 조정하고, 로깅을 추가하거나, 더 큰 자동화 시스템에 통합해 보세요.
📦 준비된 솔루션
- spotify-to-youtube – 트랙 매칭을 위한 NPM 패키지
- SpotTransfer – 전체 마이그레이션 도구 (GUI)
- spotify_to_ytmusic – 파이썬 CLI 도구
🛠️ 견고한 구현을 위한 팁
- Rate limiting – API 호출 사이에 지연(
300 ms등)을 추가하여 스로틀링을 방지합니다. - Error handling – 일부 트랙은 정확히 일치하지 않을 수 있으므로 수동 검토를 위해 로그에 기록합니다.
- Fuzzy matching – 매칭 정확도를 높이기 위해 퍼지 문자열 라이브러리(예:
fuzzball)를 사용합니다. - Batch processing – 큰 플레이리스트의 경우 청크 단위로 처리하고 중간 결과를 지속합니다.
- Logging – 나중에 수동으로 추가할 수 있도록 실패한 매치를 CSV/JSON 형태로 보관합니다.
🚀 스크립트 확장
- Sync scheduling – 주기적인 업데이트를 위해 크론 작업으로 스크립트를 실행합니다.
- Two‑way sync – 양쪽 플랫폼에서 추가/삭제를 감지하고 동기화 상태를 유지합니다.
- Web UI – 간단한 프론트엔드를 위해 로직을 Express.js 앱으로 감쌉니다.
- Discord bot – 슬래시 명령어를 통해 기능을 제공합니다.
전체 소스 코드(위 스크립트 포함)는 GitHub에서 확인할 수 있습니다. 즐거운 코딩 되세요!
📚 리소스
- Spotify Web API TypeScript SDK –
- ytmusic-api –
- youtube-music-ts-api –
- Spotify Developer Documentation –