Creating an HTML Application to Convert Text Files to Audio Files

I'd like to take a brief departure from my normal collage of web-related and server-management examples and share a rather eclectic code sample.

Here's the scenario: I am presently working on another college degree, and I was recently taking a class which required a great deal of reading.  These assignments were all in digital form: I was using PDF or Kindle-based versions of the textbooks, and the remaining reading consisted of online articles. However, I am also an avid bicyclist, and the voluminous amount of reading was preventing me from going on some of my normal weekly rides. This gave me an idea: if I could convert the digital text to audio, I could bring my assignments with me on my longer rides and have my MP3 player read my assignments to me as I pedaled my way around the Arizona deserts.

I had experimented with Microsoft's built-in text-to-speech features some years ago, so I thought that this would be the perfect opportunity to revisit some of those APIs and write a full application to do the conversions for me. That being said, I could have written this application by using C# (which is my preferred language), but I decided to create an HTML Application for two reasons: 1) it is easier for me to share HTMLA applications in a blog, and 2) I keep this application in my OneDrive, so it's easier for me to modify the source code on systems where I don't have Visual Studio installed.

With that in mind, here is the resulting script which will convert a Text File to a Wave (*.WAV) File.

Using the HTML Application

As with most of my HTML Applications, the user interface is pretty simple to use; when you double click the HTA file, it will present you with the following user interface:

Clicking the Browse button will obviously allow you to browse to a text file, clicking the Close button will close the application, and clicking the Write File button will create a Wave file that is in the same path as the source text file. For example, if you have a text file at "C:\Text\Test.txt", the script will create a Wave file at "C:\Text\Test.txt.wav".

That being said, there are a few options which you can set:

  • Depending on your version of Windows and which languages you have installed, you will be presented with a list of available voices in the first drop-down menu; the following example is from a Windows 8 computer:
  • The second drop-down menu allows you to vary the playback speed; sometimes I prefer the playback speed to be slighter faster than normal:
  • The third drop-down menu allows you to alter the volume of the resulting Wave file:

Note: You can modify the script to alter the values that are used in the playback speed and volume drop-down menus, but the list of voices is obtained dynamically from your operating system.

Creating the HTML Application

To create this HTML Application, save the following HTMLA code as "Text to Wave File.hta" to your computer, and then double-click its icon to run the application. (Note that in a few places I added code comments which contain the MSDN URL for the APIs that I am using for this sample.)

<html>
<head>
<title>Text-to-Speech Writer</title>
<HTA:APPLICATION
  APPLICATIONNAME="Text-to-Speech Writer"
  ID="TextToSpeech"
  VERSION="1.0"
  BORDER="dialog"
  BORDERSTYLE="static"
  INNERBORDER="no"
  CAPTION="yes"
  SYSMENU="no"
  MAXIMIZEBUTTON="no"
  MINIMIZEBUTTON="no"
  SCROLL="no"
  SCROLLFLAT="yes"
  SINGLEINSTANCE="yes"
  CONTEXTMENU="no"
  SELECTION="no"/>
</head>

<script language="VBScript">

Option Explicit

Dim blnCancelBubble

' ----------------------------------------
' 
' OnLoad event handler for the application.
' 
' ----------------------------------------

Sub Window_OnLoad
  blnCancelBubble = False
  ' Set up the UI dimensions.
  Const intDialogWidth = 550
  Const intDialogHeight = 125
  ' Specify the window position and size.
  Self.resizeTo intDialogWidth,intDialogHeight
  Self.moveTo (Screen.AvailWidth - intDialogWidth) / 2,_
    (Screen.AvailHeight - intDialogHeight) / 2
  ' Load the list of text-to-speech voices into the drop-down menu.
  ' See the notes in WriteFile() for more information.
  Dim objSAPI, objVoice, objSelect, objOption
  Set objSAPI = CreateObject("SAPI.SpVoice")
  For Each objVoice In objSAPI.GetVoices("","")
    Set objSelect = Document.getElementById("optVoices")
    Set objOption = Document.createElement("option")
    objOption.text = objVoice.GetDescription() 
    objSelect.Add objOption
  Next
End Sub

' ----------------------------------------
' 
' Click handler for the Write button.
' 
' ----------------------------------------

Sub btnWrite_OnClick()
  ' Test for a file name.
  If Len(txtFile.Value) > 0 Then
    ' Test if we need to cancel bubbling of events.
    If blnCancelBubble = False Then
        ' Write the input file.
        Call WriteFile(txtFile.Value)
      End If
    End If
    ' Specify whether to bubble events.
    blnCancelBubble = IIf(blnCancelBubble=True,False,True)
