Auto-DJ 사건 파일: YouTube 습관으로부터 스스로 학습하는 음악 브레인 구축하기
요약
YouTube 시청 기록과 행동 패턴을 분석하여 개인화된 음악 플레이리스트를 생성하는 'Auto-DJ' 시스템 구축 가이드를 소개합니다. 브라우저 확장 프로그램, 백엔드, 머신러닝 모델을 결합한 풀스택 프로젝트의 설계 과정을 다룹니다.
핵심 포인트
- YouTube 시청 이벤트를 감지하는 브라우저 확장 프로그램 설계
- 사용자의 시청 패턴(스킵, 반복 재생 등)을 데이터화하는 방법
- 데이터 수집을 위한 이벤트 API 및 배치(Batch) 처리 전략
- 개인화된 음악 추천을 위한 데이터 파이프라인 구축
브라우징 습관을 AI 오케스트라로 바꾸기 위한 3~5개월간의 현장 가이드
프롤로그: 스킵 버튼에서 시작된 사건
모든 위대한 조사는 작고 짜증 나는 무언가에서 시작됩니다. 당신의 조사는 어느 화요일 밤에 시작됩니다. 당신은 YouTube의 rabbit hole(토끼굴)에 빠져 3시간째 헤매고 있습니다. lo-fi beats, 베트남 시티팝(city-pop)의 숨겨진 명곡, 의도치 않게 두 번이나 보게 된 라이브 세션 등등 — 그리고 당신은 생각합니다. 누군가는 이 패턴에 주목해야 해.
그 누군가는 바로 당신이 될 것입니다. 청취자로서가 아니라, 탐정으로서 말이죠.
사건 파일은 이렇습니다: 당신의 YouTube 기록 어딘가에는 _패턴(pattern)_이 있습니다. 어떤 형태죠. 당신은 어떤 노래는 4초 만에 스킵합니다. 어떤 노래는 자신도 모르게 연속으로 세 번씩 다시 듣습니다. 당신은 항상 밤 11시에 synthwave를 듣게 되고, 오후 6시 이전에는 절대 듣지 않습니다. 지금 당장 그 패턴은 보이지 않습니다. YouTube가 검색조차 거의 허용하지 않고, 추론하는 것은 더더욱 불가능하게 만드는 히스토리 페이지 여기저기에 흩어져 있기 때문입니다. 앞으로 몇 달 동안 당신의 임무는 그 패턴을 가시화하는 악기를 만드는 것입니다. 즉, 당신이 실제로 무엇을 하는지 관찰하고, 그것이 무엇을 의미하는지 파악하며, 마치 당신을 잘 아는 누군가가 만든 것 같은 플레이리스트를 당신에게 다시 건네주는 시스템을 구축하는 것입니다. 왜냐하면 실제로 그렇기 때문입니다. 그 누군가는 바로 당신이라는 단 하나의 데이터셋(dataset of one)으로 학습된, 당신이 직접 만든 기계입니다.
이것은 주말 동안 끝낼 수 있는 프로젝트가 아닙니다. 작은 오케스트라를 구축하는 것에 더 가깝습니다. 현장 요원으로서의 브라우저 확장 프로그램(browser extension), 증거 보관소로서의 백엔드(backend), 법의학 실험실로서의 몇 가지 ML(머신러닝) 모델, 그리고 최종 공연이 펼쳐지는 무대인 프론트엔드(frontend)가 필요합니다. 모든 조각은 서로에게 의존합니다. 그것이 이 프로젝트를 어렵게 만드는 요소이며, 동시에 모든 레이어에 당신의 손길을 담아 공개적으로 천천히 진행할 가치가 있는 이유이기도 합니다.
모든 좋은 다큐멘터리가 시작되는 방식대로 시작해 봅시다. 범죄 현장에서 엔딩 크레딧까지 노래 한 곡을 따라가 보는 것입니다.
제1장: 반복되는 후렴구 사건
노래 한 곡의 여정 따라가기
오후 9시 47분입니다. 당신은 YouTube 영상 하나를 클릭합니다: "Kiss of Life – Midas Touch (Live Performance)". 41초 동안 시청하다가 후렴구로 건너뛰고, 다시 90초를 더 본 뒤 탭을 닫습니다. 이 평범한 순간이 결국 "Monday Chill"이라는 플레이리스트를 형성하기 위해서는 다음과 같은 과정이 일어나야 합니다:
-
목격자 (브라우저 확장 프로그램, Browser Extension). YouTube 탭 안에 조용히 자리 잡고 있는 콘텐츠 스크립트 (content script)가 비디오 요소의 로드를 감지합니다. 스크립트는 페이지에서 비디오 ID, 제목, 채널 이름, 재생 시간을 읽어옵니다. 그리고 속기사가 메모를 하듯
<video>요소의timeupdate,pause,seeked,ended이벤트를 관찰합니다: 0:00에 재생, 1:32로 이동, 3:04에 일시정지, 총 시청 시간 41초 + 90초 = 210초 중 131초 (62%). -
제보 (이벤트 API, Event API). 서버를 계속 두드리는 눈치 없는 확장 프로그램이 되고 싶지 않으므로, 매 순간이 아닌 일정 간격으로(batched) 이 관찰 내용들을 작은 JSON 페이로드로 묶어 백엔드로 POST 요청을 보냅니다:
{video_id, title, channel, watch_segments, timestamp, session_id}. -
증거 보관소 (데이터베이스, Database). FastAPI 백엔드가 제보를 받으면 이를 검증하고 PostgreSQL에 가공되지 않은 이벤트 행(raw event row)을 기록합니다. 아직은 별다른 영리한 작업이 일어나지 않습니다. 이는 단지 증거물 보관 체계(chain-of-custody)를 구축하는 단계입니다. 가공되지 않은 신호(raw signal)를 절대 잃어버려서는 안 됩니다. 나중에 수행할 모든 영리한 작업들은 이 신호로부터 '파생(derived)'될 것이며, 파생된 데이터는 언제든 재계산(recomputable) 가능해야 하기 때문입니다.
-
실험실 작업 (ML 파이프라인, 오프라인). 정해진 일정에 따라 — 예를 들어 매일 밤 한 번 — 파이프라인이 깨어나 새로운 원시 이벤트를 찾아 실제 탐정 업무를 수행합니다:
- 이것이 음악 영상인가? (분류, classification)
- 이것의 실제 곡 제목은 무엇인가? (제목 파싱 및 외부 조회를 통한 메타데이터 해결, metadata resolution)
- 어떤 소리나 느낌이 나는가? (오디오 임베딩 및 무드 태깅, audio embedding + mood tagging)
- 이것이 당신이 시청한 다른 모든 것과 어떻게 어울리는가? (행동 신호: 시청 %를 통한 암묵적 평점, 건너뛰기 패턴, 재재생 횟수, 시간대 등, behavioral signal)
사건 기록부 (Vector store + 관계형 테이블 (relational tables)). 곡의 오디오 임베딩 (audio embedding)은 pgvector를 통해 벡터 컬럼에 기록되고, 메타데이터는 일반적인 관계형 테이블에 저장되며, 행동 점수 (behavioral score)는 새로운 증거가 들어올 때마다 용의자의 파일을 업데이트하는 형사처럼 점진적으로 업데이트됩니다.
-
정체 공개 (플레이리스트 생성기 (Playlist Generator)). 주기적으로 클러스터링 (clustering) 작업이 전체 사건 기록부 — 즉, 당신의 모든 곡, 임베딩, 무드 태그, 시간대별 패턴 — 를 살펴보고 이들을 그룹화합니다. "chill", "lofi", 또는 "acoustic" 태그가 붙은, 분위기 있고 에너지가 낮으며 저녁 시간대에 집중된 트랙들의 클러스터가 나타나고, 대부분의 청취가 월요일에 발생했기 때문에 "Monday Chill"이라는 이름이 붙게 됩니다.
-
방송 (프론트엔드 (Frontend) + 플레이어 (Player)). 당신의 React 앱이 백엔드 (backend)에 쿼리(query)를 보내 트랙이 포함된 플레이리스트 목록을 받아와 이를 렌더링 (render)합니다. 재생 버튼을 누르면 어색하게 YouTube iframe을 삽입하는 대신, 개인적 용도로 정당하게 추출한 오디오를 스트리밍하거나, 제어 가능한 YouTube 플레이어로 전환하며, 그 재생 정보 또한 기록하여 다시 피드백 루프 (feedback loop)에 공급합니다.
이것이 엔드 투 엔드 (end to end)로 이루어진 전체 유기체입니다. 6주 후에는 이것이 당연하게 느껴질 것입니다. 지금은 그 구조를 주목하세요: 관찰 (observe) → 전송 (transmit) → 저장 (store) → 이해 (understand) → 조직화 (organize) → 제시 (present), 그리고 1단계로 다시 돌아가 원을 완성하는 피드백 루프 (feedback loop)가 있습니다. 이 가이드의 모든 단계는 이러한 기관 중 하나를 구축합니다.
아키텍처 (architecture), 초보자를 위한 설명
[Browser: YouTube 탭]
|
content script (비디오 요소를 감시)
...
쉽게 말해, 브라우저 확장 프로그램 (browser extension)은 이 시스템에서 가공되지 않은 YouTube 행동을 볼 수 있는 유일한 부분이며, 따라서 여러분의 센서 역할을 합니다. 그 이후의 모든 과정은 가공되지 않은 노이즈를 점진적으로 구조화된 의미로 증류(distilling)하는 과정입니다. 즉, 처음에는 "이것이 무엇인가", 그다음에는 "어떤 소리가 나는가", 그다음에는 "당신이 이것을 시청했다는 것이 무엇을 의미하는가", 마지막으로 "이것이 어떤 노래 그룹에 속하는가"를 파악하는 것입니다. 파이프라인 (pipeline)은 단지 "실시간 대신 정해진 일정에 따라 실행되는 이러한 증류 단계들의 연속"을 일컫는 이름일 뿐입니다. 오디오 분석 (audio analysis)과 머신러닝 추론 (ML inference)은 브라우징을 하는 동안 인라인 (inline)으로 실행하기에는 너무 느리기 때문에 이 방식이 중요합니다.
왜 하필 이 도구들인가 (더 화려한 대안들이 아닌 이유)
-
Django나 순수 Flask가 아닌 FastAPI. 여러분은 단순한 CRUD 엔드포인트와 ML 추론 엔드포인트가 섞인 환경을 노출하게 될 것입니다. Pydantic을 통한 FastAPI의 자동 요청 검증 (request validation) 덕분에 "왜 내 확장 프로그램이 잘못된 페이로드 (malformed payload)를 보내서 데이터를 조용히 오염시켰을까" 고민하며 밤을 지새우는 일이 줄어듭니다. 또한, 오디오를 스트리밍하거나 외부 API (메타데이터 조회)를 호출할 때 차단 (blocking) 없이 처리할 수 있는 비동기 (async) 지원도 매우 중요합니다.
-
별도의 벡터 데이터베이스가 아닌 PostgreSQL + pgvector. Pinecone이나 Weaviate를 구축할 수도 있습니다. 하지만 개인 규모의 프로젝트(수천 곡, 많아야 수만 곡 — 수십억 곡이 아님)에서는, 실행하고 보안을 유지하며 백업해야 할 서비스를 하나 더 늘리는 비용을 들여 아직 발생하지도 않은 문제를 해결하는 셈이 됩니다.
pgvector를 사용하면 관계형 메타데이터 (제목, 채널, 무드 태그)와 임베딩 (embeddings)을 동일한 데이터베이스에 유지할 수 있습니다. 따라서 "이 노래와 유사하면서 '공부' 태그가 붙어 있고 오후 9시 이후에 시청한 노래를 찾아라"와 같은 쿼리는 두 시스템 간의 조인 (join)이 아니라, 벡터 거리 연산자를 사용한 단 하나의 SQL 쿼리로 해결됩니다. -
거대한 엔드투엔드 트랜스포머 (end-to-end transformer)가 아닌 오디오 분류를 위한 CNN (또는 사전 학습된 오디오 모델). "이것이 음성인가, 음악인가, 아니면 소음인가" 및 "어떤 소리가 나는가" 수준의 오디오 분류 (audio classification)는 스펙트로그램 (spectrograms) 상에서 작동하는 비교적 작은 합성곱 구조 (convolutional architectures, 예: YAMNet, VGGish)를 통해 이미 잘 해결되었습니다.
트랙이 고에너지 일렉트로닉인지 부드러운 어쿠스틱 세션인지를 판단하는 데 GPT급 컴퓨팅 파워는 필요하지 않습니다. 이는 컴퓨팅 예산을 사람이 감당할 수 있는 수준으로 유지하기 위한 의도적인 선택입니다. 아마 노트북이나 소규모 클라우드 인스턴스에서 실행하게 될 것이지, GPU 클러스터에서는 아닐 것입니다.
- 오디오 자체가 아닌 태깅을 위한 트랜스포머(Transformers). 트랜스포머가 진정으로 복잡성을 발휘하는 곳은 _텍스트_입니다. 지저분한 YouTube 제목(
이 가이드의 나머지 부분을 관통하는 멘탈 모델(mental model)은 다음과 같습니다. 이 시스템에는 항상 감지하고 있는 신경계 (nervous system) (확장 프로그램 + 이벤트 API), 당신이 잠든 사이 작동하며 가공되지 않은 감각을 구조화된 이해로 변환하는 잠재의식 (subconscious) (야간 파이프라인), 그리고 큐레이션된 설명 가능한 결과를 제시하는 의식적인 목소리 (conscious voice) (프론트엔드)가 있습니다. 신경계를 망가뜨리면 아무것도 감지되지 않습니다. 잠재의식을 망가뜨리면 감각들이 처리되지 않은 채 쌓이게 됩니다. 의식적인 목소리를 망가뜨리면 그 모든 이해가 표현될 곳이 없어집니다. 당신은 앞으로 몇 달 동안 이 각각의 기관들을 구축하게 될 것이며 — 이 부분이 중요합니다 — 이들은 서로 연결되기 전에 각각 독립적으로 테스트하고 데모할 수 있도록 구축될 것입니다. 이것이 모든 마일스톤(milestone)마다 작동하는 프로토타입을 보유한 프로젝트와, 운이 좋다면 마감 직전 주에야 겨우 "작동"하는 프로젝트 사이의 차이점입니다.
제2장: 학습 로드맵 — 5개월, 6단계
당신은 기본적인 Python과 JavaScript를 알고 있습니다. 하지만 아직 이 조각들이 서로 어떻게 통신하는지, 혹은 오디오와 행동을 데이터로서 어떻게 생각해야 하는지는 모릅니다. 따라서 이 로드맵은 퇴적층처럼 기술을 쌓아 올리도록 (stack skills like sediment layers) 설계되었습니다. 각 단계는 얇고 투박하지만 실제로 작동하는 시스템의 한 조각을 남기며, 각 단계의 조각은 다음 단계가 구축할 토대가 됩니다. 당신은 머릿속에 연결되지 않은 6가지 개념만을 가진 채 빈 캔버스를 바라보게 되는 일은 결코 없을 것입니다. 당신은 항상 이미 실행 중인 무언가를 확장해 나갈 것입니다.
매 단계는 주당 가용 시간에 따라 2~4주로 잡으세요. 주당 10시간 이상 투자할 수 있다면 짧은 쪽을 목표로 하고, 풀타임 직업을 병행하며 진행한다면 긴 쪽을 택하세요. 5개월은 "편안하게 깊이 있게 배우는" 속도입니다. 이미 속도가 빠르고 몇 가지 "도전적인 (stretch)" 항목들을 제외한다면 3개월로도 달성 가능합니다.
단계 0: 정찰 (Recon) (1주 차가 본격적으로 시작되기 전, 3~5일간)
목표 (Goal): 각 구성 요소가 거의 아무런 기능도 수행하지 않더라도, 향후 제작될 모든 조각의 뼈대를 구축합니다.
- 레포지토리 (repo) 구조 생성:
extension/,backend/,ml/,frontend/,infra/. - 하나의 라우트인
GET /ping을 포함한 FastAPI "hello world"를 로컬에서 실행. - youtube.com 페이지에서 콘솔에
"extension loaded"를 로그로 남기는 기능 외에는 아무것도 하지 않는 빈 Chrome 확장 프로그램 (extension)을 개발자 모드로 로드. - 로컬 PostgreSQL 인스턴스 실행 (이 단계에서는 Docker가 유용합니다 —
docker run postgres).
학습 정지 지점 (Learning pit stop):
- 15분: FastAPI 공식 "First Steps" 튜토리얼 — 서버를 실행할 수 있을 정도만 학습.
- 20분: Chrome 확장 프로그램 "Getting Started" 튜토리얼 — 매니페스트 (manifest)와 콘텐츠 스크립트 (content scripts)에 집중하고, 나머지는 일단 건너뜁니다.
준비 완료 확인 기준: YouTube 탭을 열었을 때 콘솔에서 "extension loaded"를 확인하고, 브라우저에서 http://localhost:8000/ping을 입력했을 때 {"status": "ok"}가 나오면 됩니다. 그것으로 충분합니다. 두 개의 독립적인 심박동(heartbeats)이 확인된 것입니다.
단계 1: 목격자 (The Witness) — 이벤트 트래킹 (1~3주 차)
목표 (Goal): 실제 YouTube 시청 이벤트를 기록하는 Chrome 확장 프로그램과 이를 저장하는 백엔드 엔드포인트 (endpoint) 구축.
습득 기술: DOM 이벤트 리스너 (event listeners), Chrome 확장 프로그램 메시징 (content script <-> background script), REST API 설계 기초, 이벤트 로그를 위한 관계형 스키마 (relational schema) 설계.
구축할 내용:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기