Sign inSign up

tmalex/clean-tts

By tmalex

Updated 10 days ago

Neural TTS for Sonoff iHost & smart home with MaryTTS and Node-RED.

Image
Integration & delivery
Message queues
Internet of things
0

2.2K

tmalex/clean-tts repository overview

🔊 Clean TTS for Sonoff iHost & Smart Home

Studio-Grade Neural Voice Announcements for Your Home Automations

Give your Sonoff iHost and smart home a natural, human-like voice. Clean TTS brings lifelike neural text-to-speech directly to the iHost's built-in speaker and to any smart media player on your local network.


🌟 What Clean TTS Does

  • 🎙️ Natural Neural Speech: Sounds like a real person talking in your room, with natural intonation, breathing rhythm, and clear pronunciation across multiple languages (English, Romanian, German, French, Italian, Spanish, Catalan, etc.).
  • 🔊 Speaks on iHost Internal Speaker: Plays announcements directly out of your Sonoff iHost built-in speaker — zero extra cables, amplifiers, or audio accessories needed.
  • 🚦 Never Cuts Off Announcements: If multiple sensors trigger at the exact same moment (e.g. front door opens while a motion alert sounds), Clean TTS speaks every announcement clearly one after another instead of cutting words off or playing noisy overlapping sound.
  • 🔔 Pleasant Wake-Up Chime: Softly alerts household members that an announcement is coming before speaking.
  • 📖 Smart Pronunciation Dictionary: Correctly pronounces smart home brand names and units instead of awkwardly spelling them out (e.g., "Zigbee" sounds like "zig-bi", "Shelly" sounds like "she-lee", "kWh" is read as "kilowatt hours"). Includes a live ▶️ Play button to test words before saving.
  • 🎛️ Live Web Studio: Friendly dashboard at http://ihost.local:8123 to test voices, adjust defaults, and test speech right from your browser.
  • 🤖 AI Assistant Ready (MCP): Connects natively with AI assistants (Claude Desktop, Hermes Agent, Antigravity) so your AI agents can speak out loud on your iHost.

🚀 Quick 1-Minute Setup on Sonoff iHost

  1. In the eWeLink CUBE web interface, navigate to DockerImagesSearch: tmalex/clean-tts
  2. Download the latest image.
  3. Run the container with:
    • Network Mode: Select host (do NOT use bridge mode; bridge causes connection errors with the internal speaker API). In host mode, the port is fixed to 8123.
    • Volume Mount: Create a dedicated partition / folder specifically for Clean TTS (e.g. clean-tts) and map it to /data. Do NOT mount it to another container's directory (like Node-RED). This dedicated folder keeps your custom words, generated voices, and settings permanently safe across container updates.
  4. Open your browser at: 👉 http://ihost.local:8123
  5. On your first visit, click "Request Access Token" and confirm Allow on your iHost screen to authorize physical speaker playback.

🎛️ How Parameters Work (Override on the Fly)

In Clean TTS, you can configure your favorite default voice, volume, and speed in the web dashboard.

Whenever you send an announcement from Node-RED, Home Assistant, or an HTTP request, you can simply send the text, or you can optionally override any setting for that specific announcement:

ParameterTypeExampleWhat it does
textstring"Front door opened"Required. The message to speak.
langstring"en", "ro", "de", "fr", "ca"Language code. Automatically picks the best native voice.
voicestring"en-US-JennyNeural"Select a specific male or female voice.
volumestring"+20%", "+0%"Audio Generation Volume (MP3/WAV): Software loudness level encoded directly into the synthesized audio file via SSML/FFmpeg for all devices and streams. (Not hardware speaker level).
ratestring"+10%", "-10%"Speech speed (faster or slower).
pitchstring"+5Hz", "-5Hz"Voice pitch adjustment.
bellbooleantrue / falseEnable or disable the notification chime before speaking.
nocachebooleantrue / falseSet true for spontaneous one-off alerts (current time, live sensor readings) to skip disk cache.

