edge-tts WSServerHandshakeError, 무료 TTS 대안 Piper·gTTS 비교
서버에서 한국어 안내 멘트를 파일로 뽑아 두려는 상황을 가정하고, Debian 13에서 edge-tts와 Piper, gTTS가 실제로 음성을 만드는지, 실패할 때는 어떤 흔적을 남기는지를 실행 로그와 터미널 캡처로 남겼습니다. API 키 발급 없이 짧은 샘플을 만들려는 분에게 궁금한 것은 설치가 됐느냐보다 재생할 파일이 남느냐일 것입니다. 외부 음성 서비스에 붙는 경로와 로컬 모델을 돌리는 경로는 장애가 났을 때 손볼 곳이 다르다는 점도 처음부터 염두에 뒀습니다. 출발점은 edge-tts 저장소 README가 소개하는 온라인 음성 연결이고, 이를 재현한 뒤 같은 문장을 다른 도구로도 파일에 남겼습니다.
문제는 Debian 13에 이미 설치되어 있던 edge-tts 7.2.8에서 시작됐습니다. 한국어 음성 ko-KR-SunHiNeural로 한 문장을 합성하다가 웹소켓 핸드셰이크에서 거절된 것입니다. 여기서는 그 오류와 릴리스 사이의 차이를 짚은 다음, 같은 문장을 파일로 만들어 낸 세 경로를 차례로 보여 드립니다. edge-tts 7.3.1 업그레이드, Piper의 ko_KR-kss-medium, gTTS의 언어 코드 ko입니다. 합성 문장은 모두 「오늘 회의는 오후 세 시에 시작합니다.」로 맞췄습니다.
edge-tts 7.2.8이 WSServerHandshakeError로 빈 음성 파일을 남길 때
비교 환경은 아래 표와 같습니다. 시스템에 깔린 edge-tts와 가상환경 안의 패키지를 나눠 적은 데는 이유가 있습니다. 둘이 섞이면 실패한 실행과 성공한 실행이 어느 쪽 패키지에서 나왔는지 헷갈립니다. 실행 시각은 재현 출력의 date 행에 남아 있습니다.
| 항목 | 값 |
|---|---|
| OS | Debian GNU/Linux 13 (trixie), VERSION 13, DEBIAN_VERSION_FULL 13.7 |
| Python | 3.13.5 |
| 실행일 | 2026-10-14 (재현 출력 기준) |
| LANG | C.UTF-8 |
| 시스템 edge-tts | 7.2.8 |
| 가상환경 | piper-tts 1.8.0, gTTS 2.5.4, edge-tts 7.3.1, onnxruntime 1.31.0 |
재현은 시스템 python3의 edge-tts를 그대로 사용합니다. 음성은 ko-KR-SunHiNeural이고, 문장은 text.txt에 넣은 뒤 –text로 넘깁니다. 미디어 경로는 work/edge-7.2.8.mp3입니다. 스크립트는 같은 패키지의 constants.py에서 SEC_MS_GEC_VERSION 접두 숫자, BASE_URL, CHROMIUM_FULL_VERSION만 출력합니다.
실패를 다시 만들 때는 문장과 음성 이름을 고정하고 시스템 python3로 돌렸습니다. –write-media가 가리키는 파일의 바이트 수까지 봐야, 명령이 실패했을 때 산출물에 그 흔적이 어떻게 남는지 알 수 있습니다. 합성 로그는 별도 파일로 떨어지니 오류 내용은 이어지는 출력에서 같이 읽으면 됩니다.
TEXT='오늘 회의는 오후 세 시에 시작합니다.' printf '%s\n' "$TEXT" > work/text.txt python3 -m edge_tts --voice ko-KR-SunHiNeural --text "$TEXT" --write-media work/edge-7.2.8.mp3 > work/edge-7.2.8.log 2>&1 wc -c work/edge-7.2.8.mp3
Debian 13, Python 3.13.5에서 시스템 edge-tts 7.2.8을 실행한 기록입니다. sec_ms_gec_prefix가 1로 찍혔고, 웹소켓 연결은 403 응답으로 거절됐습니다. 재시도 뒤에도 같은 예외가 나오고, 종료 값은 1, MP3 크기는 0입니다. 문장이나 입력을 의심하기 전에 온라인 연결 자체가 성립하는지부터 봐야 하는 장면입니다.
+ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
DEBIAN_VERSION_FULL=13.7
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
+ python3 --version
Python 3.13.5
+ printf LANG
LANG=C.UTF-8
+ date -Iseconds
2026-10-14T13:38:47+09:00
+ python3 -c edge-tts version and Sec-MS-GEC prefix
edge-tts 7.2.8
sec_ms_gec_prefix 1
base_url speech.platform.bing.com/consumer/speech/synthesize/readaloud
chromium_full_version 143.0.3650.75
+ printf text.txt
오늘 회의는 오후 세 시에 시작합니다.
+ python3 -m edge_tts --voice ko-KR-SunHiNeural --text "$(cat work/text.txt)" --write-media work/edge-7.2.8.mp3
Traceback (most recent call last):
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/communicate.py", line 588, in stream
async for message in self.__stream():
yield message
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/communicate.py", line 465, in __stream
) as session, session.ws_connect(
~~~~~~~~~~~~~~~~~~^
f"{WSS_URL}&ConnectionId={connect_id()}"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<5 lines>...
ssl=_SSL_CTX,
^^^^^^^^^^^^^
) as websocket:
^
File "/home/user/.local/lib/python3.13/site-packages/aiohttp/client.py", line 1756, in __aenter__
self._resp: _RetType_co = await self._coro
^^^^^^^^^^^^^^^^
File "/home/user/.local/lib/python3.13/site-packages/aiohttp/client.py", line 1329, in _ws_connect
raise WSServerHandshakeError(
...<5 lines>...
)
aiohttp.client_exceptions.WSServerHandshakeError: 403, message='Invalid response status', url='wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/__main__.py", line 6, in <module>
main()
~~~~^^
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/util.py", line 141, in main
asyncio.run(amain())
~~~~~~~~~~~^^^^^^^^^
File "/usr/lib/python3.13/asyncio/runners.py", line 195, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "/usr/lib/python3.13/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/usr/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/util.py", line 136, in amain
await _run_tts(args)
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/util.py", line 73, in _run_tts
async for chunk in communicate.stream():
...<3 lines>...
submaker.feed(chunk)
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/communicate.py", line 596, in stream
async for message in self.__stream():
yield message
File "/home/user/.local/lib/python3.13/site-packages/edge_tts/communicate.py", line 465, in __stream
) as session, session.ws_connect(
~~~~~~~~~~~~~~~~~~^
f"{WSS_URL}&ConnectionId={connect_id()}"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<5 lines>...
ssl=_SSL_CTX,
^^^^^^^^^^^^^
) as websocket:
^
File "/home/user/.local/lib/python3.13/site-packages/aiohttp/client.py", line 1756, in __aenter__
self._resp: _RetType_co = await self._coro
^^^^^^^^^^^^^^^^
File "/home/user/.local/lib/python3.13/site-packages/aiohttp/client.py", line 1329, in _ws_connect
raise WSServerHandshakeError(
...<5 lines>...
)
aiohttp.client_exceptions.WSServerHandshakeError: 403, message='Invalid response status', url='wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1'
edge_tts_exit=1
+ wc -c work/edge-7.2.8.mp3
0 work/edge-7.2.8.mp3
터미널에서도 같은 실패가 그대로 보입니다. 연결 단계의 403과 크기 0인 MP3가 나란히 남았으니, 이 실행을 음성 생성 완료로 처리하면 안 됩니다. 배치 작업이라면 이 조합을 실패로 잡아 두어야 빈 안내 파일이 다음 단계로 흘러가지 않습니다.

