SQLite κΈ°λ° κ³‘Β·μ€λμ€ νΉμ± μ μ₯, FAISS μ μ¬λ κ²μ, JWT μΈμ¦(bcrypt), FastAPI, μ μ μΉ UIλ₯Ό ν¬ν¨ν μμ μΆμ² μμ€ν λ² μ΄μ€μ λλ€.
music-rec-base/
βββ api/
β βββ main.py # FastAPI: μΈμ¦, μΆμ²/κ²μ/μΈν°λμ
/μ’μμ API
βββ data/
β βββ melon_pipeline.py # Melon ν¬λ‘€λ§ + YouTube λ§€μΉ + μ€λμ€ νΉμ± μΆμΆ
β βββ database.py # SQLite μ΄κΈ°ν/CRUD, interactionsΒ·μ’μμ μ‘°ν
β βββ faiss_index.py # μ€λμ€ νΉμ± κΈ°λ° FAISS μΈλ±μ€
βββ algorithms/
β βββ base.py # μΆμ²κΈ° μΈν°νμ΄μ€ λ² μ΄μ€
β βββ faiss_cbf.py # λ² μ΄μ€ μν: FAISS μ€λμ€ μ μ¬λ + ν€μλ κ²μ
βββ evaluation/
β βββ metrics.py # μΆμ² νκ° μ νΈ
βββ static/ # μΉ UI (μΈμ¦ κ²μ΄νΈ, νΒ·κ²μΒ·μ’μμ, νλ μ΄μ΄)
βββ utils/
β βββ config.py # νκ²½λ³μ, JWT, μμ§/μΆμ² μ€μ
βββ docs/ # νμ₯ λ‘컬 λ¬Έμ (Git μ μΈ β `.gitignore`μ `docs/`)
βββ requirements.txt
βββ README.md
- Python 3.11 κΆμ₯
- macOS/Linux κΈ°μ€ λͺ λ Ήμ΄
ffmpegμ€μΉ κΆμ₯ (yt-dlpμ€λμ€ νμ²λ¦¬μ νμ)
cd music-rec-base
python3.11 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -U pip setuptools wheel
pip install -r requirements.txtμ°Έκ³ :
scikit-surpriseλ μΌλΆ macOS νκ²½μμ λΉλ μ΄μκ° μμ΄ κΈ°λ³Έ μμ‘΄μ±μμ λΆλ¦¬λμ΄ μμ΅λλ€. νμ μμλ§ μλ μ€μΉνμΈμ.pip install scikit-surprise==1.1.4
ν
νλ¦Ώ .env.example μ 볡μ¬ν΄ .env λ₯Ό λ§λ λ€ κ°μ μ±μ°μΈμ.
cp .env.example .envDB_PATH=./music_rec.db
JWT_SECRET=κ°ν_λλ€_λ¬Έμμ΄λ‘_λ³κ²½
JWT_EXPIRE_HOURS=24
# μ ν
# GEMINI_API_KEY=
# YOUTUBE_API_KEY=| λ³μ | μ€λͺ |
|---|---|
DB_PATH |
SQLite νμΌ κ²½λ‘ (κΈ°λ³Έ ./music_rec.db) |
JWT_SECRET |
JWT μλͺ ν€. λ‘컬 κΈ°λ³Έκ°μ κ°λ°μ©μ΄λ―λ‘ λ°°ν¬ μ λ°λμ λ³κ²½ |
JWT_EXPIRE_HOURS |
μ‘μΈμ€ ν ν° λ§λ£ μκ°(μκ° λ¨μ) |
GEMINI_API_KEY |
LangChain/LangGraphΒ·Gemini μ°λ μ |
YOUTUBE_API_KEY |
YouTube Data API μ¬μ© μ |
python data/melon_pipeline.py- λ©λ‘ μ¬λ¬ μ°¨νΈ (κΈ°λ³Έ: TOP100, HOT100, μΌκ°, μ£Όκ°) + μ₯λ₯΄λ³ 곑 λͺ©λ‘ (νμ΄μ§, 곑 μλ
utils/config.pyμμ μ‘°μ ) - (μ ν)
**MELON_EXTRA_SONG_PAGE_URLS** β νλ μ΄λ¦¬μ€νΈ λ± κ³‘ 리μ€νΈ URL. HTMLμ΄ μ°¨νΈν(lst50)μ΄κ±°λ μ₯λ₯΄ν(wrap_song_info)μΌ λλ§ νμ±λ©λλ€ (JS μ μ© νμ΄μ§λ μ€ν¨ν μ μμ). - μμ€λ₯Ό ν©μΉ λ€ μ λͺ©+κ°μ κΈ°μ€ μ€λ³΅ μ κ±° β
yt-dlpλ‘ YouTube λ§€μΉ - μ€λμ€ λ€μ΄λ‘λ ν
librosaνΉμ±(MFCC, BPM, energy λ±) μΆμΆ songs+audio_featuresλμ μ μ₯, μμ μ€λμ€ νμΌ μ¦μ μμ
곑 μΒ·μμ€λ utils/config.pyμ MELON_CHART_URLS, MELON_MAX_SONGS_PER_CHART, MELON_GENRE_CODES, MELON_MAX_SONGS_PER_GENREλ‘ μ‘°μ ν©λλ€.
YouTube Data API v3λ‘ λ©νλ§ λͺ¨μΌλ λͺ¨λμ΄ νμνλ©΄ νμμ λ³λ νμΌμ λλ©΄ λ©λλ€. requirements.txtμ google-api-python-clientλ κ·Έλ° νμ₯μ©μ
λλ€.
uvicorn api.main:app --reloadλΈλΌμ°μ μμ http://localhost:8000 μ μ.
μ± μμ μ λμ:
- DB μ΄κΈ°ν λ° κ³‘ λ°μ΄ν° λ‘λ
MusicFaissIndexμΈλ±μ€ μΊμ λ‘λ λλ μ¬κ΅¬μΆ (μ€ν¨ν΄λ 곑 API λ±μ κΈ°λ μ μ§)- FAISSκ° μ μ λΉλλλ©΄
**faiss_cbf**μν μκ³ λ¦¬μ¦μ΄recommendersμ μλ λ±λ‘λ©λλ€.
MusicFaissIndex μΈμ€ν΄μ€λ **startup()μ try λΈλ‘ μ**μμλ§ μμ±λ©λλ€. ν μκ³ λ¦¬μ¦μ λ±λ‘ν λλ κ°μ ν¨μ μμμ λ°©κΈ λ§λ faiss_index λ³μλ₯Ό μμ±μμ λκΈ°λ©΄ λ©λλ€. λ‘μ§μ λ€λ₯Έ ν¨μλ‘ λΉΌλ©΄ μΈμλ‘ μ λ¬νκ±°λ, λͺ¨λ μ μ(μ: app.state)μ 보κ΄νλ μμΌλ‘ μ°Έμ‘°λ₯Ό μ μ§νμΈμ.
| λ©μλ | κ²½λ‘ | μΈμ¦ | μ€λͺ |
|---|---|---|---|
| POST | /api/signup |
β | νμκ°μ
β access_token |
| POST | /api/login |
β | λ‘κ·ΈμΈ β access_token |
| GET | /api/me |
Bearer | νμ¬ μ¬μ©μλͺ |
| GET | /api/likes |
Bearer | DB κΈ°μ€ μ’μμ 곑 λͺ©λ‘ |
| GET | /api/songs |
β | ?limit=30&genre=... |
| GET | /api/songs/{song_id} |
β | 곑 μμΈ |
| POST | /api/recommend |
β | 곑 κΈ°λ° μΆμ² |
| POST | /api/recommend/user |
Bearer | μ μ κΈ°λ° μΆμ² |
| POST | /api/search |
β | μμ°μ΄ κ²μ (search_by_query ꡬν μ) |
| POST | /api/interact |
Bearer | play / like / skip / unlike |
| GET | /api/algorithms |
β | { "algorithms": [...] } Β· λΉμ΄ μμΌλ©΄ notice μλ΄ λ¬Έκ΅¬ ν¬ν¨ |
보νΈλ μλν¬μΈνΈλ ν€λμ Authorization: Bearer <access_token>μ λΆμ
λλ€.
FAISSκ° λΉμ΄ μκ±°λ μ΄κΈ°νμ μ€ν¨νλ©΄ recommendersκ° λΉμ΄ μμ μ μμ΅λλ€. κ·Έλλ μ¬μ΄λλ°μ μλ΄κ° νμλκ³ , GET /api/algorithms μλ΅μ noticeλ₯Ό μ°Έκ³ νλ©΄ λ©λλ€.
- λ‘κ·ΈμΈ/νμκ°μ
ν ν ν°μ λΈλΌμ°μ
localStorageμ μ μ₯λ©λλ€. - μκ³ λ¦¬μ¦ 0κ°μΌ λ μ¬μ΄λλ°μ νμ μλ΄ λ¬Έκ΅¬κ° λμ΅λλ€.
- ν: μΆμ² μ΅λ 3곑 그리λ + κ°μ νλ©΄μ κ²μμ°½(λ³λ κ²μ ν μμ).
- μ’μμ: μλ²
interactionsμ μ μ₯λλ©°, μλ‘κ³ μΉ¨ μGET /api/likesλ‘ λ³΅μλ©λλ€.
sqlite3 music_rec.db.tables
.schema
SELECT * FROM songs LIMIT 10;algorithms/base.pyκΈ°λ°μΌλ‘ μΆμ²κΈ° ꡬν (fitλμself.is_fitted = True)- μ°Έκ³ κ΅¬ν:
algorithms/faiss_cbf.py(FaissContentRecommender) fit(...),recommend(...)μΈν°νμ΄μ€ μ€μ- μ μ λ§μΆ€μ΄ νμνλ©΄
recommend_for_user(user_id, top_k)ꡬν - μμ°μ΄ κ²μμ΄ νμνλ©΄
search_by_query(query, top_k)ꡬν - λ°ν ν¬λ§·μ
{song_id: score}λμ λ리 κΆμ₯ api/main.pyμstartup()μμ μΈμ€ν΄μ€ μμ±Β·νμ΅Β·recommenders["ν€"]λ±λ‘
docs/project-overview.mdβ κΈ°νΒ·μν λΆλ΄docs/team-faq.mdβ μ€νΒ·μ½λ Q&A
docs/ λ νμ₯ λ‘컬 νμΈμ©μΌλ‘ .gitignoreμ λμ΄ Gitμ ν¬ν¨νμ§ μμ΅λλ€. νμμκ²λ νμ μ νμΌμ λ°λ‘ 곡μ νμΈμ.