diff --git a/CLAUDE.md b/CLAUDE.md index f531031..51ab67b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,6 +84,7 @@ Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포. | `/api/agent-office/` | `agent-office:8000` | AI 에이전트 오피스 API + WebSocket | | `/webhook`, `/webhook/` | `deployer:9000` | Gitea Webhook | | `/media/music/` | `/data/music/` (파일 직접 서빙) | 생성된 오디오 파일 | +| `/media/videos/` | `/data/videos/` (파일 직접 서빙) | YouTube 영상 MP4 | | `/media/travel/.thumb/` | `/data/thumbs/` (파일 직접 서빙) | 썸네일 캐시 | | `/media/travel/` | `/data/travel/` (파일 직접 서빙) | 원본 사진 | | `/assets/` | 정적 파일 (장기 캐시) | Vite 해시 파일 | @@ -249,10 +250,11 @@ docker compose up -d - 15:40 평일 — 총 자산 스냅샷 저장 (`save_daily_snapshot`) ### music-lab (music-lab/) -- 듀얼 프로바이더 음악 생성 서비스 (Suno API + 로컬 MusicGen) +- 듀얼 프로바이더 음악 생성 서비스 (Suno API + 로컬 MusicGen) + YouTube 영상 제작 + 시장 조사 트렌드 - 생성된 오디오 파일: `/app/data/music/` (Nginx가 `/media/music/`로 직접 서빙) -- DB: `/app/data/music.db` (music_tasks, music_library 테이블) -- 파일 구조: `main.py`, `db.py`, `suno_provider.py`, `local_provider.py` +- 생성된 영상 파일: `/app/data/videos/` (Nginx가 `/media/videos/`로 직접 서빙) +- DB: `/app/data/music.db` (music_tasks, music_library, video_projects, revenue_records, market_trends, trend_reports 테이블) +- 파일 구조: `main.py`, `db.py`, `suno_provider.py`, `local_provider.py`, `video_producer.py`, `market.py` - 생성 흐름: POST generate (provider 지정) → task_id 반환 → BackgroundTask → 파일 저장 → 라이브러리 자동 등록 **Provider 구조** @@ -288,12 +290,51 @@ docker compose up -d | POST | `/api/music/lyrics/library` | 가사 저장 | | PUT | `/api/music/lyrics/library/{id}` | 가사 수정 | | DELETE | `/api/music/lyrics/library/{id}` | 가사 삭제 | +| POST | `/api/music/video-project` | 영상 프로젝트 생성 (track_id, format, target_countries) | +| GET | `/api/music/video-projects` | 영상 프로젝트 목록 | +| GET | `/api/music/video-project/{id}` | 영상 프로젝트 상세 | +| POST | `/api/music/video-project/{id}/render` | FFmpeg 렌더링 시작 (BackgroundTask) | +| GET | `/api/music/video-project/{id}/export` | 내보내기 패키지 (mp4+thumbnail+metadata.json) | +| DELETE | `/api/music/video-project/{id}` | 영상 프로젝트 삭제 | +| GET | `/api/music/revenue/dashboard` | 수익 대시보드 (총수익·조회수·가중평균 RPM) | +| GET | `/api/music/revenue` | 수익 기록 목록 | +| POST | `/api/music/revenue` | 수익 기록 추가 (UNIQUE: yt_video_id+record_month+country) | +| PUT | `/api/music/revenue/{id}` | 수익 기록 수정 | +| DELETE | `/api/music/revenue/{id}` | 수익 기록 삭제 | +| POST | `/api/music/market/ingest` | agent-office 트렌드 수신 + 리포트 생성 | +| GET | `/api/music/market/trends` | 트렌드 조회 (country, genre, source, days=7) | +| GET | `/api/music/market/report/latest` | 최신 트렌드 리포트 | +| GET | `/api/music/market/report` | 트렌드 리포트 목록 (limit=10) | +| GET | `/api/music/market/suggest` | Suno 프롬프트 추천 (limit=5) | **환경변수** - `SUNO_API_KEY`: Suno API 키 (미설정 시 Suno provider 비활성화) - `MUSIC_AI_SERVER_URL`: 로컬 MusicGen 서버 URL (미설정 시 local provider 비활성화) - `MUSIC_MEDIA_BASE`: 오디오 파일 공개 URL prefix (기본 `/media/music`) - `MUSIC_DATA_PATH`: NAS 오디오 파일 저장 경로 (기본 `./data/music`) +- `PEXELS_API_KEY`: Pexels 스톡 이미지 API 키 (미설정 시 슬라이드쇼 Pexels 이미지 비활성화) +- `ANTHROPIC_API_KEY`: Claude Haiku — YouTube 메타데이터 생성 + 시장 인사이트 (미설정 시 폴백 텍스트) +- `VIDEO_DATA_DIR`: 영상 파일 저장 경로 (기본 `/app/data/videos`) + +**video_projects 테이블** +- format: `visualizer` | `slideshow` +- status: `pending` → `rendering` → `done` | `failed` +- target_countries: JSON 배열 (예: `["BR","US"]`) +- render_params: JSON 객체 (FFmpeg 파라미터 캐시) + +**revenue_records 테이블** +- UNIQUE(yt_video_id, record_month, country) +- avg_rpm 계산: 가중평균 `SUM(revenue_usd)/SUM(views)*1000` (단순 AVG 아님) + +**market_trends 테이블** +- source: `youtube` | `google_trends` | `billboard` +- metadata: JSON 객체 (원본 API 응답 부분) +- 인덱스: `idx_mt_country_source` ON (country, source, collected_at DESC) + +**trend_reports 테이블** +- report_date UNIQUE — 같은 날 두 번 ingest 시 upsert +- top_genres: JSON 배열 `[{genre, score, countries}]` (최대 10개, score 내림차순) +- recommended_styles: JSON 배열 `[{genre, suno_prompt, target_countries, reason}]` (최대 5개) **music_library 테이블 (확장 컬럼)** - `provider`: `suno` | `local` — 생성에 사용된 프로바이더 @@ -492,6 +533,16 @@ docker compose up -d - `CONVERSATION_RATE_PER_MIN`: 채팅당 분당 최대 메시지 (기본 6) - `LOTTO_BACKEND_URL`: 기본 `http://lotto:8000` - `LOTTO_CURATOR_MODEL`: 기본 `claude-sonnet-4-5` +- `YOUTUBE_DATA_API_KEY`: YouTube Data API v3 키 (미설정 시 YouTube trending 수집 skip) + +**YouTubeResearchAgent (`agents/youtube.py`)** +- `agent_id = "youtube"` — AGENT_REGISTRY에 등록 +- 09:00 매일 `on_schedule()` → 국가별 YouTube 트렌딩 + Google Trends + Billboard Top20 수집 → music-lab push +- `on_command("research", {countries: []})` → 수동 트리거 (백그라운드 asyncio.create_task) +- 수집 소스: `youtube_researcher.py` (fetch_youtube_trending, fetch_google_trends, fetch_billboard_top20) +- DB: `youtube_research_jobs` 테이블에 실행 이력 기록 +- 동시실행 방지: `self.state == "working"` 체크 후 거부 +- 월요일 08:00 `send_weekly_report()` → music-lab 최신 리포트 → 텔레그램 발송 **텔레그램 자연어 대화 (옵션 B)** - 슬래시 명령이 아닌 일반 문장을 보내면 Claude Haiku 4.5가 응답 @@ -505,6 +556,8 @@ docker compose up -d - 매주 월요일 07:00 — 로또 큐레이터 브리핑 (`lotto_curate`) - 60초 간격 — 유휴 에이전트 휴식 체크 (`idle_check_job`) - ~~09:15 매일 — 청약 매칭 데일리 리포트~~ (Task 2026-04-28에서 폐기. realestate-lab의 push 트리거로 전환) +- 09:00 매일 — YouTube 트렌드 수집 (`youtube_research`) → music-lab `/api/music/market/ingest` push +- 매주 월요일 08:00 — YouTube 주간 리포트 텔레그램 발송 (`youtube_weekly_report`) **RealestateAgent (`agents/realestate.py`)** - 진입점: `on_new_matches(matches: list[dict]) -> {sent, sent_ids, message_id}` @@ -534,6 +587,8 @@ docker compose up -d | POST | `/api/agent-office/realestate/notify` | realestate-lab 전용 push 수신 → 텔레그램 송신 | | GET | `/api/agent-office/states` | 전체 에이전트 상태 조회 | | GET | `/api/agent-office/conversation/stats` | 텔레그램 자연어 대화 토큰·캐시 통계 (`days` 필터) | +| POST | `/api/agent-office/youtube/research` | YouTube 트렌드 수집 수동 트리거 (body: `{countries: []}`) | +| GET | `/api/agent-office/youtube/research/status` | 마지막 수집 작업 상태 | ### personal (personal/) - 개인 서비스 (포트폴리오 + 블로그 + 투두 통합)