SSD Nodes Learn Hosting plans →
Rehberler Matt ConnorYazan Matt Connor

EMA Lightning ile Türkçe TTS: VPS'te çevrimdışı kurulum

EMA Lightning Türkçe TTS modelini kendi VPS'inize kurun: CPU'ya özel PyTorch, doğru Python sürümü, çevrimdışı önbellek, nginx arkasında WebSocket ve Asterisk IVR anonsları.

EMA Lightning bir VPS'te nasıl çalışır?

EMA Lightning'i bir VPS'te çalıştırmak için ema-lightning==1.0.4 paketini CPU'ya özel PyTorch ile bir Python sanal ortamına kurun. Ağırlıkları bir kez indirip önbelleğe alın ve servisi systemd altında yalnızca 127.0.0.1 adresinde başlatın. Model Türkçe metni sese çevirir. Ağırlıklar diske indikten sonra internet bağlantısı olmadan çalışır, bu yüzden metin ve ses sunucunuzdan dışarı çıkmaz.

TTS (text-to-speech, metinden konuşmaya) için bulut API'si kullanıyorsanız, faturanız büyük olasılıkla karakter sayısına bağlıdır. Fatura dövizle kesiliyorsa, kur her yükseldiğinde aynı anonsun maliyeti de yükselir. Kendi sunucunuzda maliyet, kiraladığınız makinenin aylık ücretidir. Kaç karakter ürettiğiniz bu ücreti değiştirmez. Konuşma tanıma ile ses üretimini birlikte düşünüyorsanız, kendi sunucunuzda konuşma tanıma ve ses üretimi rehberi genel çerçeveyi veriyor. Burada yalnızca Türkçeye odaklanan tek bir modeli uçtan uca kuruyoruz.

EMA Lightning ne yapar, ne yapmaz?

Yazarın README dosyasına göre EMA Lightning küçük bir modeldir: yaklaşık 8,6 milyon parametre ve yaklaşık 34 MB ağırlık. Hugging Face model kartı bunu iki dosyaya ayırıyor: ema.pt (5,6 milyon parametre) ve decoder.pt (3 milyon parametre). Kod ve ağırlıklar Apache-2.0 lisanslıdır. README ticari kullanıma izin verildiğini açıkça yazıyor.

Sınırları da açıkça yazılmış. Kurmadan önce bilmeniz gerekir:

  • Tek bir ses var. Ses klonlama (voice cloning) ve duygu kontrolü yok.
  • Model yalnızca Türkçe konuşur. Yabancı kelimeleri Türkçe yazım kurallarıyla okur. Bir marka adının doğru çıkmasını istiyorsanız, metne Türkçe okunuşunu yazın.
  • Metni hazırlamak için normalizer-tr paketini kullanır. Sayılar, tarihler, saatler, para birimleri, birimler ve semboller Türkçe olarak okunur. 1.0.4 sürümünde tamamı büyük harfle yazılmış kelimeler harf harf okunur. Bir kısaltmayı kelime gibi okutmak istiyorsanız küçük harfle yazın. 1.1.0 ile bu kural değişti, sürüm bölümünde anlatıyoruz.
  • Yazarın ölçümüne göre doğallık puanı UTMOS yaklaşık 3,30'dur. UTMOS, insan dinleyicilerin vereceği MOS (mean opinion score, ortalama görüş puanı) değerini tahmin eden otomatik bir ölçüdür. Yazar bu puanı Freya-TR-Eval üzerinde ölçmüş. Sonuca göre model ElevenLabs v4 ile aynı düzeyde, Gemini 3.8 ve Trendyol-TTS'in altında.
  • README, Freya-TR-Eval üzerinde %0,92 WER (word error rate, kelime hata oranı) bildiriyor. Bu oran Whisper ile ölçülmüş. Yani sayılan hataların bir kısmı modelden değil, konuşmayı tanıyan Whisper'dan gelebilir. Yazarın eski ema-tts modelinin kartında başka bir WER değeri var. İki modeli karıştırmayın.

Bu rakamların hiçbiri bizim ölçümümüz değil. Hepsi yazarın kendi bildirdiği değerlerdir.

EMA Lightning ne kadar hızlı?

Hız rakamları da yazara ait. Aşağıdaki grafik README ve model kartında yazan değerleri gösteriyor. Değerler "gerçek zamanın kaç katı" olarak verilmiş: 6 kat demek, 1 saniyelik sesin yaklaşık 1/6 saniyede üretilmesi demektir.

