<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Enrico Marzorati</title>
    <description>The latest articles on DEV Community by Enrico Marzorati (@henry_1953).</description>
    <link>https://dev.to/henry_1953</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4070071%2Feb6ce21e-6643-40f7-8700-5d02b88ec481.jpg</url>
      <title>DEV Community: Enrico Marzorati</title>
      <link>https://dev.to/henry_1953</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/henry_1953"/>
    <language>en</language>
    <item>
      <title>Zero RAM Tax: Native macOS STT/TTS Behind an OpenAI-Compatible API</title>
      <dc:creator>Enrico Marzorati</dc:creator>
      <pubDate>Sun, 09 Aug 2026 16:53:01 +0000</pubDate>
      <link>https://dev.to/henry_1953/zero-ram-tax-native-macos-stttts-behind-an-openai-compatible-api-1b8k</link>
      <guid>https://dev.to/henry_1953/zero-ram-tax-native-macos-stttts-behind-an-openai-compatible-api-1b8k</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5ve9ycc045y9fagznpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5ve9ycc045y9fagznpd.png" alt="Cover Image" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Running local LLMs (Llama 3, Qwen, Mistral, etc.) already pushes RAM and VRAM to the limit. Adding Whisper on top of that often becomes the final straw.&lt;/p&gt;

&lt;p&gt;A typical &lt;code&gt;whisper-large-v3&lt;/code&gt; or even a quantized medium model can easily consume &lt;strong&gt;2–6 GB of memory&lt;/strong&gt; (and significant GPU/ANE resources). When you also want real-time or near-real-time speech-to-text and text-to-speech in the same pipeline, the system starts swapping, latency spikes, or the LLM context window has to be artificially reduced.&lt;/p&gt;

