Module pedtts

Expand source code
#!/usr/bin/env python

from __future__ import absolute_import
from __future__ import print_function

import os, sys, getopt, signal, subprocess

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Pango

from pedlib import  pedconfig

from pedlib.pedutil import *

# Set this to non zero if you want festival to speak
USE_FESTIVAL = 1
# Otherwise it calls espeak

# -----------------------------------------------------------------------
# Call func with all processes, func called with stat as its argument
# Function may return True to stop iteration

def withps(func, opt = None):
    ret = False
    dl = os.listdir("/proc")
    for aa in dl:
        fname = "/proc/" + aa + "/stat"
        try:
            if os.path.isfile(fname):
                fd = open(fname, "r")
                ff = fd.read().split()
                ret = func(ff, opt)
                fd.close()
            if ret:
                break
        except:
            pass
    return ret

# ------------------------------------------------------------------------
# Create it with status bar text setter

class tts():

    def __init__(self, prog = None):
        self.stopspeak = False
        self.haltspeak = False
        self.speech_pid = None
        self.prog_set_text = prog
        self.prog_set_text("Inited TTS")

    # Create a file, send it to festival
    def _speak(self, cstr):

        self.stopspeak = False
        fname = pedconfig.conf.data_dir + "/festival.txt"

        try:
            fh = open(fname, "w")
            fh.write(cstr)
        except:
            put_exception("Cannot create festival file")
            return
        finally:
            fh.close()
        try:
            if USE_FESTIVAL:
                self.speech_pid = subprocess.Popen(["festival", "--tts", fname])
            else:
                # This is more configurable, add option as it is displayed below.
                #self.speech_pid = subprocess.Popen(["espeak", "-f", fname, "-s", "100"])
                #self.speech_pid = subprocess.Popen(["espeak", "-f", fname, "-v", "english-us"])
                self.speech_pid = subprocess.Popen(["espeak-ng", "-f", fname,])
        except:
            print ("Cannot start TTS espeak-ng", sys.exc_info())
            return

        #print ("started", self.speech_pid.pid)
        GLib.timeout_add(100, self.check_speak)
        return True

    def check_speak2(self, ss, opt):
        if self.speech_pid:
            if  self.speech_pid.pid == int(ss[0]):
                # Zombie does not count:
                if ss[2] != "Z":
                    return True

    # Wait for speech to end (sync)
    def   wait_done(self):

        while True:
            if self.stopspeak:
                break
            if self.haltspeak:
                return
            if not self.speech_pid:
                break
            usleep(100)

        self.stopspeak = False

    # Timer to see if the speaker has terminated
    def check_speak(self):
        # If found, the search returns True
        ret = withps(self.check_speak2)
        if not ret:
            self.speech_pid = None
        else:
            # Look for termination again
            GLib.timeout_add(1000, self.check_speak)

    # --------------------------------------------------------------------
    # Stop tts instances, kill (all) children
    # Start speak

    def read_tts(self, self2, butt = None):

        # Running?

        if self.haltspeak and self.speech_pid:
            return

        self.haltspeak = True
        self.prog_set_text("Stopping TTS processes (please wait)")
        usleep(100)
        if self.speech_pid:
            self.stop_tts()
            return

        self.haltspeak = False
        self.stopspeak = False
        self.prog_set_text("Started Reading")

        #self._speak("Started reading"); self.wait_done()
        #print("After start")

        if self2.xsel == -1 or self2.ysel == -1:
            self2.mained.update_statusbar("Nothing selected")
            #self._speak("Nothing selected")

            # Speak from current point
            #xxx = self2.xpos + self2.caret[0]

            # Speak from current line
            xxx = 0
            yyy = self2.ypos + self2.caret[1]

            # Collect until dot - repeat
            sss = ""

            self2.xsel = xxx; self2.ysel = yyy;

            for yy in range(len(self2.text) - yyy):
                yy2 = yyy + yy
                ttt = self2.text[yy2]
                if "." in ttt:
                    idx = ttt.find(".") + 1  # point after dot
                    sss += ttt[:idx]

                    self2.xsel2 = idx; self2.ysel2 = yy2;
                    self2.gotoxy(self2.xsel2, self2.ysel2, None, True)
                    self2.invalidate()

                    #print("line %d: '%s'" % (yy, sss))
                    self.prog_set_text("Reading at line %d" % (yy2))
                    if not self._speak(sss):
                        self2.mained.update_statusbar("TTS cannot start (installed?)")
                        break

                    self.wait_done()

                    self2.xsel = idx; self2.ysel = yy2;
                    self2.invalidate()
                    sss = ttt[idx:] + " "
                else:
                    sss += ttt + " "

                if  self.haltspeak:
                    break
            return

        # Normalize
        xssel = min(self2.xsel, self2.xsel2)
        xesel = max(self2.xsel, self2.xsel2)
        yssel = min(self2.ysel, self2.ysel2)
        yesel = max(self2.ysel, self2.ysel2)

        cnt = yssel; cnt2 = 0; cumm = ""

        while True:
            if cnt > yesel: break
            #self.pad_list(self2, cnt)

            line = self2.text[cnt]
            if self2.colsel:
                frag = line[xssel:xesel]
            else :                                  # startsel - endsel
                if cnt == yssel and cnt == yesel:   # sel on the same line
                    frag = line[xssel:xesel]
                elif cnt == yssel:                  # start line
                    frag = line[xssel:]
                elif cnt == yesel:                  # end line
                    frag = line[:xesel]
                else:
                    frag = line[:]

            if cnt2: frag = "\n" + frag
            cumm += frag
            cnt += 1; cnt2 += 1

        print ("clip:", cumm)
        self._speak(cumm)

    def stop_tts2(self, ss, opt):
        #print ( "stop_tts2", ss[1])
        if USE_FESTIVAL:
            if ss[1] == "(audsp)":
                #print ("killing", ss)
                os.kill(int(ss[0]), signal.SIGKILL)
        else:
            if ss[1] == "(espeak)":
                #print ("killing", ss)
                os.kill(int(ss[0]), signal.SIGKILL)

    def stop_tts(self):
        self.stopspeak = True
        self.prog_set_text("Stopped Reading")
        try:
            withps(self.stop_tts2)
        except:
            put_exception("Cannot kill")
            #self.speech_pid.pid

        self.speech_pid = None