GrafikYazarın bildirdiği hız, gerçek zamanın katı olarak
Bu grafiğin arkasındaki veriler
[
  {
    "config": "CPU (i\u015flemci modeli belirtilmemi\u015f)",
    "kat": 6
  },
  {
    "config": "RTX 4090, d\u00fcz PyTorch, tek istek",
    "kat": 87
  },
  {
    "config": "RTX 4090, .lightning(), tek istek",
    "kat": 440
  },
  {
    "config": "RTX 4090, 64 istek toplu",
    "kat": 1316
  }
]

Yazara göre model CPU üzerinde gerçek zamanın yaklaşık 6 katı hızda çalışıyor. Model kartı bu ölçümün bir bulut konteynerindeki CPU'da yapıldığını söylüyor, ama işlemci modelini ve çekirdek sayısını vermiyor. Bir RTX 4090 üzerinde .lightning() ile tek istek için 440 kat, 64 isteği toplu işlerken 1316 kat bildiriliyor. Kendi VPS'inizin değeri farklı olacaktır, çünkü hızı doğrudan CPU modeli ve size ayrılan çekirdek sayısı belirler. Rehberin sonunda bu değeri kendi makinenizde nasıl ölçeceğinizi gösteriyoruz. Makine boyutunu seçerken yaygın iş yükleri için VPS boyutlandırma rehberi işinize yarar, ama karar için kendi ölçümünüzü kullanın.

Neden 1.0.4 sürümüne sabitliyoruz?

PyPI'da altı gün içinde altı sürüm yayımlandı: 1.0.0 (5 Ekim 2026), 1.0.1 (6 Ekim), 1.0.3 ve 1.0.4 (ikisi de 7 Ekim), 1.1.0 ve 1.1.2 (ikisi de 10 Ekim). PyPI'da 1.0.2 ve 1.1.1 yok. GitHub deposunda ise v1.0.0 ile v1.1.2 arasındaki git etiketlerinin hepsi var, v1.0.2 ve v1.1.1 dahil. Depoda artık GitHub Releases kayıtları da var, ama yalnızca PyPI'daki altı sürüm için. CHANGELOG dosyasında tarih yok, 1.0.2 ve 1.1.1 için bir kayıt da yok.

Bu kadar hızlı değişen bir pakette pip install ema-lightning yazarsanız, bugün kurduğunuz sürüm ile yarın kurduğunuz sürüm farklı olabilir. Bu yüzden test ettiğiniz sürümü yazın: ema-lightning==1.0.4. Bu rehberdeki komutları bu sürümle yazdık. 8 Ekim 2026'da PyPI'daki en yeni sürüm buydu. 14 Ekim 2026 itibarıyla en yenisi 1.1.2. Değişiklik günlüğünde 1.0.4 için tek bir kayıt var: ağırlıklar artık torch.load(..., weights_only=True) ile yükleniyor. Böylece bir model dosyası yalnızca tensörleri ve temel değerleri taşıyabiliyor, yüklenirken de kod çalıştıramıyor. Günlük bunu bir güvenlik açığının düzeltmesi olarak anlatmıyor. Bu, önceden alınmış bir sağlamlaştırma önlemi. Kelime bazlı zamanlamalar (Speech.words) 1.0.3 ile eklendi.

1.1 serisi metnin okunuşunu değiştiriyor. Değişiklik günlüğüne göre 1.1.0 normalizer-tr 0.5 sürümüne, 1.1.2 ise 0.6 sürümüne geçti. Güncel README'ye göre artık yalnızca TRT gibi yaygın kısaltmalar harf harf okunuyor, büyük harfle yazılmış diğer kelimeler ise kelime olarak okunuyor. 1.1.0 ayrıca CPU'da akışın ilk bir saniyelik penceresini ayrı çözüyor, böylece ses daha erken başlıyor. Yükseltmek isterseniz önce anonslarınızı yeni sürümle yeniden üretip dinleyin, çünkü aynı metin farklı okunabilir.

Ubuntu'nun Python sürümü uyuyor mu?

Paketin pyproject.toml dosyası requires-python = ">=3.11" diyor. README ise desteklenen aralığı CPython 3.11 ile 3.13 arası ve PyTorch 2.1 veya daha yenisi olarak veriyor. Paket meta verisinde üst sınır olmadığı için pip, Python 3.14'te kurulumu engellemez. Kurulum başarılı görünür, ama yazarın desteklediği aralığın dışında kalırsınız.

Ubuntu'nun kendi paketlerine bakınca durum şöyle:

  • Ubuntu 24.04, python3 paketi olarak 3.12.3 sürümünü getiriyor. Bu sürüm desteklenen aralığın içinde, sistem Python'unu kullanabilirsiniz.
  • Ubuntu 26.04, python3 paketi olarak 3.14.3 sürümünü getiriyor. Bu sürüm desteklenen aralığın dışında. Burada uv ile ayrı bir Python 3.12 kuracağız.

