본문 바로가기
python

Amazon Polly로 정확한 음성 재생 시간(Duration) 추출하기: Speech Marks 완벽 가이드 (Python)

by 타닥타닥 토다토닥 부부 2025. 6. 21.
반응형

Python과 Amazon Polly의 Speech Marks 기능을 사용하여 텍스트 음성 변환(TTS) 오디오의 정확한 단어별, 문장별 재생 시간(duration)을 추출하는 방법을 알아보세요. 본문에는 영상 자막 싱크, 오디오북 제작 등에 바로 활용 가능한 상세한 코드 분석과 실용적인 팁이 포함되어 있습니다.

Amazon Polly로 음성 재생 시간(Duration) 추출하기: Speech Marks 완벽 가이드 (Python)

Amazon Polly로 텍스트 음성 변환(TTS) 서비스를 활용할때,  가장 어려운 과제 중 하나는 바로 정확한 음성 재생 시간(duration)을 예측하는 것입니다.

단순히 글자 수에 비례하여 시간을 계산하는 방법은 가장 간단하지만, 매우 부정확합니다. "안녕하세요"와 "대한민국"은 글자 수는 같지만 실제 발음 길이는 다릅니다. 또한, 쉼표(,)나 마침표(.)에서의 미세한停頓(pause), 특정 단어에 대한 강조 등은 예측을 더욱 어렵게 만듭니다.

이러한 문제 때문에 영상 자막의 싱크가 맞지 않거나, 음성 길이에 맞춰 설계된 애니메이션이 어색하게 동작하는 경우가 빈번하게 발생합니다. 오늘 이 글에서는 Amazon Polly의 강력한 기능인 Speech Marks를 사용하여 이러한 문제를 해결하고, 단어 수준까지의 정밀한 음성 합성 시간 추출 방법을 제공된 Python 코드를 통해 심도 있게 알아보겠습니다.

 

Amazon Polly Speech Marks란 무엇이며 왜 필수적인가?

Amazon Polly의 Speech Marks는 합성된 오디오 스트림에서 특정 단어, 문장, SSML 태그 등이 시작되는 정확한 시간(타임스탬프)을 알려주는 메타데이터입니다.

단순히 전체 오디오의 총 길이만 알려주는 것이 아니라, "이 문장은 1.5초에 시작해서 4.8초에 끝나" 또는 "'Christmas'라는 단어는 3.016초에 정확히 발음되기 시작해" 와 같이 세밀한 시간 정보를 제공합니다.

Speech Marks를 사용해야 하는 이유:

  • 비교 불가능한 정확성: 글자 수 기반 예측과는 차원이 다른, 실제 발음 기준의 정확한 타임코드를 제공합니다.
  • 동기화 작업의 핵심: 영상 자막, 캐릭터 립싱크, 시각 효과 등 오디오와 다른 미디어를 동기화하는 작업에 필수적입니다.
  • 다양한 기준 제공: 단어(word), 문장(sentence), SSML 태그 등 다양한 기준으로 시간 정보를 추출할 수 있어 활용도가 높습니다.

이제, 이 강력한 Speech Marks 기능을 Python 코드에서 어떻게 활용하는지 자세히 살펴보겠습니다.

 

Python 코드로 살펴보는 Polly Speech Marks 활용법

아래 코드는 boto3 라이브러리를 사용하여 Amazon Polly로부터 특정 문장 목록(sentence_list)과 자막을 구분할 기준 단어 목록(subtitle_separator)을 받아 각 자막 구간의 재생 시간을 계산하는 함수의 예시입니다.

import boto3
import json

def ssml_sentence(rate, sentence_list):
    """
    SSML 문장을 생성하는 함수.
    
    Args:
        rate (int): 말하기 속도 (예: 100은 기본 속도).
        sentence_list (list): 문장 리스트.
        
    Returns:
        str: SSML 형식의 텍스트.
    """
    sentences = " ".join(sentence_list)
    return f"<speak><prosody rate='{rate}%'>{sentences}</prosody></speak>"


def calculate_duration(sentence_list, subtitle_separator, rate, voice_type, voice_full_length=15000,
                       region='us-east-1', aws_access_key_id="", aws_secret_access_key=""):
    """
    Polly 음성 합성으로부터 단어별 시간 정보를 받아 각 자막 구간의 지속 시간을 계산하는 함수.
    
    Args:
        sentence_list (list): 전체 문장 리스트.
        subtitle_separator (list): 자막을 구분할 단어 리스트.
        rate (int): 말하기 속도 (퍼센트).
        voice_type (str): Polly에서 사용할 음성 ID.
        voice_full_length (int): 오디오 전체 길이 (밀리초 단위).
        region (str): AWS 리전.
        aws_access_key_id (str): AWS 접근 키.
        aws_secret_access_key (str): AWS 비밀 접근 키.
        
    Returns:
        list: 각 단어와 해당 시간 정보가 포함된 리스트 (Speech Marks).
    """
    # Polly 클라이언트 초기화
    polly = boto3.client(
        'polly',
        region_name=region,
        aws_access_key_id=aws_access_key_id,
        aws_secret_access_key=aws_secret_access_key
    )

    # 1. SSML 생성
    ssml = ssml_sentence(rate, sentence_list)

    # 2. Polly에 요청 (Speech Marks 추출용)
    response = polly.synthesize_speech(
        Text=ssml,
        OutputFormat='json',
        VoiceId=voice_type,
        SpeechMarkTypes=["word"],
        Engine='neural',
        TextType='ssml'
    )
    
    # TTS 음성 생성은 아래 코드와 같이 OutputFormat을 mp3로 변경하여 따로 생성해주어야 합니다.
	# voice = self.polly.synthesize_speech(
    #       Text=ssml_sentence, 
    #       OutputFormat='mp3', 
    #       VoiceId=voice_type, 
    #       Engine='neural', 
    #      TextType='ssml'
    #    )

    # 3. JSON 스트림 파싱 (단어별 시작 시간 정보)
    timecode_stream = response['AudioStream']
    timecode = [json.loads(line.decode('utf-8').strip()) for line in timecode_stream.readlines()]

    # 4. 전체 길이를 이용해 마지막 타임스탬프 강제로 추가 (마지막 단어 끝 시점)
    timecode.append({'time': voice_full_length, 'value': 'LAST'})
    timecode[0]['time'] = 0  # 시작 시간은 0

    # 5. 구분자에서 특수문자 제거
    clean_separator = [s.replace(",", "").replace(".", "") for s in subtitle_separator]

    # 6. 매칭되는 단어 정보 출력 (디버깅 용도)
    matched = [(ti["time"], ti["value"]) for ti in timecode if ti["value"] in clean_separator]
    print("Total length (seconds):", voice_full_length * 0.001)
    print("Matched separator words with time:", matched)
    print("New separator length:", len(matched))

    return timecode

 

