seslen-tts 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seslen_tts-0.1.0/LICENSE +21 -0
- seslen_tts-0.1.0/PKG-INFO +142 -0
- seslen_tts-0.1.0/README.md +111 -0
- seslen_tts-0.1.0/pyproject.toml +43 -0
- seslen_tts-0.1.0/seslen_tts/__init__.py +13 -0
- seslen_tts-0.1.0/seslen_tts/__main__.py +41 -0
- seslen_tts-0.1.0/seslen_tts/model.py +153 -0
- seslen_tts-0.1.0/seslen_tts/seslen.py +117 -0
- seslen_tts-0.1.0/seslen_tts.egg-info/PKG-INFO +142 -0
- seslen_tts-0.1.0/seslen_tts.egg-info/SOURCES.txt +14 -0
- seslen_tts-0.1.0/seslen_tts.egg-info/dependency_links.txt +1 -0
- seslen_tts-0.1.0/seslen_tts.egg-info/entry_points.txt +2 -0
- seslen_tts-0.1.0/seslen_tts.egg-info/requires.txt +14 -0
- seslen_tts-0.1.0/seslen_tts.egg-info/top_level.txt +1 -0
- seslen_tts-0.1.0/setup.cfg +4 -0
- seslen_tts-0.1.0/tests/test_model.py +54 -0
seslen_tts-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mehmet Veysel Delibaş
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seslen-tts
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: İşlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli (TTS): PyTorch'suz, yalnız ONNX Runtime
|
|
5
|
+
Author: Mehmet Veysel Delibaş
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/seslen-tr/seslen-tts
|
|
8
|
+
Project-URL: Source, https://github.com/seslen-tr/seslen-tts
|
|
9
|
+
Project-URL: Issues, https://github.com/seslen-tr/seslen-tts/issues
|
|
10
|
+
Project-URL: Model, https://huggingface.co/seslen-tr/seslen-tts
|
|
11
|
+
Keywords: turkish,tts,text-to-speech,speech-synthesis,onnx,styletts2,türkçe,ses
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Natural Language :: Turkish
|
|
14
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: onnxruntime>=1.16
|
|
21
|
+
Requires-Dist: huggingface_hub>=0.20
|
|
22
|
+
Requires-Dist: turkce-vurgu[tam]>=0.1
|
|
23
|
+
Provides-Extra: duygu
|
|
24
|
+
Requires-Dist: turkce-duygu[model]>=0.1; extra == "duygu"
|
|
25
|
+
Provides-Extra: uslup
|
|
26
|
+
Requires-Dist: turkce-uslup[bert]>=0.1; extra == "uslup"
|
|
27
|
+
Provides-Extra: hepsi
|
|
28
|
+
Requires-Dist: turkce-duygu[model]>=0.1; extra == "hepsi"
|
|
29
|
+
Requires-Dist: turkce-uslup[bert]>=0.1; extra == "hepsi"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# seslen-tts
|
|
33
|
+
|
|
34
|
+
> **English:** An open Turkish text-to-speech model that runs on CPU, without PyTorch and without internet: 6 voices × 9
|
|
35
|
+
> emotions, correct Turkish lexical stress and question intonation, automatic emotion per sentence and reading style per
|
|
36
|
+
> text. ONNX Runtime only (~330 MB model, downloaded from Hugging Face on first use). Real-time factor ≈ 0.2–0.4 on a
|
|
37
|
+
> laptop CPU.
|
|
38
|
+
|
|
39
|
+
**İşlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli.** GPU, internet ve PyTorch gerekmez.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from seslen_tts import Seslen, wav_yaz
|
|
43
|
+
|
|
44
|
+
s = Seslen() # model ilk kullanımda Hugging Face'ten iner (~330 MB)
|
|
45
|
+
wav_yaz("merhaba.wav", s.tum("Merhaba! Yarın bize gelecek misiniz? Dikkat edin, yol kaygan.", ses="zeynep"))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
seslen-tts "Bir varmış bir yokmuş, ormanda meraklı bir sincap yaşarmış." -s ayse -o masal.wav
|
|
50
|
+
seslen-tts --sesler
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Neler yapar
|
|
54
|
+
|
|
55
|
+
- **6 ses, 9 duygu:** nötr, neşeli, heyecan, uyarı, merak, özür, üzgün, kızgın, fısıltı.
|
|
56
|
+
- **Türkçe vurgu doğru okunur:** "ANkara", "geLİyor", "GELmedi". Okunuş [turkce-vurgu](https://github.com/seslen-tr/turkce-vurgu)
|
|
57
|
+
ile çıkarılır; sayılar, tarihler, saatler, para ve kısaltmalar [turkce-normalize](https://github.com/seslen-tr/turkce-normalize)
|
|
58
|
+
ile okunur hâle gelir ("14:30'da" → "on dört otuzda").
|
|
59
|
+
- **Soru ezgisi:** evet/hayır sorusunda ezgi soru ekinden önceki sözcüğün vurgulu hecesinde yükselir
|
|
60
|
+
("yardımcı olaBİLİR misin?").
|
|
61
|
+
- **Otomatik duygu:** her cümlenin tonu metinden seçilir: soru merakla, uyarı uyarıyla, sevinç neşeyle okunur.
|
|
62
|
+
[turkce-duygu](https://github.com/seslen-tr/turkce-duygu) gerekir (`pip install "seslen-tts[duygu]"`).
|
|
63
|
+
- **Otomatik üslup:** haber tempolu ve düz, masal yavaş ve hareketli, tarif adım adım okunur.
|
|
64
|
+
[turkce-uslup](https://github.com/seslen-tr/turkce-uslup) gerekir (`pip install "seslen-tts[uslup]"`).
|
|
65
|
+
|
|
66
|
+
## Kurulum
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
pip install seslen-tts # temel: sesler, vurgu, soru ezgisi
|
|
70
|
+
pip install "seslen-tts[hepsi]" # + otomatik duygu ve üslup
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Python 3.9+, Windows / Linux / macOS. Model `SESLEN_TTS_MODEL` ortam değişkeniyle yerel bir klasörden de okunur.
|
|
74
|
+
|
|
75
|
+
## Sesler
|
|
76
|
+
|
|
77
|
+
| Ses | Kim | Doğallık (UTMOS) |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `ayse` | olgun kadın (~45), orta-kalın, güven veren | 4,04 |
|
|
80
|
+
| `zeynep` | genç kız (~17), ince, canlı | 3,95 |
|
|
81
|
+
| `koca` | iri kalın dev, gürleyen ama sevimli (çizgi karakter) | 3,90 |
|
|
82
|
+
| `fatma` | yaşlı kadın (~70), ince, şefkatli nine | 3,53 |
|
|
83
|
+
| `emre` | genç erkek (~18), orta, enerjik | 2,93 |
|
|
84
|
+
| `hasan` | yaşlı erkek (~72), kalın, bilge dede | 2,78 |
|
|
85
|
+
|
|
86
|
+
Örnek sesler: [Hugging Face model sayfası](https://huggingface.co/seslen-tr/seslen-tts/tree/main/ornekler).
|
|
87
|
+
|
|
88
|
+
## Kullanım
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
s = Seslen()
|
|
92
|
+
for p in s.seslendir(metin, ses="emre", ton="otomatik", uslup="otomatik"): # cümle cümle, akış hâlinde
|
|
93
|
+
print(p.ton, p.cumle) # p.ses: 24 kHz float32 dizi; p.sessizlik_ms: sonraki duraklama
|
|
94
|
+
|
|
95
|
+
s.tum(metin, ses="fatma", ton="uzgun") # bütün metin tek tonda
|
|
96
|
+
s.tum(metin, ses="hasan", hiz=0.9, perde=1.05) # hız ve ses perdesi
|
|
97
|
+
s.tum(metin, ses="zeynep", ton="neseli", yogunluk=0.6) # duygu şiddeti (0-1, 1 = tam ton)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Alt düzey: `s.model.konus(cumle, s.model.stil("ayse", "merak"))` tek cümleyi seslendirir.
|
|
101
|
+
|
|
102
|
+
## Ölçümler
|
|
103
|
+
|
|
104
|
+
- **Anlaşılırlık** (Whisper medium, 6 ses × 4 yeni cümle): %100. Whisper sayıları rakamla yazdığı için ("onda" →
|
|
105
|
+
"10'da") ham ölçüm %93,2 görünür.
|
|
106
|
+
- **Doğallık:** UTMOS 2,8–4,0 (tablo). UTMOS İngilizce dinleyicilerle eğitildi; Türkçe vurguyu yargılayamaz,
|
|
107
|
+
yapaylığı yakalar.
|
|
108
|
+
- **Hız** (i7-12700H, 6 iş parçacığı): gerçek zaman oranı ≈ 0,2–0,4. Yani 5 saniyelik cümle 1–2 saniyede üretilir.
|
|
109
|
+
Windows'un arka plan süreçlerine uyguladığı enerji kısıtı paket tarafından kapatılır (yoksa 4–5 kat yavaşlıyordu).
|
|
110
|
+
|
|
111
|
+
## Nasıl yapıldı
|
|
112
|
+
|
|
113
|
+
[StyleTTS2](https://github.com/yl4579/StyleTTS2) (iSTFTNet çözücü) öğrenci modeli. Ses ve duygu verisini öğretmen model
|
|
114
|
+
[VoxCPM2](https://huggingface.co/openbmb/VoxCPM2) üretti: sesler tariften tasarlandı, gerçek bir kişinin sesi değildir.
|
|
115
|
+
Eğitim üç aşamalıdır: temel, difüzyon ve çekişmeli ince ayar, son olarak vurgu işaretli okunuş ile
|
|
116
|
+
[Türkçe PL-BERT](https://huggingface.co/seslen-tr/turkce-plbert). Eğitim tek RTX 4090'da yapıldı. Ağlar ONNX'e
|
|
117
|
+
aktarıldı; aktarımın sesi değiştirmediği sınandı (log-mel farkı, modelin kendi iki çalıştırması arasındaki farkın altında).
|
|
118
|
+
|
|
119
|
+
## Sınırlılıklar
|
|
120
|
+
|
|
121
|
+
- **Ses klonlama yok.** Bu bilerek böyle: açık modelle başkasının sesini taklit etmek mümkün olmasın diye yalnız hazır
|
|
122
|
+
sesler var.
|
|
123
|
+
- Yalnız Türkçe. Yabancı sözcükler Türkçe okunuşla okunur.
|
|
124
|
+
- Uzun yabancı adlarda, kısaltma yığınlarında ve çok uzun cümlelerde okunuş hataları olabilir.
|
|
125
|
+
- Duygular seslere göre farklı belirginliktedir (ölçülenler arasında en belirgin: Ayşe ve Zeynep).
|
|
126
|
+
|
|
127
|
+
## Lisans ve kullanım koşulları
|
|
128
|
+
|
|
129
|
+
- **Kod:** MIT ([LICENSE](LICENSE)).
|
|
130
|
+
- **Model ağırlıkları ve sesler:** CC BY-SA 4.0. Türkçe PL-BERT Vikipedi metniyle eğitildi; öğretmen VoxCPM2
|
|
131
|
+
Apache-2.0, StyleTTS2 MIT.
|
|
132
|
+
- **Kullanım koşulu (öğretmen modelin ve StyleTTS2'nin koşullarından):**
|
|
133
|
+
- Üretilen sesin yapay zekâ sesi olduğu dinleyiciye belirtilmelidir.
|
|
134
|
+
- Gerçek bir kişiyi taklit etmek, dolandırıcılık ve dezenformasyon için kullanmak yasaktır.
|
|
135
|
+
|
|
136
|
+
Ticari ürün sesleri, özel ses tasarımı, stüdyo arayüzü ve kurum içi kurulum: Seslen ([iletişim](https://github.com/seslen-tr)).
|
|
137
|
+
|
|
138
|
+
## Kardeş projeler
|
|
139
|
+
|
|
140
|
+
[turkce-normalize](https://github.com/seslen-tr/turkce-normalize) · [turkce-vurgu](https://github.com/seslen-tr/turkce-vurgu) ·
|
|
141
|
+
[turkce-duygu](https://github.com/seslen-tr/turkce-duygu) · [turkce-uslup](https://github.com/seslen-tr/turkce-uslup) ·
|
|
142
|
+
[turkce-plbert](https://github.com/seslen-tr/turkce-plbert) · [turkce-tts-sinav](https://github.com/seslen-tr/turkce-tts-sinav)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# seslen-tts
|
|
2
|
+
|
|
3
|
+
> **English:** An open Turkish text-to-speech model that runs on CPU, without PyTorch and without internet: 6 voices × 9
|
|
4
|
+
> emotions, correct Turkish lexical stress and question intonation, automatic emotion per sentence and reading style per
|
|
5
|
+
> text. ONNX Runtime only (~330 MB model, downloaded from Hugging Face on first use). Real-time factor ≈ 0.2–0.4 on a
|
|
6
|
+
> laptop CPU.
|
|
7
|
+
|
|
8
|
+
**İşlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli.** GPU, internet ve PyTorch gerekmez.
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from seslen_tts import Seslen, wav_yaz
|
|
12
|
+
|
|
13
|
+
s = Seslen() # model ilk kullanımda Hugging Face'ten iner (~330 MB)
|
|
14
|
+
wav_yaz("merhaba.wav", s.tum("Merhaba! Yarın bize gelecek misiniz? Dikkat edin, yol kaygan.", ses="zeynep"))
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
seslen-tts "Bir varmış bir yokmuş, ormanda meraklı bir sincap yaşarmış." -s ayse -o masal.wav
|
|
19
|
+
seslen-tts --sesler
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Neler yapar
|
|
23
|
+
|
|
24
|
+
- **6 ses, 9 duygu:** nötr, neşeli, heyecan, uyarı, merak, özür, üzgün, kızgın, fısıltı.
|
|
25
|
+
- **Türkçe vurgu doğru okunur:** "ANkara", "geLİyor", "GELmedi". Okunuş [turkce-vurgu](https://github.com/seslen-tr/turkce-vurgu)
|
|
26
|
+
ile çıkarılır; sayılar, tarihler, saatler, para ve kısaltmalar [turkce-normalize](https://github.com/seslen-tr/turkce-normalize)
|
|
27
|
+
ile okunur hâle gelir ("14:30'da" → "on dört otuzda").
|
|
28
|
+
- **Soru ezgisi:** evet/hayır sorusunda ezgi soru ekinden önceki sözcüğün vurgulu hecesinde yükselir
|
|
29
|
+
("yardımcı olaBİLİR misin?").
|
|
30
|
+
- **Otomatik duygu:** her cümlenin tonu metinden seçilir: soru merakla, uyarı uyarıyla, sevinç neşeyle okunur.
|
|
31
|
+
[turkce-duygu](https://github.com/seslen-tr/turkce-duygu) gerekir (`pip install "seslen-tts[duygu]"`).
|
|
32
|
+
- **Otomatik üslup:** haber tempolu ve düz, masal yavaş ve hareketli, tarif adım adım okunur.
|
|
33
|
+
[turkce-uslup](https://github.com/seslen-tr/turkce-uslup) gerekir (`pip install "seslen-tts[uslup]"`).
|
|
34
|
+
|
|
35
|
+
## Kurulum
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
pip install seslen-tts # temel: sesler, vurgu, soru ezgisi
|
|
39
|
+
pip install "seslen-tts[hepsi]" # + otomatik duygu ve üslup
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Python 3.9+, Windows / Linux / macOS. Model `SESLEN_TTS_MODEL` ortam değişkeniyle yerel bir klasörden de okunur.
|
|
43
|
+
|
|
44
|
+
## Sesler
|
|
45
|
+
|
|
46
|
+
| Ses | Kim | Doğallık (UTMOS) |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `ayse` | olgun kadın (~45), orta-kalın, güven veren | 4,04 |
|
|
49
|
+
| `zeynep` | genç kız (~17), ince, canlı | 3,95 |
|
|
50
|
+
| `koca` | iri kalın dev, gürleyen ama sevimli (çizgi karakter) | 3,90 |
|
|
51
|
+
| `fatma` | yaşlı kadın (~70), ince, şefkatli nine | 3,53 |
|
|
52
|
+
| `emre` | genç erkek (~18), orta, enerjik | 2,93 |
|
|
53
|
+
| `hasan` | yaşlı erkek (~72), kalın, bilge dede | 2,78 |
|
|
54
|
+
|
|
55
|
+
Örnek sesler: [Hugging Face model sayfası](https://huggingface.co/seslen-tr/seslen-tts/tree/main/ornekler).
|
|
56
|
+
|
|
57
|
+
## Kullanım
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
s = Seslen()
|
|
61
|
+
for p in s.seslendir(metin, ses="emre", ton="otomatik", uslup="otomatik"): # cümle cümle, akış hâlinde
|
|
62
|
+
print(p.ton, p.cumle) # p.ses: 24 kHz float32 dizi; p.sessizlik_ms: sonraki duraklama
|
|
63
|
+
|
|
64
|
+
s.tum(metin, ses="fatma", ton="uzgun") # bütün metin tek tonda
|
|
65
|
+
s.tum(metin, ses="hasan", hiz=0.9, perde=1.05) # hız ve ses perdesi
|
|
66
|
+
s.tum(metin, ses="zeynep", ton="neseli", yogunluk=0.6) # duygu şiddeti (0-1, 1 = tam ton)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Alt düzey: `s.model.konus(cumle, s.model.stil("ayse", "merak"))` tek cümleyi seslendirir.
|
|
70
|
+
|
|
71
|
+
## Ölçümler
|
|
72
|
+
|
|
73
|
+
- **Anlaşılırlık** (Whisper medium, 6 ses × 4 yeni cümle): %100. Whisper sayıları rakamla yazdığı için ("onda" →
|
|
74
|
+
"10'da") ham ölçüm %93,2 görünür.
|
|
75
|
+
- **Doğallık:** UTMOS 2,8–4,0 (tablo). UTMOS İngilizce dinleyicilerle eğitildi; Türkçe vurguyu yargılayamaz,
|
|
76
|
+
yapaylığı yakalar.
|
|
77
|
+
- **Hız** (i7-12700H, 6 iş parçacığı): gerçek zaman oranı ≈ 0,2–0,4. Yani 5 saniyelik cümle 1–2 saniyede üretilir.
|
|
78
|
+
Windows'un arka plan süreçlerine uyguladığı enerji kısıtı paket tarafından kapatılır (yoksa 4–5 kat yavaşlıyordu).
|
|
79
|
+
|
|
80
|
+
## Nasıl yapıldı
|
|
81
|
+
|
|
82
|
+
[StyleTTS2](https://github.com/yl4579/StyleTTS2) (iSTFTNet çözücü) öğrenci modeli. Ses ve duygu verisini öğretmen model
|
|
83
|
+
[VoxCPM2](https://huggingface.co/openbmb/VoxCPM2) üretti: sesler tariften tasarlandı, gerçek bir kişinin sesi değildir.
|
|
84
|
+
Eğitim üç aşamalıdır: temel, difüzyon ve çekişmeli ince ayar, son olarak vurgu işaretli okunuş ile
|
|
85
|
+
[Türkçe PL-BERT](https://huggingface.co/seslen-tr/turkce-plbert). Eğitim tek RTX 4090'da yapıldı. Ağlar ONNX'e
|
|
86
|
+
aktarıldı; aktarımın sesi değiştirmediği sınandı (log-mel farkı, modelin kendi iki çalıştırması arasındaki farkın altında).
|
|
87
|
+
|
|
88
|
+
## Sınırlılıklar
|
|
89
|
+
|
|
90
|
+
- **Ses klonlama yok.** Bu bilerek böyle: açık modelle başkasının sesini taklit etmek mümkün olmasın diye yalnız hazır
|
|
91
|
+
sesler var.
|
|
92
|
+
- Yalnız Türkçe. Yabancı sözcükler Türkçe okunuşla okunur.
|
|
93
|
+
- Uzun yabancı adlarda, kısaltma yığınlarında ve çok uzun cümlelerde okunuş hataları olabilir.
|
|
94
|
+
- Duygular seslere göre farklı belirginliktedir (ölçülenler arasında en belirgin: Ayşe ve Zeynep).
|
|
95
|
+
|
|
96
|
+
## Lisans ve kullanım koşulları
|
|
97
|
+
|
|
98
|
+
- **Kod:** MIT ([LICENSE](LICENSE)).
|
|
99
|
+
- **Model ağırlıkları ve sesler:** CC BY-SA 4.0. Türkçe PL-BERT Vikipedi metniyle eğitildi; öğretmen VoxCPM2
|
|
100
|
+
Apache-2.0, StyleTTS2 MIT.
|
|
101
|
+
- **Kullanım koşulu (öğretmen modelin ve StyleTTS2'nin koşullarından):**
|
|
102
|
+
- Üretilen sesin yapay zekâ sesi olduğu dinleyiciye belirtilmelidir.
|
|
103
|
+
- Gerçek bir kişiyi taklit etmek, dolandırıcılık ve dezenformasyon için kullanmak yasaktır.
|
|
104
|
+
|
|
105
|
+
Ticari ürün sesleri, özel ses tasarımı, stüdyo arayüzü ve kurum içi kurulum: Seslen ([iletişim](https://github.com/seslen-tr)).
|
|
106
|
+
|
|
107
|
+
## Kardeş projeler
|
|
108
|
+
|
|
109
|
+
[turkce-normalize](https://github.com/seslen-tr/turkce-normalize) · [turkce-vurgu](https://github.com/seslen-tr/turkce-vurgu) ·
|
|
110
|
+
[turkce-duygu](https://github.com/seslen-tr/turkce-duygu) · [turkce-uslup](https://github.com/seslen-tr/turkce-uslup) ·
|
|
111
|
+
[turkce-plbert](https://github.com/seslen-tr/turkce-plbert) · [turkce-tts-sinav](https://github.com/seslen-tr/turkce-tts-sinav)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seslen-tts"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "İşlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli (TTS): PyTorch'suz, yalnız ONNX Runtime"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Mehmet Veysel Delibaş" }]
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
keywords = ["turkish", "tts", "text-to-speech", "speech-synthesis", "onnx", "styletts2", "türkçe", "ses"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Natural Language :: Turkish",
|
|
18
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy",
|
|
23
|
+
"onnxruntime>=1.16",
|
|
24
|
+
"huggingface_hub>=0.20",
|
|
25
|
+
"turkce-vurgu[tam]>=0.1",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
duygu = ["turkce-duygu[model]>=0.1"]
|
|
30
|
+
uslup = ["turkce-uslup[bert]>=0.1"]
|
|
31
|
+
hepsi = ["turkce-duygu[model]>=0.1", "turkce-uslup[bert]>=0.1"]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
seslen-tts = "seslen_tts.__main__:main"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/seslen-tr/seslen-tts"
|
|
38
|
+
Source = "https://github.com/seslen-tr/seslen-tts"
|
|
39
|
+
Issues = "https://github.com/seslen-tr/seslen-tts/issues"
|
|
40
|
+
Model = "https://huggingface.co/seslen-tr/seslen-tts"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
include = ["seslen_tts*"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
seslen-tts — işlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli.
|
|
4
|
+
|
|
5
|
+
from seslen_tts import Seslen, wav_yaz
|
|
6
|
+
s = Seslen() # model ilk kullanımda Hugging Face'ten iner
|
|
7
|
+
wav_yaz("merhaba.wav", s.tum("Merhaba! Bugün size ne okumamı istersiniz?", ses="zeynep"))
|
|
8
|
+
"""
|
|
9
|
+
from .model import ORAN, SesModeli
|
|
10
|
+
from .seslen import TONLAR, Parca, Seslen, wav_yaz
|
|
11
|
+
|
|
12
|
+
__all__ = ["ORAN", "SesModeli", "Seslen", "Parca", "TONLAR", "wav_yaz"]
|
|
13
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
python -m seslen_tts "Merhaba! Yarın bize gelecek misiniz?" -s zeynep -o merhaba.wav
|
|
4
|
+
python -m seslen_tts --sesler
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from . import ORAN, Seslen, wav_yaz
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> int:
|
|
14
|
+
ap = argparse.ArgumentParser(prog="seslen_tts", description="Türkçe metni sese çevirir (işlemcide, internetsiz).")
|
|
15
|
+
ap.add_argument("metin", nargs="?", help="okunacak metin (yoksa standart girdiden)")
|
|
16
|
+
ap.add_argument("-s", "--ses", default="zeynep")
|
|
17
|
+
ap.add_argument("-t", "--ton", default="otomatik", help="otomatik ya da notr, neseli, heyecan, uyari, merak, ozur, "
|
|
18
|
+
"uzgun, kizgin, fisilti")
|
|
19
|
+
ap.add_argument("-u", "--uslup", default="otomatik", help="otomatik ya da haber, bilgi, talimat, deneme, anlati, "
|
|
20
|
+
"masal, sohbet")
|
|
21
|
+
ap.add_argument("--hiz", type=float, default=None)
|
|
22
|
+
ap.add_argument("--perde", type=float, default=1.0)
|
|
23
|
+
ap.add_argument("-o", "--cikti", default="seslen.wav")
|
|
24
|
+
ap.add_argument("--sesler", action="store_true", help="sesleri listele")
|
|
25
|
+
a = ap.parse_args()
|
|
26
|
+
s = Seslen()
|
|
27
|
+
if a.sesler:
|
|
28
|
+
for ad, b in s.sesler.items():
|
|
29
|
+
print(f"{ad:8s} {b['gorunen']:8s} {b['aciklama']} [{', '.join(b['tonlar'])}]")
|
|
30
|
+
return 0
|
|
31
|
+
metin = a.metin or sys.stdin.read()
|
|
32
|
+
t0 = time.perf_counter()
|
|
33
|
+
dalga = s.tum(metin, ses=a.ses, ton=a.ton, uslup=a.uslup, hiz=a.hiz, perde=a.perde)
|
|
34
|
+
wav_yaz(a.cikti, dalga)
|
|
35
|
+
sure = len(dalga) / ORAN
|
|
36
|
+
print(f"{a.cikti}: {sure:.1f} sn ses, {time.perf_counter() - t0:.1f} sn'de", file=sys.stderr)
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
SES MODELİ — Türkçe okunuştan sese, yalnız ONNX Runtime ile (PyTorch gerekmez), işlemcide.
|
|
4
|
+
|
|
5
|
+
İki ağ (Seslen'in StyleTTS2 öğrencisinden aktarıldı):
|
|
6
|
+
metin.onnx işaretler + stil → süre kodlayıcı çıkışı (d), metin kodlayıcı çıkışı (t_en), işaret başına süre
|
|
7
|
+
ses.onnx hizalanmış özellikler + stil + perde/tonlama + soru ezgisi → 24 kHz dalga
|
|
8
|
+
Aradaki hizalama (süreleri yuvarlama, çerçevelere yayma) burada numpy ile yapılır.
|
|
9
|
+
|
|
10
|
+
Model klasörü (öncelik sırasıyla): SesModeli(klasor), SESLEN_TTS_MODEL ortam değişkeni, Hugging Face
|
|
11
|
+
(SESLEN_TTS_HF deposu, ilk kullanımda iner ve önbellekte kalır; SESLEN_TTS_INDIRME=0 ile kapalı).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
ORAN = 24000
|
|
23
|
+
HF_DEPO = os.environ.get("SESLEN_TTS_HF", "seslen-tr/seslen-tts")
|
|
24
|
+
DOSYALAR = ["metin.onnx", "ses.onnx", "stiller.npz", "sesler.json", "semboller.json"]
|
|
25
|
+
BAS_DOLGU = 4 # baştaki dolgu işaretinin en uzun süresi (25 ms birim); uzarsa model boşluğu sesle dolduruyor
|
|
26
|
+
BIRIM_MS = 25 # bir süre birimi = 2 mel çerçevesi = 600 örnek
|
|
27
|
+
# SORU EZGİSİ: evet/hayır sorusunda ezginin tepesi soru ekinden (mı/mi/mu/mü + kişi ekleri) önceki sözcüğün vurgulu
|
|
28
|
+
# hecesindedir ("yardımcı olaBİLİR misin?", "ANkara mı?"; Göksel & Kerslake §21.2). Model bunu düz geçtiği için o
|
|
29
|
+
# heceye bu kadar yarım ton eklenir (ses.onnx içinde ~175 ms ile yumuşatılır). Soru sözcüklü sorulara dokunmaz.
|
|
30
|
+
SORU_TEPE = 6.0
|
|
31
|
+
_SORU_EKI = re.compile(
|
|
32
|
+
r"^m[ɯiuy](s[ɯiuy]n([ɯiuy]z)?|j[ɯiuy][mz]|d[ɯiuy]ɾ?|jd[ɯiuy](m|n|[ɯiuy]z)?|jmɯʃ|jmiʃ|jmuʃ|jmyʃ)?[?.,!]*$")
|
|
33
|
+
_UNLU = set("aeɯiouøyœæɛɪʊ")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def model_klasoru(klasor: str | Path | None = None) -> Path:
|
|
37
|
+
adaylar = [Path(klasor)] if klasor else []
|
|
38
|
+
if os.environ.get("SESLEN_TTS_MODEL"):
|
|
39
|
+
adaylar.append(Path(os.environ["SESLEN_TTS_MODEL"]))
|
|
40
|
+
for a in adaylar:
|
|
41
|
+
if all((a / f).exists() for f in DOSYALAR):
|
|
42
|
+
return a
|
|
43
|
+
if os.environ.get("SESLEN_TTS_INDIRME") != "0":
|
|
44
|
+
try:
|
|
45
|
+
from huggingface_hub import snapshot_download
|
|
46
|
+
a = Path(snapshot_download(HF_DEPO, allow_patterns=DOSYALAR))
|
|
47
|
+
if all((a / f).exists() for f in DOSYALAR):
|
|
48
|
+
return a
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
raise FileNotFoundError(f"ses modeli bulunamadı: Hugging Face'ten ({HF_DEPO}) indirilemedi ve SESLEN_TTS_MODEL "
|
|
52
|
+
f"ile klasör gösterilmedi. Gereken dosyalar: {', '.join(DOSYALAR)}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def soru_egrisi(okunus: str, sozluk: dict) -> np.ndarray:
|
|
56
|
+
"""Okunuşun modele giren her işaretine yarım ton eki: soru ekinden önceki sözcüğün vurgulu hecesine SORU_TEPE,
|
|
57
|
+
gerisine 0. Uzunluk, sözlükte olan karakter sayısıdır (olmayanlar işarete çevrilmez)."""
|
|
58
|
+
ek = np.zeros(len(okunus), np.float32)
|
|
59
|
+
if "?" in okunus:
|
|
60
|
+
sozcukler, i = [], 0
|
|
61
|
+
for s in okunus.split(" "):
|
|
62
|
+
sozcukler.append((i, s))
|
|
63
|
+
i += len(s) + 1
|
|
64
|
+
for j in range(1, len(sozcukler)):
|
|
65
|
+
bas, s = sozcukler[j]
|
|
66
|
+
if not _SORU_EKI.match(s.replace("ˈ", "")) or "?" not in okunus[bas:]:
|
|
67
|
+
continue
|
|
68
|
+
b0, onceki = sozcukler[j - 1]
|
|
69
|
+
if not onceki or onceki[-1] in "?.,!;:":
|
|
70
|
+
continue
|
|
71
|
+
v = onceki.rfind("ˈ")
|
|
72
|
+
if v < 0: # vurgu işareti yoksa son hece: son ünlü + önündeki ünsüz
|
|
73
|
+
v = max((k for k, c in enumerate(onceki) if c in _UNLU), default=0)
|
|
74
|
+
v -= v > 0 and onceki[v - 1] not in _UNLU
|
|
75
|
+
# hecenin sonu: vurgulu ünlüden sonraki ilk ayrı ünlünün (bitişik ünlü aynı çekirdek) önündeki ünsüz
|
|
76
|
+
k = next((k for k in range(v, len(onceki)) if onceki[k] in _UNLU), len(onceki))
|
|
77
|
+
while k < len(onceki) and (onceki[k] in _UNLU or onceki[k] == "ː"):
|
|
78
|
+
k += 1
|
|
79
|
+
sonraki = next((m for m in range(k, len(onceki)) if onceki[m] in _UNLU), None)
|
|
80
|
+
son = len(onceki) if sonraki is None else max(sonraki - 1, k)
|
|
81
|
+
ek[b0 + v: b0 + son] = SORU_TEPE
|
|
82
|
+
return np.array([e for c, e in zip(okunus, ek) if c in sozluk], np.float32)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def windows_kisitini_kaldir() -> None:
|
|
86
|
+
"""Windows arka plandaki süreçleri enerji tasarrufu için yavaşlatıyor (EcoQoS): ölçümde ses üretimi 4-5 kat
|
|
87
|
+
yavaşladı. Bu süreç için kısıt kapatılır; önceliğe dokunulmaz. SESLEN_TTS_SUREC=0 ile kapalı."""
|
|
88
|
+
if os.name != "nt" or os.environ.get("SESLEN_TTS_SUREC") == "0":
|
|
89
|
+
return
|
|
90
|
+
import ctypes
|
|
91
|
+
import ctypes.wintypes as w
|
|
92
|
+
|
|
93
|
+
class _Kisit(ctypes.Structure):
|
|
94
|
+
_fields_ = [("Version", w.ULONG), ("ControlMask", w.ULONG), ("StateMask", w.ULONG)]
|
|
95
|
+
k = ctypes.windll.kernel32
|
|
96
|
+
k.OpenProcess.restype = w.HANDLE
|
|
97
|
+
h = k.OpenProcess(0x0200 | 0x0400, False, os.getpid()) # PROCESS_SET_INFORMATION | QUERY_INFORMATION
|
|
98
|
+
if h:
|
|
99
|
+
s = _Kisit(1, 0x1, 0) # PROCESS_POWER_THROTTLING_EXECUTION_SPEED: kapalı
|
|
100
|
+
k.SetProcessInformation(w.HANDLE(h), 4, ctypes.byref(s), ctypes.sizeof(s))
|
|
101
|
+
k.CloseHandle(w.HANDLE(h))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def hizala(sure: np.ndarray) -> np.ndarray:
|
|
105
|
+
"""İşaret başına süre (birim) → hizalama matrisi (1, L, F): her işaret kendi süresi kadar çerçeveye yayılır."""
|
|
106
|
+
return np.repeat(np.eye(len(sure), dtype=np.float32), sure, axis=1)[None]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class SesModeli:
|
|
110
|
+
def __init__(self, klasor: str | Path | None = None, is_parcacigi: int = 0):
|
|
111
|
+
import onnxruntime as ort
|
|
112
|
+
windows_kisitini_kaldir()
|
|
113
|
+
k = model_klasoru(klasor)
|
|
114
|
+
se = ort.SessionOptions()
|
|
115
|
+
se.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
|
116
|
+
se.intra_op_num_threads = is_parcacigi or min(6, os.cpu_count() or 1)
|
|
117
|
+
self.metin = ort.InferenceSession(str(k / "metin.onnx"), se, providers=["CPUExecutionProvider"])
|
|
118
|
+
self.ses = ort.InferenceSession(str(k / "ses.onnx"), se, providers=["CPUExecutionProvider"])
|
|
119
|
+
v = np.load(k / "stiller.npz")
|
|
120
|
+
self.stiller = {a: v[a].astype(np.float32) for a in v.files}
|
|
121
|
+
self.sesler: dict = json.loads((k / "sesler.json").read_text(encoding="utf-8"))
|
|
122
|
+
self.semboller: dict = json.loads((k / "semboller.json").read_text(encoding="utf-8"))
|
|
123
|
+
self._virgul = {self.semboller[c] for c in ",;" if c in self.semboller}
|
|
124
|
+
|
|
125
|
+
def okunus(self, metin: str) -> str:
|
|
126
|
+
from turkce_vurgu import g2p
|
|
127
|
+
return g2p(metin, vurgu=True)
|
|
128
|
+
|
|
129
|
+
def stil(self, ses: str, ton: str = "notr") -> np.ndarray:
|
|
130
|
+
try:
|
|
131
|
+
return self.stiller[f"{ses}/{ton}"]
|
|
132
|
+
except KeyError:
|
|
133
|
+
raise KeyError(f"bilinmeyen ses/ton: {ses}/{ton}; sesler: {', '.join(self.sesler)}") from None
|
|
134
|
+
|
|
135
|
+
def konus(self, metin: str, stil: np.ndarray, hiz: float = 1.0, perde: float = 1.0, tonlama: float = 1.0,
|
|
136
|
+
virgul_ms: int = 0) -> np.ndarray:
|
|
137
|
+
"""Tek cümle → 24 kHz dalga (float32). hiz > 1 hızlı; perde: F0 çarpanı; tonlama: perde dalgalanması
|
|
138
|
+
çarpanı (1 doğal); virgul_ms: virgül ve noktalı virgülde ek duraklama."""
|
|
139
|
+
okunus = self.okunus(metin)
|
|
140
|
+
kod = [self.semboller[c] for c in okunus if c in self.semboller]
|
|
141
|
+
tokens = np.array([[0, *kod, 0]], np.int64)
|
|
142
|
+
egri = np.concatenate([[0], soru_egrisi(okunus, self.semboller), [0]]).astype(np.float32)[None]
|
|
143
|
+
stil = np.asarray(stil, np.float32).reshape(1, -1)
|
|
144
|
+
d, t_en, sure = self.metin.run(None, {"tokens": tokens, "stil": stil})
|
|
145
|
+
sure = np.maximum(np.round(sure / hiz), 1).astype(np.int64)
|
|
146
|
+
sure[0] = min(sure[0], BAS_DOLGU)
|
|
147
|
+
if virgul_ms:
|
|
148
|
+
for i, k in enumerate(kod, 1):
|
|
149
|
+
if k in self._virgul:
|
|
150
|
+
sure[i] += round(virgul_ms / BIRIM_MS)
|
|
151
|
+
g = {"d": d, "t_en": t_en, "hiza": hizala(sure), "s": stil[:, 128:], "ref": stil[:, :128],
|
|
152
|
+
"perde": np.array([perde], np.float32), "tonlama": np.array([tonlama], np.float32), "egri": egri}
|
|
153
|
+
return self.ses.run(None, g)[0][..., :-50].reshape(-1) # sondaki tık
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
SESLEN — metni cümle cümle, duygusu ve üslubuyla seslendirir.
|
|
4
|
+
|
|
5
|
+
ses 6 ses: ayse, zeynep, emre, fatma, hasan, koca (Seslen.sesler)
|
|
6
|
+
ton "otomatik" (her cümleye turkce-duygu karar verir) ya da notr, neseli, heyecan, uyari, merak, ozur,
|
|
7
|
+
uzgun, kizgin, fisilti (bütün metin o tonda)
|
|
8
|
+
uslup "otomatik" (turkce-uslup metnin türünü bulur: haber, masal, talimat…) ya da üslup adı; hız, ezgi
|
|
9
|
+
genişliği ve duraklamaları belirler. hiz verilirse üslubun hızını ezer.
|
|
10
|
+
turkce-duygu ve turkce-uslup isteğe bağlıdır (pip install "seslen-tts[hepsi]"); yoksa her cümle nötr, üslup düz.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Iterator
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from .model import ORAN, SesModeli
|
|
21
|
+
|
|
22
|
+
TONLAR = ("notr", "neseli", "heyecan", "uyari", "merak", "ozur", "uzgun", "kizgin", "fisilti")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Parca:
|
|
27
|
+
cumle: str
|
|
28
|
+
ses: np.ndarray # 24 kHz, float32
|
|
29
|
+
sessizlik_ms: int # bu cümleden sonraki duraklama
|
|
30
|
+
ton: str
|
|
31
|
+
bilgi: dict = field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _cumleler(metin: str) -> list[str]:
|
|
35
|
+
try:
|
|
36
|
+
from turkce_duygu import cumlelere_bol
|
|
37
|
+
return cumlelere_bol(metin)
|
|
38
|
+
except ImportError:
|
|
39
|
+
return [c for c in re.split(r"(?<=[.!?…])\s+", " ".join(metin.split())) if c]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Seslen:
|
|
43
|
+
def __init__(self, klasor=None, is_parcacigi: int = 0):
|
|
44
|
+
self.model = SesModeli(klasor, is_parcacigi)
|
|
45
|
+
self._duygu: dict = {}
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def sesler(self) -> dict:
|
|
49
|
+
return self.model.sesler
|
|
50
|
+
|
|
51
|
+
def tonlar(self, ses: str) -> tuple[str, ...]:
|
|
52
|
+
return tuple(self.model.sesler[ses]["tonlar"])
|
|
53
|
+
|
|
54
|
+
def _okuma(self, metin: str, uslup: str, hiz: float | None):
|
|
55
|
+
varsayilan = {"hiz": 1.0, "tonlama": 1.0, "duraklama_ms": 350, "virgul_ms": 0}
|
|
56
|
+
try:
|
|
57
|
+
from turkce_uslup import okuma_ayari
|
|
58
|
+
from turkce_uslup import uslup as uslup_bul
|
|
59
|
+
except ImportError:
|
|
60
|
+
return "duz", {**varsayilan, **({"hiz": hiz} if hiz else {})}
|
|
61
|
+
ad = uslup_bul(metin[:4000]).uslup if uslup == "otomatik" else uslup
|
|
62
|
+
a = okuma_ayari(ad, **({"hiz": hiz} if hiz else {}))
|
|
63
|
+
return ad, {"hiz": a.hiz, "tonlama": a.tonlama, "duraklama_ms": a.duraklama_ms, "virgul_ms": a.virgul_ms}
|
|
64
|
+
|
|
65
|
+
def _tonlar(self, ses: str, cumleler: list[str]) -> list[tuple[str, float]]:
|
|
66
|
+
"""Her cümle için (ton, yoğunluk 0-1); turkce-duygu yoksa nötr."""
|
|
67
|
+
try:
|
|
68
|
+
from turkce_duygu import Cozumleyici
|
|
69
|
+
except ImportError:
|
|
70
|
+
return [("notr", 0.0)] * len(cumleler)
|
|
71
|
+
if ses not in self._duygu:
|
|
72
|
+
self._duygu[ses] = Cozumleyici(stiller=self.tonlar(ses))
|
|
73
|
+
sonuc = self._duygu[ses].metin_cozumle(" ".join(cumleler))
|
|
74
|
+
if len(sonuc) != len(cumleler): # bölücüler ayrıştıysa cümle cümle
|
|
75
|
+
sonuc = [self._duygu[ses].metin_cozumle(c)[0] for c in cumleler]
|
|
76
|
+
return [(r.stil, float(r.yogunluk)) for r in sonuc]
|
|
77
|
+
|
|
78
|
+
def seslendir(self, metin: str, ses: str = "zeynep", ton: str = "otomatik", yogunluk: float | None = None,
|
|
79
|
+
hiz: float | None = None, perde: float = 1.0, uslup: str = "otomatik") -> Iterator[Parca]:
|
|
80
|
+
"""Cümle cümle Parca üretir: ilk cümle hazır olur olmaz çalınabilir."""
|
|
81
|
+
if ses not in self.model.sesler:
|
|
82
|
+
raise ValueError(f"bilinmeyen ses: {ses}; sesler: {', '.join(self.model.sesler)}")
|
|
83
|
+
if ton != "otomatik" and ton not in self.tonlar(ses):
|
|
84
|
+
raise ValueError(f"{ses} için ton yok: {ton}; tonlar: {', '.join(self.tonlar(ses))}")
|
|
85
|
+
uslup_ad, ok = self._okuma(metin, uslup, hiz)
|
|
86
|
+
cumleler = _cumleler(metin)
|
|
87
|
+
secim = self._tonlar(ses, cumleler) if ton == "otomatik" else [(ton, 1.0)] * len(cumleler)
|
|
88
|
+
notr = self.model.stil(ses, "notr")
|
|
89
|
+
for c, (t, y) in zip(cumleler, secim):
|
|
90
|
+
t = t if t in self.tonlar(ses) else "notr"
|
|
91
|
+
s = self.model.stil(ses, t)
|
|
92
|
+
if t != "notr": # yoğunluk: nötrden tona doğru; otomatikte hafif duygu da duyulsun diye 0,5 +
|
|
93
|
+
k = yogunluk if yogunluk is not None else (0.5 + y if ton == "otomatik" else 1.0)
|
|
94
|
+
s = notr + k * (s - notr)
|
|
95
|
+
w = self.model.konus(c, s, hiz=ok["hiz"], perde=perde, tonlama=ok["tonlama"], virgul_ms=ok["virgul_ms"])
|
|
96
|
+
yield Parca(c, w, ok["duraklama_ms"], t, {"uslup": uslup_ad, "yogunluk": round(y, 2)})
|
|
97
|
+
|
|
98
|
+
def tum(self, metin: str, **ayar) -> np.ndarray:
|
|
99
|
+
"""Bütün metin tek dalga (cümle aralarında üslubun duraklaması)."""
|
|
100
|
+
parcalar = list(self.seslendir(metin, **ayar))
|
|
101
|
+
dalga = []
|
|
102
|
+
for i, p in enumerate(parcalar):
|
|
103
|
+
dalga.append(p.ses)
|
|
104
|
+
if i < len(parcalar) - 1:
|
|
105
|
+
dalga.append(np.zeros(int(ORAN * p.sessizlik_ms / 1000), np.float32))
|
|
106
|
+
return np.concatenate(dalga) if dalga else np.zeros(0, np.float32)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def wav_yaz(yol, dalga: np.ndarray, oran: int = ORAN) -> None:
|
|
110
|
+
"""16 bit tek kanal WAV (yalnız standart kütüphane)."""
|
|
111
|
+
import wave
|
|
112
|
+
x = np.clip(dalga / max(1e-6, float(np.abs(dalga).max())) * 0.9, -1, 1)
|
|
113
|
+
with wave.open(str(yol), "wb") as w:
|
|
114
|
+
w.setnchannels(1)
|
|
115
|
+
w.setsampwidth(2)
|
|
116
|
+
w.setframerate(oran)
|
|
117
|
+
w.writeframes((x * 32767).astype("<i2").tobytes())
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seslen-tts
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: İşlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli (TTS): PyTorch'suz, yalnız ONNX Runtime
|
|
5
|
+
Author: Mehmet Veysel Delibaş
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/seslen-tr/seslen-tts
|
|
8
|
+
Project-URL: Source, https://github.com/seslen-tr/seslen-tts
|
|
9
|
+
Project-URL: Issues, https://github.com/seslen-tr/seslen-tts/issues
|
|
10
|
+
Project-URL: Model, https://huggingface.co/seslen-tr/seslen-tts
|
|
11
|
+
Keywords: turkish,tts,text-to-speech,speech-synthesis,onnx,styletts2,türkçe,ses
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Natural Language :: Turkish
|
|
14
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: onnxruntime>=1.16
|
|
21
|
+
Requires-Dist: huggingface_hub>=0.20
|
|
22
|
+
Requires-Dist: turkce-vurgu[tam]>=0.1
|
|
23
|
+
Provides-Extra: duygu
|
|
24
|
+
Requires-Dist: turkce-duygu[model]>=0.1; extra == "duygu"
|
|
25
|
+
Provides-Extra: uslup
|
|
26
|
+
Requires-Dist: turkce-uslup[bert]>=0.1; extra == "uslup"
|
|
27
|
+
Provides-Extra: hepsi
|
|
28
|
+
Requires-Dist: turkce-duygu[model]>=0.1; extra == "hepsi"
|
|
29
|
+
Requires-Dist: turkce-uslup[bert]>=0.1; extra == "hepsi"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# seslen-tts
|
|
33
|
+
|
|
34
|
+
> **English:** An open Turkish text-to-speech model that runs on CPU, without PyTorch and without internet: 6 voices × 9
|
|
35
|
+
> emotions, correct Turkish lexical stress and question intonation, automatic emotion per sentence and reading style per
|
|
36
|
+
> text. ONNX Runtime only (~330 MB model, downloaded from Hugging Face on first use). Real-time factor ≈ 0.2–0.4 on a
|
|
37
|
+
> laptop CPU.
|
|
38
|
+
|
|
39
|
+
**İşlemcide çalışan, duygulu, vurguyu bilen açık Türkçe ses modeli.** GPU, internet ve PyTorch gerekmez.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from seslen_tts import Seslen, wav_yaz
|
|
43
|
+
|
|
44
|
+
s = Seslen() # model ilk kullanımda Hugging Face'ten iner (~330 MB)
|
|
45
|
+
wav_yaz("merhaba.wav", s.tum("Merhaba! Yarın bize gelecek misiniz? Dikkat edin, yol kaygan.", ses="zeynep"))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
seslen-tts "Bir varmış bir yokmuş, ormanda meraklı bir sincap yaşarmış." -s ayse -o masal.wav
|
|
50
|
+
seslen-tts --sesler
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Neler yapar
|
|
54
|
+
|
|
55
|
+
- **6 ses, 9 duygu:** nötr, neşeli, heyecan, uyarı, merak, özür, üzgün, kızgın, fısıltı.
|
|
56
|
+
- **Türkçe vurgu doğru okunur:** "ANkara", "geLİyor", "GELmedi". Okunuş [turkce-vurgu](https://github.com/seslen-tr/turkce-vurgu)
|
|
57
|
+
ile çıkarılır; sayılar, tarihler, saatler, para ve kısaltmalar [turkce-normalize](https://github.com/seslen-tr/turkce-normalize)
|
|
58
|
+
ile okunur hâle gelir ("14:30'da" → "on dört otuzda").
|
|
59
|
+
- **Soru ezgisi:** evet/hayır sorusunda ezgi soru ekinden önceki sözcüğün vurgulu hecesinde yükselir
|
|
60
|
+
("yardımcı olaBİLİR misin?").
|
|
61
|
+
- **Otomatik duygu:** her cümlenin tonu metinden seçilir: soru merakla, uyarı uyarıyla, sevinç neşeyle okunur.
|
|
62
|
+
[turkce-duygu](https://github.com/seslen-tr/turkce-duygu) gerekir (`pip install "seslen-tts[duygu]"`).
|
|
63
|
+
- **Otomatik üslup:** haber tempolu ve düz, masal yavaş ve hareketli, tarif adım adım okunur.
|
|
64
|
+
[turkce-uslup](https://github.com/seslen-tr/turkce-uslup) gerekir (`pip install "seslen-tts[uslup]"`).
|
|
65
|
+
|
|
66
|
+
## Kurulum
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
pip install seslen-tts # temel: sesler, vurgu, soru ezgisi
|
|
70
|
+
pip install "seslen-tts[hepsi]" # + otomatik duygu ve üslup
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Python 3.9+, Windows / Linux / macOS. Model `SESLEN_TTS_MODEL` ortam değişkeniyle yerel bir klasörden de okunur.
|
|
74
|
+
|
|
75
|
+
## Sesler
|
|
76
|
+
|
|
77
|
+
| Ses | Kim | Doğallık (UTMOS) |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `ayse` | olgun kadın (~45), orta-kalın, güven veren | 4,04 |
|
|
80
|
+
| `zeynep` | genç kız (~17), ince, canlı | 3,95 |
|
|
81
|
+
| `koca` | iri kalın dev, gürleyen ama sevimli (çizgi karakter) | 3,90 |
|
|
82
|
+
| `fatma` | yaşlı kadın (~70), ince, şefkatli nine | 3,53 |
|
|
83
|
+
| `emre` | genç erkek (~18), orta, enerjik | 2,93 |
|
|
84
|
+
| `hasan` | yaşlı erkek (~72), kalın, bilge dede | 2,78 |
|
|
85
|
+
|
|
86
|
+
Örnek sesler: [Hugging Face model sayfası](https://huggingface.co/seslen-tr/seslen-tts/tree/main/ornekler).
|
|
87
|
+
|
|
88
|
+
## Kullanım
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
s = Seslen()
|
|
92
|
+
for p in s.seslendir(metin, ses="emre", ton="otomatik", uslup="otomatik"): # cümle cümle, akış hâlinde
|
|
93
|
+
print(p.ton, p.cumle) # p.ses: 24 kHz float32 dizi; p.sessizlik_ms: sonraki duraklama
|
|
94
|
+
|
|
95
|
+
s.tum(metin, ses="fatma", ton="uzgun") # bütün metin tek tonda
|
|
96
|
+
s.tum(metin, ses="hasan", hiz=0.9, perde=1.05) # hız ve ses perdesi
|
|
97
|
+
s.tum(metin, ses="zeynep", ton="neseli", yogunluk=0.6) # duygu şiddeti (0-1, 1 = tam ton)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Alt düzey: `s.model.konus(cumle, s.model.stil("ayse", "merak"))` tek cümleyi seslendirir.
|
|
101
|
+
|
|
102
|
+
## Ölçümler
|
|
103
|
+
|
|
104
|
+
- **Anlaşılırlık** (Whisper medium, 6 ses × 4 yeni cümle): %100. Whisper sayıları rakamla yazdığı için ("onda" →
|
|
105
|
+
"10'da") ham ölçüm %93,2 görünür.
|
|
106
|
+
- **Doğallık:** UTMOS 2,8–4,0 (tablo). UTMOS İngilizce dinleyicilerle eğitildi; Türkçe vurguyu yargılayamaz,
|
|
107
|
+
yapaylığı yakalar.
|
|
108
|
+
- **Hız** (i7-12700H, 6 iş parçacığı): gerçek zaman oranı ≈ 0,2–0,4. Yani 5 saniyelik cümle 1–2 saniyede üretilir.
|
|
109
|
+
Windows'un arka plan süreçlerine uyguladığı enerji kısıtı paket tarafından kapatılır (yoksa 4–5 kat yavaşlıyordu).
|
|
110
|
+
|
|
111
|
+
## Nasıl yapıldı
|
|
112
|
+
|
|
113
|
+
[StyleTTS2](https://github.com/yl4579/StyleTTS2) (iSTFTNet çözücü) öğrenci modeli. Ses ve duygu verisini öğretmen model
|
|
114
|
+
[VoxCPM2](https://huggingface.co/openbmb/VoxCPM2) üretti: sesler tariften tasarlandı, gerçek bir kişinin sesi değildir.
|
|
115
|
+
Eğitim üç aşamalıdır: temel, difüzyon ve çekişmeli ince ayar, son olarak vurgu işaretli okunuş ile
|
|
116
|
+
[Türkçe PL-BERT](https://huggingface.co/seslen-tr/turkce-plbert). Eğitim tek RTX 4090'da yapıldı. Ağlar ONNX'e
|
|
117
|
+
aktarıldı; aktarımın sesi değiştirmediği sınandı (log-mel farkı, modelin kendi iki çalıştırması arasındaki farkın altında).
|
|
118
|
+
|
|
119
|
+
## Sınırlılıklar
|
|
120
|
+
|
|
121
|
+
- **Ses klonlama yok.** Bu bilerek böyle: açık modelle başkasının sesini taklit etmek mümkün olmasın diye yalnız hazır
|
|
122
|
+
sesler var.
|
|
123
|
+
- Yalnız Türkçe. Yabancı sözcükler Türkçe okunuşla okunur.
|
|
124
|
+
- Uzun yabancı adlarda, kısaltma yığınlarında ve çok uzun cümlelerde okunuş hataları olabilir.
|
|
125
|
+
- Duygular seslere göre farklı belirginliktedir (ölçülenler arasında en belirgin: Ayşe ve Zeynep).
|
|
126
|
+
|
|
127
|
+
## Lisans ve kullanım koşulları
|
|
128
|
+
|
|
129
|
+
- **Kod:** MIT ([LICENSE](LICENSE)).
|
|
130
|
+
- **Model ağırlıkları ve sesler:** CC BY-SA 4.0. Türkçe PL-BERT Vikipedi metniyle eğitildi; öğretmen VoxCPM2
|
|
131
|
+
Apache-2.0, StyleTTS2 MIT.
|
|
132
|
+
- **Kullanım koşulu (öğretmen modelin ve StyleTTS2'nin koşullarından):**
|
|
133
|
+
- Üretilen sesin yapay zekâ sesi olduğu dinleyiciye belirtilmelidir.
|
|
134
|
+
- Gerçek bir kişiyi taklit etmek, dolandırıcılık ve dezenformasyon için kullanmak yasaktır.
|
|
135
|
+
|
|
136
|
+
Ticari ürün sesleri, özel ses tasarımı, stüdyo arayüzü ve kurum içi kurulum: Seslen ([iletişim](https://github.com/seslen-tr)).
|
|
137
|
+
|
|
138
|
+
## Kardeş projeler
|
|
139
|
+
|
|
140
|
+
[turkce-normalize](https://github.com/seslen-tr/turkce-normalize) · [turkce-vurgu](https://github.com/seslen-tr/turkce-vurgu) ·
|
|
141
|
+
[turkce-duygu](https://github.com/seslen-tr/turkce-duygu) · [turkce-uslup](https://github.com/seslen-tr/turkce-uslup) ·
|
|
142
|
+
[turkce-plbert](https://github.com/seslen-tr/turkce-plbert) · [turkce-tts-sinav](https://github.com/seslen-tr/turkce-tts-sinav)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
seslen_tts/__init__.py
|
|
5
|
+
seslen_tts/__main__.py
|
|
6
|
+
seslen_tts/model.py
|
|
7
|
+
seslen_tts/seslen.py
|
|
8
|
+
seslen_tts.egg-info/PKG-INFO
|
|
9
|
+
seslen_tts.egg-info/SOURCES.txt
|
|
10
|
+
seslen_tts.egg-info/dependency_links.txt
|
|
11
|
+
seslen_tts.egg-info/entry_points.txt
|
|
12
|
+
seslen_tts.egg-info/requires.txt
|
|
13
|
+
seslen_tts.egg-info/top_level.txt
|
|
14
|
+
tests/test_model.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
seslen_tts
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from seslen_tts.model import SORU_TEPE, hizala, soru_egrisi
|
|
7
|
+
from seslen_tts.seslen import _cumleler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _Hepsi(dict):
|
|
11
|
+
def __contains__(self, k):
|
|
12
|
+
return True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _tepe(okunus):
|
|
16
|
+
e = soru_egrisi(okunus, _Hepsi())
|
|
17
|
+
return "".join(c for c, x in zip(okunus, e) if x > 0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_soru_tepesi_vurgulu_hecede():
|
|
21
|
+
assert _tepe("baˈna jaɾdɯmˈdʒɯ oɫabiˈliɾ misin?") == "ˈliɾ" # olaBİLİR misin
|
|
22
|
+
assert _tepe("ˈankaɾa ɟyˈzel mi?") == "ˈzel" # güZEL mi
|
|
23
|
+
assert _tepe("buˈnu ˈsen mi japˈtɯn?") == "ˈsen" # SEN mi yaptın
|
|
24
|
+
assert _tepe("ɟeˈlijoɾ musunuz?") == "ˈli" # geLİyor musunuz
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_soru_sozcuklu_ve_duz_cumleye_dokunmaz():
|
|
28
|
+
assert _tepe("neˈden ˈɟelmedin?") == ""
|
|
29
|
+
assert _tepe("mɯˈsɯɾ ˈtʃok ɟyzelˈdi.") == "" # "Mısır" soru eki değil
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_egri_sozlukte_olmayani_atlar():
|
|
33
|
+
e = soru_egrisi("ab mi?", {"a": 1, "b": 2, " ": 3, "m": 4, "i": 5}) # "?" sözlükte yok
|
|
34
|
+
assert len(e) == 5 and e.max() == SORU_TEPE
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_hizala():
|
|
38
|
+
h = hizala(np.array([2, 1, 3]))
|
|
39
|
+
assert h.shape == (1, 3, 6)
|
|
40
|
+
assert h[0].sum(axis=0).tolist() == [1] * 6 and h[0].sum(axis=1).tolist() == [2, 1, 3]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_cumleler():
|
|
44
|
+
assert _cumleler("Merhaba! Nasılsın? İyiyim.") == ["Merhaba!", "Nasılsın?", "İyiyim."]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.skipif(not os.environ.get("SESLEN_TTS_MODEL"), reason="model yok (SESLEN_TTS_MODEL)")
|
|
48
|
+
def test_seslendirme():
|
|
49
|
+
from seslen_tts import ORAN, Seslen
|
|
50
|
+
s = Seslen()
|
|
51
|
+
assert "notr" in s.tonlar("zeynep")
|
|
52
|
+
parcalar = list(s.seslendir("Merhaba. Yarın gelecek misiniz?", ses="zeynep", ton="notr"))
|
|
53
|
+
assert len(parcalar) == 2
|
|
54
|
+
assert all(0.4 < len(p.ses) / ORAN < 5 and np.isfinite(p.ses).all() for p in parcalar)
|