Hangi sürümde olduğunuzu kontrol edin:

lsb_release -rs
python3 --version

Sunucuya hangi Python'u kurmanız gerektiğini daha geniş ele alan Ubuntu sunucuda hangi Python sürümünü kurmalı yazısına da bakabilirsiniz.

Ortak hazırlık: servis kullanıcısı ve dizinler

Servis kendi kullanıcısıyla çalışacak. Sanal ortam /opt/ema altında, model önbelleği ise /var/lib/ema altında duracak. İkisi de ev dizinlerinin dışında, çünkü birazdan systemd ile ev dizinlerini servise kapatacağız.

sudo useradd --system --home-dir /var/lib/ema --create-home --shell /usr/sbin/nologin ema
sudo install -d -o ema -g ema -m 750 /var/lib/ema/hf
sudo install -d -o "$USER" -g "$USER" -m 755 /opt/ema
sudo apt update && sudo apt install -y ffmpeg sox

/opt/ema sizin kullanıcınıza ait. Kurulumu sudo olmadan yaparsınız, ema kullanıcısı ise dosyaları yalnızca okuyabilir. ffmpeg ve sox ses dosyalarını dönüştürmek için gerekli.

Ubuntu 24.04: sistem Python'u ile sanal ortam

sudo apt install -y python3-venv
python3 -m venv /opt/ema/venv
/opt/ema/venv/bin/python --version

Son komut Python 3.12.3 gibi bir satır yazmalı. 26.04 adımını atlayın ve kurulum bölümüne geçin.

Ubuntu 26.04: uv ile Python 3.12

uv, Astral'ın Python araç zinciridir. Kendi yönettiği CPython sürümlerini indirip kurabilir. Bu sürümler Astral'ın python-build-standalone derlemeleridir. uv'yi resmi kurulum betiğiyle kurun:

curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv --version

uv, yönettiği Python sürümlerini varsayılan olarak sizin ev dizininizin altına kurar. Bu bizim için yanlış yer, çünkü ema kullanıcısı oraya erişemez. Ayrıca ProtectHome=true ayarı servisi ev dizinlerinden tamamen ayırır. UV_PYTHON_INSTALL_DIR değişkeni bu konumu değiştirir:

export UV_PYTHON_INSTALL_DIR=/opt/ema/python
uv python install 3.12
"$(uv python find 3.12)" -m venv /opt/ema/venv
/opt/ema/venv/bin/python --version

uv python install 3.12, 3.12 serisinin en son yama sürümünü kurar. uv python find önce uv'nin yönettiği kurulumlara bakar, sonra sistem Python'una. Bu yüzden 3.12 sürümünü açıkça istiyoruz. Sistemde 3.14 olduğu için sürüm vermeden çağırırsanız yanlış yorumlayıcıyı alabilirsiniz. Son komut Python 3.12.x yazmalı. Python 3.14.3 görüyorsanız sanal ortam sistem Python'u ile oluşmuştur. /opt/ema/venv dizinini silip adımı tekrarlayın.

venv, pipx ve uv arasındaki farkları ve sunucuda hangisini ne zaman seçeceğinizi sunucuda venv, pipx ve uv karşılaştırması yazısında ayrıntılı anlattık.

CPU'ya özel PyTorch ile EMA Lightning kurulumu

README bir konuya özellikle dikkat çekiyor: Linux'ta varsayılan PyTorch derlemesi CUDA içerir ve yaklaşık 1,5 GB NVIDIA paketi indirir. NVIDIA GPU'su olmayan bir VPS'te bu paketler hiçbir işe yaramaz. Sadece diski doldurur ve kurulumu uzatır. README bu yüzden PyTorch'un CPU paket dizinini ek dizin olarak vermeyi öneriyor:

/opt/ema/venv/bin/python -m pip install --upgrade pip
/opt/ema/venv/bin/python -m pip install "ema-lightning==1.0.4" --extra-index-url https://download.pytorch.org/whl/cpu
/opt/ema/venv/bin/python -m pip install fastapi "uvicorn[standard]"

FastAPI'nin WebSocket belgeleri projeye websockets kütüphanesinin eklenmesini istiyor. Uvicorn da WebSocket bağlantılarını bu kütüphane kuruluysa onunla işler. uvicorn[standard] eki websockets paketini de kurar, bu yüzden onu ayrıca yazmadık.

Şimdi gerçekten CPU sürümünün kurulduğunu kontrol edin:

/opt/ema/venv/bin/python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
/opt/ema/venv/bin/python -m pip list | grep -i nvidia

