A Mac OS X text-to-speech web service (Play Framework, Scala)

I’ve written a variety of small Scala apps that take advantage of the “text-to-speech” capabilities on Mac OS X (Sarah, Wikipedia Page Reader), and a few days ago I started thinking about consolidating what I was doing by creating a Mac “text-to-speech service” that all of these apps could use.

I initially created that service as an Akka actors-based server (here on Github), then thought to make it a little more generic as a REST web service. So I whipped up a quick web service with the Play Framework (in Scala).

Right now this is just a little starter project, but I thought I’d share it here for anyone who might be interested in it.

Video

Sorry, there is no video; it would be pretty boring. All you have to do is POST some data to the web service and your Mac will speak whatever text you post. You’re going to have to trust me on that for now.

Testing the web service (sample client)

Testing the web service is easy. Just start the Play server from the root directory of the project like this:

$ play

[MacSpeechService] $ run 8080

Note: I created this project using the Play Framework v2.2.2.

Once the web service is started, the postData.sh shell script I included in the root directory of the project shows how you can send text to the service using a curl command:

curl \
    --request POST \
    --data "text=hello%2C%20world&voice=Samantha" \
    http://localhost:8080/speak

When you run this script you should hear your Mac say “hello, world” using the “Samantha” voice, which is similar to what Siri sounds like.

Possible uses

Of course the main use case for this service is when you want to have your Mac “speak” something to you. So you can write code in any programming language that can hit a web service do things like this:

  • Tell you the time (“The current time is 10 o’clock a.m.”)
  • Tell you when you have new mail
  • Have IoT devices speak messages to you (“You’re out of beer,” “The dryer just lost a stocking,” “The Roomba just locked itself in the bathroom again,” etc.)
  • More ...

Possible problems

One problem with the current web service approach is that there’s no way for your clients to know when the computer is finished speaking its text. For simple things like an hourly notification this isn’t a big deal, but this web service isn’t currently suitable for larger efforts.

I use Akka actors for my interactive text-to-speech apps so I can get a message back when the speaking is finished. I suppose providing a callback URL is the rough equivalent in the REST world.

The Play Framework code

Because of everything it can do, the Play Framework feels like a little overkill for this, but it’s easy to write a web service with, and it’s also very easy to deploy a Play app, which is important to me.

The relevant Scala/Play code is shown next. First, here’s the route in the conf/routes file:

POST  /speak   controllers.SpeechService.speak

If you’re familiar with the Play Framework, you know that this maps the /speak URI to the speak method in the SpeechService class.

Here’s the controller code, which has the mapping to handle the POST data, a form derived from the mapping, the model it uses, and the controller’s speak method:

package controllers

import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import models._
import utils._

case class TextToSpeak(text: String, voice: Option[String] = Some("Alex"))

object SpeechService extends Controller {
  
    // a mapping for the post data i expect
    val formMapping = mapping(
        "text"  -> nonEmptyText,
        "voice" -> optional(text)
    )(TextToSpeak.apply)(TextToSpeak.unapply)

    // turn the mapping into a form
    val form: Form[TextToSpeak] = Form(formMapping)

    // the method/action the user sends the data to
    def speak = Action { implicit request => 
        form.bindFromRequest.fold(
            errors => {
                // the form did not bind to the post data
                BadRequest("That didn't work")
            },
            postData => {
                // the form data bound, process it
                SpeechServiceUtils.runInThread(speakText(postData.text, postData.voice))
                Ok("Thanks")
            }
        ) 
    }

    private def speakText(sentence: String, voice: Option[String]) {
        voice match {
            case None    => SpeechServiceUtils.speak(sentence, Resources.ALEX_VOICE)
            case Some(v) => SpeechServiceUtils.speak(sentence, v)
        }
    }

}

I kept the Application controller in the project (including the routes file) so you can easily verify that your project is running properly, but of course you can delete that.

The META-INF folder

One quick note about the code: You’ll find a META-INF directory under the Play conf directory. The configuration file under the META-INF directory is needed to run AppleScript scripts from the JVM.

Deploying the service

You can easily deploy this app using the =play dist= command. I already wrote about how to deploy Play Framework applications, so I won’t go into detail here. Please see that tutorial for more information.

Source code

The full source code for this little project is available on Github at the following URL:

It’s a simple Play Framework project with only one important controller class. The “text to speech” capability comes from (a) the Mac’s built-in text-to-speech capability, and (b) my AppleScript utilities library, which lets you run the AppleScript say command to make your Mac “talk.”

Another important piece is in the contents of the META-INF folder. The configuration file under that directory is needed to let you run AppleScript from the JVM.

This project depends on my AppleScriptUtils project, which is included as a Jar file in the lib directory.

Related

I’m currently working on an Akka text-to-speech server (rather than service) for Mac OS X systems. You can find that code on Github here:

If you’re interested in other text-to-speech applications, I’ve previously spent a little time developing these apps:

As mentioned, I also wrote about how to deploy Play Framework apps here:

This project depends on my AppleScript Utilities project, which is here on Github:

Notes

I haven’t tested my AppleScript utilities library too much yet, so it may be (easily) possible to send some text to the REST server that causes problems.

To-Do List

Right now this project just supports the speaking of text with a given voice. You can do other things with the Mac text-to-speech service, including controlling the speed at which words are spoken, and control the volume, among other things. I’ll add features like that whenever I have some more free time.

Summary

If you are interested in a Mac OS X “text to speech” web service (or Akka actors server), I hope this example is helpful.