#!/bin/bash # youread # read clipboard with flite or gtts, assuming input is text with some punctuations. # needs: flite or gtts, xclip, mpv # to install google text to speech # pip install gTTS # Usage # youread # will read whatever is in clipboard # youread path/to/text.txt # will read the text file # flite or google or ibm (you will need your own api key) #engine="google" ; speed="1.2" #engine="flite" ; speed="1" engine="ibm" ; speed="1" #engine="" # google tld voices https://gtts.readthedocs.io/en/latest/module.html tld=(co.uk com co.in ie) # disabled ca co.za com.au # ibm voices ibmvoice=(en-GB_CharlotteV3Voice en-GB_JamesV3Voice en-GB_KateVoice en-GB_KateV3Voice en-US_AllisonVoice en-US_AllisonV3Voice en-US_EmilyV3Voice en-US_HenryV3Voice en-US_KevinV3Voice en-US_LisaVoice en-US_LisaV3Voice en-US_MichaelVoice en-US_MichaelV3Voice en-US_OliviaV3Voice) # play play (){ mpv --no-resume-playback --msg-level=all=no --no-video --speed="$speed" "$1" } # echo line echoline (){ echo (( all > 1 )) && echo "($part/$all)" echo "$line" | fmt -w 80 } # tmp dir tmp="/tmp/$RANDOM-$$" trap '[ -n "$tmp" ] && rm -fr "$tmp"' EXIT mkdir -m 700 "$tmp" || { echo '!! unable to create a tmp dir' >&2; tmp=; exit 1; } # read clipboard or file (assuming text) if [ "$#" -eq "0" ] then # store clipboard to tmp (assuming text) xclip -selection clipboard -o > "$tmp/some.txt" 2>/dev/null || powershell.exe Get-clipboard > "$tmp/some.txt" || exit 1 else # file cp "$1" "$tmp/some.txt" fi # reflow so each line is sentence (assuming text with some punctuations) cat "$tmp/some.txt" | tr '\r\n' ' ' | sed 's/[.!?] */&\n/g' | sed 's/ */ /g' > "$tmp/some2.txt" printf "\n\n\n\n" >> "$tmp/some2.txt" # new lines # measure to get # of parts all="0" while read -r line1; read -r line2; read -r line3; read -r line4; read -r line5; do ((all=all+1)) #echo "$all" done < "$tmp/some2.txt" # loop over lines and read each part="0" while read -r line1; read -r line2; read -r line3; read -r line4; read -r line5; do line="$line1 $line2 $line3 $line4 $line5" # Let's throw 5 lines at once, so that the engine tokenizer has a chance to be smart. ((part=part+1)) if [ "$engine" == "flite" ]; then echoline flite -voice slt -t "$line" -o "$tmp/some.wav" play "$tmp/some.wav" 2>/dev/null elif [ "$engine" == "google" ]; then echoline rand=$[$RANDOM % ${#tld[@]}] echo "(tld ${tld[$rand]})" gtts-cli "$(echo "$line")" -t ${tld[$rand]} | play - 2>/dev/null elif [ "$engine" == "ibm" ]; then scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" source "$scriptdir/ibmapi.txt" || exit 1 # ibmapi.txt should look like # apikey="{key}" # ibmurl="{url}" # line to json jq -c -n --arg input "${line}" '{ text: $input }' > "$tmp/ibm.json" # ibm action rand=$[$RANDOM % ${#ibmvoice[@]}] if (( all > 3 )); then # preload curl -X POST -u "apikey:${apikey}" --header "Content-Type: application/json" --header "Accept: audio/flac" --data-binary "@$tmp/ibm.json" "${ibmurl}/v1/synthesize?voice=${ibmvoice[$rand]}" --output "$tmp/$part" 2>/dev/null wait # after the download the previous playback is probably still running, so wait here mv "$tmp/$part" "$tmp/read" echoline echo "(voice ${ibmvoice[$rand]}) *" # the 'previous' playback play "$tmp/read" 2>/dev/null & # go to next while loop downloading next synth else # just pipe play echoline echo "(voice ${ibmvoice[$rand]})" curl -X POST -u "apikey:${apikey}" --header "Content-Type: application/json" --header "Accept: audio/flac" --data-binary "@$tmp/ibm.json" "${ibmurl}/v1/synthesize?voice=${ibmvoice[$rand]}" 2>/dev/null | play - 2>/dev/null fi else # no engine test echoline echo "(test)" fi done < "$tmp/some2.txt" wait # just in case?