# EOF

Functions

def withps(func, opt=None)
Expand source code
def withps(func, opt = None):
    ret = False
    dl = os.listdir("/proc")
    for aa in dl:
        fname = "/proc/" + aa + "/stat"
        try:
            if os.path.isfile(fname):
                fd = open(fname, "r")
                ff = fd.read().split()
                ret = func(ff, opt)
                fd.close()
            if ret:
                break
        except:
            pass
    return ret

Classes

class tts (prog=None)
Expand source code
class tts():

    def __init__(self, prog = None):
        self.stopspeak = False
        self.haltspeak = False
        self.speech_pid = None
        self.prog_set_text = prog
        self.prog_set_text("Inited TTS")

    # Create a file, send it to festival
    def _speak(self, cstr):

        self.stopspeak = False
        fname = pedconfig.conf.data_dir + "/festival.txt"

        try:
            fh = open(fname, "w")
            fh.write(cstr)
        except:
            put_exception("Cannot create festival file")
            return
        finally:
            fh.close()
        try:
            if USE_FESTIVAL:
                self.speech_pid = subprocess.Popen(["festival", "--tts", fname])
            else:
                # This is more configurable, add option as it is displayed below.
                #self.speech_pid = subprocess.Popen(["espeak", "-f", fname, "-s", "100"])
                #self.speech_pid = subprocess.Popen(["espeak", "-f", fname, "-v", "english-us"])
                self.speech_pid = subprocess.Popen(["espeak-ng", "-f", fname,])
        except:
            print ("Cannot start TTS espeak-ng", sys.exc_info())
            return

        #print ("started", self.speech_pid.pid)
        GLib.timeout_add(100, self.check_speak)
        return True

    def check_speak2(self, ss, opt):
        if self.speech_pid:
            if  self.speech_pid.pid == int(ss[0]):
                # Zombie does not count:
                if ss[2] != "Z":
                    return True

    # Wait for speech to end (sync)
    def   wait_done(self):

        while True:
            if self.stopspeak:
                break
            if self.haltspeak:
                return
            if not self.speech_pid:
                break
            usleep(100)

        self.stopspeak = False

    # Timer to see if the speaker has terminated
    def check_speak(self):
        # If found, the search returns True
        ret = withps(self.check_speak2)
        if not ret:
            self.speech_pid = None
        else:
            # Look for termination again
            GLib.timeout_add(1000, self.check_speak)

    # --------------------------------------------------------------------
    # Stop tts instances, kill (all) children
    # Start speak

    def read_tts(self, self2, butt = None):

        # Running?

        if self.haltspeak and self.speech_pid:
            return

        self.haltspeak = True
        self.prog_set_text("Stopping TTS processes (please wait)")
        usleep(100)
        if self.speech_pid:
            self.stop_tts()
            return

        self.haltspeak = False
        self.stopspeak = False
        self.prog_set_text("Started Reading")

        #self._speak("Started reading"); self.wait_done()
        #print("After start")

        if self2.xsel == -1 or self2.ysel == -1:
            self2.mained.update_statusbar("Nothing selected")
            #self._speak("Nothing selected")

            # Speak from current point
            #xxx = self2.xpos + self2.caret[0]

            # Speak from current line
            xxx = 0
            yyy = self2.ypos + self2.caret[1]

            # Collect until dot - repeat
            sss = ""

            self2.xsel = xxx; self2.ysel = yyy;

            for yy in range(len(self2.text) - yyy):
                yy2 = yyy + yy
                ttt = self2.text[yy2]
                if "." in ttt:
                    idx = ttt.find(".") + 1  # point after dot
                    sss += ttt[:idx]

                    self2.xsel2 = idx; self2.ysel2 = yy2;
                    self2.gotoxy(self2.xsel2, self2.ysel2, None, True)
                    self2.invalidate()

                    #print("line %d: '%s'" % (yy, sss))
                    self.prog_set_text("Reading at line %d" % (yy2))
                    if not self._speak(sss):
                        self2.mained.update_statusbar("TTS cannot start (installed?)")
                        break

                    self.wait_done()

                    self2.xsel = idx; self2.ysel = yy2;
                    self2.invalidate()
                    sss = ttt[idx:] + " "
                else:
                    sss += ttt + " "

                if  self.haltspeak:
                    break
            return

        # Normalize
        xssel = min(self2.xsel, self2.xsel2)
        xesel = max(self2.xsel, self2.xsel2)
        yssel = min(self2.ysel, self2.ysel2)
        yesel = max(self2.ysel, self2.ysel2)

        cnt = yssel; cnt2 = 0; cumm = ""

        while True:
            if cnt > yesel: break
            #self.pad_list(self2, cnt)

            line = self2.text[cnt]
            if self2.colsel:
                frag = line[xssel:xesel]
            else :                                  # startsel - endsel
                if cnt == yssel and cnt == yesel:   # sel on the same line
                    frag = line[xssel:xesel]
                elif cnt == yssel:                  # start line
                    frag = line[xssel:]
                elif cnt == yesel:                  # end line
                    frag = line[:xesel]
                else:
                    frag = line[:]

            if cnt2: frag = "\n" + frag
            cumm += frag
            cnt += 1; cnt2 += 1

        print ("clip:", cumm)
        self._speak(cumm)

    def stop_tts2(self, ss, opt):
        #print ( "stop_tts2", ss[1])
        if USE_FESTIVAL:
            if ss[1] == "(audsp)":
                #print ("killing", ss)
                os.kill(int(ss[0]), signal.SIGKILL)
        else:
            if ss[1] == "(espeak)":
                #print ("killing", ss)
                os.kill(int(ss[0]), signal.SIGKILL)

    def stop_tts(self):
        self.stopspeak = True
        self.prog_set_text("Stopped Reading")
        try:
            withps(self.stop_tts2)
        except:
            put_exception("Cannot kill")
            #self.speech_pid.pid

        self.speech_pid = None

