zaewc/Cantletnoise

★ 0Forks 0TypeScriptGitHub ↗Compare

README

cantletnoise — Spotify Genre Map Explorer

내 Spotify 플레이리스트의 모든 곡을 장르 맵(Every Noise at Once 좌표) 위에 시각화하고, 하단 채팅창에 듣고 싶은 느낌을 자연어로 적으면 로컬 LLM이 해석해 맵을 해당 영역으로 이동시키고 매칭 곡을 골라줍니다.

https://observablehq.com/@mjbo/genre-map-explorer-for-spotify 의 아이디어를 참고했습니다.

⚠️ 왜 "오디오 특성"이 아니라 "장르"인가

원본 노트북은 Spotify의 audio-features(energy/valence 등)로 곡을 배치합니다. 하지만 Spotify는 2024‑11‑27부로 audio-features, recommendations, related-artists 엔드포인트를 신규 앱에 차단했습니다(이제 403). (공지)

그래서 이 앱은 아직 사용 가능한 아티스트 장르(/artists)를 받아, Every Noise at Once가 매긴 4,536개 장르의 2D 좌표에 곡을 배치합니다. 한 곡의 위치 = 아티스트 장르들의 평균 좌표.

Spotify가 장르를 비워둔 아티스트는 다음 순서로 장르를 추론해 배치합니다:

  1. 로컬 LLM(Ollama)에게 아티스트 이름으로 장르 추정
  2. 그래도 없으면 MusicBrainz(웹, 키 불필요) 태그 조회
  3. 끝내 미상이면 내 라이브러리 전체의 무게중심 근처에 배치 (버려지지 않음)
  • 세로축: ↑ 전자적/기계적 · ↓ 어쿠스틱/유기적
  • 가로축: ← 조밀/앰비언트 · → 경쾌/날카로움

1. 설치 / 실행

npm install
npm run dev          # http://127.0.0.1:5173 에서 열림

2. Spotify 앱 만들기 (최초 1회)

  1. https://developer.spotify.com/dashboard → Create app
  2. Redirect URI에 정확히 다음을 추가: http://127.0.0.1:5173/ (Spotify는 127.0.0.1 루프백 http를 허용합니다. localhost는 안 됩니다.)
  3. 생성된 Client ID를 복사
  4. src/config.ts 의 SPOTIFY_CLIENT_ID 에 붙여넣기 → 저장
  5. 앱 화면에서 Spotify 연결

모든 설정은 UI가 아니라 src/config.ts 코드 상수로 관리합니다. Client Secret은 필요 없습니다(PKCE). 토큰은 브라우저 localStorage에만 저장됩니다.

3. 로컬 LLM 띄우기 (Docker / Ollama)

자연어 채팅과 장르 추론을 가장 똑똑하게 쓰려면 로컬 LLM 서버가 필요합니다.

# Ollama를 Docker로 실행 (브라우저 호출을 위해 OLLAMA_ORIGINS=* 필수)
docker run -d --name ollama -p 11434:11434 -e OLLAMA_ORIGINS=* ollama/ollama

# 모델 받기 (가볍고 JSON 출력 잘하는 모델 추천)
docker exec -it ollama ollama pull llama3.2

엔드포인트/모델은 src/config.ts의 LLM_URL, LLM_MODEL 에서 변경.

LLM 서버가 없거나 응답이 없으면 채팅은 키워드 매칭으로, 장르 추론은 MusicBrainz 웹 조회로 자동 폴백하므로 Docker 없이도 동작합니다.

4. 사용법

  • 마우스 휠 = 확대/축소, 드래그 = 이동, 점에 마우스 = 곡 정보, 점 클릭 = 미리듣기
  • 하단 채팅: 비 오는 밤에 어울리는 몽환적인 곡, 운동할 때 듣는 신나는 힙합 등
  • AI가 장르를 고르면 맵이 그 영역으로 날아가고 매칭 곡이 하이라이트됩니다. 우측 상단 패널의 곡을 클릭하면 미리듣기.

알려진 한계

  • 장르 추론은 LLM/웹 품질에 의존합니다. 무명 아티스트는 추정이 부정확할 수 있고, 끝내 못 찾으면 라이브러리 무게중심 근처(회색 점)에 배치됩니다. 추정 곡은 툴팁에 (추정) 표시.
  • MusicBrainz 폴백은 1req/sec 제한이 있어 config의 상한 (musicBrainzMaxLookups)까지만 조회합니다.
  • 미리듣기(preview_url) 는 Spotify 정책상 다수 곡에서 null입니다.
  • 대형 라이브러리는 최초 로딩 시 API 호출이 많아 시간이 걸릴 수 있습니다(레이트리밋 자동 대기).

구조

src/
  config.ts          # 코드단 설정 (Client ID, LLM, 추론 옵션) — UI 노출 없음
  data/genres.json   # Every Noise에서 추출한 4,536개 장르 좌표/색
  genres.ts          # 좌표 정규화 + 장르 이름 → 좌표 해석(퍼지 매칭)
  spotify.ts         # PKCE 인증 + 플레이리스트/트랙/아티스트 장르 로드
  genre-infer.ts     # 장르 없는 아티스트 보정 (LLM → MusicBrainz)
  map.ts             # canvas + d3-zoom 시각화, 히트테스트, flyTo 애니메이션
  chat.ts            # 자연어 → 장르 (Ollama LLM, 키워드 폴백)
  main.ts            # 전체 연결, 로딩/추론 오케스트레이션, 미리듣기

Issues