💡 Tip: Any parameter you omit automatically falls back to your saved settings from the web dashboard!

Caches vs. No-Cache: Fixed alerts ("Front door opened") are cached on disk for instant (0ms) replay. Spontaneous alerts ("It is 12:45 PM, 21.5°C") can be sent with "nocache": true so 0 bytes are wasted on your storage drive. You can also run the container with -e NOCACHE=true to enforce streaming mode by default.


🔊 Sonoff iHost Hardware Volume vs. Audio Generation Volume

  • Audio Generation Volume (volume: "+20%"): Regulates the sound loudness of the generated MP3 file itself. Use this in your message payloads to make individual announcements louder or quieter without triggering any hardware side-effects.
  • Physical Speaker Volume (POST /api/ihost/volume or Web UI Slider): Controls the actual physical speaker amplifier on Sonoff iHost [0-100].

    ⚠️ Important: Whenever iHost receives a hardware volume change command, the iHost firmware automatically plays an unskippable native feedback chime/jingle on the built-in speaker. Because this feedback sound temporarily occupies the speaker channel, always send hardware volume changes as a standalone command (e.g. for Day/Night schedules) and maintain an interval of at least 4–5 seconds before or after any TTS announcement.

Adjust Hardware Volume in Web UI or Node-RED:
  • Web UI: Simply move the iHost Hardware Volume slider at http://ihost.local:8123 (automatically saved).
  • Node-RED Schedule (Day / Night Volume):
// Function node connected to an HTTP Request node (POST http://127.0.0.1:8123/api/ihost/volume)
msg.url = "http://127.0.0.1:8123/api/ihost/volume";
msg.method = "POST";
msg.headers = { "Content-Type": "application/json" };
msg.payload = { "volume": 100 }; // Set 100 for Daytime / 30 for Nighttime
return msg;

🔴 Node-RED Integration (Step-by-Step)

Option 1: 🚀 1-Click Import or Copy Flow (Clean TTS Help & Docs)
  1. Open Clean TTS Help & Docs at: 👉 http://ihost.local:8123/help
  2. In the 🔴 Node-RED Flow & Subflow Template card:
    • On Sonoff iHost: Click "🚀 1-Click Import to Node-RED" to deploy automatically.
    • Or click "📋 Copy Flow JSON" and import via Node-RED Menu (≡) → ImportPaste.