&lt;p&gt;Most people end up with one of these compromises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Run Whisper only when the LLM is idle&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offload STT/TTS to the cloud&lt;/strong&gt; (privacy and latency trade-offs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accept that voice features are “nice to have”&lt;/strong&gt; rather than always-on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a Mac, this feels particularly wasteful. Apple already ships high-quality, hardware-accelerated speech recognition (&lt;code&gt;SFSpeechRecognizer&lt;/code&gt;) and a mature TTS engine (&lt;code&gt;say&lt;/code&gt; + system voices). Both run with almost zero additional memory footprint and leverage the Neural Engine / system frameworks efficiently.&lt;/p&gt;

&lt;p&gt;The missing piece was a clean, drop-in OpenAI-compatible API so existing tools (Open WebUI, SillyTavern, custom agents, Home Assistant, etc.) could talk to these native services without code changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyigaagnaxo17xpkc9f7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyigaagnaxo17xpkc9f7e.png" alt="Architecture overview" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The solution is deliberately minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────┐
│  Any OpenAI client  │
│ (Open WebUI, etc.)  │
└──────────┬──────────┘
           │  /v1/audio/speech
           │  /v1/audio/transcriptions
           ▼
┌─────────────────────┐
│  Flask API Server   │  ← OpenAI-compatible façade
│  (app.py)           │
└──────────┬──────────┘
           │
     ┌─────┴─────┐
     │           │
     ▼           ▼
┌─────────┐  ┌──────────────────────┐
│  say    │  │  macos-transcribe    │
│ (TTS)   │  │  (Swift +            │
│         │  │   SFSpeechRecognizer)│
└─────────┘  └──────────────────────┘
     │                   │
     └─────────┬─────────┘
               ▼
        Native macOS
     (zero extra model RAM)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;To run the project you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS 14 Sonoma or later&lt;/strong&gt; (tested primarily on Sonoma and newer)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python 3.8+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ffmpeg&lt;/strong&gt; (install with &lt;code&gt;brew install ffmpeg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xcode Command Line Tools&lt;/strong&gt; (&lt;code&gt;xcode-select --install&lt;/code&gt;) — required to compile the Swift &lt;code&gt;macos-transcribe&lt;/code&gt; binary&lt;/li&gt;
&lt;li&gt;A working Swift toolchain (comes with the Command Line Tools / Xcode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Permissions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first time you use Speech-to-Text, macOS will ask for &lt;strong&gt;Speech Recognition&lt;/strong&gt; permission.&lt;br&gt;&lt;br&gt;
Go to &lt;strong&gt;System Settings → Privacy &amp;amp; Security → Speech Recognition&lt;/strong&gt; and make sure the terminal (or the process running &lt;code&gt;macos-transcribe&lt;/code&gt;) is allowed.&lt;/p&gt;

&lt;p&gt;The Swift tool forces &lt;strong&gt;on-device recognition&lt;/strong&gt; (&lt;code&gt;requiresOnDeviceRecognition = true&lt;/code&gt;), so no audio leaves your Mac.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware compatibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project works on both &lt;strong&gt;Apple Silicon&lt;/strong&gt; and &lt;strong&gt;Intel&lt;/strong&gt; Macs.&lt;br&gt;&lt;br&gt;
On Intel machines it requires &lt;strong&gt;macOS Tahoe (26)&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The “no &lt;code&gt;-v&lt;/code&gt;” trick for higher-quality TTS
&lt;/h3&gt;

&lt;p&gt;A small but important detail in the TTS implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;say_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;say&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp_aiff&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# deliberately NO -v flag
&lt;/span&gt;&lt;span class="n"&gt;say_cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wpm&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;span class="n"&gt;say_cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By &lt;strong&gt;not&lt;/strong&gt; passing the &lt;code&gt;-v&lt;/code&gt; (voice) parameter, &lt;code&gt;say&lt;/code&gt; falls back to the &lt;strong&gt;system default voice&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is intentional. If you set the system voice to &lt;strong&gt;Siri Voice 1&lt;/strong&gt; (the highest-quality neural voice) in:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System Settings → Accessibility → Spoken Content → System Voice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;…you automatically get the best synthesis quality macOS can offer, without having to maintain a complex mapping of “Enhanced / Premium” voices that can change between macOS versions.&lt;/p&gt;

&lt;p&gt;The voice mapping in &lt;code&gt;config.py&lt;/code&gt; is still present for future flexibility, but the current default path prefers the system voice for maximum quality and simplicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key design decisions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TTS&lt;/strong&gt; → Shell out to the system &lt;code&gt;say&lt;/code&gt; command, then convert the resulting AIFF to the requested format with &lt;code&gt;ffmpeg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STT&lt;/strong&gt; → A small Swift CLI (&lt;code&gt;macos-transcribe&lt;/code&gt;) that uses Apple’s &lt;code&gt;SFSpeechRecognizer&lt;/code&gt;. Audio is normalized to 16 kHz mono WAV. Files longer than ~15 seconds are automatically chunked (Apple’s recognizer has an empirical ~16 s limit per recognition request), processed sequentially, and reassembled. Long jobs return a &lt;code&gt;job_id&lt;/code&gt; (HTTP 202) with a polling endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything stays local.&lt;/strong&gt; No model weights are loaded by the service itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: STT and TTS become essentially &lt;strong&gt;free&lt;/strong&gt; from a memory perspective while the heavy LLM can keep all the RAM/VRAM it needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code &amp;amp; Result
&lt;/h2&gt;

&lt;p&gt;Configuration is intentionally simple. Voice mapping lives in &lt;code&gt;config.py&lt;/code&gt; (kept for future flexibility):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;VOICE_MAPPING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alloy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Alice (Enhanced)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Luca (Enhanced)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nova&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Emma (Premium)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;onyx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fred&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;shimmer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Zoe (Premium)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fable&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Samantha&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;LANG_VOICE_MAPPING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Alice (Enhanced)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Samantha&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Thomas&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;de&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Anna&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;es&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Monica&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal &lt;code&gt;.env&lt;/code&gt; controls the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5050
&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0
&lt;span class="nv"&gt;USE_HTTP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True          &lt;span class="c"&gt;# recommended for local / Home Assistant use&lt;/span&gt;
&lt;span class="nv"&gt;FFMPEG_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/homebrew/bin/ffmpeg
&lt;span class="c"&gt;# MACOS_TRANSCRIBE_BIN=./macos-transcribe/.build/arm64-apple-macosx/release/macos-transcribe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical usage looks exactly like the official OpenAI endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# TTS&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5050/v1/audio/speech &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"input": "Hello, I am your Mac speaking!", "voice": "nova", "speed": 1.1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; speech.mp3

&lt;span class="c"&gt;# STT (short file)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5050/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@recording.wav"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"language=en-US"&lt;/span&gt;

&lt;span class="c"&gt;# Long audio → async job&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5050/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@long_interview.mp3"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"language=it-IT"&lt;/span&gt;
&lt;span class="c"&gt;# → {"job_id": "..."}&lt;/span&gt;

curl http://localhost:5050/v1/audio/transcriptions/&amp;lt;job_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service also exposes &lt;code&gt;/v1/voices&lt;/code&gt; so clients can discover the available mappings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Client (Quick Testing UI)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjt5ijj0s0fl7hv1a1lao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjt5ijj0s0fl7hv1a1lao.png" alt="Web client screenshot" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alongside the API server there is a minimal but practical web client located in the &lt;code&gt;web-app/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;It is a small Node.js + Express application that acts as a thin proxy and provides a clean browser interface for testing both TTS and STT without writing curl commands every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple form to generate speech (text → audio) using the native macOS voices&lt;/li&gt;
&lt;li&gt;File upload for transcription with language selection&lt;/li&gt;
&lt;li&gt;Real-time progress bar for long audio files (the ones that trigger automatic chunking)&lt;/li&gt;
&lt;li&gt;Automatic handling of the async job polling so you can see chunk-by-chunk progress&lt;/li&gt;
&lt;li&gt;Respects the same &lt;code&gt;USE_HTTP&lt;/code&gt; / HTTPS settings as the main API server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to start it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;web-app
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://localhost:3000&lt;/code&gt; (or the HTTPS equivalent if you are not using &lt;code&gt;USE_HTTP=True&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;It is intentionally lightweight — just enough to verify that the OpenAI-compatible endpoints work correctly and to debug long transcriptions without leaving the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/emme99/openai-macos-stt-tts" rel="noopener noreferrer"&gt;https://github.com/emme99/openai-macos-stt-tts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Flask API server&lt;/li&gt;
&lt;li&gt;The Swift &lt;code&gt;macos-transcribe&lt;/code&gt; tool (needs a one-time &lt;code&gt;swift build -c release&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A small web tester with progress bar for long transcriptions&lt;/li&gt;
&lt;li&gt;Full English and Italian READMEs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If you already run local models on a Mac, there is rarely a good reason to pay the “Whisper memory tax” for everyday STT/TTS. Apple’s native engines are fast, private, and essentially free from a resource standpoint. Wrapping them behind the familiar OpenAI audio endpoints removes the integration friction.&lt;/p&gt;

&lt;p&gt;The project is deliberately small and focused. It does one job well: &lt;strong&gt;give your local LLM stack high-quality voice I/O without stealing RAM or VRAM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MIT licensed. Feedback and PRs welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>macos</category>
      <category>stt</category>
      <category>tts</category>
    </item>
  </channel>
</rss>
