수노 타임아웃 증가

insta
Dohyun Lim 2026-01-23 18:26:58 +09:00
parent 2ac4b75d96
commit 7e2646337f
1 changed files with 38 additions and 39 deletions

View File

@ -179,7 +179,7 @@ class SunoService:
raise ValueError("Suno API returned empty response for task status") raise ValueError("Suno API returned empty response for task status")
return data return data
async def get_lyric_timestamp(self, task_id: str, audio_id: str) -> dict[str, Any]: async def get_lyric_timestamp(self, task_id: str, audio_id: str) -> dict[str, Any]:
""" """
음악 타임스탬프 정보 추출 음악 타임스탬프 정보 추출
@ -189,27 +189,24 @@ class SunoService:
audio_id: 사용할 audio id audio_id: 사용할 audio id
Returns: Returns:
data.alignedWords: 수노 가사 input - startS endS 시간 데이터 매핑 data.alignedWords: 수노 가사 input - startS endS 시간 데이터 매핑
""" """
payload = { payload = {"task_id": task_id, "audio_id": audio_id}
"task_id" : task_id,
"audio_id" : audio_id
}
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.post( response = await client.post(
f"{self.BASE_URL}/generate/get-timestamped-lyrics", f"{self.BASE_URL}/generate/get-timestamped-lyrics",
headers=self.headers, headers=self.headers,
json=payload, json=payload,
timeout=30.0, timeout=120.0,
) )
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
if not data or not data['data']: if not data or not data["data"]:
raise ValueError("Suno API returned empty response for task status") raise ValueError("Suno API returned empty response for task status")
return data['data']['alignedWords'] return data["data"]["alignedWords"]
def parse_status_response(self, result: dict | None) -> PollingSongResponse: def parse_status_response(self, result: dict | None) -> PollingSongResponse:
"""Suno API 상태 응답을 파싱하여 PollingSongResponse로 변환합니다. """Suno API 상태 응답을 파싱하여 PollingSongResponse로 변환합니다.
@ -284,83 +281,85 @@ class SunoService:
message=status_messages.get(status, f"상태: {status}"), message=status_messages.get(status, f"상태: {status}"),
error_message=None, error_message=None,
) )
def align_lyrics(self, word_data: list[dict], sentences: list[str]) -> list[dict]: def align_lyrics(self, word_data: list[dict], sentences: list[str]) -> list[dict]:
""" """
word의 시작/ 포지션만 저장하고, 시간은 word에서 참조 word의 시작/ 포지션만 저장하고, 시간은 word에서 참조
""" """
# Step 1: 전체 텍스트 + word별 포지션 범위 저장 # Step 1: 전체 텍스트 + word별 포지션 범위 저장
full_text = "" full_text = ""
word_ranges = [] # [(start_pos, end_pos, entry), ...] word_ranges = [] # [(start_pos, end_pos, entry), ...]
for entry in word_data: for entry in word_data:
word = entry['word'] word = entry["word"]
start_pos = len(full_text) start_pos = len(full_text)
full_text += word full_text += word
end_pos = len(full_text) - 1 end_pos = len(full_text) - 1
word_ranges.append((start_pos, end_pos, entry)) word_ranges.append((start_pos, end_pos, entry))
# Step 2: 메타데이터 제거 + 포지션 재매핑 # Step 2: 메타데이터 제거 + 포지션 재매핑
meta_ranges = [] meta_ranges = []
i = 0 i = 0
while i < len(full_text): while i < len(full_text):
if full_text[i] == '[': if full_text[i] == "[":
start = i start = i
while i < len(full_text) and full_text[i] != ']': while i < len(full_text) and full_text[i] != "]":
i += 1 i += 1
meta_ranges.append((start, i + 1)) meta_ranges.append((start, i + 1))
i += 1 i += 1
clean_text = "" clean_text = ""
new_to_old = {} # 클린 포지션 -> 원본 포지션 new_to_old = {} # 클린 포지션 -> 원본 포지션
for old_pos, char in enumerate(full_text): for old_pos, char in enumerate(full_text):
in_meta = any(s <= old_pos < e for s, e in meta_ranges) in_meta = any(s <= old_pos < e for s, e in meta_ranges)
if not in_meta: if not in_meta:
new_to_old[len(clean_text)] = old_pos new_to_old[len(clean_text)] = old_pos
clean_text += char clean_text += char
# Step 3: 포지션으로 word 찾기 # Step 3: 포지션으로 word 찾기
def get_word_at(old_pos: int): def get_word_at(old_pos: int):
for start, end, entry in word_ranges: for start, end, entry in word_ranges:
if start <= old_pos <= end: if start <= old_pos <= end:
return entry return entry
return None return None
# Step 4: 문장 매칭 # Step 4: 문장 매칭
def normalize(text): def normalize(text):
return ''.join(c for c in text if c not in ' \n\t-') return "".join(c for c in text if c not in " \n\t-")
norm_clean = normalize(clean_text) norm_clean = normalize(clean_text)
norm_to_clean = [i for i, c in enumerate(clean_text) if c not in ' \n\t-'] norm_to_clean = [i for i, c in enumerate(clean_text) if c not in " \n\t-"]
results = [] results = []
search_pos = 0 search_pos = 0
for sentence in sentences: for sentence in sentences:
norm_sentence = normalize(sentence) norm_sentence = normalize(sentence)
found_pos = norm_clean.find(norm_sentence, search_pos) found_pos = norm_clean.find(norm_sentence, search_pos)
if found_pos != -1: if found_pos != -1:
clean_start = norm_to_clean[found_pos] clean_start = norm_to_clean[found_pos]
clean_end = norm_to_clean[found_pos + len(norm_sentence) - 1] clean_end = norm_to_clean[found_pos + len(norm_sentence) - 1]
old_start = new_to_old[clean_start] old_start = new_to_old[clean_start]
old_end = new_to_old[clean_end] old_end = new_to_old[clean_end]
word_start = get_word_at(old_start) word_start = get_word_at(old_start)
word_end = get_word_at(old_end) word_end = get_word_at(old_end)
results.append({ results.append(
'text': sentence, {
'start_sec': word_start['startS'], "text": sentence,
'end_sec': word_end['endS'], "start_sec": word_start["startS"],
}) "end_sec": word_end["endS"],
}
)
search_pos = found_pos + len(norm_sentence) search_pos = found_pos + len(norm_sentence)
else: else:
results.append({'text': sentence, 'start_sec': None, 'end_sec': None}) results.append({"text": sentence, "start_sec": None, "end_sec": None})
return results return results