수노 타임아웃 증가
parent
2ac4b75d96
commit
7e2646337f
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue