diff --git a/app/home/api/routers/v1/home.py b/app/home/api/routers/v1/home.py index e813de5..bc5bccf 100644 --- a/app/home/api/routers/v1/home.py +++ b/app/home/api/routers/v1/home.py @@ -787,6 +787,8 @@ async def tag_images_if_not_exist( if null_tags: tag_datas = await autotag_images([img.img_url for img in null_tags]) + print(tag_datas) + for tag, tag_data in zip(null_tags, tag_datas): tag.img_tag = tag_data.model_dump(mode="json") diff --git a/app/lyric/schemas/lyric.py b/app/lyric/schemas/lyric.py index 13a3100..be1e65b 100644 --- a/app/lyric/schemas/lyric.py +++ b/app/lyric/schemas/lyric.py @@ -42,7 +42,7 @@ class GenerateLyricRequest(BaseModel): "region": "군산", "detail_region_info": "군산 신흥동 말랭이 마을", "language": "Korean", - "m_id" : 1, + "m_id" : 2, "orientation" : "vertical" } """ diff --git a/app/lyric/worker/lyric_task.py b/app/lyric/worker/lyric_task.py index 42c9321..a5d5175 100644 --- a/app/lyric/worker/lyric_task.py +++ b/app/lyric/worker/lyric_task.py @@ -169,7 +169,7 @@ async def generate_subtitle_background( ) -> None: logger.info(f"[generate_subtitle_background] task_id: {task_id}, {orientation}") creatomate_service = CreatomateService(orientation=orientation) - template = await creatomate_service.get_one_template_data_async(creatomate_service.template_id) + template = await creatomate_service.get_one_template_data(creatomate_service.template_id) pitchings = creatomate_service.extract_text_format_from_template(template) subtitle_generator = SubtitleContentsGenerator() diff --git a/app/song/services/song.py b/app/song/services/song.py index 9955e6b..decd4fb 100644 --- a/app/song/services/song.py +++ b/app/song/services/song.py @@ -7,7 +7,7 @@ from sqlalchemy import Connection, text from sqlalchemy.exc import SQLAlchemyError from app.utils.logger import get_logger -from app.lyrics.schemas.lyrics_schema import ( +from app.lyric.schemas.lyrics_schema import ( AttributeData, PromptTemplateData, SongFormData, diff --git a/app/utils/autotag.py b/app/utils/autotag.py index ae2213f..176cee1 100644 --- a/app/utils/autotag.py +++ b/app/utils/autotag.py @@ -27,7 +27,20 @@ async def autotag_images(image_url_list : list[str]) -> list[dict]: #tag_list "motion_recommended" : list(MotionRecommended) }for image_url in image_url_list] - image_result_tasks = [chatgpt.generate_structured_output(image_autotag_prompt, image_input_data, image_input_data['img_url'], False) for image_input_data in image_input_data_list] - image_result_list = await asyncio.gather(*image_result_tasks) + image_result_tasks = [chatgpt.generate_structured_output(image_autotag_prompt, image_input_data, image_input_data['img_url'], False, silent = True) for image_input_data in image_input_data_list] + image_result_list = await asyncio.gather(*image_result_tasks, return_exceptions=True) + MAX_RETRY = 3 # 하드코딩, 어떻게 처리할지는 나중에 + for _ in range(MAX_RETRY): + failed_idx = [i for i, r in enumerate(image_result_list) if isinstance(r, Exception)] + print("Failed", failed_idx) + if not failed_idx: + break + retried = await asyncio.gather( + *[chatgpt.generate_structured_output(image_autotag_prompt, image_input_data_list[i], image_input_data_list[i]['img_url'], False, silent=True) for i in failed], + return_exceptions=True + ) + for i, result in zip(failed_idx, retried): + image_result_list[i] = result + print("Failed", failed_idx) return image_result_list \ No newline at end of file diff --git a/app/utils/chatgpt_prompt.py b/app/utils/chatgpt_prompt.py index 2138985..54f31f7 100644 --- a/app/utils/chatgpt_prompt.py +++ b/app/utils/chatgpt_prompt.py @@ -101,12 +101,14 @@ class ChatgptService: prompt : Prompt, input_data : dict, img_url : Optional[str] = None, - img_detail_high : bool = False + img_detail_high : bool = False, + silent : bool = False ) -> BaseModel: - prompt_text = prompt.build_prompt(input_data) - + prompt_text = prompt.build_prompt(input_data, silent) + logger.debug(f"[ChatgptService] Generated Prompt (length: {len(prompt_text)})") - logger.info(f"[ChatgptService] Starting GPT request with structured output with model: {prompt.prompt_model}") + if not silent: + logger.info(f"[ChatgptService] Starting GPT request with structured output with model: {prompt.prompt_model}") # GPT API 호출 #response = await self._call_structured_output_with_response_gpt_api(prompt_text, prompt.prompt_output, prompt.prompt_model) diff --git a/app/utils/creatomate.py b/app/utils/creatomate.py index ccff6ae..7b7795e 100644 --- a/app/utils/creatomate.py +++ b/app/utils/creatomate.py @@ -31,11 +31,13 @@ response = await creatomate.make_creatomate_call(template_id, modifications) import copy import time +from enum import StrEnum from typing import Literal import httpx from app.utils.logger import get_logger +from app.utils.prompts.schemas.image import SpaceType,Subject,Camera,MotionRecommended,NarrativePhase from config import apikey_settings, creatomate_settings, recovery_settings # 로거 설정 @@ -226,8 +228,9 @@ DVST0003 = "e1fb5b00-1f02-4f63-99fa-7524b433ba47" DHST0001 = "660be601-080a-43ea-bf0f-adcf4596fa98" DHST0002 = "3f194cc7-464e-4581-9db2-179d42d3e40f" DHST0003 = "f45df555-2956-4a13-9004-ead047070b3d" +DVST0001T = "fe11aeab-ff29-4bc8-9f75-c695c7e243e6" HST_LIST = [DHST0001,DHST0002,DHST0003] -VST_LIST = [DVST0001,DVST0002,DVST0003] +VST_LIST = [DVST0001,DVST0002,DVST0003, DVST0001T] SCENE_TRACK = 1 AUDIO_TRACK = 2 @@ -238,7 +241,7 @@ def select_template(orientation:OrientationType): if orientation == "horizontal": return DHST0001 elif orientation == "vertical": - return DVST0001 + return DVST0001T else: raise @@ -399,14 +402,6 @@ class CreatomateService: return copy.deepcopy(data) - # 하위 호환성을 위한 별칭 (deprecated) - async def get_one_template_data_async(self, template_id: str) -> dict: - """특정 템플릿 ID로 템플릿 정보를 조회합니다. - - Deprecated: get_one_template_data()를 사용하세요. - """ - return await self.get_one_template_data(template_id) - def parse_template_component_name(self, template_source: list) -> dict: """템플릿 정보를 파싱하여 리소스 이름을 추출합니다.""" @@ -441,65 +436,72 @@ class CreatomateService: return tag_list - async def template_matching_taged_image( + def template_matching_taged_image( self, - template_id : str, - taged_image_list : list, - address : str + template : dict, + taged_image_list : list, # [{"image_name" : str , "image_tag" : dict}] + music_url: str, + address : str, + duplicate : bool = False ) -> list: - - template_data = await self.get_one_template_data(template_id) - source_elements = template_data["source"]["elements"] + source_elements = template["source"]["elements"] template_component_data = self.parse_template_component_name(source_elements) modifications = {} - for idx, (template_component_name, template_type) in enumerate(template_component_data.items()): + for slot_idx, (template_component_name, template_type) in enumerate(template_component_data.items()): match template_type: case "image": - # modifications[template_component_name] = somethingtagedimage() + image_score_list = self.calculate_image_slot_score_multi(taged_image_list, template_component_name) + maximum_idx = image_score_list.index(max(image_score_list)) + if duplicate: + selected = taged_image_list[maximum_idx] + else: + selected = taged_image_list.pop(maximum_idx) + image_name = selected["image_url"] + modifications[template_component_name] =image_name pass case "text": if "address_input" in template_component_name: modifications[template_component_name] = address - # modifications["audio-music"] = music_url - - async def template_connect_resource_blackbox( - self, - template_id: str, - image_url_list: list[str], - music_url: str, - address: str = None - ) -> dict: - """템플릿 정보와 이미지/가사/음악 리소스를 매핑합니다. - - Note: - - 이미지는 순차적으로 집어넣기 - - 가사는 개행마다 한 텍스트 삽입 - - Template에 audio-music 항목이 있어야 함 - """ - template_data = await self.get_one_template_data(template_id) - template_component_data = self.parse_template_component_name( - template_data["source"]["elements"] - ) - modifications = {} - - for idx, (template_component_name, template_type) in enumerate( - template_component_data.items() - ): - match template_type: - case "image": - modifications[template_component_name] = image_url_list[ - idx % len(image_url_list) - ] - case "text": - if "address_input" in template_component_name: - modifications[template_component_name] = address - modifications["audio-music"] = music_url - return modifications + + def calculate_image_slot_score_multi(self, taged_image_list : list[dict], slot_name : str): + image_tag_list = [taged_image["image_tag"] for taged_image in taged_image_list] + slot_tag_dict = self.parse_slot_name_to_tag(slot_name) + image_score_list = [0] * len(image_tag_list) + + for slot_tag_cate, slot_tag_item in slot_tag_dict.items(): + if slot_tag_cate == "narrative_preference": + slot_tag_narrative = slot_tag_item + continue + for idx, image_tag in enumerate(image_tag_list): + if slot_tag_item.value in image_tag[slot_tag_cate]: #collect! + image_score_list[idx] += 1 / (len(image_tag) - 1) + + for idx, image_tag in enumerate(image_tag_list): + image_narrative_score = image_tag["narrative_preference"][slot_tag_narrative] + image_score_list[idx] = image_score_list[idx] * image_narrative_score + + return image_score_list + + def parse_slot_name_to_tag(self, slot_name : str) -> dict[str, StrEnum]: + tag_list = slot_name.split("-") + space_type = SpaceType(tag_list[0]) + subject = Subject(tag_list[1]) + camera = Camera(tag_list[2]) + motion = MotionRecommended(tag_list[3]) + narrative = NarrativePhase(tag_list[4]) + tag_dict = { + "space_type" : space_type, + "subject" : subject, + "camera" : camera, + "motion_recommended" : motion, + "narrative_preference" : narrative, + } + return tag_dict def elements_connect_resource_blackbox( self, @@ -700,14 +702,6 @@ class CreatomateService: original_response={"last_error": str(last_error)}, ) - # 하위 호환성을 위한 별칭 (deprecated) - async def make_creatomate_custom_call_async(self, source: dict) -> dict: - """템플릿 없이 Creatomate에 커스텀 렌더링 요청을 보냅니다. - - Deprecated: make_creatomate_custom_call()을 사용하세요. - """ - return await self.make_creatomate_custom_call(source) - async def get_render_status(self, render_id: str) -> dict: """렌더링 작업의 상태를 조회합니다. @@ -731,14 +725,6 @@ class CreatomateService: response.raise_for_status() return response.json() - # 하위 호환성을 위한 별칭 (deprecated) - async def get_render_status_async(self, render_id: str) -> dict: - """렌더링 작업의 상태를 조회합니다. - - Deprecated: get_render_status()를 사용하세요. - """ - return await self.get_render_status(render_id) - def calc_scene_duration(self, template: dict) -> float: """템플릿의 전체 장면 duration을 계산합니다.""" total_template_duration = 0.0 diff --git a/app/utils/prompts/prompts.py b/app/utils/prompts/prompts.py index 99e9473..5907e84 100644 --- a/app/utils/prompts/prompts.py +++ b/app/utils/prompts/prompts.py @@ -31,12 +31,13 @@ class Prompt(): return prompt_template - def build_prompt(self, input_data:dict) -> str: + def build_prompt(self, input_data:dict, silent:bool = False) -> str: verified_input = self.prompt_input_class(**input_data) build_template = self.prompt_template build_template = build_template.format(**verified_input.model_dump()) - logger.debug(f"build_template: {build_template}") - logger.debug(f"input_data: {input_data}") + if not silent: + logger.debug(f"build_template: {build_template}") + logger.debug(f"input_data: {input_data}") return build_template marketing_prompt = Prompt( diff --git a/app/video/api/routers/v1/video.py b/app/video/api/routers/v1/video.py index 4808669..cff8219 100644 --- a/app/video/api/routers/v1/video.py +++ b/app/video/api/routers/v1/video.py @@ -25,7 +25,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.database.session import get_session from app.user.dependencies.auth import get_current_user from app.user.models import User -from app.home.models import Image, Project, MarketingIntel +from app.home.models import Image, Project, MarketingIntel, ImageTag from app.lyric.models import Lyric from app.song.models import Song, SongTimestamp from app.utils.creatomate import CreatomateService @@ -39,6 +39,7 @@ from app.video.schemas.video_schema import ( VideoRenderData, ) from app.video.worker.video_task import download_and_upload_video_to_blob +from app.video.services.video import get_image_tags_by_task_id from config import creatomate_settings @@ -337,17 +338,24 @@ async def generate_video( ) # 6-1. 템플릿 조회 (비동기) - template = await creatomate_service.get_one_template_data_async( + template = await creatomate_service.get_one_template_data( creatomate_service.template_id ) logger.debug(f"[generate_video] Template fetched - task_id: {task_id}") # 6-2. elements에서 리소스 매핑 생성 - modifications = creatomate_service.elements_connect_resource_blackbox( - elements=template["source"]["elements"], - image_url_list=image_urls, - music_url=music_url, - address=store_address + # modifications = creatomate_service.elements_connect_resource_blackbox( + # elements=template["source"]["elements"], + # image_url_list=image_urls, + # music_url=music_url, + # address=store_address + taged_image_list = await get_image_tags_by_task_id(task_id) + modifications = creatomate_service.template_matching_taged_image( + template = template, + taged_image_list = taged_image_list, + music_url = music_url, + address = store_address, + duplicate = True, ) logger.debug(f"[generate_video] Modifications created - task_id: {task_id}") @@ -413,7 +421,7 @@ async def generate_video( # f"[generate_video] final_template: {json.dumps(final_template, indent=2, ensure_ascii=False)}" # ) # 6-5. 커스텀 렌더링 요청 (비동기) - render_response = await creatomate_service.make_creatomate_custom_call_async( + render_response = await creatomate_service.make_creatomate_custom_call( final_template["source"], ) @@ -565,7 +573,7 @@ async def get_video_status( ) try: creatomate_service = CreatomateService() - result = await creatomate_service.get_render_status_async(creatomate_render_id) + result = await creatomate_service.get_render_status(creatomate_render_id) logger.debug( f"[get_video_status] Creatomate API response - creatomate_render_id: {creatomate_render_id}, status: {result.get('status')}" ) diff --git a/app/video/services/video.py b/app/video/services/video.py index ba9ea5f..8cd4a33 100644 --- a/app/video/services/video.py +++ b/app/video/services/video.py @@ -5,831 +5,857 @@ from fastapi import Request, status from fastapi.exceptions import HTTPException from sqlalchemy import Connection, text from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy import select, func +from app.database.session import AsyncSessionLocal +from app.home.models import Image, ImageTag, Project -from app.lyrics.schemas.lyrics_schema import ( - AttributeData, - PromptTemplateData, - SongFormData, - SongSampleData, - StoreData, -) -from app.utils.chatgpt_prompt import chatgpt_api +# from app.lyric.schemas.lyrics_schema import ( +# AttributeData, +# PromptTemplateData, +# SongFormData, +# SongSampleData, +# StoreData, +# ) + +# from app.utils.chatgpt_prompt import chatgpt_api from app.utils.logger import get_logger logger = get_logger("video") -async def get_store_info(conn: Connection) -> List[StoreData]: - try: - query = """SELECT * FROM store_default_info;""" - result = await conn.execute(text(query)) +# async def get_store_info(conn: Connection) -> List[StoreData]: +# try: +# query = """SELECT * FROM store_default_info;""" +# result = await conn.execute(text(query)) - all_store_info = [ - StoreData( - id=row[0], - store_info=row[1], - store_name=row[2], - store_category=row[3], - store_region=row[4], - store_address=row[5], - store_phone_number=row[6], - created_at=row[7], +# all_store_info = [ +# StoreData( +# id=row[0], +# store_info=row[1], +# store_name=row[2], +# store_category=row[3], +# store_region=row[4], +# store_address=row[5], +# store_phone_number=row[6], +# created_at=row[7], +# ) +# for row in result +# ] + +# result.close() +# return all_store_info +# except SQLAlchemyError as e: +# logger.error(f"SQLAlchemy error in get_store_info: {e}") +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected error in get_store_info: {e}") +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="알수없는 이유로 서비스 오류가 발생하였습니다", +# ) + + +# async def get_attribute(conn: Connection) -> List[AttributeData]: +# try: +# query = """SELECT * FROM attribute;""" +# result = await conn.execute(text(query)) + +# all_attribute = [ +# AttributeData( +# id=row[0], +# attr_category=row[1], +# attr_value=row[2], +# created_at=row[3], +# ) +# for row in result +# ] + +# result.close() +# return all_attribute +# except SQLAlchemyError as e: +# logger.error(f"SQLAlchemy error in get_attribute: {e}") +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected error in get_attribute: {e}") +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="알수없는 이유로 서비스 오류가 발생하였습니다", +# ) + + +# async def get_attribute(conn: Connection) -> List[AttributeData]: +# try: +# query = """SELECT * FROM attribute;""" +# result = await conn.execute(text(query)) + +# all_attribute = [ +# AttributeData( +# id=row[0], +# attr_category=row[1], +# attr_value=row[2], +# created_at=row[3], +# ) +# for row in result +# ] + +# result.close() +# return all_attribute +# except SQLAlchemyError as e: +# logger.error(f"SQLAlchemy error in get_attribute: {e}") +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected error in get_attribute: {e}") +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="알수없는 이유로 서비스 오류가 발생하였습니다", +# ) + + +# async def get_sample_song(conn: Connection) -> List[SongSampleData]: +# try: +# query = """SELECT * FROM song_sample;""" +# result = await conn.execute(text(query)) + +# all_sample_song = [ +# SongSampleData( +# id=row[0], +# ai=row[1], +# ai_model=row[2], +# genre=row[3], +# sample_song=row[4], +# ) +# for row in result +# ] + +# result.close() +# return all_sample_song +# except SQLAlchemyError as e: +# logger.error(f"SQLAlchemy error in get_sample_song: {e}") +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected error in get_sample_song: {e}") +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="알수없는 이유로 서비스 오류가 발생하였습니다", +# ) + + +# async def get_prompt_template(conn: Connection) -> List[PromptTemplateData]: +# try: +# query = """SELECT * FROM prompt_template;""" +# result = await conn.execute(text(query)) + +# all_prompt_template = [ +# PromptTemplateData( +# id=row[0], +# description=row[1], +# prompt=row[2], +# ) +# for row in result +# ] + +# result.close() +# return all_prompt_template +# except SQLAlchemyError as e: +# logger.error(f"SQLAlchemy error in get_prompt_template: {e}") +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected error in get_prompt_template: {e}") +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="알수없는 이유로 서비스 오류가 발생하였습니다", +# ) + + +# async def get_song_result(conn: Connection) -> List[PromptTemplateData]: +# try: +# query = """SELECT * FROM prompt_template;""" +# result = await conn.execute(text(query)) + +# all_prompt_template = [ +# PromptTemplateData( +# id=row[0], +# description=row[1], +# prompt=row[2], +# ) +# for row in result +# ] + +# result.close() +# return all_prompt_template +# except SQLAlchemyError as e: +# logger.error(f"SQLAlchemy error in get_song_result: {e}") +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected error in get_song_result: {e}") +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="알수없는 이유로 서비스 오류가 발생하였습니다", +# ) + + +# async def make_song_result(request: Request, conn: Connection): +# try: +# # 1. Form 데이터 파싱 +# form_data = await SongFormData.from_form(request) + +# logger.info(f"{'=' * 60}") +# logger.info(f"Store ID: {form_data.store_id}") +# logger.info(f"Lyrics IDs: {form_data.lyrics_ids}") +# logger.info(f"Prompt IDs: {form_data.prompts}") +# logger.info(f"{'=' * 60}") + +# # 2. Store 정보 조회 +# store_query = """ +# SELECT * FROM store_default_info WHERE id=:id; +# """ +# store_result = await conn.execute(text(store_query), {"id": form_data.store_id}) + +# all_store_info = [ +# StoreData( +# id=row[0], +# store_info=row[1], +# store_name=row[2], +# store_category=row[3], +# store_region=row[4], +# store_address=row[5], +# store_phone_number=row[6], +# created_at=row[7], +# ) +# for row in store_result +# ] + +# if not all_store_info: +# raise HTTPException( +# status_code=status.HTTP_404_NOT_FOUND, +# detail=f"Store not found: {form_data.store_id}", +# ) + +# store_info = all_store_info[0] +# logger.info(f"Store: {store_info.store_name}") + +# # 3. 속성 조회 -- 단계별 선택 프로세서시 구현 필요 없음 + +# # 4. Sample Song 조회 및 결합 +# combined_sample_song = None + +# if form_data.lyrics_ids: +# logger.info(f"[샘플 가사 조회] - {len(form_data.lyrics_ids)}개") + +# lyrics_query = """ +# SELECT sample_song FROM song_sample +# WHERE id IN :ids +# ORDER BY created_at; +# """ +# lyrics_result = await conn.execute( +# text(lyrics_query), {"ids": tuple(form_data.lyrics_ids)} +# ) + +# sample_songs = [ +# row.sample_song for row in lyrics_result.fetchall() if row.sample_song +# ] + +# if sample_songs: +# combined_sample_song = "\n\n".join( +# [f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)] +# ) +# logger.info(f"{len(sample_songs)}개의 샘플 가사 조회 완료") +# else: +# logger.info("샘플 가사가 비어있습니다") +# else: +# logger.info("선택된 lyrics가 없습니다") + +# # 5. 템플릿 가져오기 +# if not form_data.prompts: +# raise HTTPException( +# status_code=status.HTTP_400_BAD_REQUEST, +# detail="프롬프트 ID가 필요합니다", +# ) + +# logger.info("템플릿 가져오기") + +# prompts_query = """ +# SELECT * FROM prompt_template WHERE id=:id; +# """ + +# # ✅ 수정: store_query → prompts_query +# prompts_result = await conn.execute( +# text(prompts_query), {"id": form_data.prompts} +# ) + +# prompts_info = [ +# PromptTemplateData( +# id=row[0], +# description=row[1], +# prompt=row[2], +# ) +# for row in prompts_result +# ] + +# if not prompts_info: +# raise HTTPException( +# status_code=status.HTTP_404_NOT_FOUND, +# detail=f"Prompt not found: {form_data.prompts}", +# ) + +# prompt = prompts_info[0] +# logger.debug(f"Prompt Template: {prompt.prompt}") + +# # ✅ 6. 프롬프트 조합 +# updated_prompt = prompt.prompt.replace("###", form_data.attributes_str).format( +# name=store_info.store_name or "", +# address=store_info.store_address or "", +# category=store_info.store_category or "", +# description=store_info.store_info or "", +# ) + +# updated_prompt += f""" + +# 다음은 참고해야 하는 샘플 가사 정보입니다. + +# 샘플 가사를 참고하여 작곡을 해주세요. + +# {combined_sample_song} +# """ + +# logger.debug(f"[업데이트된 프롬프트]\n{updated_prompt}") + +# # 7. 모델에게 요청 +# generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt) + +# # 글자 수 계산 +# total_chars_with_space = len(generated_lyrics) +# total_chars_without_space = len( +# generated_lyrics.replace(" ", "") +# .replace("\n", "") +# .replace("\r", "") +# .replace("\t", "") +# ) + +# # final_lyrics 생성 +# final_lyrics = f"""속성 {form_data.attributes_str} +# 전체 글자 수 (공백 포함): {total_chars_with_space}자 +# 전체 글자 수 (공백 제외): {total_chars_without_space}자\r\n\r\n{generated_lyrics}""" + +# logger.debug("=" * 40) +# logger.debug(f"[translate:form_data.attributes_str:] {form_data.attributes_str}") +# logger.debug(f"[translate:total_chars_with_space:] {total_chars_with_space}") +# logger.debug(f"[translate:total_chars_without_space:] {total_chars_without_space}") +# logger.debug(f"[translate:final_lyrics:]\n{final_lyrics}") +# logger.debug("=" * 40) + +# # 8. DB 저장 +# insert_query = """ +# INSERT INTO song_results_all ( +# store_info, store_name, store_category, store_address, store_phone_number, +# description, prompt, attr_category, attr_value, +# ai, ai_model, genre, +# sample_song, result_song, created_at +# ) VALUES ( +# :store_info, :store_name, :store_category, :store_address, :store_phone_number, +# :description, :prompt, :attr_category, :attr_value, +# :ai, :ai_model, :genre, +# :sample_song, :result_song, NOW() +# ); +# """ + +# # ✅ attr_category, attr_value 추가 +# insert_params = { +# "store_info": store_info.store_info or "", +# "store_name": store_info.store_name, +# "store_category": store_info.store_category or "", +# "store_address": store_info.store_address or "", +# "store_phone_number": store_info.store_phone_number or "", +# "description": store_info.store_info or "", +# "prompt": form_data.prompts, +# "attr_category": ", ".join(form_data.attributes.keys()) +# if form_data.attributes +# else "", +# "attr_value": ", ".join(form_data.attributes.values()) +# if form_data.attributes +# else "", +# "ai": "ChatGPT", +# "ai_model": form_data.llm_model, +# "genre": "후크송", +# "sample_song": combined_sample_song or "없음", +# "result_song": final_lyrics, +# } + +# await conn.execute(text(insert_query), insert_params) +# await conn.commit() + +# logger.info("결과 저장 완료") + +# logger.info("전체 결과 조회 중...") + +# # 9. 생성 결과 가져오기 (created_at 역순) +# select_query = """ +# SELECT * FROM song_results_all +# ORDER BY created_at DESC; +# """ + +# all_results = await conn.execute(text(select_query)) + +# results_list = [ +# { +# "id": row.id, +# "store_info": row.store_info, +# "store_name": row.store_name, +# "store_category": row.store_category, +# "store_address": row.store_address, +# "store_phone_number": row.store_phone_number, +# "description": row.description, +# "prompt": row.prompt, +# "attr_category": row.attr_category, +# "attr_value": row.attr_value, +# "ai": row.ai, +# "ai_model": row.ai_model, +# "genre": row.genre, +# "sample_song": row.sample_song, +# "result_song": row.result_song, +# "created_at": row.created_at.isoformat() if row.created_at else None, +# } +# for row in all_results.fetchall() +# ] + +# logger.info(f"전체 {len(results_list)}개의 결과 조회 완료") + +# return results_list + +# except HTTPException: +# raise +# except SQLAlchemyError as e: +# logger.error(f"Database Error: {e}", exc_info=True) +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="데이터베이스 연결에 문제가 발생했습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected Error: {e}", exc_info=True) +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="서비스 처리 중 오류가 발생했습니다.", +# ) + + +# async def get_song_result(conn: Connection): # 반환 타입 수정 +# try: +# select_query = """ +# SELECT * FROM song_results_all +# ORDER BY created_at DESC; +# """ + +# all_results = await conn.execute(text(select_query)) + +# results_list = [ +# { +# "id": row.id, +# "store_info": row.store_info, +# "store_name": row.store_name, +# "store_category": row.store_category, +# "store_address": row.store_address, +# "store_phone_number": row.store_phone_number, +# "description": row.description, +# "prompt": row.prompt, +# "attr_category": row.attr_category, +# "attr_value": row.attr_value, +# "ai": row.ai, +# "ai_model": row.ai_model, +# "season": row.season, +# "num_of_people": row.num_of_people, +# "people_category": row.people_category, +# "genre": row.genre, +# "sample_song": row.sample_song, +# "result_song": row.result_song, +# "created_at": row.created_at.isoformat() if row.created_at else None, +# } +# for row in all_results.fetchall() +# ] + +# logger.info(f"전체 {len(results_list)}개의 결과 조회 완료") + +# return results_list +# except HTTPException: # HTTPException은 그대로 raise +# raise +# except SQLAlchemyError as e: +# logger.error(f"Database Error: {e}", exc_info=True) +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="데이터베이스 연결에 문제가 발생했습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected Error: {e}", exc_info=True) +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="서비스 처리 중 오류가 발생했습니다.", +# ) + + +# async def make_automation(request: Request, conn: Connection): +# try: +# # 1. Form 데이터 파싱 +# form_data = await SongFormData.from_form(request) + +# logger.info(f"{'=' * 60}") +# logger.info(f"Store ID: {form_data.store_id}") +# logger.info(f"{'=' * 60}") + +# # 2. Store 정보 조회 +# store_query = """ +# SELECT * FROM store_default_info WHERE id=:id; +# """ +# store_result = await conn.execute(text(store_query), {"id": form_data.store_id}) + +# all_store_info = [ +# StoreData( +# id=row[0], +# store_info=row[1], +# store_name=row[2], +# store_category=row[3], +# store_region=row[4], +# store_address=row[5], +# store_phone_number=row[6], +# created_at=row[7], +# ) +# for row in store_result +# ] + +# if not all_store_info: +# raise HTTPException( +# status_code=status.HTTP_404_NOT_FOUND, +# detail=f"Store not found: {form_data.store_id}", +# ) + +# store_info = all_store_info[0] +# logger.info(f"Store: {store_info.store_name}") + +# # 3. 속성 조회 -- 단계별 선택 프로세서시 구현 필요 없음 +# attribute_query = """ +# SELECT * FROM attribute; +# """ + +# attribute_results = await conn.execute(text(attribute_query)) + +# # 결과 가져오기 +# attribute_rows = attribute_results.fetchall() + +# formatted_attributes = "" +# selected_categories = [] +# selected_values = [] + +# if attribute_rows: +# attribute_list = [ +# AttributeData( +# id=row[0], +# attr_category=row[1], +# attr_value=row[2], +# created_at=row[3], +# ) +# for row in attribute_rows +# ] + +# # ✅ 각 category에서 하나의 value만 랜덤 선택 +# formatted_pairs = [] +# for attr in attribute_list: +# # 쉼표로 분리 및 공백 제거 +# values = [v.strip() for v in attr.attr_value.split(",") if v.strip()] + +# if values: +# # 랜덤하게 하나만 선택 +# selected_value = random.choice(values) +# formatted_pairs.append(f"{attr.attr_category} : {selected_value}") + +# # ✅ 선택된 category와 value 저장 +# selected_categories.append(attr.attr_category) +# selected_values.append(selected_value) + +# # 최종 문자열 생성 +# formatted_attributes = "\n".join(formatted_pairs) + +# logger.debug(f"[포맷팅된 문자열 속성 정보]\n{formatted_attributes}") +# else: +# logger.info("속성 데이터가 없습니다") +# formatted_attributes = "" + +# # 4. 템플릿 가져오기 +# logger.info("템플릿 가져오기 (ID=1)") + +# prompts_query = """ +# SELECT * FROM prompt_template WHERE id=1; +# """ + +# prompts_result = await conn.execute(text(prompts_query)) + +# row = prompts_result.fetchone() + +# if not row: +# raise HTTPException( +# status_code=status.HTTP_404_NOT_FOUND, +# detail="Prompt ID 1 not found", +# ) + +# prompt = PromptTemplateData( +# id=row[0], +# description=row[1], +# prompt=row[2], +# ) + +# logger.debug(f"Prompt Template: {prompt.prompt}") + +# # 5. 템플릿 조합 + +# updated_prompt = prompt.prompt.replace("###", formatted_attributes).format( +# name=store_info.store_name or "", +# address=store_info.store_address or "", +# category=store_info.store_category or "", +# description=store_info.store_info or "", +# ) + +# logger.debug("=" * 80) +# logger.debug("업데이트된 프롬프트") +# logger.debug("=" * 80) +# logger.debug(updated_prompt) +# logger.debug("=" * 80) + +# # 4. Sample Song 조회 및 결합 +# combined_sample_song = None + +# if form_data.lyrics_ids: +# logger.info(f"[샘플 가사 조회] - {len(form_data.lyrics_ids)}개") + +# lyrics_query = """ +# SELECT sample_song FROM song_sample +# WHERE id IN :ids +# ORDER BY created_at; +# """ +# lyrics_result = await conn.execute( +# text(lyrics_query), {"ids": tuple(form_data.lyrics_ids)} +# ) + +# sample_songs = [ +# row.sample_song for row in lyrics_result.fetchall() if row.sample_song +# ] + +# if sample_songs: +# combined_sample_song = "\n\n".join( +# [f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)] +# ) +# logger.info(f"{len(sample_songs)}개의 샘플 가사 조회 완료") +# else: +# logger.info("샘플 가사가 비어있습니다") +# else: +# logger.info("선택된 lyrics가 없습니다") + +# # 1. song_sample 테이블의 모든 ID 조회 +# logger.info("[샘플 가사 랜덤 선택]") + +# all_ids_query = """ +# SELECT id FROM song_sample; +# """ +# ids_result = await conn.execute(text(all_ids_query)) +# all_ids = [row.id for row in ids_result.fetchall()] + +# logger.info(f"전체 샘플 가사 개수: {len(all_ids)}개") + +# # 2. 랜덤하게 3개 선택 (또는 전체 개수가 3개 미만이면 전체) +# combined_sample_song = None + +# if all_ids: +# # 3개 또는 전체 개수 중 작은 값 선택 +# sample_count = min(3, len(all_ids)) +# selected_ids = random.sample(all_ids, sample_count) + +# logger.info(f"랜덤 선택된 ID: {selected_ids}") + +# # 3. 선택된 ID로 샘플 가사 조회 +# lyrics_query = """ +# SELECT sample_song FROM song_sample +# WHERE id IN :ids +# ORDER BY created_at; +# """ +# lyrics_result = await conn.execute( +# text(lyrics_query), {"ids": tuple(selected_ids)} +# ) + +# sample_songs = [ +# row.sample_song for row in lyrics_result.fetchall() if row.sample_song +# ] + +# # 4. combined_sample_song 생성 +# if sample_songs: +# combined_sample_song = "\n\n".join( +# [f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)] +# ) +# logger.info(f"{len(sample_songs)}개의 샘플 가사 조회 완료") +# else: +# logger.info("샘플 가사가 비어있습니다") +# else: +# logger.info("song_sample 테이블에 데이터가 없습니다") + +# # 5. 프롬프트에 샘플 가사 추가 +# if combined_sample_song: +# updated_prompt += f""" + +# 다음은 참고해야 하는 샘플 가사 정보입니다. + +# 샘플 가사를 참고하여 작곡을 해주세요. + +# {combined_sample_song} +# """ +# logger.info("샘플 가사 정보가 프롬프트에 추가되었습니다") +# else: +# logger.info("샘플 가사가 없어 기본 프롬프트만 사용합니다") + +# logger.debug(f"[최종 프롬프트 길이: {len(updated_prompt)} 자]") + +# # 7. 모델에게 요청 +# generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt) + +# # 글자 수 계산 +# total_chars_with_space = len(generated_lyrics) +# total_chars_without_space = len( +# generated_lyrics.replace(" ", "") +# .replace("\n", "") +# .replace("\r", "") +# .replace("\t", "") +# ) + +# # final_lyrics 생성 +# final_lyrics = f"""속성 {formatted_attributes} +# 전체 글자 수 (공백 포함): {total_chars_with_space}자 +# 전체 글자 수 (공백 제외): {total_chars_without_space}자\r\n\r\n{generated_lyrics}""" + +# # 8. DB 저장 +# insert_query = """ +# INSERT INTO song_results_all ( +# store_info, store_name, store_category, store_address, store_phone_number, +# description, prompt, attr_category, attr_value, +# ai, ai_model, genre, +# sample_song, result_song, created_at +# ) VALUES ( +# :store_info, :store_name, :store_category, :store_address, :store_phone_number, +# :description, :prompt, :attr_category, :attr_value, +# :ai, :ai_model, :genre, +# :sample_song, :result_song, NOW() +# ); +# """ +# logger.debug("[insert_params 선택된 속성 확인]") +# logger.debug(f"Categories: {selected_categories}") +# logger.debug(f"Values: {selected_values}") + +# # attr_category, attr_value +# insert_params = { +# "store_info": store_info.store_info or "", +# "store_name": store_info.store_name, +# "store_category": store_info.store_category or "", +# "store_address": store_info.store_address or "", +# "store_phone_number": store_info.store_phone_number or "", +# "description": store_info.store_info or "", +# "prompt": prompt.id, +# # 랜덤 선택된 category와 value 사용 +# "attr_category": ", ".join(selected_categories) +# if selected_categories +# else "", +# "attr_value": ", ".join(selected_values) if selected_values else "", +# "ai": "ChatGPT", +# "ai_model": "gpt-5-mini", +# "genre": "후크송", +# "sample_song": combined_sample_song or "없음", +# "result_song": final_lyrics, +# } + +# await conn.execute(text(insert_query), insert_params) +# await conn.commit() + +# logger.info("결과 저장 완료") + +# logger.info("전체 결과 조회 중...") + +# # 9. 생성 결과 가져오기 (created_at 역순) +# select_query = """ +# SELECT * FROM song_results_all +# ORDER BY created_at DESC; +# """ + +# all_results = await conn.execute(text(select_query)) + +# results_list = [ +# { +# "id": row.id, +# "store_info": row.store_info, +# "store_name": row.store_name, +# "store_category": row.store_category, +# "store_address": row.store_address, +# "store_phone_number": row.store_phone_number, +# "description": row.description, +# "prompt": row.prompt, +# "attr_category": row.attr_category, +# "attr_value": row.attr_value, +# "ai": row.ai, +# "ai_model": row.ai_model, +# "genre": row.genre, +# "sample_song": row.sample_song, +# "result_song": row.result_song, +# "created_at": row.created_at.isoformat() if row.created_at else None, +# } +# for row in all_results.fetchall() +# ] + +# logger.info(f"전체 {len(results_list)}개의 결과 조회 완료") + +# return results_list + +# except HTTPException: +# raise +# except SQLAlchemyError as e: +# logger.error(f"Database Error: {e}", exc_info=True) +# raise HTTPException( +# status_code=status.HTTP_503_SERVICE_UNAVAILABLE, +# detail="데이터베이스 연결에 문제가 발생했습니다.", +# ) +# except Exception as e: +# logger.error(f"Unexpected Error: {e}", exc_info=True) +# raise HTTPException( +# status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, +# detail="서비스 처리 중 오류가 발생했습니다.", +# ) +from sqlalchemy.dialects import mysql +async def get_image_tags_by_task_id(task_id: str) -> list[dict]: + print("taskid", task_id) + async with AsyncSessionLocal() as session: + stmt = ( + select(Image.img_url, ImageTag.img_tag) + .join( + ImageTag, + (ImageTag.img_url_hash == func.CRC32(Image.img_url)) + & (ImageTag.img_url == Image.img_url), ) - for row in result - ] - - result.close() - return all_store_info - except SQLAlchemyError as e: - logger.error(f"SQLAlchemy error in get_store_info: {e}") - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", - ) - except Exception as e: - logger.error(f"Unexpected error in get_store_info: {e}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="알수없는 이유로 서비스 오류가 발생하였습니다", - ) - - -async def get_attribute(conn: Connection) -> List[AttributeData]: - try: - query = """SELECT * FROM attribute;""" - result = await conn.execute(text(query)) - - all_attribute = [ - AttributeData( - id=row[0], - attr_category=row[1], - attr_value=row[2], - created_at=row[3], + .where( + Image.task_id == task_id, + Image.is_deleted == False, ) - for row in result - ] - - result.close() - return all_attribute - except SQLAlchemyError as e: - logger.error(f"SQLAlchemy error in get_attribute: {e}") - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", - ) - except Exception as e: - logger.error(f"Unexpected error in get_attribute: {e}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="알수없는 이유로 서비스 오류가 발생하였습니다", - ) - - -async def get_attribute(conn: Connection) -> List[AttributeData]: - try: - query = """SELECT * FROM attribute;""" - result = await conn.execute(text(query)) - - all_attribute = [ - AttributeData( - id=row[0], - attr_category=row[1], - attr_value=row[2], - created_at=row[3], - ) - for row in result - ] - - result.close() - return all_attribute - except SQLAlchemyError as e: - logger.error(f"SQLAlchemy error in get_attribute: {e}") - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", - ) - except Exception as e: - logger.error(f"Unexpected error in get_attribute: {e}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="알수없는 이유로 서비스 오류가 발생하였습니다", - ) - - -async def get_sample_song(conn: Connection) -> List[SongSampleData]: - try: - query = """SELECT * FROM song_sample;""" - result = await conn.execute(text(query)) - - all_sample_song = [ - SongSampleData( - id=row[0], - ai=row[1], - ai_model=row[2], - genre=row[3], - sample_song=row[4], - ) - for row in result - ] - - result.close() - return all_sample_song - except SQLAlchemyError as e: - logger.error(f"SQLAlchemy error in get_sample_song: {e}") - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", - ) - except Exception as e: - logger.error(f"Unexpected error in get_sample_song: {e}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="알수없는 이유로 서비스 오류가 발생하였습니다", - ) - - -async def get_prompt_template(conn: Connection) -> List[PromptTemplateData]: - try: - query = """SELECT * FROM prompt_template;""" - result = await conn.execute(text(query)) - - all_prompt_template = [ - PromptTemplateData( - id=row[0], - description=row[1], - prompt=row[2], - ) - for row in result - ] - - result.close() - return all_prompt_template - except SQLAlchemyError as e: - logger.error(f"SQLAlchemy error in get_prompt_template: {e}") - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", - ) - except Exception as e: - logger.error(f"Unexpected error in get_prompt_template: {e}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="알수없는 이유로 서비스 오류가 발생하였습니다", - ) - - -async def get_song_result(conn: Connection) -> List[PromptTemplateData]: - try: - query = """SELECT * FROM prompt_template;""" - result = await conn.execute(text(query)) - - all_prompt_template = [ - PromptTemplateData( - id=row[0], - description=row[1], - prompt=row[2], - ) - for row in result - ] - - result.close() - return all_prompt_template - except SQLAlchemyError as e: - logger.error(f"SQLAlchemy error in get_song_result: {e}") - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.", - ) - except Exception as e: - logger.error(f"Unexpected error in get_song_result: {e}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="알수없는 이유로 서비스 오류가 발생하였습니다", - ) - - -async def make_song_result(request: Request, conn: Connection): - try: - # 1. Form 데이터 파싱 - form_data = await SongFormData.from_form(request) - - logger.info(f"{'=' * 60}") - logger.info(f"Store ID: {form_data.store_id}") - logger.info(f"Lyrics IDs: {form_data.lyrics_ids}") - logger.info(f"Prompt IDs: {form_data.prompts}") - logger.info(f"{'=' * 60}") - - # 2. Store 정보 조회 - store_query = """ - SELECT * FROM store_default_info WHERE id=:id; - """ - store_result = await conn.execute(text(store_query), {"id": form_data.store_id}) - - all_store_info = [ - StoreData( - id=row[0], - store_info=row[1], - store_name=row[2], - store_category=row[3], - store_region=row[4], - store_address=row[5], - store_phone_number=row[6], - created_at=row[7], - ) - for row in store_result - ] - - if not all_store_info: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Store not found: {form_data.store_id}", - ) - - store_info = all_store_info[0] - logger.info(f"Store: {store_info.store_name}") - - # 3. 속성 조회 -- 단계별 선택 프로세서시 구현 필요 없음 - - # 4. Sample Song 조회 및 결합 - combined_sample_song = None - - if form_data.lyrics_ids: - logger.info(f"[샘플 가사 조회] - {len(form_data.lyrics_ids)}개") - - lyrics_query = """ - SELECT sample_song FROM song_sample - WHERE id IN :ids - ORDER BY created_at; - """ - lyrics_result = await conn.execute( - text(lyrics_query), {"ids": tuple(form_data.lyrics_ids)} - ) - - sample_songs = [ - row.sample_song for row in lyrics_result.fetchall() if row.sample_song - ] - - if sample_songs: - combined_sample_song = "\n\n".join( - [f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)] - ) - logger.info(f"{len(sample_songs)}개의 샘플 가사 조회 완료") - else: - logger.info("샘플 가사가 비어있습니다") - else: - logger.info("선택된 lyrics가 없습니다") - - # 5. 템플릿 가져오기 - if not form_data.prompts: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="프롬프트 ID가 필요합니다", - ) - - logger.info("템플릿 가져오기") - - prompts_query = """ - SELECT * FROM prompt_template WHERE id=:id; - """ - - # ✅ 수정: store_query → prompts_query - prompts_result = await conn.execute( - text(prompts_query), {"id": form_data.prompts} - ) - - prompts_info = [ - PromptTemplateData( - id=row[0], - description=row[1], - prompt=row[2], - ) - for row in prompts_result - ] - - if not prompts_info: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Prompt not found: {form_data.prompts}", - ) - - prompt = prompts_info[0] - logger.debug(f"Prompt Template: {prompt.prompt}") - - # ✅ 6. 프롬프트 조합 - updated_prompt = prompt.prompt.replace("###", form_data.attributes_str).format( - name=store_info.store_name or "", - address=store_info.store_address or "", - category=store_info.store_category or "", - description=store_info.store_info or "", - ) - - updated_prompt += f""" - - 다음은 참고해야 하는 샘플 가사 정보입니다. - - 샘플 가사를 참고하여 작곡을 해주세요. - - {combined_sample_song} - """ - - logger.debug(f"[업데이트된 프롬프트]\n{updated_prompt}") - - # 7. 모델에게 요청 - generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt) - - # 글자 수 계산 - total_chars_with_space = len(generated_lyrics) - total_chars_without_space = len( - generated_lyrics.replace(" ", "") - .replace("\n", "") - .replace("\r", "") - .replace("\t", "") - ) - - # final_lyrics 생성 - final_lyrics = f"""속성 {form_data.attributes_str} - 전체 글자 수 (공백 포함): {total_chars_with_space}자 - 전체 글자 수 (공백 제외): {total_chars_without_space}자\r\n\r\n{generated_lyrics}""" - - logger.debug("=" * 40) - logger.debug(f"[translate:form_data.attributes_str:] {form_data.attributes_str}") - logger.debug(f"[translate:total_chars_with_space:] {total_chars_with_space}") - logger.debug(f"[translate:total_chars_without_space:] {total_chars_without_space}") - logger.debug(f"[translate:final_lyrics:]\n{final_lyrics}") - logger.debug("=" * 40) - - # 8. DB 저장 - insert_query = """ - INSERT INTO song_results_all ( - store_info, store_name, store_category, store_address, store_phone_number, - description, prompt, attr_category, attr_value, - ai, ai_model, genre, - sample_song, result_song, created_at - ) VALUES ( - :store_info, :store_name, :store_category, :store_address, :store_phone_number, - :description, :prompt, :attr_category, :attr_value, - :ai, :ai_model, :genre, - :sample_song, :result_song, NOW() - ); - """ - - # ✅ attr_category, attr_value 추가 - insert_params = { - "store_info": store_info.store_info or "", - "store_name": store_info.store_name, - "store_category": store_info.store_category or "", - "store_address": store_info.store_address or "", - "store_phone_number": store_info.store_phone_number or "", - "description": store_info.store_info or "", - "prompt": form_data.prompts, - "attr_category": ", ".join(form_data.attributes.keys()) - if form_data.attributes - else "", - "attr_value": ", ".join(form_data.attributes.values()) - if form_data.attributes - else "", - "ai": "ChatGPT", - "ai_model": form_data.llm_model, - "genre": "후크송", - "sample_song": combined_sample_song or "없음", - "result_song": final_lyrics, - } - - await conn.execute(text(insert_query), insert_params) - await conn.commit() - - logger.info("결과 저장 완료") - - logger.info("전체 결과 조회 중...") - - # 9. 생성 결과 가져오기 (created_at 역순) - select_query = """ - SELECT * FROM song_results_all - ORDER BY created_at DESC; - """ - - all_results = await conn.execute(text(select_query)) - - results_list = [ - { - "id": row.id, - "store_info": row.store_info, - "store_name": row.store_name, - "store_category": row.store_category, - "store_address": row.store_address, - "store_phone_number": row.store_phone_number, - "description": row.description, - "prompt": row.prompt, - "attr_category": row.attr_category, - "attr_value": row.attr_value, - "ai": row.ai, - "ai_model": row.ai_model, - "genre": row.genre, - "sample_song": row.sample_song, - "result_song": row.result_song, - "created_at": row.created_at.isoformat() if row.created_at else None, - } - for row in all_results.fetchall() - ] - - logger.info(f"전체 {len(results_list)}개의 결과 조회 완료") - - return results_list - - except HTTPException: - raise - except SQLAlchemyError as e: - logger.error(f"Database Error: {e}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="데이터베이스 연결에 문제가 발생했습니다.", - ) - except Exception as e: - logger.error(f"Unexpected Error: {e}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="서비스 처리 중 오류가 발생했습니다.", - ) - - -async def get_song_result(conn: Connection): # 반환 타입 수정 - try: - select_query = """ - SELECT * FROM song_results_all - ORDER BY created_at DESC; - """ - - all_results = await conn.execute(text(select_query)) - - results_list = [ - { - "id": row.id, - "store_info": row.store_info, - "store_name": row.store_name, - "store_category": row.store_category, - "store_address": row.store_address, - "store_phone_number": row.store_phone_number, - "description": row.description, - "prompt": row.prompt, - "attr_category": row.attr_category, - "attr_value": row.attr_value, - "ai": row.ai, - "ai_model": row.ai_model, - "season": row.season, - "num_of_people": row.num_of_people, - "people_category": row.people_category, - "genre": row.genre, - "sample_song": row.sample_song, - "result_song": row.result_song, - "created_at": row.created_at.isoformat() if row.created_at else None, - } - for row in all_results.fetchall() - ] - - logger.info(f"전체 {len(results_list)}개의 결과 조회 완료") - - return results_list - except HTTPException: # HTTPException은 그대로 raise - raise - except SQLAlchemyError as e: - logger.error(f"Database Error: {e}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="데이터베이스 연결에 문제가 발생했습니다.", - ) - except Exception as e: - logger.error(f"Unexpected Error: {e}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="서비스 처리 중 오류가 발생했습니다.", - ) - - -async def make_automation(request: Request, conn: Connection): - try: - # 1. Form 데이터 파싱 - form_data = await SongFormData.from_form(request) - - logger.info(f"{'=' * 60}") - logger.info(f"Store ID: {form_data.store_id}") - logger.info(f"{'=' * 60}") - - # 2. Store 정보 조회 - store_query = """ - SELECT * FROM store_default_info WHERE id=:id; - """ - store_result = await conn.execute(text(store_query), {"id": form_data.store_id}) - - all_store_info = [ - StoreData( - id=row[0], - store_info=row[1], - store_name=row[2], - store_category=row[3], - store_region=row[4], - store_address=row[5], - store_phone_number=row[6], - created_at=row[7], - ) - for row in store_result - ] - - if not all_store_info: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Store not found: {form_data.store_id}", - ) - - store_info = all_store_info[0] - logger.info(f"Store: {store_info.store_name}") - - # 3. 속성 조회 -- 단계별 선택 프로세서시 구현 필요 없음 - attribute_query = """ - SELECT * FROM attribute; - """ - - attribute_results = await conn.execute(text(attribute_query)) - - # 결과 가져오기 - attribute_rows = attribute_results.fetchall() - - formatted_attributes = "" - selected_categories = [] - selected_values = [] - - if attribute_rows: - attribute_list = [ - AttributeData( - id=row[0], - attr_category=row[1], - attr_value=row[2], - created_at=row[3], - ) - for row in attribute_rows - ] - - # ✅ 각 category에서 하나의 value만 랜덤 선택 - formatted_pairs = [] - for attr in attribute_list: - # 쉼표로 분리 및 공백 제거 - values = [v.strip() for v in attr.attr_value.split(",") if v.strip()] - - if values: - # 랜덤하게 하나만 선택 - selected_value = random.choice(values) - formatted_pairs.append(f"{attr.attr_category} : {selected_value}") - - # ✅ 선택된 category와 value 저장 - selected_categories.append(attr.attr_category) - selected_values.append(selected_value) - - # 최종 문자열 생성 - formatted_attributes = "\n".join(formatted_pairs) - - logger.debug(f"[포맷팅된 문자열 속성 정보]\n{formatted_attributes}") - else: - logger.info("속성 데이터가 없습니다") - formatted_attributes = "" - - # 4. 템플릿 가져오기 - logger.info("템플릿 가져오기 (ID=1)") - - prompts_query = """ - SELECT * FROM prompt_template WHERE id=1; - """ - - prompts_result = await conn.execute(text(prompts_query)) - - row = prompts_result.fetchone() - - if not row: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Prompt ID 1 not found", - ) - - prompt = PromptTemplateData( - id=row[0], - description=row[1], - prompt=row[2], - ) - - logger.debug(f"Prompt Template: {prompt.prompt}") - - # 5. 템플릿 조합 - - updated_prompt = prompt.prompt.replace("###", formatted_attributes).format( - name=store_info.store_name or "", - address=store_info.store_address or "", - category=store_info.store_category or "", - description=store_info.store_info or "", - ) - - logger.debug("=" * 80) - logger.debug("업데이트된 프롬프트") - logger.debug("=" * 80) - logger.debug(updated_prompt) - logger.debug("=" * 80) - - # 4. Sample Song 조회 및 결합 - combined_sample_song = None - - if form_data.lyrics_ids: - logger.info(f"[샘플 가사 조회] - {len(form_data.lyrics_ids)}개") - - lyrics_query = """ - SELECT sample_song FROM song_sample - WHERE id IN :ids - ORDER BY created_at; - """ - lyrics_result = await conn.execute( - text(lyrics_query), {"ids": tuple(form_data.lyrics_ids)} - ) - - sample_songs = [ - row.sample_song for row in lyrics_result.fetchall() if row.sample_song - ] - - if sample_songs: - combined_sample_song = "\n\n".join( - [f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)] - ) - logger.info(f"{len(sample_songs)}개의 샘플 가사 조회 완료") - else: - logger.info("샘플 가사가 비어있습니다") - else: - logger.info("선택된 lyrics가 없습니다") - - # 1. song_sample 테이블의 모든 ID 조회 - logger.info("[샘플 가사 랜덤 선택]") - - all_ids_query = """ - SELECT id FROM song_sample; - """ - ids_result = await conn.execute(text(all_ids_query)) - all_ids = [row.id for row in ids_result.fetchall()] - - logger.info(f"전체 샘플 가사 개수: {len(all_ids)}개") - - # 2. 랜덤하게 3개 선택 (또는 전체 개수가 3개 미만이면 전체) - combined_sample_song = None - - if all_ids: - # 3개 또는 전체 개수 중 작은 값 선택 - sample_count = min(3, len(all_ids)) - selected_ids = random.sample(all_ids, sample_count) - - logger.info(f"랜덤 선택된 ID: {selected_ids}") - - # 3. 선택된 ID로 샘플 가사 조회 - lyrics_query = """ - SELECT sample_song FROM song_sample - WHERE id IN :ids - ORDER BY created_at; - """ - lyrics_result = await conn.execute( - text(lyrics_query), {"ids": tuple(selected_ids)} - ) - - sample_songs = [ - row.sample_song for row in lyrics_result.fetchall() if row.sample_song - ] - - # 4. combined_sample_song 생성 - if sample_songs: - combined_sample_song = "\n\n".join( - [f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)] - ) - logger.info(f"{len(sample_songs)}개의 샘플 가사 조회 완료") - else: - logger.info("샘플 가사가 비어있습니다") - else: - logger.info("song_sample 테이블에 데이터가 없습니다") - - # 5. 프롬프트에 샘플 가사 추가 - if combined_sample_song: - updated_prompt += f""" - - 다음은 참고해야 하는 샘플 가사 정보입니다. - - 샘플 가사를 참고하여 작곡을 해주세요. - - {combined_sample_song} - """ - logger.info("샘플 가사 정보가 프롬프트에 추가되었습니다") - else: - logger.info("샘플 가사가 없어 기본 프롬프트만 사용합니다") - - logger.debug(f"[최종 프롬프트 길이: {len(updated_prompt)} 자]") - - # 7. 모델에게 요청 - generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt) - - # 글자 수 계산 - total_chars_with_space = len(generated_lyrics) - total_chars_without_space = len( - generated_lyrics.replace(" ", "") - .replace("\n", "") - .replace("\r", "") - .replace("\t", "") - ) - - # final_lyrics 생성 - final_lyrics = f"""속성 {formatted_attributes} - 전체 글자 수 (공백 포함): {total_chars_with_space}자 - 전체 글자 수 (공백 제외): {total_chars_without_space}자\r\n\r\n{generated_lyrics}""" - - # 8. DB 저장 - insert_query = """ - INSERT INTO song_results_all ( - store_info, store_name, store_category, store_address, store_phone_number, - description, prompt, attr_category, attr_value, - ai, ai_model, genre, - sample_song, result_song, created_at - ) VALUES ( - :store_info, :store_name, :store_category, :store_address, :store_phone_number, - :description, :prompt, :attr_category, :attr_value, - :ai, :ai_model, :genre, - :sample_song, :result_song, NOW() - ); - """ - logger.debug("[insert_params 선택된 속성 확인]") - logger.debug(f"Categories: {selected_categories}") - logger.debug(f"Values: {selected_values}") - - # attr_category, attr_value - insert_params = { - "store_info": store_info.store_info or "", - "store_name": store_info.store_name, - "store_category": store_info.store_category or "", - "store_address": store_info.store_address or "", - "store_phone_number": store_info.store_phone_number or "", - "description": store_info.store_info or "", - "prompt": prompt.id, - # 랜덤 선택된 category와 value 사용 - "attr_category": ", ".join(selected_categories) - if selected_categories - else "", - "attr_value": ", ".join(selected_values) if selected_values else "", - "ai": "ChatGPT", - "ai_model": "gpt-5-mini", - "genre": "후크송", - "sample_song": combined_sample_song or "없음", - "result_song": final_lyrics, - } - - await conn.execute(text(insert_query), insert_params) - await conn.commit() - - logger.info("결과 저장 완료") - - logger.info("전체 결과 조회 중...") - - # 9. 생성 결과 가져오기 (created_at 역순) - select_query = """ - SELECT * FROM song_results_all - ORDER BY created_at DESC; - """ - - all_results = await conn.execute(text(select_query)) - - results_list = [ - { - "id": row.id, - "store_info": row.store_info, - "store_name": row.store_name, - "store_category": row.store_category, - "store_address": row.store_address, - "store_phone_number": row.store_phone_number, - "description": row.description, - "prompt": row.prompt, - "attr_category": row.attr_category, - "attr_value": row.attr_value, - "ai": row.ai, - "ai_model": row.ai_model, - "genre": row.genre, - "sample_song": row.sample_song, - "result_song": row.result_song, - "created_at": row.created_at.isoformat() if row.created_at else None, - } - for row in all_results.fetchall() - ] - - logger.info(f"전체 {len(results_list)}개의 결과 조회 완료") - - return results_list - - except HTTPException: - raise - except SQLAlchemyError as e: - logger.error(f"Database Error: {e}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="데이터베이스 연결에 문제가 발생했습니다.", - ) - except Exception as e: - logger.error(f"Unexpected Error: {e}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="서비스 처리 중 오류가 발생했습니다.", ) + print(stmt.compile(dialect=mysql.dialect(), compile_kwargs={"literal_binds": True})) + rows = (await session.execute(stmt)).all() + print("rows", rows) + print(rows) + print("image" , [{"image_url": row.img_url, "image_tag": row.img_tag} for row in rows]) + return [{"image_url": row.img_url, "image_tag": row.img_tag} for row in rows] \ No newline at end of file