added poc

insta
bluebamus 2025-12-23 16:46:20 +09:00
parent b59139dfaf
commit 39db84c797
3 changed files with 265 additions and 2 deletions

View File

@ -100,8 +100,8 @@ class SunoService:
- 다운로드 URL: 2-3 생성
- 생성되는 노래는 1 이내의 길이
"""
# 1분 이내 노래 생성을 위한 프롬프트 조건 추가
formatted_prompt = f"[Short Song - Under 1 minute]\n{prompt}"
# 정확히 1분 길이의 노래 생성을 위한 프롬프트 조건 추가
formatted_prompt = f"[Song Duration: Exactly 1 minute - Must be precisely 60 seconds]\n{prompt}"
# callback_url이 없으면 config에서 기본값 사용 (Suno API 필수 파라미터)
actual_callback_url = callback_url or apikey_settings.SUNO_CALLBACK_URL

View File

@ -0,0 +1,206 @@
import requests
import copy
CREATOMATE_API_KEY = "df9e4382d7e84fe790bf8a2168152be195d5a3568524ceb66ed989a2dea809f7d3065d6803b2e3dd9d02b5e5ec1c9823"
#ACCOUNT_URL = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/"
# Creatomate 템플릿 정보 전부 가져오기
class Creatomate():
base_url : str = "https://api.creatomate.com"
def __init__(self, api_key):
self.api_key = api_key
def get_all_templates_data(self) -> dict:
url = Creatomate.base_url + f"/v1/templates"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
response = requests.get(url, headers=headers)
return response.json()
# Creatomate 템플릿 ID 로부터 해당 템플릿 정보 가져오기
def get_one_template_data(self, template_id:str) -> dict:
url = Creatomate.base_url + f"/v1/templates/{template_id}"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
response = requests.get(url, headers=headers)
return response.json()
# 템플릿 정보 파싱하여 리소스 이름 추출하기
def parse_template_component_name(self, template_source : dict) -> dict:
def recursive_parse_component(element:dict) -> dict:
if 'name' in element:
result_element_name_type = {element['name'] : element['type']}
else:
result_element_name_type = {}
if element['type'] == "composition":
minor_component_list = [recursive_parse_component(minor) for minor in element['elements']]
for minor_component in minor_component_list: ## WARNING : Same name component should shroud other component. be aware
result_element_name_type.update(minor_component)
return result_element_name_type
result = {}
for result_element_dict in [recursive_parse_component(component) for component in template_source]:
result.update(result_element_dict)
return result
# 템플릿 정보 이미지/가사/음악 리소스와 매핑하기
# 이미지는 순차적으로 집어넣기
# 가사는 개행마다 한 텍스트 삽입
# Template에 audio-music 항목이 있어야 함. (추가된 템플릿 Cafe뿐임)
def template_connect_resource_blackbox(self, template_id:str, image_url_list:list[str], lyric:str, music_url:str) -> dict:
template_data = self.get_one_template_data(template_id)
template_component_data = self.parse_template_component_name(template_data['source']['elements'])
lyric.replace("\r", "")
lyric_splited = lyric.split("\n")
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':
modifications[template_component_name] = lyric_splited[idx % len(lyric_splited)]
modifications["audio-music"] = music_url
return modifications
def elements_connect_resource_blackbox(self, elements:list, image_url_list:list[str], lyric:str, music_url:str) -> dict:
template_component_data = self.parse_template_component_name(elements)
lyric.replace("\r", "")
lyric_splited = lyric.split("\n")
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':
modifications[template_component_name] = lyric_splited[idx % len(lyric_splited)]
modifications["audio-music"] = music_url
return modifications
def modify_element(self, elements : list, modification : dict):
def recursive_modify(element:dict) -> dict:
if 'name' in element:
match element['type']:
case 'image':
element['source'] = modification[element['name']]
case 'audio':
element['source'] = modification.get(element['name'], "")
case 'video':
element['source'] = modification[element['name']]
case 'text':
element['source'] = modification.get(element['name'], "")
case 'composition':
for minor in element['elements']:
recursive_modify(minor)
for minor in elements:
recursive_modify(minor)
return elements
# Creatomate에 생성 요청
# response에 요청 정보 있으니 풀링 필요
def make_creatomate_call(self, template_id:str, modifications:dict):
url = Creatomate.base_url + f"/v2/renders"
data = {
"template_id": template_id,
"modifications": modifications
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
response = requests.post(url, json=data, headers=headers)
return response
# Creatomate에 생성 요청 without template
# response에 요청 정보 있으니 풀링 필요
def make_creatomate_custom_call(self, source:str):
url = Creatomate.base_url + f"/v2/renders"
data = source
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
response = requests.post(url, json=data, headers=headers)
return response
def calc_scene_duration(self, template : dict):
total_template_duration = 0
for elem in template['source']['elements']:
try:
if elem['type'] == 'audio':
continue
total_template_duration += elem['duration']
if 'animations' not in elem:
continue
for animation in elem['animations']:
assert(animation['time'] == 0) # 0이 아닌 경우 확인 필요
if animation['transition']:
total_template_duration -= animation['duration']
except:
print(elem)
return total_template_duration
def extend_template_duration(self, template : dict, target_duration : float):
template['duration'] = target_duration
total_template_duration = self.calc_scene_duration(template)
extend_rate = target_duration / total_template_duration
new_template = copy.deepcopy(template)
for elem in new_template['source']['elements']:
try:
if elem['type'] == 'audio':
continue
elem['duration'] = elem['duration'] * extend_rate
if 'animations' not in elem:
continue
for animation in elem['animations']:
assert(animation['time'] == 0) # 0이 아닌 경우 확인 필요
animation['duration'] = animation['duration'] * extend_rate
except:
print(elem)
return new_template
# Azure사용한 legacy 코드 원본
# def template_connect_resource_blackbox(template_id, user_idx, task_idx):
# secret_client = get_keyvault_client()
# account_url = secret_client.get_secret(BLOB_ACCOUNT_URL_KEY).value
# media_folder_path = f"{user_idx}/{task_idx}"
# lyric_path = f"{media_folder_path}/lyric.txt"
# lyric = az_storage.az_storage_read_ado2_media(lyric_path).readall().decode('UTF-8')
# media_list = az_storage.az_storage_get_ado2_media_list(media_folder_path)
# image_list = [media.name for media in media_list if '/crawling-images/' in media.name]
# template_data = get_one_template_data(template_id)
# template_component_data = parse_template_component_name(template_data['source']['elements'])
# lyric.replace("\r", "")
# lyric_splited = lyric.split("\n")
# modifications = {}
# for idx, (template_component_name, template_type) in enumerate(template_component_data.items()):
# match template_type:
# case 'image':
# modifications[template_component_name] = f"{account_url}/{BLOB_CONTAINER_NAME}/{image_list[idx % len(image_list)]}"
# case 'text':
# modifications[template_component_name] = lyric_splited[idx % len(lyric_splited)]
# modifications["audio-music"] = f"{account_url}/{BLOB_CONTAINER_NAME}/{BLOB_MEDIA_FOLDER}/{media_folder_path}/music_mureka.mp3"
# print(modifications)
# return modifications

View File

@ -0,0 +1,57 @@
import creatomate
CREATOMATE_API_KEY = "df9e4382d7e84fe790bf8a2168152be195d5a3568524ceb66ed989a2dea809f7d3065d6803b2e3dd9d02b5e5ec1c9823"
shortform_4_template_id = "e8c7b43f-de4b-4ba3-b8eb-5df688569193"
target_duration = 90.0 # s
creato = creatomate.Creatomate(CREATOMATE_API_KEY)
template = creato.get_one_template_data(shortform_4_template_id)
uploaded_image_url_list = ["https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/crawling-images/crawler4_img_1755818306_000_385523a5_99f2e8a8.jpg",
"https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/crawling-images/crawler4_img_1755818306_001_d4cf6ec9_b81a1fdc.jpg",
"https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/crawling-images/crawler4_img_1755818307_002_e4a0b276_680c5020.jpg",
"https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/crawling-images/crawler4_img_1755818307_003_657f8c26_9f2c7168.jpg",
"https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/crawling-images/crawler4_img_1755818307_004_9500e39d_24b9dad0.jpg",
"https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/crawling-images/crawler4_img_1755818308_005_c3536641_9d490ccf.jpg"]
lyric = """
진짜 맛있는 추어탕의 향연
청담추어정 본점이야 말로
가족이 함께 먹는
여수동 맛집으로 명성을 떨쳐
주차 가능, 단체 이용도 OK
입맛을 사로잡는
청담추어정, 진정한
말복을 지나고 느껴보세요
한산한 분위기, 편안한 식사
상황 추어탕으로 더욱 완벽
톡톡 튀는 , 입에 느껴
청담추어정에서 즐겨보세요
성남 출신의 맛집으로
여수대로에서 빛나는 그곳
청담추어정, 진짜 맛의
여러분을 초대합니다 여기에
#청담추어정 #여수동맛집
성남에서 만나는 진짜
"""
song_url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/stay.mp3"
modifications = creato.elements_connect_resource_blackbox(
template['source']['elements'],
uploaded_image_url_list,
lyric,
song_url
)
new_elements = creato.modify_element(template['source']['elements'], modifications)
template['source']['elements'] = new_elements
last_template = creato.extend_template_duration(template, target_duration)
creato.make_creatomate_custom_call(last_template['source'])