added poc
parent
b59139dfaf
commit
39db84c797
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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'])
|
||||
|
||||
|
||||
Loading…
Reference in New Issue