이 캡처에는 실행 계정의 홈 경로가 실제 이름으로 보입니다. 본문 트레이스백은 같은 출력을 /home/user/.local로 바꿔 적었고, 파일 이름과 줄 번호, 예외는 바뀌지 않았습니다.
예외 클래스는 aiohttp.client_exceptions.WSServerHandshakeError, 상태 값은 403, 메시지는 Invalid response status입니다. 캡처에도 edge_tts_exit가 1로, wc -c 결과가 0바이트로 남았습니다. 파일 경로는 생겼지만 핸드셰이크가 거절된 뒤라 재생할 음성 바이트는 한 바이트도 쌓이지 않았습니다. 파일이 있느냐만 검사하는 스크립트라면 이 실패를 그냥 지나칩니다. 로그의 URL은 쿼리 문자열을 지운 주소라서, 원인을 쫓을 때는 예외와 종료 값을 같이 봐야 합니다.
WebSocket 403을 가르는 것은 Sec-MS-GEC 접두 숫자
7.2.8의 sec_ms_gec_prefix는 1입니다. BASE_URL은 speech.platform.bing.com/consumer/speech/synthesize/readaloud이고, chromium_full_version은 143.0.3650.75입니다. 7.3.1도 호스트와 Chromium 버전 문자열은 같고, 접두만 2입니다. 이번에 출력한 상수 가운데 실패 버전과 성공 버전이 다른 항목은 이 접두 숫자입니다.
7.3.0 릴리스 본문은 Sec-MS-GEC v2 token support를 추가했다고 적습니다. 해당 항목은 풀 리퀘스트 492로 연결되어 있습니다. 7.3.1 릴리스 본문은 서버 날짜 파싱이 로캘에 의존하던 문제를 고쳤다고 적고, 풀 리퀘스트 494를 가리킵니다. 이 서버의 LANG은 C.UTF-8입니다. 접두가 2인 7.3.0도 같은 문장으로 종료 코드 0의 mp3를 만들었으므로, 이번 파일 생성은 7.3.1의 날짜 파싱 수정만으로 설명되지 않습니다. 7.2.8이 접두 1을 보낸 핸드셰이크는 상태 403으로 끝났고, 접두 2인 7.3.0과 7.3.1은 파일을 만들었습니다.
실행 시점에 PyPI에 올라 있던 edge-tts 최신 버전은 7.3.1이었습니다. 뒤에 나오는 업그레이드도 그 버전으로 고정해 설치합니다.
7.3.1 업그레이드, Piper, gTTS: 같은 문장을 파일로 만든 세 경로
복구 명령은 글 폴더의 가상환경 venv 안에서만 돌렸습니다. 설치 한 줄로 piper-tts 1.8.0, gTTS 2.5.4, edge-tts 7.3.1을 함께 고정했고, 세 명령 어디에도 API 키 인자는 들어가지 않습니다.
edge-tts를 7.3.1로 올리기
음성 이름과 문장은 실패했던 7.2.8 때와 똑같이 두고, 패키지만 7.3.1로 올려 같은 –write-media 형태로 mp3를 썼습니다. edge_731_exit는 0입니다. ffprobe는 이 파일을 format_name=mp3, codec_name=mp3, sample_rate=24000, channels=1로 읽었고, volumedetect가 mean_volume과 max_volume을 내놓았으니 빈 껍데기 파일은 아닙니다. 길이와 바이트 수는 출력 블록에 있습니다.
로컬에서 도는 Piper ko_KR-kss-medium
Piper README는 로컬 신경망 TTS이고 음소 변환에 espeak-ng를 내장한다고 적습니다. 음성은 공식 CLI 문서의 방식대로 venv/bin/python -m piper.download_voices로 받습니다. 음성 이름은 ko_KR-kss-medium이고, 저장 위치는 work/voices입니다. voices.json에는 이 키가 있습니다. onnx 파일 크기는 63221984바이트입니다. 같이 받은 설정의 model_sample_rate는 22050, model_quality는 medium, espeak_voice는 ko, phoneme_type은 espeak입니다.
합성은 venv/bin/python -m piper에 -m ko_KR-kss-medium, –data-dir, -f work/piper.wav를 주고 문장을 넘깁니다. piper_exit는 0입니다. ffprobe는 format_name=wav, codec_name=pcm_s16le, sample_rate=22050, channels=1입니다. 모델 카드의 Samplerate 22,050Hz와 같습니다. volumedetect도 mean_volume을 출력했습니다.
gTTS는 언어 코드 ko로
gTTS README는 구글에 소속되지 않은 도구이고, Google Cloud TTS와 다른 경로라고 적습니다. Google Translate의 문서화되지 않은 음성 기능을 쓰며, 상위 변경이 예고 없이 생길 수 있다고 밝힙니다. CLI는 gtts-cli이고, 언어는 -l ko, 출력은 -o work/gtts.mp3입니다. gtts_exit는 0입니다. ffprobe는 format_name=mp3, sample_rate=24000, channels=1입니다. 언어 목록에는 ko: Korean 한 줄이 있습니다.
설치와 합성 모두 venv의 실행 파일을 경로째 적어 시스템 패키지와 떼어 놓았습니다. edge-tts는 버전을 고정하고, Piper 모델은 work/voices에 받고, gTTS에는 언어 코드 ko를 줍니다. 같은 문장이라도 저장 형식이 서로 다르니 파일 확장자와 ffprobe의 판독을 함께 놓고 비교합니다.
python3 -m venv venv venv/bin/python -m pip install --disable-pip-version-check 'piper-tts==1.8.0' 'gTTS==2.5.4' 'edge-tts==7.3.1' venv/bin/python -m edge_tts --voice ko-KR-SunHiNeural --text "$TEXT" --write-media work/edge-7.3.1.mp3 ffprobe -v error -show_entries format=format_name,duration,size:stream=codec_name,sample_rate,channels -of default=noprint_wrappers=1 work/edge-7.3.1.mp3 venv/bin/python -m piper.download_voices ko_KR-kss-medium --data-dir work/voices venv/bin/python -m piper -m ko_KR-kss-medium --data-dir work/voices -f work/piper.wav -- "$TEXT" venv/bin/gtts-cli -l ko -o work/gtts.mp3 "$TEXT"
성공한 출력에서 edge-tts의 MP3는 21744바이트, Piper의 WAV는 121388바이트, gTTS의 MP3는 29568바이트입니다. 숫자만 보면 순위를 매기고 싶어지지만, Piper는 sample_rate가 22050이고 온라인 경로의 MP3는 24000이라 파일 크기로 성능을 견줄 수는 없습니다. duration도 재생 길이일 뿐 합성에 걸린 시간이 아닙니다. 그래서 이 글에서 성공으로 판정한 근거는 종료 값, 포맷 판독, 음량 출력이 세 가지 모두 남았다는 사실입니다.
+ python3 -m venv venv
+ venv/bin/python -m pip install --disable-pip-version-check piper-tts==1.8.0 gTTS==2.5.4 edge-tts==7.3.1
Requirement already satisfied: piper-tts==1.8.0 in ./run/venv/lib/python3.13/site-packages (1.8.0)
Requirement already satisfied: gTTS==2.5.4 in ./run/venv/lib/python3.13/site-packages (2.5.4)
Requirement already satisfied: edge-tts==7.3.1 in ./run/venv/lib/python3.13/site-packages (7.3.1)
Requirement already satisfied: onnxruntime<2,>=1 in ./run/venv/lib/python3.13/site-packages (from piper-tts==1.8.0) (1.31.0)
Requirement already satisfied: pathvalidate<4,>=3 in ./run/venv/lib/python3.13/site-packages (from piper-tts==1.8.0) (3.3.1)
Requirement already satisfied: requests<3,>=2.27 in ./run/venv/lib/python3.13/site-packages (from gTTS==2.5.4) (2.34.2)
Requirement already satisfied: click<8.2,>=7.1 in ./run/venv/lib/python3.13/site-packages (from gTTS==2.5.4) (8.1.8)
Requirement already satisfied: aiohttp<4.0.0,>=3.8.0 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (3.14.5)
Requirement already satisfied: certifi>=2023.11.17 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (2026.7.22)
Requirement already satisfied: tabulate<1.0.0,>=0.4.4 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (0.10.0)
Requirement already satisfied: typing-extensions<5.0.0,>=4.1.0 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (4.16.0)
Requirement already satisfied: aiohappyeyeballs>=2.5.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (2.7.1)
Requirement already satisfied: aiosignal>=1.4.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (1.4.0)
Requirement already satisfied: attrs>=17.3.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (26.1.0)
Requirement already satisfied: frozenlist>=1.1.1 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (1.8.0)
Requirement already satisfied: multidict<8.0,>=4.5 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (7.1.0)
Requirement already satisfied: propcache>=0.2.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (0.5.4)
Requirement already satisfied: yarl<2.0,>=1.25.1 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (1.25.1)
Requirement already satisfied: flatbuffers in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (25.12.19)
Requirement already satisfied: numpy>=1.21.6 in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (2.5.4)
Requirement already satisfied: packaging in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (26.3)
Requirement already satisfied: protobuf>=4.25.8 in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (7.36.2)
Requirement already satisfied: charset_normalizer<4,>=2 in ./run/venv/lib/python3.13/site-packages (from requests<3,>=2.27->gTTS==2.5.4) (3.5.2)
Requirement already satisfied: idna<4,>=2.5 in ./run/venv/lib/python3.13/site-packages (from requests<3,>=2.27->gTTS==2.5.4) (3.20)
Requirement already satisfied: urllib3<3,>=1.26 in ./run/venv/lib/python3.13/site-packages (from requests<3,>=2.27->gTTS==2.5.4) (2.8.0)
+ venv/bin/python -c versions and Sec-MS-GEC prefix
edge-tts 7.3.1
piper-tts 1.8.0
gTTS 2.5.4
onnxruntime 1.31.0
sec_ms_gec_prefix 2
base_url speech.platform.bing.com/consumer/speech/synthesize/readaloud
chromium_full_version 143.0.3650.75
+ venv/bin/python -m edge_tts --voice ko-KR-SunHiNeural --text "$(cat work/text.txt)" --write-media work/edge-7.3.1.mp3
0%| | 0KB 00:00<--:-- 86%|████████████████████████████████████████████████████████████████████████████████▎ | 18KB 00:00<00:00…(진행 표시줄 생략)…
edge_731_exit=0
+ ffprobe edge-7.3.1.mp3
codec_name=mp3
sample_rate=24000
channels=1
format_name=mp3
duration=3.624000
size=21744
+ ffmpeg volumedetect edge-7.3.1.mp3
[Parsed_volumedetect_0 @ 0x7fb9d8002e00] mean_volume: -16.4 dB
[Parsed_volumedetect_0 @ 0x7fb9d8002e00] max_volume: -3.1 dB
+ venv/bin/python -m piper.download_voices ko_KR-kss-medium --data-dir work/voices
INFO:__main__:Downloaded: ko_KR-kss-medium
+ wc -c work/voices/ko_KR-kss-medium.onnx
63221984 work/voices/ko_KR-kss-medium.onnx
+ venv/bin/python -c model config
model_sample_rate 22050
model_quality medium
espeak_voice ko
phoneme_type espeak
+ venv/bin/python -m piper -m ko_KR-kss-medium --data-dir work/voices -f work/piper.wav -- "$(cat work/text.txt)"
piper_exit=0
+ ffprobe piper.wav
codec_name=pcm_s16le
sample_rate=22050
channels=1
format_name=wav
duration=2.751565
size=121388
+ ffmpeg volumedetect piper.wav
[Parsed_volumedetect_0 @ 0x7f0b28002e00] mean_volume: -13.0 dB
[Parsed_volumedetect_0 @ 0x7f0b28002e00] max_volume: -0.0 dB
+ venv/bin/gtts-cli -l ko -o work/gtts.mp3 "$(cat work/text.txt)"
gtts_exit=0
+ ffprobe gtts.mp3
codec_name=mp3
sample_rate=24000
channels=1
format_name=mp3
duration=3.696000
size=29568
+ ffmpeg volumedetect gtts.mp3
[Parsed_volumedetect_0 @ 0x7fa9e8002e00] mean_volume: -18.8 dB
[Parsed_volumedetect_0 @ 0x7fa9e8002e00] max_volume: -5.0 dB
+ wc -c outputs
21744 work/edge-7.3.1.mp3
121388 work/piper.wav
29568 work/gtts.mp3
172700 total
해결 쪽 캡처에는 정상 경로로 만든 파일들의 크기 비교가 담겨 있습니다. gTTS의 종료 값이 0이고, edge-tts와 Piper의 산출물에도 바이트 수가 찍혔습니다. 이 결과는 기준 샘플로 보관해 두고, 패키지를 바꿀 때마다 같은 문장으로 포맷과 파일 크기를 다시 맞춰 보면 회귀를 바로 알아챕니다.