İlk komut sonu +cpu ile biten bir sürüm ve False yazmalı. İkinci komut hiçbir şey yazmamalı. nvidia- ile başlayan paketler görüyorsanız, komutta --extra-index-url seçeneği eksik kalmıştır ve pip varsayılan CUDA derlemesini almıştır. Sanal ortamı silip baştan kurun.

Çalışan kurulumu kaydedin. Bir sonraki sunucuda aynı sürümleri bu dosyayla kurarsınız:

/opt/ema/venv/bin/python -m pip freeze > /opt/ema/requirements.lock

Model ağırlıklarını önceden indirip çevrimdışı çalıştırma

Ağırlıklar (yaklaşık 34 MB) ilk kullanımda Hugging Face'teki canberkkkkkk/ema-lightning deposundan iner. Paketin kodu config.json, ema.pt ve decoder.pt dosyalarını hf_hub_download ile çeker. Belirli bir sürüm (revision) sabitlenmemiş. Paketin belgelerinde çevrimdışı mod ya da yerel dosya yolu seçeneği de yok. Dosyaları önbelleğe alan, paketin kullandığı huggingface_hub kütüphanesidir.

Önbelleğin yeri HF_HOME değişkenine bağlıdır. Değişken yoksa önbellek ~/.cache/huggingface/hub altına yazılır, yani komutu çalıştıran kullanıcının ev dizinine. Ağırlıkları root olarak indirirseniz dosyalar /root altına gider ve ema kullanıcısı onları hiç görmez. Bu yüzden indirmeyi servisin kullanıcısıyla ve servisin kullanacağı HF_HOME ile yapın:

cd /opt/ema
sudo -u ema env HF_HOME=/var/lib/ema/hf /opt/ema/venv/bin/python -c "from ema_lightning import EMA; EMA(); print('hazır')"
sudo ls /var/lib/ema/hf/hub

hazır satırını ve önbellek dizininde models--canberkkkkkk--ema-lightning klasörünü görmelisiniz.

HF_HUB_OFFLINE=1 değişkeni Hugging Face Hub'a giden bütün HTTP isteklerini kapatır. Kütüphane yalnızca önbellekteki dosyaları kullanır ve bir dosya eksikse hata verir. Şimdi gerçekten çevrimdışı çalıştığını test edin:

cd /opt/ema
sudo -u ema env HF_HOME=/var/lib/ema/hf HF_HUB_OFFLINE=1 /opt/ema/venv/bin/python -c "from ema_lightning import EMA; tts = EMA(); tts.say('Çevrimdışı test başarılı.', path='/var/lib/ema/test.wav'); print('ok')"
sudo -u ema soxi /var/lib/ema/test.wav

soxi çıktısında Sample Rate satırı 48000, Channels satırı 1 olmalı. 48 kHz, say() fonksiyonunun varsayılan örnekleme hızıdır.

Çevrimdışı modun ikinci bir faydası var. Kod belirli bir sürüm sabitlemiyor. Bu yüzden internet açıkken Hub'daki ağırlıklar değişirse servisiniz yeni ağırlıkları alabilir ve sesiniz habersizce değişebilir. HF_HUB_OFFLINE=1 ile servis yalnızca sizin indirip test ettiğiniz dosyaları kullanır.

WebSocket sunucusu: paketin kendi FastAPI örneği

README, tek bir paylaşılan model ile sesi parça parça gönderen bir FastAPI örneği veriyor. İstemci /speak adresine bağlanır ve tek bir metin mesajı gönderir. Sunucu tts.stream(text) ile üretilen her parçayı ikili (binary) mesaj olarak yollar, sonra bağlantıyı kapatır. Ses biçimi float32 PCM (pulse-code modulation, ham örnek dizisi), 48 kHz ve tek kanaldır (mono). Yazara göre tek bir EMA nesnesi birden fazla iş parçacığına aynı anda hizmet verebilir.

/opt/ema/app/app.py dosyasını oluşturun:

from fastapi import FastAPI, WebSocket
from starlette.concurrency import iterate_in_threadpool
from ema_lightning import EMA

app = FastAPI()
tts = EMA()  # README örneği EMA().lightning() kullanır; o çağrı NVIDIA GPU içindir

@app.websocket("/speak")
async def speak(ws: WebSocket):
    await ws.accept()
    text = await ws.receive_text()
    async for chunk in iterate_in_threadpool(tts.stream(text)):
        await ws.send_bytes(chunk.tobytes())  # float32 PCM, 48 kHz, mono
    await ws.close()

