source: lliurex-tts/trunk/fuentes/simple-google-tts/simple_google_tts @ 4721

Last change on this file since 4721 was 4721, checked in by joamuran, 2 years ago

included code

  • Property svn:executable set to *
File size: 9.5 KB
Line 
1#!/bin/bash
2
3# NAME:         Simple Google TTS
4# VERSION:      0.1
5# AUTHOR:       (c) 2014 - 2016 Glutanimate <https://github.com/Glutanimate/>
6# DESCRIPTION:  Wrapper script for Michal Fapso's speak.pl Google TTS script
7# DEPENDENCIES: - wrapper: xsel libttspico0 libttspico-utils libttspico-data libnotify-bin
8#               - speak.pl: libwww-perl libwww-mechanize-perl libhtml-tree-perl sox libsox-fmt-mp3
9#
10# LICENSE:      GNU GPLv3 (http://www.gnu.de/documents/gpl-3.0.en.html)
11#
12# NOTICE:       THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
13#               EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
14#               PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
15#               IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
16#               AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
17#               PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
18#               YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
19#
20#               IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
21#               COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
22#               PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
23#               INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
24#               THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
25#               INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
26#               PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
27#               PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
28#               
29# USAGE:        simple_google_tts [-p|-g|-h] languagecode ['strings'|'file.txt']
30#               
31#               please consult the README or the help output (-h) for more information
32
33############# GLOBVAR/PREP ###############
34
35ScriptPath="$(readlink -f "$0")"
36ScriptBase="$(basename "$0")"
37ParentPath="${ScriptPath%/*}"
38speakpl="$ParentPath/speak.pl"
39
40TOP_PID="$$"
41PidFile="/tmp/${0##*/}.pid"
42
43
44############### SETTINGS #################
45
46Player="play"
47
48##############  DIALOGS  #################
49
50Usage="\
51$(basename "$0") [-p|-g|-h] languagecode ['strings'|'file.txt']
52
53    -p:   use offline TTS (pico2wave) instead of Google's TTS system
54    -g:   activate gui notifications (via notify-send)
55    -h:   display this help section
56
57    Selection of valid language codes: en, es, de...
58    Check speak.pl for a list of all valid codes
59
60    Warning: offline TTS only supports en, de, es, fr, it
61
62    If an instance of the script is already running it will be terminated.
63
64    If you don't provide an input string or input file, $(basename "$0")
65    will read from the X selection (current/last highlighted text)\
66"
67
68GuiIcon="orca"
69GuiTitle="Google TTS script"
70
71MsgErrNoSpeakpl="Error: speak.pl not found. Falling back to offline playback."
72MsgErrDeps="Error: missing dependencies. Couldn't find:"
73MsgInfoExistInstance="Aborting synthesis and playback of existing script instance"
74MsgErrNoLang="Error: No language code provided."
75MsgInfoInpXsel="Reading from X selection."
76MsgInfoInpFile="Reading from text file."
77MsgInfoInpString="Reading from string."
78MsgErrInvalidInput="Error: Invalid input (file might not be a text file)."
79MsgInfoConnOff="No internet connection."
80MsgInfoModePico="Using pico2wave for TTS synthesis."
81MsgInfoModeGoogle="Using Google for TTS synthesis."
82MsgErrInvalidLang="Error: Offline TTS via pico2wave only supports the .\
83following languages: en, de, es, fr, it."
84MsgErrInputEmpty="Error: Input empty."
85MsgInfoSynthesize="Synthesizing virtual speech."
86MsgInfoPlayback="Playing synthesized speech"
87MsgInfoSectionEmpty="Skipping empty paragraph"
88MsgInfoDone="All sections processed. Waiting for playback to finish."
89
90############## FUNCTIONS #################
91
92check_deps () {
93    for i in "$@"; do
94      type "$i" > /dev/null 2>&1
95      if [[ "$?" != "0" ]]; then
96        MissingDeps+=" $i"
97      fi
98    done
99}
100
101check_environment () {
102    if [[ ! -f "$speakpl" && "$OptOffline" != "1" ]]; then
103      notify "$MsgErrNoSpeakpl"
104      OptOffline="1"
105    fi
106    check_deps sox perl
107    if [[ -n "$MissingDeps" ]]; then
108      notify "${MsgErrDeps}${MissingDeps}"
109      exit 1
110    fi
111}
112
113check_existing_instance(){
114  ExistingPID="$(cat "$PidFile" 2> /dev/null)"
115  if [[ -n "$ExistingPID" ]]; then
116    rm "$PidFile"
117    notify "$MsgInfoExistInstance"
118    kill -s TERM "$ExistingPID"
119    wait "$ExistingPID"
120  fi
121}
122
123arg_evaluate_options(){
124    # grab options if present
125    while getopts "gph" Options; do
126      case $Options in
127        g ) OptNotify="1"
128            ;;
129        p ) OptOffline="1"
130            ;;
131        h ) echo "$Usage"
132            exit 0
133            ;;
134       \? ) echo "$Usage"
135            exit 1
136            ;;
137      esac
138    done
139}
140
141arg_check_input(){
142  if [[ $# -eq 0 ]]; then
143    echo "$MsgErrNoLang"
144    echo "$Usage"
145    exit 1
146  elif [[ $# -eq 1 ]]; then
147    echo "$MsgInfoInpXsel"
148    InputMode="xsel"
149  elif [[ $# -eq 2 ]]; then
150    if [[ -f "$2" && -n "$(file --mime-type -b "$2" | grep text)" ]]; then
151      echo "$MsgInfoInpFile"
152      InputMode="file"
153    elif [[ ! -f "$2" ]]; then
154      echo "$MsgInfoInpString"
155      InputMode="string"
156    else
157      echo "$MsgErrInvalidInput"
158      echo "$Usage"
159      exit 1
160    fi
161  fi
162  LangCode="$1"
163  Input="$2"
164}
165
166notify(){
167  echo "$1"
168  if [[ "$OptNotify" = "1" ]]; then
169    notify-send -i "$GuiIcon" "$GuiTitle" "$1"
170  fi
171}
172
173check_connectivity(){
174  if ! ping -q -w 1 -c 1 \
175    "$(ip r | grep default | cut -d ' ' -f 3)" > /dev/null; then
176    echo "$MsgInfoConnOff"
177    OptOffline="1"
178  fi
179}
180
181set_tts_mode(){
182  if [[ "$OptOffline" = "1" ]]; then
183    echo "$MsgInfoModePico"
184    tts_engine="tts_pico"
185    OutFile="out.wav"
186  else
187    echo "$MsgInfoModeGoogle"
188    tts_engine="tts_google"
189    OutFile="out.mp3"
190  fi
191}
192
193set_input_mode(){
194  if [[ "$InputMode" = "xsel" ]]; then
195    InputText="$(xsel)"
196  elif [[ "$InputMode" = "string" ]]; then
197    InputText="$Input"
198  elif [[ "$InputMode" = "file" ]]; then
199    InputText="$(cat "$Input")"
200  fi
201
202  # check if input is empty or only consists of whitespace
203  if [[ -z "${InputText//[[:space:]]/}" ]]; then
204    notify "$MsgErrInputEmpty"
205    exit 1
206  fi
207}
208
209split_into_paragraphs(){
210  # Newlines aren't reliable indicators of paragraph breaks
211  # (e.g.: PDF files where each line ends with a newline).
212  # Instead we look for lines ending with a full stop and divide
213  # our text input into sections based on that
214 
215  InputTextModded="$(echo "$InputText" | \
216    sed 's/\.$/|/g' | sed 's/^\s*$/|/g' | tr '\n' ' ' | tr '|' '\n')"
217
218  #   - first sed command: replace end-of-line full stops with '|' delimiter
219  #   - second sed command: replace empty lines with same delimiter (e.g.
220  #     to separate text headings from text)
221  #   - subsequent tr commands: remove existing newlines; replace delimiter with
222  #     newlines to prepare for readarray
223  # TODO: find a more elegant and secure way to split the text by
224  # multi-character/regex patterns
225
226  # insert trailing newline to allow for short text fragments
227  readarray TextSections < <(echo -e "$InputTextModded\n")
228
229  # subtract one section because of trailing newline
230  Sections="$((${#TextSections[@]} - 1))"
231
232  # TODO: find a more elegant way to handle short inputs
233}
234
235pico_synth(){
236  pico2wave --wave="$OutFile" --lang="$LangCode" "$1"
237}
238
239speakpl_synth(){
240  "$speakpl" "$LangCode" <(echo "$1") "$OutFile" > /dev/null 2>&1
241}
242
243tts_google(){
244  split_into_paragraphs
245  for i in "${!TextSections[@]}"; do
246    if [[ "$i" = "$Sections" ]]; then
247      echo "$MsgInfoDone"
248      [[ -n "$PlayerPID" ]] && wait "$PlayerPID"
249      break
250    else
251      echo "Processing $((i+1)) out of $Sections paragraphs"
252    fi
253    OutFile="out_$i.mp3"
254    SectionText="${TextSections[$i]}"
255    if [[ -n "${SectionText//[[:space:]]/}" ]]; then
256      speakpl_synth "${TextSections[$i]}"
257      [[ -n "$PlayerPID" ]] && wait "$PlayerPID"
258      [[ -f "out_$((i-1)).mp3" ]] && rm "out_$((i-1)).mp3"
259      echo "$MsgInfoPlayback $((i+1))"
260      $Player "$OutFile" > /dev/null 2>&1 &
261      PlayerPID="$!"
262    else
263      echo "$MsgInfoSectionEmpty"
264      continue
265    fi
266  done
267}
268
269tts_pico(){
270  if [[ "$LangCode" = "en" ]]; then
271    LangCode="en-GB"
272  elif [[ "$LangCode" = "de" ]]; then
273    LangCode="de-DE"
274  elif [[ "$LangCode" = "es" ]]; then
275    LangCode="es-ES"
276  elif [[ "$LangCode" = "fr" ]]; then
277    LangCode="fr-FR"
278  elif [[ "$LangCode" = "it" ]]; then
279    LangCode="it-IT"
280  else
281    echo "$MsgErrInvalidLang"
282    exit 1
283  fi
284  OutFile="out.wav"
285  # pico2wave handles long text inputs and
286  # fixed formatting line-breaks well enough on its own.
287  # no need to use split_into_paragraphs()
288  pico_synth "$InputText"
289  echo "$MsgInfoPlayback"
290  $Player "$OutFile" > /dev/null 2>&1
291}
292
293cleanup(){
294  pkill -P "$TOP_PID"
295  [[ -n "$TmpDir" && -d "$TmpDir" ]] && rm -r "$TmpDir"
296  [[ -n "$PidFile" && -f "$PidFile" ]] && rm "$PidFile"
297}
298
299############# INSTANCECHECK ##############
300
301check_existing_instance
302
303############## USGCHECKS #################
304
305arg_evaluate_options "$@"
306shift $((OPTIND-1))
307check_environment
308arg_check_input "$@"
309check_connectivity
310
311############### PREPWORK ##################
312
313echo "$TOP_PID" > "$PidFile"
314
315TmpDir="$(mktemp -d "/tmp/${0##*/}.XXXXXX")"
316cd "$TmpDir"
317
318trap "cleanup; exit" EXIT
319
320################ MAIN ####################
321
322set_tts_mode
323set_input_mode
324notify "$MsgInfoSynthesize"
325"$tts_engine"
Note: See TracBrowser for help on using the repository browser.