📋 Click to expand/view full Node-RED Flow & Subflow JSON
[
  {
    "id": "subflow_clean_tts",
    "type": "subflow",
    "name": "Clean TTS Speaker",
    "info": "Studio-grade neural voice announcement service for smart home and speaker notifications (Clean TTS).",
    "category": "output",
    "in": [{"x": 60, "y": 80, "wires": [{"id": "sf_func_1"}]}],
    "out": [{"x": 580, "y": 80, "wires": [{"id": "sf_http_1", "port": 0}]}],
    "env": [
      {
        "name": "DEFAULT_TEXT",
        "type": "str",
        "value": "Hello! This is a voice test from Clean TTS.",
        "ui": {
          "label": {"en": "Default Announcement Text"},
          "type": "input",
          "opts": {"types": ["str"]}
        }
      }
    ],
    "meta": {
      "module": "clean-tts-ihost",
      "type": "subflow"
    },
    "color": "#38bdf8",
    "icon": "font-awesome/fa-bullhorn"
  },
  {
    "id": "sf_func_1",
    "type": "function",
    "z": "subflow_clean_tts",
    "name": "Format Payload & Set API URL",
    "func": "const defaultText = env.get(\"DEFAULT_TEXT\") || \"Hello! This is a voice test from Clean TTS.\";\n\nlet text = \"\";\nlet bell = true;\nlet volume = undefined;\nlet rate = undefined;\nlet pitch = undefined;\nlet nocache = undefined;\n\nif (typeof msg.payload === \"string\" && msg.payload.trim().length > 0) {\n    text = msg.payload.trim();\n} else if (typeof msg.payload === \"object\" && msg.payload !== null) {\n    text = msg.payload.text || msg.payload.say || defaultText;\n    if (msg.payload.bell !== undefined) bell = msg.payload.bell;\n    if (msg.payload.volume !== undefined) volume = msg.payload.volume;\n    if (msg.payload.rate !== undefined) rate = msg.payload.rate;\n    if (msg.payload.pitch !== undefined) pitch = msg.payload.pitch;\n    if (msg.payload.nocache !== undefined) nocache = msg.payload.nocache;\n} else {\n    text = defaultText;\n}\n\nmsg.url = \"http://127.0.0.1:8123/api/tts\";\nmsg.method = \"POST\";\nmsg.headers = {\n    \"Content-Type\": \"application/json\"\n};\n\nconst payloadObj = {\n    \"text\": text,\n    \"bell\": bell\n};\nif (volume !== undefined) payloadObj.volume = volume;\nif (rate !== undefined) payloadObj.rate = rate;\nif (pitch !== undefined) payloadObj.pitch = pitch;\nif (nocache !== undefined) payloadObj.nocache = nocache;\n\nmsg.payload = payloadObj;\nreturn msg;",
    "outputs": 1,
    "timeout": "",
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 240,
    "y": 80,
    "wires": [["sf_http_1"]]
  },
  {
    "id": "sf_http_1",
    "type": "http request",
    "z": "subflow_clean_tts",
    "name": "Clean TTS API",
    "method": "use",
    "ret": "obj",
    "paytoqs": "ignore",
    "url": "",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 450,
    "y": 80,
    "wires": [[]]
  },
  {
    "id": "tab_test_clean_tts",
    "type": "tab",
    "label": "Clean TTS Test",
    "disabled": false,
    "info": "Clean TTS announcement and hardware speaker test flow",
    "env": []
  },
  {
    "id": "inject_tts_test_simple",
    "type": "inject",
    "z": "tab_test_clean_tts",
    "name": "🔊 Simple Voice Test",
    "props": [{"p": "payload"}],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "Hello! This is a voice announcement test from Node-RED.",
    "payloadType": "str",
    "x": 190,
    "y": 120,
    "wires": [["test_tts_node"]]
  },
  {
    "id": "inject_tts_test_advanced",
    "type": "inject",
    "z": "tab_test_clean_tts",
    "name": "🚨 High Volume Alert (+30%)",
    "props": [{"p": "payload"}],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "{\"text\": \"Warning! Motion detected in the backyard garden.\", \"bell\": true, \"volume\": \"+30%\", \"rate\": \"+10%\"}",
    "payloadType": "json",
    "x": 210,
    "y": 180,
    "wires": [["test_tts_node"]]
  },
  {
    "id": "inject_tts_test_night_vol",
    "type": "inject",
    "z": "tab_test_clean_tts",
    "name": "🌙 Set Night Volume (30%)",
    "props": [{"p": "payload"}],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "{\"volume\": 30}",
    "payloadType": "json",
    "x": 200,
    "y": 280,
    "wires": [["hw_vol_http_node"]]
  },
  {
    "id": "inject_tts_test_day_vol",
    "type": "inject",
    "z": "tab_test_clean_tts",
    "name": "☀️ Set Day Volume (100%)",
    "props": [{"p": "payload"}],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "payload": "{\"volume\": 100}",
    "payloadType": "json",
    "x": 190,
    "y": 340,
    "wires": [["hw_vol_http_node"]]
  },
  {
    "id": "test_tts_node",
    "type": "subflow:subflow_clean_tts",
    "z": "tab_test_clean_tts",
    "name": "Clean TTS Speaker",
    "env": [{"name": "DEFAULT_TEXT", "value": "Hello from Clean TTS", "type": "str"}],
    "x": 520,
    "y": 150,
    "wires": [["test_tts_debug"]]
  },
  {
    "id": "hw_vol_http_node",
    "type": "http request",
    "z": "tab_test_clean_tts",
    "name": "POST /api/ihost/volume",
    "method": "POST",
    "ret": "obj",
    "paytoqs": "ignore",
    "url": "http://127.0.0.1:8123/api/ihost/volume",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [{"keyType": "other", "keyValue": "Content-Type", "valueType": "other", "valueValue": "application/json"}],
    "x": 530,
    "y": 310,
    "wires": [["test_tts_debug"]]
  },
  {
    "id": "test_tts_debug",
    "type": "debug",
    "z": "tab_test_clean_tts",
    "name": "API Response",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "",
    "statusType": "auto",
    "x": 770,
    "y": 230,
    "wires": []
  }
]