README'den tek fark model satırında. README tts = EMA().lightning() yazıyor. README'ye göre .lightning(), NVIDIA GPU'larda çıkarımı hızlandıran isteğe bağlı bir başlangıç çağrısıdır: toplu iş boyutunu ayarlar, modeli derler ve birkaç saniye sürer. Yazarın API belgesine göre bu çağrı CPU'da hata vermez. CUDA bulunmadığına dair bir uyarı yazar ve hemen döner, model de düz yolda çalışmaya devam eder. Yani CPU'da hiçbir şey kazandırmaz. Bu yüzden CPU'lu bir VPS'te düz EMA() kullanıyoruz. GPU'lu bir makinede README'deki satıra dönebilirsiniz. Belgelenen tek kurucu parametresi EMA(device="auto") şeklindedir: CUDA varsa onu, yoksa CPU'yu seçer.

stream() fonksiyonu float32 NumPy dizileri döndürür. Hız için speed (0,25 ile 4 arası), örnekleme hızı için sample_rate (48000, 24000, 16000 veya 8000) parametreleri vardır. Bir istemciye 16 kHz ses gerekiyorsa sunucuda tts.stream(text, sample_rate=16000) yazabilirsiniz.

Servisi elle bir kez çalıştırıp deneyin:

cd /opt/ema/app
sudo -u ema env HF_HOME=/var/lib/ema/hf HF_HUB_OFFLINE=1 /opt/ema/venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000

İkinci bir SSH oturumunda küçük bir istemci yazın. /opt/ema/app/client.py:

import asyncio
import sys

import websockets


async def main(url, text, out):
    async with websockets.connect(url) as ws:
        await ws.send(text)
        with open(out, "wb") as f:
            async for msg in ws:
                f.write(msg)


asyncio.run(main(sys.argv[1], sys.argv[2], sys.argv[3]))

İstemciyi çalıştırın ve ham sesi dinlenebilir bir WAV dosyasına çevirin:

cd /tmp
/opt/ema/venv/bin/python /opt/ema/app/client.py ws://127.0.0.1:8000/speak "Merhaba, bu ses kendi sunucumdan geliyor." cikti.f32
ffmpeg -f f32le -ar 48000 -ac 1 -i cikti.f32 cikti.wav

ffmpeg komutunda -f f32le girdinin başlıksız float32 olduğunu söyler. -ar 48000 örnekleme hızını, -ac 1 kanal sayısını verir. Ham PCM dosyası bu bilgileri taşımaz, bu yüzden üçünü de siz yazmalısınız. Yanlış hız verirseniz ses hızlanmış ya da yavaşlamış duyulur. cikti.wav dosyasını kendi bilgisayarınıza alıp dinleyin. Deneme bitince ilk oturumda uvicorn'u Ctrl+C ile durdurun.

systemd ile 127.0.0.1 üzerinde servis

/etc/systemd/system/ema-tts.service:

[Unit]
Description=EMA Lightning Türkçe TTS
After=network.target

[Service]
User=ema
Group=ema
WorkingDirectory=/opt/ema/app
Environment=HF_HOME=/var/lib/ema/hf
Environment=HF_HUB_OFFLINE=1
ExecStart=/opt/ema/venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=true
ProtectHome=true

[Install]
WantedBy=multi-user.target

Sertleştirme satırlarının görevi şöyle. NoNewPrivileges=true, servisin ve alt süreçlerinin yeni yetki kazanmasını engeller. ProtectSystem=true, /usr ve önyükleme dizinlerini salt okunur yapar. ProtectHome=true ise /home, /root ve /run/user dizinlerini servis için boş ve erişilemez yapar. Python yorumlayıcısını, sanal ortamı ve önbelleği ev dizinlerinin dışına bu son satır yüzünden koyduk. ProtectSystem=true /var dizinine dokunmaz, bu yüzden /var/lib/ema yazılabilir kalır.

sudo systemctl daemon-reload
sudo systemctl enable --now ema-tts
systemctl status ema-tts --no-pager
ss -tlnp | grep 8000

status çıktısında active (running) görmelisiniz. ss satırında adres 0.0.0.0:8000 değil, 127.0.0.1:8000 olmalı. Böylece servise dışarıdan doğrudan erişilemez. Önündeki tek kapı nginx olacak.

Servis başlamıyorsa önce günlüğe bakın: journalctl -u ema-tts -n 50. Önbellekte bir dosya eksikse, çevrimdışı mod yüzünden servis indirmeyi denemez ve hata verir. Ön indirme adımını aynı HF_HOME ile tekrar çalıştırın.

nginx arkasında parola ve WebSocket

Bu bölüm, alan adınız için TLS (transport layer security) sertifikası olan, çalışan bir nginx server bloğunuz olduğunu varsayıyor. Yoksa önce nginx ters vekil sunucu ayarlarının açıklaması yazısındaki kurulumu yapın.

