파이썬 입코딩 TTS (2차) 대표적인 TTS 라이브러리(EdgeTTS 사례)

소개

시도하고자 했던 것과 그 이유를 알려주세요.

파이썬에서 제공하는 대표적인 TTS 툴을 파악

진행 방법

어떤 도구를 사용했고, 어떻게 활용하셨나요?

퍼플렉시티와 클로드를 통해 파이썬이 제공하는 대표적인 TTS를 검색

Tip: 사용한 프롬프트 전문을 꼭 포함하고, 내용을 짧게 소개해 주세요.

Tip: 활용 이미지나 캡처 화면을 꼭 남겨주세요.

한국어 앱 스크린샷
  1. gTTS는 구글에서 제공하는 라이브러리로 가장 대표적인 서비스

    1. 코드는 사용하기 간단하나,

    2. 영어, 한국어 음성이 각각 1개,

    3. 영어 음성은 좋은 편이나

    4. 한국어 음질 부자연스러운게 단점

    https://youtu.be/Utc_L--_aro?si=x6Qy_yJgb1HkD9jq

  2. pyttsx3는 고품질 wav 파일을 지원

    1. 영어 음성은 보통 수준이고

    2. 한국어 음성이 좀 많이 부족한 편

      https://youtu.be/p9Lxevaadbw

  3. EdgeTTS는 마이크로소프트사의 서비스로

    1. 고급 음성 기술을 기본으로 자연스러운 목소리가 특징

    2. 영어는 남녀 다양한 음성을 제공하며

    3. 한국어 음성도 품질 좋은편이나, 2개의 목소리만 제공하는 점이 다소 아쉬움

    4. https://youtu.be/ABIY7EYWICs

  4. Coqui는 오늘 처음 발견

    1. 고급 음성인 WAV 파일을 제공하고

    2. 영어 음성도 고품질, 자연스럽다고 하여 기대

    3. 음질 확인 결과 영어 한글 둘다 수준에 못 미침

      검은 배경에 한국어 단어 목록

(내용 입력)

결과와 배운 점

2가지 이상의 검색 툴을 통해 TTS를 찾아야 한다는 점

  • EdgeTTS는 검색에서 처음 발견했는데 고품질 음성

과정 중에 어떤 시행착오를 겪었나요?

  • 무료 및 유로 서비스에서 대표적인 것을 비교 분석 필요

한국어 텍스트가 있는 검은 화면

앞으로의 계획이 있다면 들려주세요.

  1. CoquiTTS도 처음 봤는데 좋은 서비스라하니 검증 필요

  2. 유료 서비스 비교 : API 사용

    1. 네이버, 구글, 아마존 등 클라우드 유료 서비스

    2. 대표적인 AI 음성 서비스 Elevenlaps

    https://youtu.be/upooEg7AvCc(일레븐랩스 TTS)

  • 일레븐랩스는 다양한 음성, 자신 목소리 합성 등 기능이 좋은데,

    • 한번 해본 결과, 실시간 스트리밍에서 딜레이 문제 발생, 오류 개선 필요

도움 받은 글 (옵션)

참고한 지피터스 글이나 외부 사례를 알려주세요.

https://han.gl/JszkV

EdgeTTS 영어 단어장 코드(파이썬)

# 기본 패키지
edge-tts==6.1.9
openpyxl==3.1.2
Pillow==10.1.0
numpy==1.26.2
moviepy==1.0.3
opencv-python==4.8.1.78
pygame==2.5.2
pydub==0.25.1
soundfile==0.12.1
playsound==1.3.0

# 오디오 처리
torch==2.1.1

주요 패키지 설명:
edge-tts: Microsoft Edge TTS 음성 합성
openpyxl: 엑셀 파일 처리
Pillow: 이미지 처리
numpy: 수치 연산
moviepy: 비디오 생성 및 편집
opencv-python: 이미지/비디오 처리
pygame: 오디오 재생
pydub: 오디오 파일 변환
soundfile: 오디오 파일 처리
playsound: 오디오 재생
torch: 딥러닝 프레임워크
import asyncio
import edge_tts
from edge_tts import list_voices
import wave
import os
from pathlib import Path
import openpyxl
import time
from datetime import datetime

