1+ import os
2+
3+ import requests
14from fastapi import APIRouter , Depends , HTTPException
25from sqlalchemy .orm import Session
36from sqlalchemy import extract , text , func
47from app .database import get_db , get_mongodb , get_redis
5- from app .emotion .models import model_index_to_db_emotion_id
8+ from app .emotion .models import model_index_to_db_emotion_id , DiaryEmotionTag
69from app .emotion .router import predict_emotion
710from app .statistics .models import EmotionStatistics
811from app .user .auth import get_current_user
1114 DiaryPreviewResponse , RecommendSongResponse
1215from app .user .models import User
1316from app .embedding .models import kobert , save_diary_embedding , split_sentences , get_user_preferred_genres , \
14- get_songs_by_genre , get_song_embeddings , calculate_similarity
17+ get_songs_by_genre
1518from app .transaction import transactional_session
1619from typing import List , Set
1720import logging
2225from datetime import datetime
2326
2427router = APIRouter ()
28+ YOUTUBE_API_KEY = os .getenv ("YOUTUBE_API_KEY" )
2529
2630logging .basicConfig (level = logging .INFO )
2731logger = logging .getLogger (__name__ )
@@ -554,7 +558,7 @@ async def create_diary_with_emotion_based_recommendation(
554558 1 : ["R&B/Soul" , "댄스" ],
555559 2 : ["인디음악" , "R&B/Soul" ],
556560 3 : ["R&B/Soul" , "인디음악" ],
557- 4 : ["록/메탈 " , "인디음악" ],
561+ 4 : ["R&B/Soul " , "인디음악" ],
558562 5 : ["발라드" , "록/메탈" ],
559563 6 : ["발라드" , "R&B/Soul" ],
560564 7 : ["랩/힙합" , "록/메탈" ]
@@ -638,7 +642,7 @@ async def create_diary_with_emotion_based_recommendation(
638642 }
639643 ))
640644
641- top_3_raw = heapq .nlargest (10 , heap , key = lambda x : (x [0 ], x [1 ]))
645+ top_3_raw = heapq .nlargest (7 , heap , key = lambda x : (x [0 ], x [1 ]))
642646
643647 recent_lyrics = get_recently_recommended_lyrics (session , user_id = current_user .id )
644648
@@ -678,20 +682,43 @@ async def create_diary_with_emotion_based_recommendation(
678682 session .commit ()
679683 session .refresh (new_diary )
680684
685+ for eid , score in sorted (emotion_vote_counter .items (), key = lambda x : - x [1 ])[:3 ]:
686+ avg_score = score / len (sentences )
687+ if avg_score >= 0.15 :
688+ tag = DiaryEmotionTag (
689+ diary_id = new_diary .id ,
690+ emotiontype_id = model_index_to_db_emotion_id [eid ],
691+ score = round (avg_score , 4 )
692+ )
693+ session .add (tag )
694+
681695 save_diary_embedding (session , new_diary .id , combined_embedding )
682696
683- recommended_songs = [
684- {
685- "song_id" : match ["song_id" ],
686- "song_name" : match ["metadata" ]["song_name" ],
687- "best_lyric" : " " .join (match ["lyric_chunk" ]),
688- "similarity_score" : round (float (sim ), 4 ),
689- "album_image" : match ["metadata" ]["album_image" ],
690- "artist" : match ["metadata" ]["artist" ],
691- "genre" : match ["metadata" ]["genre" ]
692- }
693- for sim , match in top_3
694- ]
697+ recommended_songs = []
698+ for sim , match in top_3 :
699+ song_data = RecommendedSong (
700+ diary_id = new_diary .id ,
701+ song_id = match ["song_id" ],
702+ song_name = match ["metadata" ]["song_name" ],
703+ artist = match ["metadata" ]["artist" ],
704+ genre = match ["metadata" ]["genre" ],
705+ album_image = match ["metadata" ]["album_image" ],
706+ best_lyric = " " .join (match ["lyric_chunk" ]),
707+ similarity_score = round (float (sim ), 4 )
708+ )
709+ session .add (song_data )
710+ session .flush () # ✅ id 부여를 위해 flush
711+
712+ recommended_songs .append ({
713+ "id" : song_data .id , # ✅ 여기에 id 포함
714+ "song_id" : song_data .song_id ,
715+ "song_name" : song_data .song_name ,
716+ "artist" : song_data .artist ,
717+ "genre" : song_data .genre ,
718+ "album_image" : song_data .album_image ,
719+ "best_lyric" : song_data .best_lyric ,
720+ "similarity_score" : song_data .similarity_score ,
721+ })
695722
696723 for song_data in recommended_songs :
697724 new_song = RecommendedSong (
@@ -714,6 +741,7 @@ async def create_diary_with_emotion_based_recommendation(
714741 "content" : new_diary .content ,
715742 "emotiontype_id" : emotion_id_db ,
716743 "confidence" : confidence_full ,
744+ "best_sentence" : new_diary .best_sentence ,
717745 "created_at" : new_diary .created_at ,
718746 "updated_at" : new_diary .updated_at ,
719747 "recommended_songs" : recommended_songs ,
@@ -745,6 +773,8 @@ def get_diary(
745773 RecommendedSong .diary_id == diary .id
746774 ).order_by (RecommendedSong .similarity_score .desc ()).all ()
747775
776+ emotion_tags = db .query (DiaryEmotionTag ).filter (DiaryEmotionTag .diary_id == diary .id ).all ()
777+
748778 main_song = db .query (RecommendedSong ).get (diary .main_recommended_song_id )
749779
750780 return DiaryResponse (
@@ -753,9 +783,11 @@ def get_diary(
753783 content = diary .content ,
754784 emotiontype_id = diary .emotiontype_id ,
755785 confidence = diary .confidence ,
786+ best_sentence = diary .best_sentence ,
756787 created_at = diary .created_at ,
757788 updated_at = diary .updated_at ,
758789 recommended_songs = recommended_songs ,
790+ emotion_tags = emotion_tags ,
759791 main_recommend_song = main_song ,
760792 top_emotions = []
761793 )
@@ -808,6 +840,7 @@ def get_all_diaries(
808840 content = diary .content ,
809841 emotiontype_id = diary .emotiontype_id ,
810842 confidence = diary .confidence ,
843+ best_sentence = diary .best_sentence ,
811844 created_at = diary .created_at ,
812845 updated_at = diary .updated_at ,
813846 recommended_songs = recommended_songs ,
@@ -916,17 +949,91 @@ async def set_main_song(
916949 current_user = Depends (get_current_user ),
917950 db : Session = Depends (get_db )
918951):
919- diary = db .query (Diary ).filter (Diary .id == diary_id , Diary .user_id == current_user .id ).first ()
952+ diary = db .query (Diary ).filter (
953+ Diary .id == diary_id ,
954+ Diary .user_id == current_user .id
955+ ).first ()
956+
920957 if not diary :
921958 raise HTTPException (status_code = 404 , detail = "일기를 찾을 수 없습니다." )
922959
923960 song = db .query (RecommendedSong ).filter (
924961 RecommendedSong .id == recommended_song_id ,
925962 RecommendedSong .diary_id == diary .id
926963 ).first ()
964+
927965 if not song :
928966 raise HTTPException (status_code = 400 , detail = "추천곡이 일기와 일치하지 않습니다." )
929967
968+ if not song .youtube_url :
969+ query = f"{ song .song_name } { ' ' .join (song .artist )} "
970+ api_url = "https://www.googleapis.com/youtube/v3/search"
971+ params = {
972+ "part" : "snippet" ,
973+ "q" : query ,
974+ "type" : "video" ,
975+ "maxResults" : 1 ,
976+ "key" : YOUTUBE_API_KEY
977+ }
978+
979+ res = requests .get (api_url , params = params )
980+ data = res .json ()
981+
982+ if not data .get ("items" ):
983+ raise HTTPException (status_code = 404 , detail = "YouTube 영상이 없습니다." )
984+
985+ video_id = data ["items" ][0 ]["id" ]["videoId" ]
986+ song .youtube_url = f"https://www.youtube.com/watch?v={ video_id } "
987+
988+ # 🎯 대표곡 저장
930989 diary .main_recommended_song_id = song .id
931990 db .commit ()
932- return {"message" : "대표 음악이 설정되었습니다." }
991+
992+ return {
993+ "message" : "대표 음악이 설정되었습니다." ,
994+ "youtube_url" : song .youtube_url
995+ }
996+
997+ @router .get ("/recommended-songs/{recommended_song_id}/youtube-link-direct" )
998+ def get_direct_youtube_link (
999+ recommended_song_id : int ,
1000+ db : Session = Depends (get_db )
1001+ ):
1002+ song = db .query (RecommendedSong ).filter (RecommendedSong .id == recommended_song_id ).first ()
1003+ if not song :
1004+ raise HTTPException (status_code = 404 , detail = "추천곡을 찾을 수 없습니다." )
1005+
1006+ # 이미 저장된 경우 → 캐시된 URL 반환
1007+ if song .youtube_url :
1008+ return {
1009+ "recommended_song_id" : song .id ,
1010+ "youtube_url" : song .youtube_url
1011+ }
1012+
1013+ # YouTube 검색 API 호출
1014+ query = f"{ song .song_name } { ' ' .join (song .artist )} "
1015+ api_url = "https://www.googleapis.com/youtube/v3/search"
1016+ params = {
1017+ "part" : "snippet" ,
1018+ "q" : query ,
1019+ "type" : "video" ,
1020+ "maxResults" : 1 ,
1021+ "key" : YOUTUBE_API_KEY
1022+ }
1023+
1024+ res = requests .get (api_url , params = params )
1025+ data = res .json ()
1026+
1027+ if not data .get ("items" ):
1028+ raise HTTPException (status_code = 404 , detail = "YouTube 영상이 없습니다." )
1029+
1030+ video_id = data ["items" ][0 ]["id" ]["videoId" ]
1031+ youtube_url = f"https://www.youtube.com/watch?v={ video_id } "
1032+
1033+ song .youtube_url = youtube_url
1034+ db .commit ()
1035+
1036+ return {
1037+ "recommended_song_id" : song .id ,
1038+ "youtube_url" : youtube_url
1039+ }
0 commit comments