Parola dosyası için htpasswd aracını kurun ve bir kullanıcı ekleyin:

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd-tts santral

Ardından mevcut server bloğunun içine şu location bloğunu ekleyin:

location /speak {
    auth_basic           "EMA TTS";
    auth_basic_user_file /etc/nginx/.htpasswd-tts;

    proxy_pass         http://127.0.0.1:8000;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection "upgrade";
    proxy_read_timeout 120s;
}

Her satırın bir nedeni var. WebSocket bağlantısı bir HTTP isteği olarak başlar. Protokolü Upgrade ve Connection başlıklarıyla değiştirir. nginx bu iki başlığı arka uca kendiliğinden iletmez, bu yüzden ikisini açıkça yazıyoruz. nginx 1.29.7 öncesindeki sürümlerde proxy_http_version 1.1 satırı da gereklidir. Sürümünüzü nginx -v ile görebilirsiniz. Son olarak, arka uç 60 saniye boyunca veri göndermezse nginx bağlantıyı varsayılan olarak kapatır. Bağlanıp metni geç gönderen bir istemci bu yüzden kesilir. proxy_read_timeout 120s bu süreyi uzatır.

auth_basic, HTTP Basic Authentication kullanır. Bu yöntemde parola her istekle birlikte gönderilir. Bu yüzden bu yapılandırma yalnızca TLS ile, yani https ve wss üzerinden güvenlidir.

sudo nginx -t && sudo systemctl reload nginx
curl -i https://tts.example.com/speak

nginx -t çıktısında syntax is ok görmelisiniz. Parolasız curl isteği 401 Unauthorized döndürmeli. Şimdi aynı istemciyle parola vererek deneyin. websockets kütüphanesi adresteki kullanıcı adını ve parolayı Basic Authentication başlığına çevirir:

cd /tmp
/opt/ema/venv/bin/python /opt/ema/app/client.py "wss://santral:PAROLA@tts.example.com/speak" "Bağlantı nginx üzerinden geliyor." uzak.f32
ffmpeg -f f32le -ar 48000 -ac 1 -i uzak.f32 uzak.wav

Komut satırına yazdığınız parola kabuk geçmişine kaydedilir. Bu yalnızca bir deneme komutu. Gerçek istemcide parolayı bir ortam değişkeninden ya da bir dosyadan okuyun.

Asterisk santrali için Türkçe IVR anonsları

EMA Lightning'in en somut kullanımlarından biri IVR (interactive voice response, sesli yanıt sistemi) anonslarıdır: karşılama mesajı, menü seçenekleri, mesai dışı mesajı. Anons metni değiştiğinde yeni ses dosyasını bir dakikada üretirsiniz. Stüdyo kaydına ya da karakter başına ücrete gerek kalmaz. Henüz bir santraliniz yoksa VPS üzerinde kendi VoIP sunucunuzu kurma rehberi Asterisk tarafını anlatıyor.

Asterisk hangi ses formatını bekler?

Açtığımız Asterisk belge sayfaları, özel anonslar için tek bir zorunlu format ya da örnekleme hızı belirtmiyor. Belgeler şunları söylüyor:

  • Asterisk ham 16 bit işaretli doğrusal (signed linear, SLIN) ses dosyalarını okuyabilir. Dosya uzantısı örnekleme hızını belirtir: .sln 8 kHz, .sln16 16 kHz, .sln48 48 kHz.
  • wav uzantısı, WAV kabı içindeki SLIN anlamına gelir.
  • Asterisk farklı hızlar arasında dönüştürme yapabilir. Bir anonsu çalarken, o çağrının codec'ine en kolay dönüştürülebilen dosya formatını seçer.

Asterisk'in 48 kHz WAV isteyen bir kuralı da var. Ama o kural projeye ses dosyası katkısı yapanlar içindir. Kendi santralinize koyduğunuz anonslar için bir gereklilik değildir.

Bizim tercihimiz şu: her anonsu 48 kHz ana kayıt olarak üretmek, sonra ondan 8 kHz .sln ve 16 kHz .sln16 dosyaları çıkarmak. Dar bantlı bir çağrıda (örneğin G.711) Asterisk 8 kHz dosyayı, geniş bantlı bir çağrıda 16 kHz dosyayı kullanabilir. Bu bir belge kuralı değil, bizim seçimimiz. Kendi kurulumunuzda gerçek bir çağrıyla test edin.

Anonsları üretmek

/opt/ema/app/anons.py:

from pathlib import Path

from ema_lightning import EMA