async def main():
    try:
        # 음성 설정
        eng_voice = "en-US-SteffanNeural"  # 영어 음성
        kor_voice = "ko-KR-SunHiNeural"    # 한글 음성
        eng_speed = 1.2  # 영어 1.2배속
        kor_speed = 1.2  # 한국어 1.2배속
        
        # 사용 가능한 음성 목록 출력
        voices = await list_voices()
        print("\n사용 가능한 음성:")
        print("\n영어 음성:")
        english_voices = [voice for voice in voices if voice["Locale"].startswith("en")]
        for voice in english_voices:
            print(f"- {voice['ShortName']}: {voice['FriendlyName']}")
            
        print("\n한국어 음성:")
        korean_voices = [voice for voice in voices if voice["Locale"].startswith("ko")]
        for voice in korean_voices:
            print(f"- {voice['ShortName']}: {voice['FriendlyName']}")
        
        print("\n현재 설정:")
        print(f"영어 음성: {eng_voice}")
        print(f"한글 음성: {kor_voice}")
        print(f"재생 속도: 영어 {eng_speed}배속, 한국어 {kor_speed}배속")
        
        # 스크립트 파일의 디렉토리 경로 가져오기
        script_dir = Path(os.path.dirname(os.path.abspath(__file__)))
        excel_path = script_dir / 'words.xlsx'  # 스크립트와 같은 디렉토리의 엑셀 파일
        
        if not os.path.exists(excel_path):
            raise FileNotFoundError(f"엑셀 파일을 찾을 수 없습니다: {excel_path}")
            
        workbook = openpyxl.load_workbook(excel_path)
        sheet = workbook.active
        
        # A2:B11 범위의 데이터 읽기
        words = []
        for row in range(2, 12):  # 2부터 11까지
            english = sheet[f'A{row}'].value
            korean = sheet[f'B{row}'].value
            if english and korean:  # None 값 체크
                # 한글 띄어쓰기 정리
                korean = korean.replace(" ", "")  # 모든 공백 제거
                korean = korean.strip()  # 앞뒤 공백 제거
                words.append((english, korean))
        
        if not words:
            raise ValueError("엑셀 파일에서 단어를 읽어올 수 없습니다.")
        
        print("\n읽어온 단어 목록:")
        print("\n번호  영어                  한글                  시간")
        print("-" * 60)
        
        # TTS 설정
        for i, (english, korean) in enumerate(words, 1):
            # 영어 음성 생성 (영어 음성으로)
            communicate = edge_tts.Communicate(
                text=english,
                voice=eng_voice,  # 영어 음성
                rate=f"+{int((eng_speed-1)*100)}%"  # 영어 속도 조절
            )
            
            # 영어 WAV 파일 생성
            eng_file = script_dir / f"output_eng_{i}.wav"
            if os.path.exists(eng_file):
                os.remove(eng_file)
                
            await communicate.save(str(eng_file))
            
            # 한글 음성 생성 (한글 음성으로)
            communicate = edge_tts.Communicate(
                text=korean,
                voice=kor_voice,  # 한글 음성
                rate=f"+{int((kor_speed-1)*100)}%"  # 한글 속도 조절
            )
            
            # 한글 WAV 파일 생성
            kor_file = script_dir / f"output_kor_{i}.wav"
            if os.path.exists(kor_file):
                os.remove(kor_file)
                
            await communicate.save(str(kor_file))
            
            # 한글 문자열의 실제 표시 길이 계산 (한글은 2칸, 영문/숫자는 1칸 차지)
            korean_display_length = sum(2 if ord(c) > 127 else 1 for c in korean)
            padding = 40 - (28 + korean_display_length)  # 28은 고정된 부분의 길이 (번호4 + 공백2 + 영단어20 + 공백2)
            padding = max(padding, 1)  # 최소 1칸의 공백 보장
            
            # 로그 출력 및 재생
            current_time = datetime.now().strftime("%H:%M:%S.%f")[:-3]  # 밀리초까지 표시
            print(f"{i:4d}  {english:20}  {korean}{' ' * padding}{current_time}")
            os.system(f"afplay {eng_file}")
            os.system(f"afplay {kor_file}")
            
            # 임시 파일 삭제
            os.remove(eng_file)
            os.remove(kor_file)
            
    except FileNotFoundError as e:
        print(f"\n파일 오류: {e}")
        print("스크립트 위치:", script_dir)
    except Exception as e:
        print(f"\n에러가 발생했습니다: {str(e)}")
        # 임시 파일 정리
        for f in ['eng_file', 'kor_file']:
            if f in locals() and os.path.exists(locals()[f]):
                os.remove(locals()[f])

if __name__ == "__main__":
    asyncio.run(main())

👉 이 게시글도 읽어보세요