End Sub

' ----------------------------------------
' 
' Change handler for the input box.
' 
' ----------------------------------------

Sub txtFile_OnChange()
  ' Enable the Write button.
  btnWrite.Disabled = False
  ' Enable event bubbling.
  blnCancelBubble = False
End Sub

' ----------------------------------------
' 
' Click handler for the Close button.
' 
' ----------------------------------------

Sub btnClose_OnClick()
  ' Test if we need to cancel bubbling of events.
  If blnCancelBubble = False Then
    ' Prompt the user to exit.
    If MsgBox("Are you sure you wish to exit?", _
      vbYesNo+vbDefaultButton2+vbQuestion+vbSystemModal, _
      TextToSpeech.applicationName)=vbYes Then
      ' Enable event bubbling.
      blnCancelBubble = True
      ' Close the application.
      Window.close
    End If
  End If
  ' Specify whether to bubble events.
     blnCancelBubble = IIf(blnCancelBubble=True,False,True)
End Sub

' ----------------------------------------
' 
' This is an ultra-lame workaround for the lack
' of a DoEvents() feature in HTA applications.
' 
' ----------------------------------------

Sub DoEvents()
  On Error Resume Next
  ' Create a shell object.
  Dim objShell : Set objShell = CreateObject("Wscript.Shell")
  ' Call out to the shell and essentially do nothing.
  objShell.Run "ver", 0, True
End Sub

' ----------------------------------------
' 
' This is an ultra-lame workaround for the lack
' of an IIf() function in vbscript applications.
' 
' ----------------------------------------

Function IIf(tx,ty,tz)
  If (tx) Then IIf = ty Else IIf = tz
End Function

' ----------------------------------------
' 
' Main text-to-speech function
' 
' ----------------------------------------

Sub WriteFile(strInputFileName)
  On Error Resume Next
  Dim objFSO
  Dim objFile
  Dim objSAPI
  Dim objFileStream
  Dim strOldTitle
  Dim strOutputFilename
  Const strProcessing = "Creating WAV file... "
  
  ' Define the audio format as 44.1kHz / 16-bit audio.
  ' See http://msdn.microsoft.com/en-us/library/ms720595.aspx
  Const SAFT44kHz16BitStereo = 35
  ' Allow text to be read as well as written.
  ' See http://msdn.microsoft.com/en-us/library/ms720858.aspx
  Const SSFMCreateForWrite = 3
  
  ' Define the output WAV filename.
  strOutputFilename = strInputFileName & ".wav"

  ' Create a file system object and open the input file.
  Set objFSO = CreateObject("Scripting.FileSystemObject")
  Set objFile = objFSO.OpenTextFile(strInputFileName, 1)
  
  ' Disable the form fields.
  optVoices.Disabled = True
  optRate.Disabled = True
  optVolume.Disabled = True
  txtFile.Disabled = True
  btnWrite.Disabled = True
  btnClose.Value = "Cancel"

  ' Test for an error.
  If Err.Number <> 0 Then
      MsgBox "Error: " & Err.Number & vbCrLf & Err.Description
  Else
    ' Store the original dialog title.
    strOldTitle = Document.title
    ' Display a status message.
    Document.title = strProcessing & Time()
    ' Pause briefly to let the screen refresh and capture events.
    Call DoEvents()
    ' Create a text-to-speech object.
    ' See http://msdn.microsoft.com/en-us/library/ms720149.aspx
    Set objSAPI = CreateObject("SAPI.SpVoice")
    ' Create a SAPI file stream object.
    ' See http://msdn.microsoft.com/en-us/library/ms722561.aspx
    Set objFileStream = CreateObject("SAPI.SpFileStream")
    ' Specify the stream format.
    ' See http://msdn.microsoft.com/en-us/library/ms720998.aspx
    objFileStream.Format.Type = SAFT44kHz16BitStereo
    ' Open the output file stream.
    objFileStream.Open strOutputFilename, SSFMCreateForWrite
    ' Specify the output file stream.
    ' See http://msdn.microsoft.com/en-us/library/ms723597.aspx
    Set objSAPI.AudioOutputStream = objFileStream
    
    ' Specify the speaking rate.
    ' See http://msdn.microsoft.com/en-us/library/ms723606.aspx
    objSAPI.Rate = optRate.Options(optRate.SelectedIndex).Value
    ' Specify the speaking volume.
    ' See http://msdn.microsoft.com/en-us/library/ms723615.aspx
    objSAPI.Volume = optVolume.Options(optVolume.SelectedIndex).Value
    ' Specify the voice to use.
    ' See http://msdn.microsoft.com/en-us/library/ms723601.aspx
    ' See http://msdn.microsoft.com/en-us/library/ms723614.aspx
    Set objSAPI.Voice = objSAPI.GetVoices("","").Item(optVoices.SelectedIndex)
    
    ' Loop through the lines in the input file.
    Do While Not objFile.AtEndOfStream
      ' Test if we need to cancel bubbling of events.
      If blnCancelBubble = True Then
        Exit Do
      Else
        ' Display a status message.
        Document.title = strProcessing & Time()
        ' Pause briefly to let the screen refresh and capture events.
        Call DoEvents()
        ' Speak one line from the input file.
        ' See http://msdn.microsoft.com/en-us/library/ms723609.aspx
        objSAPI.Speak objFile.ReadLine
      End If
    Loop
    ' Close the output file stream.
    objFileStream.Close
  End If

  ' Close the input file.
  objFile.Close

  ' Destroy all objects.
  Set objFileStream = Nothing
  Set objSAPI = Nothing
  Set objFile = Nothing
  Set objFSO = Nothing
  
  ' Reset the original dialog title.
  Document.title = strOldTitle

  ' Notify the user that the file has been written.
  MsgBox "Finished!", vbInformation, strOldTitle
  
  ' Re-enable the form fields.
  btnClose.Value = "Close"
  optVoices.Disabled = False
  optRate.Disabled = False
  optVolume.Disabled = False
  txtFile.Disabled = False
  btnWrite.Disabled = False