anonslar = {
    "hosgeldiniz": "Merhaba, Yıldız Bilişim'e hoş geldiniz.",
    "menu": "Satış için bire, teknik destek için ikiye basın.",
    "mesai-disi": "Şu anda mesai saatleri dışındayız. Lütfen hafta içi sabah dokuz ile akşam altı arasında tekrar arayın.",
    "bekleyin": "Lütfen hatta kalın, sizi bir temsilciye aktarıyoruz.",
}

hedef = Path("/var/lib/ema/anons")
hedef.mkdir(exist_ok=True)

tts = EMA()
for ad, metin in anonslar.items():
    tts.say(metin, path=str(hedef / f"{ad}.wav"))
    print(ad, "tamam")
cd /opt/ema/app
sudo -u ema env HF_HOME=/var/lib/ema/hf HF_HUB_OFFLINE=1 /opt/ema/venv/bin/python anons.py

Her anons için bir tamam satırı görmelisiniz. Menüde rakam yerine "bire" ve "ikiye" yazdık. Normalleştirici rakamları da okur, ama hangi çekim ekinin duyulacağını metinde görmek daha kolaydır.

sox veya ffmpeg ile dönüştürmek

Asterisk belgelerindeki örnek sox input.wav -t raw -b 16 -r 32000 output.sln biçimindedir. Biz buna iki seçenek ekliyoruz: tek kanal için -c 1, tam sayı çıktı için -e signed-integer. İkinci seçeneği ekliyoruz, çünkü kaynak dosya float formatında olabilir. Bu iki ekleme bizim, belgede yazmıyor.

