Commitok összehasonlítása
3 Commit-ok
feature/os
...
main
| Szerző | SHA1 | Dátum | |
|---|---|---|---|
|
|
29d0c0e503 | ||
|
|
fdf8bf9743 | ||
|
|
aa241f55c8 |
17
README.md
17
README.md
@@ -12,17 +12,26 @@ A simple menu bar app for voice dictation using OpenAI Whisper (local, offline).
|
|||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
|
|
||||||
|
### What's new in 1.3.0
|
||||||
|
|
||||||
|
- ⚡ **Warm transcription server** — the Whisper model stays loaded in a background `whisper-server`, so each dictation is a fast local call (~0.5s) instead of reloading the 1.5–1.6 GB model on every utterance
|
||||||
|
- 📊 **Live mic-level HUD** while recording, which warns ("No audio input?") if the microphone stays silent
|
||||||
|
- 📖 **Custom dictionary** — an editable *Heard → Replacement* table that fixes names/jargon in the transcript and also biases recognition
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- 🎤 Global hotkey (⌃⌥D) to start/stop recording
|
- 🎤 Global hotkey (⌃⌥D) to start/stop recording
|
||||||
|
- ⚡ Fast transcription via a warm whisper-server (model stays loaded — no per-dictation reload), with automatic fallback to `whisper-cli`
|
||||||
|
- 📊 Live mic-level meter while recording, with a "no audio input" warning
|
||||||
|
- 📖 Custom dictionary (heard → replacement) that also biases recognition
|
||||||
- 🔒 Fully offline - uses local Whisper model
|
- 🔒 Fully offline - uses local Whisper model
|
||||||
- ⚡ Automatic paste into any focused app
|
- ⌨️ Automatic paste into any focused app
|
||||||
- 📋 Clipboard preservation - your copied content is restored after paste
|
- 📋 Clipboard preservation - your copied content is restored after paste
|
||||||
- ⚙️ Settings window with model selection dropdown
|
- ⚙️ Settings window with model selection dropdown
|
||||||
- 📥 Built-in model downloader with progress indicator
|
- 📥 Built-in model downloader with progress indicator
|
||||||
- 🚀 Launch at login support
|
- 🚀 Launch at login support
|
||||||
- 🔊 Sound feedback (optional)
|
- 🔊 Sound feedback (optional)
|
||||||
- 📦 Self-contained - whisper-cli bundled in app
|
- 📦 Self-contained - whisper-cli and whisper-server bundled in the app
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
@@ -75,6 +84,7 @@ make install
|
|||||||
Click the menu bar icon → Settings to configure:
|
Click the menu bar icon → Settings to configure:
|
||||||
- **Language**: Auto-detect or 31 supported languages
|
- **Language**: Auto-detect or 31 supported languages
|
||||||
- **Model**: Select from installed models or download new ones
|
- **Model**: Select from installed models or download new ones
|
||||||
|
- **Dictionary**: Add *Heard → Replacement* pairs to fix names/jargon (use `\n` for a newline); the replacement terms also improve recognition
|
||||||
- **Sound feedback**: Toggle audio feedback on/off
|
- **Sound feedback**: Toggle audio feedback on/off
|
||||||
- **Launch at login**: Start automatically when you log in
|
- **Launch at login**: Start automatically when you log in
|
||||||
|
|
||||||
@@ -93,6 +103,8 @@ Download models directly from the app or manually:
|
|||||||
|
|
||||||
Models are stored in `~/.whisper-models/`
|
Models are stored in `~/.whisper-models/`
|
||||||
|
|
||||||
|
> With the warm server (1.3.0+), the selected model loads once at startup (a few seconds); after that each dictation skips the reload, so the speeds above are roughly the per-dictation transcription time.
|
||||||
|
|
||||||
### Audio Feedback
|
### Audio Feedback
|
||||||
|
|
||||||
- 🔔 **Tink** - Recording started
|
- 🔔 **Tink** - Recording started
|
||||||
@@ -114,6 +126,7 @@ Grant these in System Settings → Privacy & Security:
|
|||||||
## Security
|
## Security
|
||||||
|
|
||||||
- All processing is done locally - no data leaves your device
|
- All processing is done locally - no data leaves your device
|
||||||
|
- The bundled `whisper-server` is bound to `127.0.0.1` (loopback only) and is never exposed on the network
|
||||||
- Audio files are stored in private temp directory and deleted after transcription
|
- Audio files are stored in private temp directory and deleted after transcription
|
||||||
- Clipboard is cleared after paste (transcript doesn't remain accessible)
|
- Clipboard is cleared after paste (transcript doesn't remain accessible)
|
||||||
- Original clipboard content is preserved and restored after paste
|
- Original clipboard content is preserved and restored after paste
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
APP_NAME = WhisperDictate
|
APP_NAME = WhisperDictate
|
||||||
APP_BUNDLE = $(APP_NAME).app
|
APP_BUNDLE = $(APP_NAME).app
|
||||||
VERSION = 1.2.1
|
VERSION = 1.3.0
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
SRC_DIR = src
|
SRC_DIR = src
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.2.1</string>
|
<string>1.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>2</string>
|
<string>3</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>13.0</string>
|
<string>13.0</string>
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ final class MeterView: NSView {
|
|||||||
NSColor(calibratedWhite: 0.10, alpha: 0.93).setFill()
|
NSColor(calibratedWhite: 0.10, alpha: 0.93).setFill()
|
||||||
bg.fill()
|
bg.fill()
|
||||||
|
|
||||||
let label = silent ? "🎤 Nincs hangbemenet?" : "🎤 Felvétel…"
|
let label = silent ? "🎤 No audio input?" : "🎤 Recording…"
|
||||||
label.draw(at: NSPoint(x: 16, y: bounds.height - 26), withAttributes: [
|
label.draw(at: NSPoint(x: 16, y: bounds.height - 26), withAttributes: [
|
||||||
.font: NSFont.systemFont(ofSize: 12, weight: .medium),
|
.font: NSFont.systemFont(ofSize: 12, weight: .medium),
|
||||||
.foregroundColor: silent ? NSColor.systemRed : NSColor.white
|
.foregroundColor: silent ? NSColor.systemRed : NSColor.white
|
||||||
@@ -729,7 +729,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
|
|||||||
contentView.addSubview(loginCheck)
|
contentView.addSubview(loginCheck)
|
||||||
|
|
||||||
// Custom dictionary (recognised -> replacement)
|
// Custom dictionary (recognised -> replacement)
|
||||||
let dictLabel = NSTextField(labelWithString: "Szótár (felismert → csere):")
|
let dictLabel = NSTextField(labelWithString: "Dictionary (heard → replacement):")
|
||||||
dictLabel.frame = NSRect(x: 20, y: 304, width: 300, height: 20)
|
dictLabel.frame = NSRect(x: 20, y: 304, width: 300, height: 20)
|
||||||
contentView.addSubview(dictLabel)
|
contentView.addSubview(dictLabel)
|
||||||
|
|
||||||
@@ -740,9 +740,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
|
|||||||
table.usesAlternatingRowBackgroundColors = true
|
table.usesAlternatingRowBackgroundColors = true
|
||||||
table.rowHeight = 22
|
table.rowHeight = 22
|
||||||
let colFrom = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("from"))
|
let colFrom = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("from"))
|
||||||
colFrom.title = "Felismert"; colFrom.width = 195; colFrom.isEditable = true
|
colFrom.title = "Heard"; colFrom.width = 195; colFrom.isEditable = true
|
||||||
let colTo = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("to"))
|
let colTo = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("to"))
|
||||||
colTo.title = "Helyette"; colTo.width = 195
|
colTo.title = "Replacement"; colTo.width = 195
|
||||||
table.addTableColumn(colFrom)
|
table.addTableColumn(colFrom)
|
||||||
table.addTableColumn(colTo)
|
table.addTableColumn(colTo)
|
||||||
table.dataSource = self
|
table.dataSource = self
|
||||||
@@ -760,7 +760,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
|
|||||||
delBtn.bezelStyle = .rounded
|
delBtn.bezelStyle = .rounded
|
||||||
contentView.addSubview(delBtn)
|
contentView.addSubview(delBtn)
|
||||||
|
|
||||||
let dictHint = NSTextField(labelWithString: "A „Helyette” oszlop a felismerést is pontosítja. \\n = új sor.")
|
let dictHint = NSTextField(labelWithString: "The Replacement column also improves recognition. \\n = newline.")
|
||||||
dictHint.frame = NSRect(x: 100, y: 74, width: 330, height: 18)
|
dictHint.frame = NSRect(x: 100, y: 74, width: 330, height: 18)
|
||||||
dictHint.font = NSFont.systemFont(ofSize: 10)
|
dictHint.font = NSFont.systemFont(ofSize: 10)
|
||||||
dictHint.textColor = .tertiaryLabelColor
|
dictHint.textColor = .tertiaryLabelColor
|
||||||
@@ -882,7 +882,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate,
|
|||||||
field.font = NSFont.systemFont(ofSize: 12)
|
field.font = NSFont.systemFont(ofSize: 12)
|
||||||
field.lineBreakMode = .byTruncatingTail
|
field.lineBreakMode = .byTruncatingTail
|
||||||
field.delegate = self
|
field.delegate = self
|
||||||
field.placeholderString = id.rawValue == "from" ? "felismert szó" : "csere (\\n = új sor)"
|
field.placeholderString = id.rawValue == "from" ? "heard word" : "replacement (\\n = newline)"
|
||||||
}
|
}
|
||||||
field.stringValue = replacements[row][id.rawValue] ?? ""
|
field.stringValue = replacements[row][id.rawValue] ?? ""
|
||||||
return field
|
return field
|
||||||
|
|||||||
Reference in New Issue
Block a user