Option 2: Manual Flow Setup

To send announcements directly with standard Node-RED nodes:

  1. Add an http request node to your flow:
    • Method: POST
    • URL: http://127.0.0.1:8123/api/tts
    • Return: a parsed JSON object
  2. Set msg.payload in a function or change node:
// Standard announcement on iHost speaker
msg.payload = {
    "text": "Motion detected in the backyard garden.",
    "bell": true,
    "volume": "+20%"
};
return msg;

For quiet night-time notifications without a chime:

msg.payload = {
    "text": "Living room lights turned off. Good night!",
    "bell": false,
    "volume": "+0%",
    "rate": "-5%"
};
return msg;

To stream audio directly to external DLNA / Sonos speakers or Home Assistant:

http://ihost.local:8123/api/tts?text=Welcome+home&lang=en&bell=true

In Home Assistant:

service: media_player.play_media
target:
  entity_id: media_player.living_room_speaker
data:
  media_content_id: "http://ihost.local:8123/api/tts?text=Front+door+opened&bell=true"
  media_content_type: "music"

🏠 Home Assistant Integration (Plug-and-Play MaryTTS)

Clean TTS emulates the native Home Assistant MaryTTS protocol out of the box. No HACS or custom integrations required!

1. Add to configuration.yaml:
tts:
  - platform: marytts
    host: "<clean-tts-host>" # IP or hostname of your Clean TTS server or Sonoff iHost
    port: 8123
    voice: "en-US-JennyNeural" # Or any supported voice (e.g. ro-RO-AlinaNeural)
    codec: "WAVE_FILE" # Or "MP3"
    cache: false # Optional: set false to prevent Home Assistant from caching dynamic speech locally

Restart Home Assistant to apply the configuration.

2. Use in Automations or Developer Tools:
action: tts.speak
target:
  entity_id: tts.marytts
data:
  media_player_entity_id: media_player.living_room_speaker
  message: "Living room temperature is 22 degrees."

Clean TTS applies your phonetic dictionary corrections, loudness normalization, and chime bell automatically!


📱 Web Dashboard Overview

Access http://ihost.local:8123 from any browser on your network:

  • 🎛️ Studio (/): Test any text, preview voices, and adjust global volume and speed sliders.
  • 📖 Phonetic Dictionary (/dict): Teach Clean TTS how to pronounce tricky local words or smart home brands with instant audio preview.
  • 📦 Backup & Restore (/backup): Download a full ZIP backup of your dictionaries and settings with 1 click.
  • 🌐 Languages (/languages): Switch between English, Romanian, French, German, Spanish, Italian, and Catalan.
  • 🤖 AI Agent Hub (/mcp): Connect AI assistants like Claude Desktop to control speech hands-free.

🤝 Community & Feedback

Built for the Sonoff iHost and home automation community to deliver clear, beautiful, and reliable smart home voice announcements.

Tag summary

Content type

Image

Digest

sha256:7072427bf

Size

219.7 MB

Last updated

10 days ago

docker pull tmalex/clean-tts