Methods

def check_speak(self)
Expand source code
def check_speak(self):
    # If found, the search returns True
    ret = withps(self.check_speak2)
    if not ret:
        self.speech_pid = None
    else:
        # Look for termination again
        GLib.timeout_add(1000, self.check_speak)
def check_speak2(self, ss, opt)
Expand source code
def check_speak2(self, ss, opt):
    if self.speech_pid:
        if  self.speech_pid.pid == int(ss[0]):
            # Zombie does not count:
            if ss[2] != "Z":
                return True
def read_tts(self, self2, butt=None)
Expand source code
def read_tts(self, self2, butt = None):

    # Running?

    if self.haltspeak and self.speech_pid:
        return

    self.haltspeak = True
    self.prog_set_text("Stopping TTS processes (please wait)")
    usleep(100)
    if self.speech_pid:
        self.stop_tts()
        return

    self.haltspeak = False
    self.stopspeak = False
    self.prog_set_text("Started Reading")

    #self._speak("Started reading"); self.wait_done()
    #print("After start")

    if self2.xsel == -1 or self2.ysel == -1:
        self2.mained.update_statusbar("Nothing selected")
        #self._speak("Nothing selected")

        # Speak from current point
        #xxx = self2.xpos + self2.caret[0]

        # Speak from current line
        xxx = 0
        yyy = self2.ypos + self2.caret[1]

        # Collect until dot - repeat
        sss = ""

        self2.xsel = xxx; self2.ysel = yyy;

        for yy in range(len(self2.text) - yyy):
            yy2 = yyy + yy
            ttt = self2.text[yy2]
            if "." in ttt:
                idx = ttt.find(".") + 1  # point after dot
                sss += ttt[:idx]

                self2.xsel2 = idx; self2.ysel2 = yy2;
                self2.gotoxy(self2.xsel2, self2.ysel2, None, True)
                self2.invalidate()

                #print("line %d: '%s'" % (yy, sss))
                self.prog_set_text("Reading at line %d" % (yy2))
                if not self._speak(sss):
                    self2.mained.update_statusbar("TTS cannot start (installed?)")
                    break

                self.wait_done()

                self2.xsel = idx; self2.ysel = yy2;
                self2.invalidate()
                sss = ttt[idx:] + " "
            else:
                sss += ttt + " "

            if  self.haltspeak:
                break
        return

    # Normalize
    xssel = min(self2.xsel, self2.xsel2)
    xesel = max(self2.xsel, self2.xsel2)
    yssel = min(self2.ysel, self2.ysel2)
    yesel = max(self2.ysel, self2.ysel2)

    cnt = yssel; cnt2 = 0; cumm = ""

    while True:
        if cnt > yesel: break
        #self.pad_list(self2, cnt)

        line = self2.text[cnt]
        if self2.colsel:
            frag = line[xssel:xesel]
        else :                                  # startsel - endsel
            if cnt == yssel and cnt == yesel:   # sel on the same line
                frag = line[xssel:xesel]
            elif cnt == yssel:                  # start line
                frag = line[xssel:]
            elif cnt == yesel:                  # end line
                frag = line[:xesel]
            else:
                frag = line[:]

        if cnt2: frag = "\n" + frag
        cumm += frag
        cnt += 1; cnt2 += 1

    print ("clip:", cumm)
    self._speak(cumm)
def stop_tts(self)
Expand source code
def stop_tts(self):
    self.stopspeak = True
    self.prog_set_text("Stopped Reading")
    try:
        withps(self.stop_tts2)
    except:
        put_exception("Cannot kill")
        #self.speech_pid.pid

    self.speech_pid = None
def stop_tts2(self, ss, opt)
Expand source code
def stop_tts2(self, ss, opt):
    #print ( "stop_tts2", ss[1])
    if USE_FESTIVAL:
        if ss[1] == "(audsp)":
            #print ("killing", ss)
            os.kill(int(ss[0]), signal.SIGKILL)
    else:
        if ss[1] == "(espeak)":
            #print ("killing", ss)
            os.kill(int(ss[0]), signal.SIGKILL)
def wait_done(self)
Expand source code
def   wait_done(self):

    while True:
        if self.stopspeak:
            break
        if self.haltspeak:
            return
        if not self.speech_pid:
            break
        usleep(100)

    self.stopspeak = False