Trim both READMEs, and fix what they claimed about shortcuts

"Shortcut presses during Transcribing… are ignored" read as though Dikte
blocked the session's shortcuts while it worked. It only ignores its own:
pressing Ctrl+Space with a transcript in flight does nothing and queues
nothing, while the rest of KDE keeps working normally. That is now said
where it belongs, next to the indicator.

The rest is shorter. Silence detection, misheard words and file
transcription became bullets under one heading, the settings table is gone
because the window already says what each setting does, and Known limits
folded into a line under the file map. The numbers worth knowing stayed: the
10 dB over each recording's own noise floor, the 0.3 s, the -55 dBFS, and
the one logout KWin needs before the global shortcut fires.
This commit is contained in:
yusufipk
2026-07-25 20:12:21 +07:00
parent 2cbc369132
commit f901c995c4
2 changed files with 102 additions and 247 deletions
+48 -121
View File
@@ -22,25 +22,22 @@ just the Python standard library and PyQt6.
## Install ## Install
```sh ```sh
sudo pacman -S --needed pipewire-audio wl-clipboard ydotool ffmpeg python-pyqt6
systemctl --user enable --now ydotool # needed for auto-paste
./install.sh # or: ./install.sh "Ctrl+Alt+Space" ./install.sh # or: ./install.sh "Ctrl+Alt+Space"
dikte # the settings window opens on first run dikte # the settings window opens on first run
``` ```
System packages (Arch/CachyOS): `install.sh` adds the `dikte` command, a menu entry, an autostart entry and the
KDE shortcut.
```sh Two keys go in the settings window: **OpenAI** for speech to text
sudo pacman -S --needed pipewire-audio wl-clipboard ydotool ffmpeg python-pyqt6 (`gpt-4o-transcribe`) and **OpenRouter** for the cleanup
systemctl --user enable --now ydotool # needed for auto-paste (`google/gemini-3.5-flash-lite` by default, any model on the list works). They
``` fall back to `OPENAI_API_KEY` and `OPENROUTER_API_KEY`, and are stored in
`~/.config/dikte/config.json`, mode 600. Cleanup can be switched off, in which
Two API keys go in the settings window: case the raw transcript is pasted.
- **OpenAI**: speech to text (`gpt-4o-transcribe`). Falls back to the
`OPENAI_API_KEY` environment variable when left empty.
- **OpenRouter**: transcript cleanup (`google/gemini-3.5-flash-lite` by
default, any model on the list works). Falls back to `OPENROUTER_API_KEY`.
Cleanup can be switched off entirely, in which case the raw transcript is
pasted.
## Using it ## Using it
@@ -52,114 +49,50 @@ Two API keys go in the settings window:
| Reload after an update | Tray menu → *Restart*, or `dikte restart` | | Reload after an update | Tray menu → *Restart*, or `dikte restart` |
| Quit | Tray menu → *Quit*, or `dikte quit` | | Quit | Tray menu → *Quit*, or `dikte quit` |
While recording, a small indicator sits in the bottom-left corner of the An indicator in the screen corner shows a red dot, a live waveform and the
screen: a red dot, a live waveform, the elapsed time. Then it walks through elapsed time, then the stage it is on. It never takes focus. Pressing
"Transcribing…", "Cleaning up…" and finally shows the first line of what it `Ctrl+Space` again while Dikte is still working does nothing; nothing queues up.
pasted. The indicator never takes focus, so you stay in the window you were
working in.
## Silence never reaches the API ## What it does
Handed near-silence, a transcription model does not return an empty string. - **Silence never reaches the API.** Handed near-silence, a transcription model
It invents one. Whisper is notorious for answering a quiet two seconds with invents a sentence instead of returning nothing ("Thanks for watching", or in
"Thanks for watching" or, in Turkish, "Altyazı M.K.". An accidental Turkish "Altyazı M.K."). A recording is dropped when nothing rose 10 dB above
`Ctrl+Space` would otherwise cost you an API call and paste a sentence you *that recording's own* noise floor for at least 0.3 s, which is also what
never said. removes steady fan noise however loud, or when its loud end sits below
-55 dBFS. The indicator reports the level it measured, which is what you
calibrate the threshold against.
- **Misheard words are repaired.** Speech models fail phonetically on proper
nouns, so the cleanup model is asked to fix those from context, and to leave
the word alone when the context does not make the intended one clear. The names
you list under Cleanup rules go to the transcription model as a hint and to the
cleanup model as a glossary, which is what lets it recognise "kuber netis":
Dikte checks before spending the call, and the check is relative rather than ```
absolute, because microphone gain varies far too much between machines for a raw ıı bugün şey kuber netis üzerinde çalışan servisleri güncelledim
fixed threshold to mean anything. A recording is dropped when any of these holds:
- the loud end of it sits below the absolute floor (default -55 dBFS);
- nothing rose 10 dB above *this recording's own* noise floor for at least
0.3 s, which is also what removes steady fan or hiss, however loud;
- the level never moved at all near the floor.
When something slips through anyway, a second filter catches the handful of
stock phrases the models fall back on, but only for clips under six seconds,
so a genuine "thanks for watching the demo" survives.
The indicator reports the level it measured (`No speech detected (-56 dB)`),
which is what you calibrate the threshold against if your microphone is
unusually quiet or unusually noisy.
## Repairing misheard words
Speech models mangle proper nouns. Product names, technical terms and acronyms
come back as something that sounds right and means nothing, and no amount of
punctuation fixing helps if the word itself is wrong. The cleanup model is asked
to repair those from context, and to leave the word alone when the context does
not make the intended one clear, so it corrects rather than guesses.
The list of names you enter under Cleanup rules does double duty here: it goes
to the transcription model as a hint, and to the cleanup model as a glossary.
Knowing how a name is spelled is what lets the second model recognise it in a
garbled transcript. With `Kubernetes, Grafana, PyQt`
in that box:
```
raw ıı bugün şey kuber netis üzerinde çalışan servisleri güncelledim
yani sonra grafanada bir panel açtım hani ve pay kut ile arayüzü yani sonra grafanada bir panel açtım hani ve pay kut ile arayüzü
şey bitirdim işte şey bitirdim işte
result Bugün Kubernetes üzerinde çalışan servisleri güncelledim. Sonra result Bugün Kubernetes üzerinde çalışan servisleri güncelledim. Sonra
Grafana'da bir panel açtım ve PyQt ile arayüzü bitirdim. Grafana'da bir panel açtım ve PyQt ile arayüzü bitirdim.
``` ```
- **A failed cleanup is never silent.** The raw transcript is still pasted so the
dictation is not lost, but the indicator turns amber with the reason instead of
looking like a normal run.
- **Audio and video files** run through the same models under Settings → Audio
file, optionally with `[mm:ss]` timestamps, chunked through ffmpeg when long.
- **History** of every dictation under Settings → History, with a size limit and
right-click to delete.
- **Turkish and English interface**, following the system locale by default.
When cleanup itself fails, a rejected key or an empty account, the raw ## The global shortcut needs one logout
transcript is still pasted so the dictation is never lost, but the indicator
turns amber and says what went wrong, with the full reason in a notification.
It never quietly hands you an uncleaned transcript as though the model had run.
## Transcribing a file KWin only reads `kglobalshortcutsrc` at startup, so the shortcut `install.sh`
writes will not fire until you log out and back in. Until then, Settings →
Settings**Audio file** takes any audio or video file and runs it through the Shortcut → **built-in listener** reads `/dev/input` and catches the combination
same models. Two options, both remembered between runs: itself. The difference: it does not swallow the key, so `Ctrl+Space` also reaches
the focused application (some editors will pop up autocomplete). The listener
- **Add timestamps**: prefixes every segment with `[mm:ss]`. This switches to needs your user in the `input` group: `sudo usermod -aG input $USER`.
`whisper-1`, the only model that returns segment times.
- **Run the cleanup model afterwards**: same cleanup as live dictation, with an
extra rule telling the model to leave the timestamps alone.
Long files are converted to 16 kHz mono with ffmpeg and split into ten-minute
chunks, each transcribed in turn with its timestamps shifted into place. The
result can be copied or saved as `.txt`.
## About the global shortcut
KWin only reads `kglobalshortcutsrc` at startup. `install.sh` writes the
shortcut to the right place, but **it will not fire until you log out and back
in.** Two ways around that:
1. Log out and in. This is the clean solution: the key is swallowed by KWin,
so it never leaks into other applications.
2. Settings → Shortcut → turn on the **built-in listener**. It reads
`/dev/input` and catches the combination itself, working immediately. The
difference: it does not swallow the key, so `Ctrl+Space` also reaches the
focused application (some editors will pop up autocomplete). If that bothers
you, change the shortcut to something like `Ctrl+Alt+Space`.
The built-in listener needs your user to be in the `input` group:
`sudo usermod -aG input $USER`.
## Settings
Stored in `~/.config/dikte/config.json`, mode 600, since the API keys live there.
| Setting | What it does |
| --- | --- |
| Interface language | Turkish, English, or follow the system locale |
| Microphone | Pick a specific source or use the default |
| Speech language | Language hint for transcription, or automatic detection |
| Paste key | `ctrl+v` / `ctrl+shift+v` / `shift+insert`. Terminals usually want the second |
| Restore clipboard | Puts your previous clipboard back after pasting |
| Skip silent recordings | Drops recordings with no speech before any API call, see above |
| Cleanup rules | The system prompt handed to the cleanup model. This is where you decide how much it may touch your words |
| Names and terms | A hint for the transcription model and a glossary for the cleanup model, so your proper nouns survive |
| Keep audio files | WAVs are kept in `~/.local/share/dikte/recordings` |
History lives in `~/.local/share/dikte/history.jsonl`; the last 200 entries are
browsable under Settings → History.
## Layout ## Layout
@@ -177,14 +110,8 @@ paste.py wl-clipboard and ydotool wrappers
i18n.py the string table i18n.py the string table
``` ```
## Known limits The indicator is drawn through XWayland, because a Wayland client cannot place a
window in a screen corner; `dikte.py` sets `QT_QPA_PLATFORM=xcb` for that.
- The indicator is drawn through XWayland, because a Wayland client cannot
place a window in a screen corner. `dikte.py` sets `QT_QPA_PLATFORM=xcb` for
this reason.
- Auto-paste goes through `ydotool`'s virtual keyboard. Without a running
`ydotoold` the text is only copied, and the indicator says so.
- Shortcut presses during "Transcribing…" are ignored.
## License ## License
+48 -120
View File
@@ -21,25 +21,22 @@ sadece Python standart kütüphanesi ve PyQt6.
## Kurulum ## Kurulum
```sh ```sh
sudo pacman -S --needed pipewire-audio wl-clipboard ydotool ffmpeg python-pyqt6
systemctl --user enable --now ydotool # otomatik yapıştırma için
./install.sh # ya da: ./install.sh "Ctrl+Alt+Space" ./install.sh # ya da: ./install.sh "Ctrl+Alt+Space"
dikte # ilk açılışta ayarlar penceresi gelir dikte # ilk açılışta ayarlar penceresi gelir
``` ```
Gereken sistem paketleri (Arch/CachyOS): `install.sh` `dikte` komutunu, menü girdisini, oturum açılışında otomatik
başlatmayı ve KDE kısayolunu kurar.
```sh Ayarlar penceresinde iki anahtar istenir: sesi yazıya çeviren **OpenAI**
sudo pacman -S --needed pipewire-audio wl-clipboard ydotool ffmpeg python-pyqt6 (`gpt-4o-transcribe`) ve transkripti temizleyen **OpenRouter** (varsayılan
systemctl --user enable --now ydotool # otomatik yapıştırma için `google/gemini-3.5-flash-lite`, listedeki her model çalışır). Boş bırakırsan
``` `OPENAI_API_KEY` ve `OPENROUTER_API_KEY` kullanılır; anahtarlar
`~/.config/dikte/config.json` içinde, izinler 600. Temizlemeyi tamamen
Ayarlar penceresinde iki anahtar istenir: kapatabilirsin, o zaman ham transkript yapıştırılır.
- **OpenAI**: sesi yazıya çevirir (`gpt-4o-transcribe`). Boş bırakırsan
`OPENAI_API_KEY` ortam değişkeni kullanılır.
- **OpenRouter**: transkripti temizler (varsayılan
`google/gemini-3.5-flash-lite`, listedeki her model çalışır). Boşsa
`OPENROUTER_API_KEY` kullanılır. Temizlemeyi tamamen kapatabilirsin; o zaman
ham transkript yapıştırılır.
## Kullanım ## Kullanım
@@ -51,113 +48,50 @@ Ayarlar penceresinde iki anahtar istenir:
| Güncelleme sonrası yeniden yükle | Tepsi menüsü → *Yeniden başlat*, ya da `dikte restart` | | Güncelleme sonrası yeniden yükle | Tepsi menüsü → *Yeniden başlat*, ya da `dikte restart` |
| Çık | Tepsi menüsü → *Çık*, ya da `dikte quit` | | Çık | Tepsi menüsü → *Çık*, ya da `dikte quit` |
Kayıt sırasında ekranın sol alt köşesinde küçük bir gösterge belirir: kırmızı Ekranın köşesindeki gösterge kırmızı kayıt noktasını, canlı ses dalgasını ve
kayıt noktası, canlı ses dalgası, süre. Ardından "Yazıya çevriliyor…", süreyi, ardından hangi aşamada olduğunu gösterir. Odak almaz. Dikte çalışırken
"Temizleniyor…" ve son olarak yapıştırılan metnin ilk satırı görünür. Gösterge `Ctrl+Space`'e tekrar basmak bir şey yapmaz, sıraya da girmez.
odak almaz, yani yazdığın pencereden çıkmazsın.
## Sessizlik API'ye gitmez ## Neler yapıyor
Sessize yakın bir ses verildiğinde transkripsiyon modeli boş dize döndürmez, - **Sessizlik API'ye gitmez.** Sessize yakın bir ses verildiğinde model boş dize
bir cümle uydurur. Whisper bunun ünlü örneği: iki saniyelik sessizliğe döndürmez, bir cümle uydurur ("Altyazı M.K.", "Thanks for watching"). *O
"Altyazı M.K." ya da "Thanks for watching" der. Yanlışlıkla basılan bir kaydın kendi* gürültü tabanının 10 dB üstüne en az 0,3 saniye çıkan bir şey
`Ctrl+Space` yoksa sana hem bir API çağrısına mal olur hem de hiç söylemediğin yoksa kayıt atılır; ne kadar yüksek olursa olsun sabit fanı ya da cızırtıyı
bir cümleyi yapıştırır. eleyen de budur. Kaydın gürültülü ucu -55 dBFS altındaysa da atılır. Gösterge
ölçtüğü seviyeyi yazar, eşiği ona bakarak ayarlarsın.
- **Yanlış duyulan kelimeler düzeltilir.** Konuşma modelleri özel isimlerde sesçe
benzer bir şeye kayıyor; temizleme modelinden bunları bağlamdan onarması,
bağlam netleştirmiyorsa dokunmaması isteniyor. Temizleme kuralları sekmesine
yazdığın isimler transkripsiyon modeline ipucu, temizleme modeline sözlük
olarak gidiyor; "kuber netis"i tanımasını sağlayan da bu:
Dikte çağrıyı harcamadan önce kontrol eder ve bu kontrol mutlak değil göreli ```
yapılır, çünkü mikrofon kazancı makineden makineye o kadar değişir ki sabit bir ham ıı bugün şey kuber netis üzerinde çalışan servisleri güncelledim
eşik bir şey ifade etmez. Şunlardan biri bile geçerliyse kayıt atılır:
- kaydın gürültülü ucu mutlak tabanın altındaysa (varsayılan -55 dBFS);
- **o kaydın kendi** gürültü tabanının 10 dB üstüne en az 0,3 saniye çıkan bir
şey yoksa; ne kadar yüksek olursa olsun sabit fan ya da cızırtıyı eleyen de
budur;
- seviye taban civarında hiç hareket etmediyse.
Yine de bir şey sızarsa, ikinci bir filtre modellerin sığındığı kalıp cümleleri
yakalar; ama yalnızca altı saniyeden kısa kayıtlarda, ki gerçekten söylenmiş
bir "izlediğiniz için teşekkürler" elenmesin.
Gösterge ölçtüğü seviyeyi de yazar (`Ses algılanmadı (-56 dB)`); mikrofonun
alışılmadık ölçüde kısık ya da gürültülüyse eşiği buna bakarak ayarlarsın.
## Yanlış duyulan kelimeleri düzeltme
Konuşma modelleri özel isimleri katlediyor. Ürün adları, teknik terimler ve
kısaltmalar sesçe benzeyen ama anlamsız bir şeye dönüşüyor; kelimenin kendisi
yanlışsa noktalama düzeltmenin bir faydası olmuyor. Temizleme modelinden bunları
bağlamdan onarması isteniyor, bağlam hangi kelime olduğunu netleştirmiyorsa da
dokunmaması söyleniyor; yani tahmin etmiyor, düzeltiyor.
Temizleme kuralları sekmesine girdiğin isim listesi burada iki iş görüyor:
transkripsiyon modeline ipucu, temizleme modeline sözlük olarak gidiyor. İkinci
modelin bozuk bir transkriptte o ismi tanıyabilmesi, doğru yazımını bilmesine
bağlı. Kutuya `Kubernetes, Grafana, PyQt` yazıldığında:
```
ham ıı bugün şey kuber netis üzerinde çalışan servisleri güncelledim
yani sonra grafanada bir panel açtım hani ve pay kut ile arayüzü yani sonra grafanada bir panel açtım hani ve pay kut ile arayüzü
şey bitirdim işte şey bitirdim işte
sonuç Bugün Kubernetes üzerinde çalışan servisleri güncelledim. Sonra sonuç Bugün Kubernetes üzerinde çalışan servisleri güncelledim. Sonra
Grafana'da bir panel açtım ve PyQt ile arayüzü bitirdim. Grafana'da bir panel açtım ve PyQt ile arayüzü bitirdim.
``` ```
- **Başarısız temizleme sessizce geçmez.** Dikte kaybolmasın diye ham transkript
yine yapıştırılır ama gösterge kehribar rengine döner ve nedenini söyler,
normal bir çalışma gibi görünmez.
- **Ses ve video dosyaları** Ayarlar → Ses dosyası sekmesinde aynı modellerden
geçer; istersen `[dd:ss]` zaman damgalarıyla, uzun dosyalar ffmpeg ile
parçalanarak.
- **Geçmiş** Ayarlar → Geçmiş sekmesinde; boyut sınırı var, sağ tıklayıp
silebilirsin.
- **Türkçe ve İngilizce arayüz**, varsayılan olarak sistem dilini izler.
Temizlemenin kendisi başarısız olursa, reddedilen bir anahtar ya da boşalmış bir ## Global kısayol için bir kez oturum kapatmak gerekir
hesap yüzünden, dikte kaybolmasın diye ham transkript yine yapıştırılır; ama
gösterge kehribar rengine döner ve neyin ters gittiğini söyler, tam gerekçe de
bildirimde yazar. Temizlenmemiş bir metni model çalışmış gibi sessizce eline
tutuşturmaz.
## Dosyadan transkript KWin `kglobalshortcutsrc` dosyasını yalnızca açılışta okur, yani `install.sh`'ın
yazdığı kısayol oturumu yeniden açana kadar tetiklenmez. O zamana kadar Ayarlar →
Ayarlar**Ses dosyası** sekmesi, herhangi bir ses ya da video dosyasını aynı Kısayol → **yerleşik dinleyici** `/dev/input` üzerinden kombinasyonu kendisi
modellerden geçirir. İki seçenek var, ikisi de hatırlanır: yakalar. Tek farkı: tuşu yutmaz, yani `Ctrl+Space` odaktaki uygulamaya da iletilir
(bazı editörlerde otomatik tamamlama açılabilir). Dinleyici kullanıcının `input`
- **Zaman damgası ekle**: her bölümün başına `[dd:ss]` koyar. Bunun için bölüm grubunda olmasını gerektirir: `sudo usermod -aG input $USER`.
zamanı döndüren tek model olan `whisper-1` kullanılır.
- **Sonrasında temizleme modelinden geçir**: canlı diktedeki temizlemenin
aynısı, üstüne modele damgalara dokunmamasını söyleyen bir kural eklenir.
Uzun dosyalar ffmpeg ile 16 kHz mono'ya çevrilip onar dakikalık parçalara
bölünür; her parça sırayla çevrilir ve zaman damgaları kendi yerine kaydırılır.
Sonuç panoya kopyalanabilir ya da `.txt` olarak kaydedilebilir.
## Global kısayol hakkında
KWin, `kglobalshortcutsrc` dosyasını yalnızca açılışta okur. `install.sh`
kısayolu doğru yere yazar ama **oturumu yeniden açana kadar tetiklenmez.**
İki seçenek:
1. Oturumu kapat-aç. Temiz çözüm bu: tuşu KWin yuttuğu için diğer uygulamalara
sızmaz.
2. Ayarlar → Kısayol → **Yerleşik dinleyici**'yi aç. `/dev/input` üzerinden
kombinasyonu kendisi yakalar, anında çalışır. Tek farkı: tuşu yutmaz, yani
`Ctrl+Space` odaktaki uygulamaya da iletilir (bazı editörlerde otomatik
tamamlama açılabilir). Rahatsız ederse kısayolu `Ctrl+Alt+Space` gibi bir
kombinasyona çevir.
Yerleşik dinleyici `input` grubunda olmayı gerektirir:
`sudo usermod -aG input $USER`.
## Ayarlar
`~/.config/dikte/config.json` içinde, izinler 600, çünkü API anahtarları orada durur.
| Ayar | Açıklama |
| --- | --- |
| Arayüz dili | Türkçe, İngilizce ya da sistem diline uy |
| Mikrofon | Belirli bir kaynak seç, ya da varsayılanı kullan |
| Konuşma dili | Transkripsiyona dil ipucu verir; otomatik algılama da olur |
| Yapıştırma tuşu | `ctrl+v` / `ctrl+shift+v` / `shift+insert`. Terminaller genelde ikincisini ister |
| Panoyu geri koy | Yapıştırdıktan sonra eski pano içeriğini iade eder |
| Sessiz kayıtları atla | Konuşma içermeyen kayıtları API'ye gitmeden eler, yukarıya bak |
| Temizleme kuralları | Temizleme modeline verilen sistem talimatı. Ne kadar müdahale edeceğini burada belirlersin |
| İsimler ve terimler | Transkripsiyon modeline ipucu, temizleme modeline sözlük; özel isimlerin doğru yazılması için |
| Ses kayıtlarını sakla | WAV'lar `~/.local/share/dikte/recordings` altında kalır |
Geçmiş `~/.local/share/dikte/history.jsonl` dosyasında tutulur; son 200 kayıt
Ayarlar → Geçmiş sekmesinden görülebilir.
## Dosyalar ## Dosyalar
@@ -175,14 +109,8 @@ paste.py wl-clipboard ve ydotool sarmalayıcıları
i18n.py metin tablosu i18n.py metin tablosu
``` ```
## Bilinen sınırlar Gösterge XWayland üzerinden çizilir; Wayland'da bir pencereyi belirli bir köşeye
yerleştirmenin yolu yok, `dikte.py` bu yüzden `QT_QPA_PLATFORM=xcb` ayarlar.
- Gösterge XWayland üzerinden çizilir; Wayland'da bir pencereyi belirli bir
köşeye yerleştirmenin yolu yok. `dikte.py` bu yüzden `QT_QPA_PLATFORM=xcb`
ayarlar.
- Otomatik yapıştırma `ydotool`'un sanal klavyesiyle yapılır; `ydotoold`
çalışmıyorsa metin yalnızca panoya kopyalanır ve gösterge bunu söyler.
- "Yazıya çevriliyor…" sürerken gelen kısayol basışları yok sayılır.
## Lisans ## Lisans