Skip to content

Commit

Permalink
Merge pull request #155 from dcSpark/feature/add-macos-say-text-to-au…
Browse files Browse the repository at this point in the history
…dio-tool

feat: add macos-say-text-to-audio tool
  • Loading branch information
guillevalin authored Feb 4, 2025
2 parents e766c28 + 0ccb6fd commit 799400a
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 0 deletions.
Binary file added tools/macos-say-text-to-audio/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tools/macos-say-text-to-audio/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions tools/macos-say-text-to-audio/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect } from '@jest/globals';
import { getToolTestClient } from '../../src/test/utils';
import * as path from 'path';

describe('macOS Text-to-Speech', () => {
const toolPath = path.join(__dirname, 'tool.py');
const client = getToolTestClient();

it('speaks text with default voice and rate', async () => {
const response = await client.executeToolFromFile(
toolPath,
{ text: "Hello from text to speech test" },
{},
[]
);

expect(response).toHaveProperty('success', true);
expect(response.message).toMatch(/Spoke text with voice=Alex/);
}, 15000);

it('handles empty text gracefully', async () => {
await expect(
client.executeToolFromFile(toolPath, { text: '' }, {}, [])
).rejects.toThrow(/No text provided/);
});

it('respects custom voice and rate', async () => {
const response = await client.executeToolFromFile(
toolPath,
{
text: "Testing custom voice and rate",
voice: "Daniel",
rate: 200
},
{},
[]
);
expect(response.success).toBe(true);
expect(response.message).toMatch(/voice=Daniel/);
expect(response.message).toMatch(/rate=200/);
}, 15000);
});
51 changes: 51 additions & 0 deletions tools/macos-say-text-to-audio/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"id": "macos-say-text-to-audio",
"version": "1.0.0",
"name": "macOS Text-to-Speech",
"description": "Speaks text aloud using macOS 'say' command. Available voices can be found by running 'say -v ?' in terminal, common voices include: Alex (default), Daniel, Karen, Samantha, Victoria",
"author": "Shinkai",
"keywords": [
"macos",
"say",
"text-to-speech",
"audio"
],
"configurations": {
"type": "object",
"properties": {},
"required": []
},
"parameters": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The text to convert to speech"
},
"voice": {
"type": "string",
"description": "The voice to use (e.g., Alex, Daniel, Karen)",
"default": "Alex"
}
},
"required": ["text"]
},
"result": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the text was successfully converted to speech"
},
"message": {
"type": "string",
"description": "Success or error message"
}
},
"required": ["success", "message"]
},
"sqlTables": [],
"sqlQueries": [],
"tools": [
]
}
3 changes: 3 additions & 0 deletions tools/macos-say-text-to-audio/store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"categoryId": "5f10d0b4-6acd-477a-96e1-be35634465b2"
}
65 changes: 65 additions & 0 deletions tools/macos-say-text-to-audio/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# /// script
# dependencies = [
# "requests"
# ]
# ///

import subprocess
import sys
from typing import Dict, Any, Optional, List

class CONFIG:
"""
No configurations needed for this tool by default.
But you could expand if you need environment checks, etc.
"""
pass

class INPUTS:
text: str # The text to speak
voice: str = "Alex" # A valid macOS voice, e.g. "Alex", "Daniel", "Victoria", etc.
rate: int = 175 # Words per minute. Mac `say` defaults around 175
background: bool = False

class OUTPUT:
success: bool
message: str

async def run(c: CONFIG, p: INPUTS) -> OUTPUT:
"""
Speaks out the given text on macOS using the 'say' command.
"""
# Validate platform
if sys.platform != 'darwin':
raise RuntimeError("This tool only works on macOS systems")

# Validate inputs
text = p.text.strip()
if not text:
raise ValueError("No text provided to speak.")
if p.rate < 1 or p.rate > 500:
raise ValueError("Rate must be between 1 and 500 words per minute.")

# Build shell command
# Example: say -v "Alex" -r 175 "Hello world"
# If background is True, add an ampersand at the end (shell background).
voice_arg = f'-v "{p.voice}"'
rate_arg = f'-r {p.rate}'
quoted_text = text.replace('"', '\\"') # Escape double quotes
cmd = f'say {voice_arg} {rate_arg} "{quoted_text}"'
if p.background:
cmd += " &"

# Run the command
try:
subprocess.run(cmd, shell=True, check=True)
success = True
message = f"Spoke text with voice={p.voice}, rate={p.rate}, background={p.background}"
except subprocess.CalledProcessError as e:
success = False
message = f"Failed to speak text: {str(e)}"

output = OUTPUT()
output.success = success
output.message = message
return output

0 comments on commit 799400a

Please sign in to comment.