sudo -u ema mkdir -p /var/lib/ema/asterisk
for f in /var/lib/ema/anons/*.wav; do
  ad=$(basename "$f" .wav)
  sudo -u ema sox "$f" -t raw -e signed-integer -b 16 -r 8000 -c 1 /var/lib/ema/asterisk/"$ad".sln
  sudo -u ema sox "$f" -t raw -e signed-integer -b 16 -r 16000 -c 1 /var/lib/ema/asterisk/"$ad".sln16
done
sudo ls -l /var/lib/ema/asterisk

ffmpeg tercih ederseniz eşdeğer komut ffmpeg -i hosgeldiniz.wav -ar 8000 -ac 1 -f s16le hosgeldiniz.sln olur. -ar örnekleme hızını, -ac kanal sayısını ayarlar. İkisini yazmazsanız ffmpeg girdinin değerlerini korur. Bu durumda 48 kHz bir dosya 48 kHz kalır, ama .sln uzantısıyla 8 kHz sanılır ve yanlış hızda çalınır.

Ham dosyada başlık yoktur, bu yüzden süreyi boyuttan hesaplayabilirsiniz. 8 kHz, 16 bit, tek kanal ses saniyede 16.000 bayttır. Yani 48.000 baytlık bir .sln dosyası 3 saniyelik bir anonstur. Kulakla kontrol etmek için dosyayı tekrar WAV'a çevirin:

sudo sox -t raw -e signed-integer -b 16 -r 8000 -c 1 /var/lib/ema/asterisk/hosgeldiniz.sln /tmp/kontrol.wav

Dosyaları Asterisk'in ses klasöründe custom adlı bir alt klasöre kopyalayın. Bu klasörün yeri Asterisk'i nasıl kurduğunuza bağlıdır. Dialplan içinde dosya adını uzantısız yazın. Asterisk'in Playback belgesi uzantı yazmamanızı söylüyor, çünkü formatı Asterisk kendisi seçer:

[gelen-cagri]
exten => s,1,Answer()
 same => n,Playback(custom/hosgeldiniz)
 same => n,Background(custom/menu)
 same => n,WaitExten(5)

Kendi VPS'inizde hızı ölçmek

Yazarın CPU rakamını kendi makinenizle karşılaştırmak kolaydır. Gerçek zaman çarpanı, sesin süresinin üretim süresine bölümüdür. Uzun bir metin seçin, üretim süresini ölçün, sonra WAV dosyasının süresine bakın:

cd /opt/ema
time sudo -u ema env HF_HOME=/var/lib/ema/hf HF_HUB_OFFLINE=1 /opt/ema/venv/bin/python -c "from ema_lightning import EMA; EMA().say('Bu cümle hız ölçümü için yazıldı. Sunucunun işlemcisi sesi ne kadar sürede üretiyor, bunu görmek istiyoruz.', path='/var/lib/ema/olcum.wav')"
sudo -u ema soxi -D /var/lib/ema/olcum.wav

soxi -D sesin süresini saniye olarak yazar. Bu ölçümde time çıktısı Python'un açılmasını ve modelin yüklenmesini de içerir. Yani gerçek üretim hızı bu hesaptan biraz daha yüksektir. Daha temiz bir ölçüm için modeli bir kez yükleyin ve birkaç cümleyi aynı süreçte üretin.

Ticari kullanımdan önce: ses verisinin kaynağı

Apache-2.0 lisansı ticari kullanıma izin veriyor. Ama bu lisans, yazarın kendi kodu ve ağırlıkları için verdiği izindir. Modelin konuştuğu sesin kime ait olduğunu söylemez.

Hugging Face model kartı eğitim verisini yalnızca şöyle tanımlıyor: yaklaşık 1000 saatlik, tek konuşmacılı, şirket içi bir Türkçe konuşma derlemi. Konuşmacının kim olduğu, kayıtların nereden geldiği ve konuşmacının rızasının alınıp alınmadığı ne model kartında ne de README'de yazıyor. Bu sesi bir çağrı merkezinde ya da bir üründe her gün binlerce kişiye dinletecekseniz, yayına almadan önce bu soruyu yazara sorun. Gerekirse bir hukukçuya danışın.

Model kartı ayrıca iki kullanım kuralı koyuyor. Dinleyicilere yapay zekâ ile üretilmiş bir ses duyduklarını söyleyin. Bu sesi gerçek bir kişinin sesi gibi sunmayın. IVR için ilk kuralı uygulamak kolaydır: karşılama anonsuna kısa bir not eklemek yeterli.

FAQ: sık sorulan sorular

EMA Lightning internet bağlantısı olmadan çalışır mı?

Evet, ağırlıklar önbelleğe indikten sonra çalışır. Ağırlıklar (yaklaşık 34 MB) ilk kullanımda Hugging Face'ten iner ve HF_HOME altındaki önbelleğe yazılır. Servisi sonra HF_HUB_OFFLINE=1 ile başlatırsanız, kütüphane Hub'a hiçbir HTTP isteği göndermez ve yalnızca önbellekteki dosyaları kullanır. Ön indirmeyi servisin çalışacağı kullanıcıyla ve aynı HF_HOME ile yapın. Aksi halde dosyalar başka bir ev dizinine iner ve servis onları bulamaz.

Ubuntu 26.04'te neden sistem Python'unu kullanmıyoruz?

Ubuntu 26.04, Python 3.14.3 getiriyor. EMA Lightning README'si ise CPython 3.11 ile 3.13 arasını destekliyor. Paket meta verisi yalnızca >=3.11 dediği için pip 3.14'te kurulumu engellemez, ama yazarın desteklediği aralığın dışında kalırsınız. uv python install 3.12 ile ayrı bir Python 3.12 kurup sanal ortamı onunla oluşturmak bu sorunu çözer. Ubuntu 24.04'teki 3.12.3 ise aralığın içindedir ve olduğu gibi kullanılabilir.

pip neden yaklaşık 1,5 GB NVIDIA paketi indiriyor?

Linux'ta varsayılan PyTorch derlemesi CUDA içerir. README'ye göre bu derleme yaklaşık 1,5 GB NVIDIA paketi getirir. GPU'su olmayan bir VPS'te bu paketler boşuna yer kaplar. Kurulum komutuna --extra-index-url https://download.pytorch.org/whl/cpu ekleyin. pip list | grep -i nvidia hiçbir şey yazmıyorsa ve torch.__version__ değeri +cpu ile bitiyorsa doğru sürüm kuruludur.

Asterisk anonslar için hangi ses formatını istiyor?

Asterisk belgeleri tek bir zorunlu format belirtmiyor. Asterisk ham 16 bit SLIN dosyalarını okur ve örnekleme hızını uzantıdan anlar: .sln 8 kHz, .sln16 16 kHz demektir. Çağrının codec'ine en kolay dönüşen dosyayı kendisi seçer. Her anonsu hem .sln hem .sln16 olarak koyarsanız dar bantlı ve geniş bantlı çağrılar için uygun dosya hazır olur. Dialplan içinde dosya adını uzantısız yazın ve sonucu gerçek bir çağrıyla test edin.

EMA Lightning'i ticari bir üründe kullanabilir miyim?

Kod ve ağırlıklar Apache-2.0 lisanslıdır ve README ticari kullanıma izin veriyor. Ancak model kartı eğitim verisini yalnızca yaklaşık 1000 saatlik, tek konuşmacılı, şirket içi bir derlem olarak tanımlıyor. Konuşmacının kim olduğu ve rızası belgelenmemiş. Sesi bir ürünün parçası yapmadan önce bunu yazara sorun. Model kartı ayrıca dinleyicilere yapay zekâ sesi duyduklarını söylemenizi ve sesi gerçek bir kişinin sesi gibi sunmamanızı istiyor.

#tts#turkish#self-hosted-ai#python#hugging-face#asterisk