이미 ko-KR-SunHiNeural을 쓰던 작업이라면 edge-tts 업그레이드가 손댈 범위가 가장 좁습니다. 이번에도 명령 형태는 그대로 두고 패키지만 올려 MP3를 얻었습니다. 다만 온라인 서비스의 연결 방식에 따라 다시 깨질 수 있는 경로라서, 패키지를 고정한 뒤에도 실제 합성이 되는지 주기적으로 실행하는 점검 절차를 붙여 둬야 합니다.
Piper는 모델과 설정을 받아 두면 로컬에서 합성하는 구성을 선택할 수 있습니다. 시스템의 espeak-ng 패키지가 absent인 상태에서도 WAV가 만들어졌고, 모델 파일은 63221984바이트였습니다. 모델 카드의 Dataset 절에는 CC BY-NC-SA 4.0이 적혀 있으므로, 업무용 음성을 만들기 전에 데이터셋과 모델의 이용 조건을 검토해야 합니다.
gTTS는 모델 파일을 들고 있지 않아도 짧은 MP3를 뽑아 주는 흐름에 잘 맞았습니다. 그 대신 Translate 음성 기능에 기대는 도구라서 상위 서비스가 바뀌면 합성 경로를 다시 확인해야 합니다. 한국어 입력의 언어 코드는 목록에 있는 ko로 통일하고, 다른 도구에서 쓰던 음성 이름이나 지역 태그를 그대로 가져오지 않습니다.
버전, 음성 이름, 언어 코드, GPU 옵션을 바꿔 본 결과
정리해 보면 edge-tts 7.2.8은 접두 1로 403을 받았고, 7.3.0과 7.3.1은 접두 2로 파일을 만들었습니다. 릴리스 기록의 Sec-MS-GEC v2 지원과 딱 맞물리는 차이입니다. 같은 주소에 붙는다고 해서 설치해 둔 클라이언트가 언제까지나 호환된다고 기대하기 어려운 이유가 여기 있습니다. 7.3.1에는 날짜 파싱 수정도 들어 있지만 7.3.0도 성공했으므로, 이번 복구를 날짜 처리 하나의 덕으로 돌리지는 않습니다.
운영 헬스체크도 그래서 패키지 import 성공에서 끝내면 안 됩니다. 고정 문장으로 음성을 실제로 만든 뒤 종료 값과 파일 내용을 검사하도록 바꿔 둡니다. 합성 작업 설정에는 venv 실행 경로와 work/voices 모델 경로를 같이 묶어 관리해, 시스템 패키지나 엉뚱한 모델 디렉터리를 읽는 혼선을 막습니다. Kubernetes에 올린다면 외부 음성 서비스의 일시적인 실패 때문에 컨테이너가 재시작을 되풀이하지 않도록, 생존 여부를 보는 probe와 실제 합성 경로를 검사하는 작업을 떼어 두는 구성을 권합니다. 로컬 모델 작업과 온라인 합성 작업을 따로 두면 외부 연결 오류와 모델 로딩 오류도 자연스럽게 갈라져 보입니다.
7.3.0을 가상환경에 설치하면 sec_ms_gec_prefix는 2입니다. 같은 문장과 같은 음성 이름의 edge_730_exit는 0이고, ffprobe의 format_name은 mp3, sample_rate는 24000입니다. 측정이 끝나면 가상환경의 edge-tts를 다시 7.3.1로 설치해 두었습니다. 복구 후 버전 출력은 edge-tts 7.3.1입니다.
Piper에 없는 음성 이름 ko_KR-no-such-medium을 주면 ValueError가 납니다. 메시지는 Unable to find voice이며, piper.download_voices를 쓰라고 안내합니다. missing_voice_exit는 1입니다. download_voices가 ko_KR-kss-medium을 받은 뒤에야 -m에 그 이름을 쓸 수 있습니다.
v1.8.0 VOICES.md에서 ko_KR과 Korean을 검색하면 일치 줄이 없고, 출력은 voices_md_ko=none입니다. 같은 시점에 voices.json의 voices_json_has_ko_KR-kss-medium은 True입니다. 문서의 언어 목차만 보고 한국어 음성이 없다고 멈추기 쉽지만, 음성 목록 JSON과 Hugging Face의 ko/ko_KR/kss/medium 트리에는 이번 모델이 있습니다. 모델 카드는 언어를 ko_KR로 적고, 화자 수는 1, 품질은 medium, 샘플레이트는 22,050Hz입니다.
gTTS에 -l ko-KR을 주면 언어 검사 오류가 납니다. 출력 문장은 ‘ko-KR’ not in list of supported languages입니다. gtts_bad_lang_exit는 2입니다. 지역 코드를 붙인 BCP 형태가 아니라, 목록에 있는 ko를 써야 합니다. 같은 문장에 –slow를 붙이면 gtts_slow_exit는 0이고, ffprobe는 다시 mp3와 sample_rate 24000을 보고합니다. 기본 합성과 –slow의 duration 차이는 출력에서 비교하면 됩니다.
GPU 옵션도 이번 박스의 제약에 맞춰 확인했습니다. nvidia-smi는 명령이 없어 not-found로 남았습니다. onnxruntime이 보고한 공급자는 AzureExecutionProvider와 CPUExecutionProvider입니다. CLI 문서는 –cuda가 GPU 가속이며 onnxruntime-gpu 패키지가 필요하다고 적습니다. 그 패키지 대신 onnxruntime 1.31.0이 설치된 상태에서 –cuda를 켜면, CUDAExecutionProvider가 available provider names에 없다는 UserWarning이 나옵니다. piper_cuda_exit는 그래도 0이고, ffprobe는 wav, pcm_s16le, sample_rate 22050을 보고합니다. CUDA 공급자가 없다는 경고에도 파일은 생겼습니다.
시스템 패키지 espeak-ng는 설치되어 있지 않습니다. Piper README가 내장 espeak-ng를 말하므로, 배포판 패키지가 없어도 이번 한국어 음성의 합성 명령은 끝났습니다.
정상 합성을 확인한 다음에는 일부러 버전과 입력 조건을 바꿔 실패가 어떻게 달라지는지 봤습니다. edge-tts는 7.3.0을 시험한 뒤 7.3.1로 되돌리고, Piper에는 없는 음성 이름과 –cuda를 하나씩 줍니다. gTTS에는 ko-KR과 –slow를 넣어 언어 검사에서 막히는 경우와 옵션이 정상 적용되는 경우를 갈라 봅니다.
venv/bin/python -m pip install --disable-pip-version-check 'piper-tts==1.8.0' 'gTTS==2.5.4' 'edge-tts==7.3.0' venv/bin/python -m edge_tts --voice ko-KR-SunHiNeural --text "$TEXT" --write-media work/edge-7.3.0.mp3 venv/bin/python -m piper -m ko_KR-no-such-medium --data-dir work/voices -f work/missing.wav -- '테스트' venv/bin/python -m piper --cuda -m ko_KR-kss-medium --data-dir work/voices -f work/piper-cuda.wav -- "$TEXT" venv/bin/gtts-cli -l ko-KR -o work/gtts-bad.mp3 '테스트' venv/bin/gtts-cli -l ko --slow -o work/gtts-slow.mp3 "$TEXT"
로그를 따라가면 edge-tts 7.3.0도 접두 2로 MP3를 만들었고, 시험이 끝난 뒤 설치 버전은 7.3.1로 돌아왔습니다. Piper는 없는 음성 이름에서 종료 값 1로 실패했지만, –cuda는 공급자 경고를 남기고도 종료 값 0으로 파일을 만들었습니다. gTTS의 ko-KR은 종료 값 2, ko에 –slow를 붙인 쪽은 종료 값 0입니다. 종료 값 하나만 보는 성공 판정으로는 언어 오류와 GPU 공급자 불일치를 가려낼 수 없다는 것이 이 결과에서 드러납니다.
+ python3 -m venv venv
+ venv/bin/python -m pip install --disable-pip-version-check piper-tts==1.8.0 gTTS==2.5.4 edge-tts==7.3.0
Requirement already satisfied: piper-tts==1.8.0 in ./run/venv/lib/python3.13/site-packages (1.8.0)
Requirement already satisfied: gTTS==2.5.4 in ./run/venv/lib/python3.13/site-packages (2.5.4)
Collecting edge-tts==7.3.0
Using cached edge_tts-7.3.0-py3-none-any.whl.metadata (6.1 kB)
Requirement already satisfied: onnxruntime<2,>=1 in ./run/venv/lib/python3.13/site-packages (from piper-tts==1.8.0) (1.31.0)
Requirement already satisfied: pathvalidate<4,>=3 in ./run/venv/lib/python3.13/site-packages (from piper-tts==1.8.0) (3.3.1)
Requirement already satisfied: requests<3,>=2.27 in ./run/venv/lib/python3.13/site-packages (from gTTS==2.5.4) (2.34.2)
Requirement already satisfied: click<8.2,>=7.1 in ./run/venv/lib/python3.13/site-packages (from gTTS==2.5.4) (8.1.8)
Requirement already satisfied: aiohttp<4.0.0,>=3.8.0 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.0) (3.14.5)
Requirement already satisfied: certifi>=2023.11.17 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.0) (2026.7.22)
Requirement already satisfied: tabulate<1.0.0,>=0.4.4 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.0) (0.10.0)
Requirement already satisfied: typing-extensions<5.0.0,>=4.1.0 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.0) (4.16.0)
Requirement already satisfied: aiohappyeyeballs>=2.5.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (2.7.1)
Requirement already satisfied: aiosignal>=1.4.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (1.4.0)
Requirement already satisfied: attrs>=17.3.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (26.1.0)
Requirement already satisfied: frozenlist>=1.1.1 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (1.8.0)
Requirement already satisfied: multidict<8.0,>=4.5 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (7.1.0)
Requirement already satisfied: propcache>=0.2.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (0.5.4)
Requirement already satisfied: yarl<2.0,>=1.25.1 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.0) (1.25.1)
Requirement already satisfied: flatbuffers in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (25.12.19)
Requirement already satisfied: numpy>=1.21.6 in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (2.5.4)
Requirement already satisfied: packaging in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (26.3)
Requirement already satisfied: protobuf>=4.25.8 in ./run/venv/lib/python3.13/site-packages (from onnxruntime<2,>=1->piper-tts==1.8.0) (7.36.2)
Requirement already satisfied: charset_normalizer<4,>=2 in ./run/venv/lib/python3.13/site-packages (from requests<3,>=2.27->gTTS==2.5.4) (3.5.2)
Requirement already satisfied: idna<4,>=2.5 in ./run/venv/lib/python3.13/site-packages (from requests<3,>=2.27->gTTS==2.5.4) (3.20)
Requirement already satisfied: urllib3<3,>=1.26 in ./run/venv/lib/python3.13/site-packages (from requests<3,>=2.27->gTTS==2.5.4) (2.8.0)
Using cached edge_tts-7.3.0-py3-none-any.whl (38 kB)
Installing collected packages: edge-tts
Attempting uninstall: edge-tts
Found existing installation: edge-tts 7.3.1
Uninstalling edge-tts-7.3.1:
Successfully uninstalled edge-tts-7.3.1
Successfully installed edge-tts-7.3.0
+ venv/bin/python -c edge-tts 7.3.0 prefix
edge-tts 7.3.0
sec_ms_gec_prefix 2
+ venv/bin/python -m edge_tts --voice ko-KR-SunHiNeural --text "$(cat work/text.txt)" --write-media work/edge-7.3.0.mp3
0%| | 0KB 00:00<--:-- 76%|███████████████████████████████████████████████████████████████████████ | 16KB 00:00<00:00…(진행 표시줄 생략)…
edge_730_exit=0
+ ffprobe edge-7.3.0.mp3
codec_name=mp3
sample_rate=24000
channels=1
format_name=mp3
duration=3.624000
size=21744
+ venv/bin/python -m pip install --disable-pip-version-check edge-tts==7.3.1
Collecting edge-tts==7.3.1
Using cached edge_tts-7.3.1-py3-none-any.whl.metadata (6.1 kB)
Requirement already satisfied: aiohttp<4.0.0,>=3.8.0 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (3.14.5)
Requirement already satisfied: certifi>=2023.11.17 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (2026.7.22)
Requirement already satisfied: tabulate<1.0.0,>=0.4.4 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (0.10.0)
Requirement already satisfied: typing-extensions<5.0.0,>=4.1.0 in ./run/venv/lib/python3.13/site-packages (from edge-tts==7.3.1) (4.16.0)
Requirement already satisfied: aiohappyeyeballs>=2.5.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (2.7.1)
Requirement already satisfied: aiosignal>=1.4.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (1.4.0)
Requirement already satisfied: attrs>=17.3.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (26.1.0)
Requirement already satisfied: frozenlist>=1.1.1 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (1.8.0)
Requirement already satisfied: multidict<8.0,>=4.5 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (7.1.0)
Requirement already satisfied: propcache>=0.2.0 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (0.5.4)
Requirement already satisfied: yarl<2.0,>=1.25.1 in ./run/venv/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (1.25.1)
Requirement already satisfied: idna>=2.0 in ./run/venv/lib/python3.13/site-packages (from yarl<2.0,>=1.25.1->aiohttp<4.0.0,>=3.8.0->edge-tts==7.3.1) (3.20)
Using cached edge_tts-7.3.1-py3-none-any.whl (38 kB)
Installing collected packages: edge-tts
Attempting uninstall: edge-tts
Found existing installation: edge-tts 7.3.0
Uninstalling edge-tts-7.3.0:
Successfully uninstalled edge-tts-7.3.0
Successfully installed edge-tts-7.3.1
+ venv/bin/python -c edge-tts restored
edge-tts 7.3.1
+ dpkg espeak-ng
espeak_ng_pkg=absent
+ command nvidia-smi
nvidia-smi not-found
+ venv/bin/python -c onnxruntime providers
onnxruntime_providers AzureExecutionProvider,CPUExecutionProvider
+ venv/bin/python -m piper.download_voices ko_KR-kss-medium --data-dir work/voices
INFO:__main__:Downloaded: ko_KR-kss-medium
+ venv/bin/python -m piper -m ko_KR-no-such-medium --data-dir work/voices -f work/missing.wav -- 테스트
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/home/user/free/run/venv/lib/python3.13/site-packages/piper/__main__.py", line 258, in <module>
main()
~~~~^^
File "/home/user/free/run/venv/lib/python3.13/site-packages/piper/__main__.py", line 143, in main
raise ValueError(
f"Unable to find voice: {model_path} (use piper.download_voices)"
)
ValueError: Unable to find voice: ko_KR-no-such-medium (use piper.download_voices)
missing_voice_exit=1
+ venv/bin/python -m piper --cuda -m ko_KR-kss-medium --data-dir work/voices -f work/piper-cuda.wav -- "$(cat work/text.txt)"
/home/user/free/run/venv/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:197: UserWarning: Specified provider 'CUDAExecutionProvider' is not in available provider names.Available providers: 'AzureExecutionProvider, CPUExecutionProvider'
warnings.warn(
piper_cuda_exit=0
+ ffprobe piper-cuda.wav
codec_name=pcm_s16le
sample_rate=22050
channels=1
format_name=wav
size=127020
+ venv/bin/gtts-cli -l ko-KR -o work/gtts-bad.mp3 테스트
Usage: gtts-cli [OPTIONS] <text>
Try 'gtts-cli -h' for help.
Error: 'ko-KR' not in list of supported languages.
Use --all to list languages or add --nocheck to disable language check.
gtts_bad_lang_exit=2
+ venv/bin/gtts-cli --all
+ grep ko: Korean
34: ko: Korean
+ venv/bin/gtts-cli -l ko --slow -o work/gtts-slow.mp3 "$(cat work/text.txt)"
gtts_slow_exit=0
+ ffprobe gtts-slow.mp3
codec_name=mp3
sample_rate=24000
channels=1
format_name=mp3
duration=4.464000
size=35712
+ curl docs
+ grep edge readme
3:`edge-tts` is a Python module that allows you to use Microsoft Edge's online text-to-speech service from within your Python code or using the provided `edge-tts` or `edge-playback` command.
0
+ grep piper readme
3:A fast and local neural text-to-speech engine that embeds [espeak-ng][] for phonemization.
3:A fast and local neural text-to-speech engine that embeds [espeak-ng][] for phonemization.
+ grep gtts readme
40:This project is *not* affiliated with Google or Google Cloud. Breaking upstream changes *can* occur without notice. This project is leveraging the undocumented [Google Translate](https://translate.google.com) speech functionality and is *different* from [Google Cloud Text-to-Speech](https://cloud.google.com/text-to-speech/).
+ grep cli cuda
50:* `--cuda` - enable GPU acceleration (requires `onnxruntime-gpu` package)
+ grep voices ko
voices_md_ko=none
+ sed model card
# Model card for kss (medium)
* Language: ko_KR (Korean, Korea)
* Speakers: 1
* Quality: medium
* Samplerate: 22,050Hz
## Dataset
* URL: https://www.kaggle.com/datasets/bryanpark/korean-single-speaker-speech-dataset
* License: https://creativecommons.org/licenses/by-nc-sa/4.0/
## Training
Finetuned from LibriTTS-R base model.
+ curl release notes
release 7.3.0
## What's Changed
* Show a progress bar on stderr during synthesis by @afunTW in https://github.com/rany2/edge-tts/pull/488
* feat: add Sec-MS-GEC v2 token support by @HarryXiaoCN in https://github.com/rany2/edge-tts/pull/492
* fix: work around mpv CoreAudio playback failure on macOS by @rany2 in https://github.com/rany2/edge-tts/pull/493
## New Contributors
* @afunTW made their first contribution in https://github.com/rany2/edge-tts/pull/488
* @HarryXiaoCN made their first contribution in https://github.com/rany2/edge-tts/pull/492
**Full Changelog**: https://github.com/rany2/edge-tts/compare/7.2.8...7.3.0
---
release 7.3.1
## What's Changed
* Fix locale-dependent server date parsing by @rany2 in https://github.com/rany2/edge-tts/pull/494
**Full Changelog**: https://github.com/rany2/edge-tts/compare/7.3.0...7.3.1
---
+ pypi latest edge-tts
pypi_edge_tts_latest 7.3.1
+ voices.json key
voices_json_has_ko_KR-kss-medium True
+ curl -sI
+ curl -sI https://pypi.org/project/edge-tts/7.2.8/
200 https://pypi.org/project/edge-tts/7.2.8/
+ curl -sI https://pypi.org/project/edge-tts/7.3.0/
200 https://pypi.org/project/edge-tts/7.3.0/
+ curl -sI https://pypi.org/project/edge-tts/7.3.1/
200 https://pypi.org/project/edge-tts/7.3.1/
+ curl -sI https://github.com/rany2/edge-tts
200 https://github.com/rany2/edge-tts
+ curl -sI https://github.com/rany2/edge-tts/blob/master/README.md
200 https://github.com/rany2/edge-tts/blob/master/README.md
+ curl -sI https://github.com/rany2/edge-tts/releases/tag/7.2.8
200 https://github.com/rany2/edge-tts/releases/tag/7.2.8
+ curl -sI https://github.com/rany2/edge-tts/releases/tag/7.3.0
200 https://github.com/rany2/edge-tts/releases/tag/7.3.0
+ curl -sI https://github.com/rany2/edge-tts/releases/tag/7.3.1
200 https://github.com/rany2/edge-tts/releases/tag/7.3.1
+ curl -sI https://github.com/rany2/edge-tts/pull/492
200 https://github.com/rany2/edge-tts/pull/492
+ curl -sI https://github.com/rany2/edge-tts/pull/494
200 https://github.com/rany2/edge-tts/pull/494
+ curl -sI https://raw.githubusercontent.com/rany2/edge-tts/master/README.md
200 https://raw.githubusercontent.com/rany2/edge-tts/master/README.md
+ curl -sI https://pypi.org/project/piper-tts/1.8.0/
200 https://pypi.org/project/piper-tts/1.8.0/
+ curl -sI https://github.com/OHF-Voice/piper1-gpl
200 https://github.com/OHF-Voice/piper1-gpl
+ curl -sI https://github.com/OHF-Voice/piper1-gpl/blob/main/docs/CLI.md
200 https://github.com/OHF-Voice/piper1-gpl/blob/main/docs/CLI.md
+ curl -sI https://github.com/OHF-Voice/piper1-gpl/blob/v1.8.0/docs/VOICES.md
200 https://github.com/OHF-Voice/piper1-gpl/blob/v1.8.0/docs/VOICES.md
+ curl -sI https://github.com/OHF-Voice/piper1-gpl/blob/v1.8.0/README.md
200 https://github.com/OHF-Voice/piper1-gpl/blob/v1.8.0/README.md
+ curl -sI https://raw.githubusercontent.com/OHF-Voice/piper1-gpl/v1.8.0/README.md
200 https://raw.githubusercontent.com/OHF-Voice/piper1-gpl/v1.8.0/README.md
+ curl -sI https://huggingface.co/rhasspy/piper-voices/tree/main/ko/ko_KR/kss/medium
200 https://huggingface.co/rhasspy/piper-voices/tree/main/ko/ko_KR/kss/medium
+ curl -sI https://huggingface.co/rhasspy/piper-voices/blob/main/ko/ko_KR/kss/medium/MODEL_CARD
200 https://huggingface.co/rhasspy/piper-voices/blob/main/ko/ko_KR/kss/medium/MODEL_CARD
+ curl -sI https://creativecommons.org/licenses/by-nc-sa/4.0/
200 https://creativecommons.org/licenses/by-nc-sa/4.0/
+ curl -sI https://pypi.org/project/gTTS/2.5.4/
200 https://pypi.org/project/gTTS/2.5.4/
+ curl -sI https://github.com/pndurette/gTTS
200 https://github.com/pndurette/gTTS
+ curl -sI https://github.com/pndurette/gTTS/blob/main/README.md
200 https://github.com/pndurette/gTTS/blob/main/README.md
+ curl -sI https://raw.githubusercontent.com/pndurette/gTTS/main/README.md
200 https://raw.githubusercontent.com/pndurette/gTTS/main/README.md
점검 캡처에는 Piper와 gTTS 문서, 모델 주소에 curl -sI를 보낸 결과가 모여 있습니다. 여기 찍힌 200 상태는 그 주소에 닿았다는 뜻일 뿐, 음성 합성이 됐다는 판정을 대신하지 않습니다. 합성 상태는 앞에서 본 종료 값과 ffprobe 출력으로 판단하고, 문서 주소 점검은 장애를 조사할 때 참고 자료를 빨리 찾기 위한 용도로만 씁니다.

faster-whisper metadata_errors TypeError, av 18.1.0으로 전사 성공 글은 그 TypeError의 원인과 PyAV 18.1.0 고정 방법을 설명합니다. LLM 메모리 계산: CPU에서 잰 Q4_K_M 6.16비트와 Ollama 적재 크기 글은 4비트 추정이 Ollama size와 어긋나는 원인과 텐서·KV 분리 계산을 설명합니다.
403을 받았다면 7.3.1부터, 외부 연결이 없으면 Piper
비교표에는 이번 실행에서 실제로 파일을 만들어 낸 선택지만 올렸습니다. 고르는 기준은 두 가지로 좁혀집니다. 온라인 연결이 필요한가, 로컬 모델을 보관해야 하는가입니다. 표의 종료 값은 그 실행 한 번의 결과이므로, 배포한 뒤에도 같은 문장으로 산출물을 검사하는 절차는 계속 이어져야 합니다.
| 구분 | edge-tts 7.3.0 | edge-tts 7.3.1 | piper-tts 1.8.0 | gTTS 2.5.4 |
|---|---|---|---|---|
| 경로 | 온라인 Edge, 접두 2 | 온라인 Edge, 접두 2 | 로컬 onnx ko_KR-kss-medium | Translate 음성, 언어 ko |
| 이번 종료 | 0, mp3, 24000 Hz, 모노 | 0, mp3, 24000 Hz, 모노 | 0, wav, pcm_s16le, 22050 Hz, 모노 | 0, mp3, 24000 Hz, 모노 |
| API 키 인자 | 없음 | 없음 | 없음 | 없음 |
| 같이 볼 조건 | 7.3.0 릴리스의 Sec-MS-GEC v2 지원 | 날짜 파싱 수정이 추가로 들어 있음 | 모델 카드 Dataset 절 CC BY-NC-SA 4.0, onnx 63221984바이트 | ko-KR은 종료 코드 2 |
이미 edge-tts 명령과 ko-KR-SunHiNeural을 쓰는데 7.2.8에서 상태 403을 받았다면, 7.3.1로 올리는 것이 첫 번째 선택입니다. PyPI 최신이 7.3.1이었고, 같은 문장으로 mp3가 열렸습니다. 올린 뒤에는 ffprobe로 format_name과 sample_rate만 확인하면 됩니다.
외부 연결 없이 로컬에서 합성해야 한다면 Piper를 고르겠습니다. download_voices로 ko_KR-kss-medium을 받고 -m에 그 이름을 주니 wav가 생겼습니다. 다만 상업 목적의 음성이라면 모델 카드의 Dataset 절이 가리키는 CC BY-NC-SA 4.0 조건부터 읽고 결정해야 합니다.
모델 파일을 디스크에 두기 싫고 짧은 mp3만 있으면 되는 경우는 gTTS가 맞습니다. 언어는 ko로 줍니다. ko-KR을 넣으면 목록 검사에서 거절됩니다. README가 밝히듯 공식 Cloud TTS와는 다른 경로이고, 서비스가 예고 없이 바뀔 수 있다는 점은 감안해야 합니다.
GPU 가속을 기대하고 piper에 –cuda를 켰다면 결과를 다시 봐야 합니다. 이번 환경에서는 CUDAExecutionProvider가 없다는 경고가 뜬 뒤에도 파일이 만들어졌습니다. onnxruntime-gpu를 설치하지 않은 상태라면, 파일이 나왔다고 해서 가속이 적용됐다고 볼 근거는 없습니다.
결국 이번에 먼저 고칠 곳은 음성 문장이 아니라 연결을 맡은 edge-tts 버전이었습니다. 기존 Edge 음성을 유지해야 한다면 저는 7.3.1을 가상환경에 고정하고, 파일 크기와 ffprobe 판독을 배치 작업의 완료 조건에 넣겠습니다. 외부 연결을 줄여야 하는 안내 파일 생성에는 이용 조건을 검토한 Piper 모델을 미리 배치하는 구성을 택하겠습니다. gTTS는 ko를 명시한 짧은 샘플용 경로로 남기되, 서비스 변경에 대비해 이미 만든 산출물도 보관하겠습니다. 실시간 응답이 필요한 서비스라면 합성 작업을 요청 처리와 분리하고, 정해진 안내 문장은 미리 만들어 재사용하는 쪽을 먼저 검토하겠습니다. GPU 가속을 고를 때도 종료 값만 믿지 말고 실제 공급자 목록과 경고를 확인해야, CPU로 만든 파일을 GPU 처리 결과로 착각하지 않습니다.
참고한 패키지 페이지, 릴리스, 모델 라이선스
- edge-tts: PyPI edge-tts 7.2.8, PyPI edge-tts 7.3.0, PyPI edge-tts 7.3.1, rany2/edge-tts, edge-tts README, edge-tts 7.3.0 릴리스, edge-tts 7.3.1 릴리스, pull 492, pull 494
- Piper: PyPI piper-tts 1.8.0, OHF-Voice/piper1-gpl, Piper CLI, Piper VOICES.md v1.8.0, rhasspy/piper-voices ko_KR-kss-medium, kss medium 모델 카드, CC BY-NC-SA 4.0
- gTTS: PyPI gTTS 2.5.4, pndurette/gTTS, gTTS README