End Sub

</script>

<body bgcolor="white" id="HtmlBody">
<div id="FormControls">
  <table>
    <tr>
      <td align="left">
        <input type="file"
        style="width:250px;height:22px"
        name="txtFile"
        id="txtFile"
        onchange="txtFile_OnChange">
      </td>
      <td align="left">
        <input type="button"
        style="width:125px;height:22px"
        name="btnWrite"
        id="btnWrite"
        value="Write File"
        disabled
        onclick="btnWrite_OnClick">
      </td>
      <td align="right">
        <input type="button"
        style="width:125px;height:22px"
        name="btnClose"
        id="btnClose"
        value="Close"
        onclick="btnClose_OnClick">
      </td>
    </tr>
    <tr>
      <td align="left">
        <select name="optVoices"
          style="width:250px;height:22px">
        </select>
      </td>
      <td align="left">
        <select name="optRate"
          style="width:125px;height:22px">
          <option value="-2">Slowest</option>
          <option value="-1">Slower</option>
          <option value="0" selected>Normal Speed</option>
          <option value="1">Faster</option>
          <option value="2">Fastest</option>
        </select>
      </td>
      <td align="right">
        <select name="optVolume"
          style="width:125px;height:22px">
          <option value="25">25% Volume</option>
          <option value="50">50% Volume</option>
          <option value="75">75% Volume</option>
          <option value="100" selected>Full Volume</option>
        </select>
      </td>
    </tr>
  </table>
</div>
</body>
</html>

Note that I intentionally chose to have this HTML Application convert the text to audio one line at a time; this slows the down the conversion process, but it allows the conversion to be cancelled if necessary. (My original version of this script would convert an entire text file at one time; since there was no way to cancel the operation, the script appeared to hang when converting larger text files.)

Additional Notes

Once you have created a Wave (*.WAV) file, you can optionally convert it to an MP3 file for use in an MP3 player or mobile phone. (Most devices should playback Wave files, but MP3 files are considerably smaller and more portable.) There are a variety of Wave-to-MP3 converters out there, but I prefer to use the LAME encoder, which is an open-source code project that is available on SourceForge. Once you have the LAME encoder project compiled, (or you have located and downloaded a pre-compiled version), you can use LAME.EXE from a command prompt to convert your Wave files into MP3 files.

That being said, I prefer to automate as much as possible, so I have written a batch file which converts all of the Wave files in a directory to MP3 files and renames the source Wave files with a "*.old" filename extension:

@echo off

for /f "usebackq delims=|" %%a in (`dir /b *.wav`) do (
  for %%b in (^"%%a^") do (
    if not exist "%%~db%%~pb%%~nb.mp3" (
      lame.exe -b 128 -m j "%%a" "%%~nb.mp3"
    )
    if exist "%%~db%%~pb%%~nb.mp3" (
      move "%%a" "%%a.old"
    )
  )
)

Note that the above batch file was written for text-to-speech use, and as such it defines a bit rate of 128kbps, which would be pretty low for music files. If you want to repurpose this batch file for higher bitrates, modify the value of the "-b" parameter for the LAME.EXE command.

That wraps it up for today's post.

No Comments