💡 사용 예시

sentences = ["A Christmas Carol.", "It was a sensation in Berlin that year.", "The first snow fell in Berlin, not in California."]
separators = ["Carol", "Berlin", "California"]

# 실제 사용 시에는 AWS 키 입력 필요
result = calculate_duration(
    sentence_list=sentences,
    subtitle_separator=separators,
    rate=100,
    voice_type="Joanna",  # 예시 음성
    aws_access_key_id="YOUR_KEY",
    aws_secret_access_key="YOUR_SECRET"
)

# output
# Total length (seconds): 15.0
# Matched separator words with time: [(625, 'Carol'), (2855, 'Berlin'), (5372, 'Berlin'), (6385, 'California')]
# New separator length: 4

 

실제 활용 사례: 어디에 적용할 수 있을까?

Polly Speech Marks로 추출한 정확한 재생 시간 정보는 다양한 애플리케이션의 품질을 획기적으로 높일 수 있습니다.

  • 영상 자동 자막 생성: 영상의 내레이션 음성을 기반으로 생성된 자막(SRT, VTT 파일)의 싱크를 100% 정확하게 맞출 수 있습니다.
  • 오디오북 플레이어: 챕터나 문단별로 북마크를 하거나, 특정 문장을 하이라이트하며 재생하는 기능을 정교하게 구현할 수 있습니다.
  • 인터랙티브 교육 콘텐츠: 사용자가 특정 단어를 클릭하면 그 단어가 포함된 음성 구간만 재생해주거나, 음성 길이에 맞춰 퀴즈를 제시하는 등의 상호작용이 가능합니다.
  • 음성 기반 AI 아바타: 아바타의 입 모양(립싱크)을 단어 발음 시점에 정확히 맞춰 매우 자연스러운 움직임을 구현할 수 있습니다. (이 경우 viseme 타입을 사용하면 더욱 좋습니다.)

 

 마치며: 정교한 음성 애플리케이션의 시작

지금까지 Python과 Amazon Polly의 Speech Marks 기능을 활용하여 음성 합성 시간 추출을 얼마나 정확하게 할 수 있는지 알아보았습니다. 단순히 글자 수로 시간을 어림짐작하던 시대를 지나, 이제 우리는 단어 하나하나의 발음 시점까지 밀리초 단위로 제어할 수 있게 되었습니다.

오늘 살펴본 _calculate_duration 코드는 Polly duration 추출의 기본 원리를 잘 보여줍니다. 이 원리를 이해하고 응용한다면, 여러분의 애플리케이션에 한 차원 높은 사용자 경험을 더할 수 있을 것입니다.

 

자주 묻는 질문 (FAQ)

Q1: Speech Marks를 사용하면 추가 비용이 발생하나요? A1: 아니요, Speech Marks 기능 자체에 대한 추가 비용은 없습니다. 다만 OutputFormat을 json으로 설정하여 synthesize_speech API를 호출하는 것 자체는 표준 Polly 가격 정책에 따라 요청된 문자 수 기준으로 요금이 청구됩니다.

Q2: 단어(word) 단위가 아닌 문장(sentence) 단위로도 시간 정보를 얻을 수 있나요? A2: 네, 가능합니다. SpeechMarkTypes 파라미터를 ["sentence"]로 설정하면, 각 문장이 시작되는 시간 정보를 얻을 수 있습니다. ["word", "sentence"] 와 같이 여러 타입을 동시에 요청할 수도 있습니다.

Q3: 모든 Polly 음성(Voice)이 Speech Marks를 지원하나요? A3: Speech Marks는 표준(Standard) 음성과 뉴럴(Neural) 음성 모두에서 지원됩니다. 하지만 더 자연스러운 발음과 정확한 시간 정보를 위해 가급적 뉴럴 음성 사용을 권장합니다. 코드 예제에서도 Engine='neural'을 사용했습니다.

Q4: 오디오 파일(mp3)과 Speech Marks 정보를 동시에 받을 수는 없나요? A4: synthesize_speech API는 한 번의 호출로 하나의 OutputFormat만 지원합니다. 따라서 mp3 파일과 Speech Marks(json)를 모두 얻으려면 API를 두 번 호출해야 합니다. (한 번은 mp3, 한 번은 json으로). 이는 비용 측면에서 고려해야 할 사항입니다.

반응형

댓글