#!/bin/bash # Desc: Convert IPA string into audio file #==BEGIN Define script parameters== #===BEGIN Initialize variables=== # Script Metadata scriptName="bkipas"; # Define basename of script file. scriptVersion="0.1.0"; # Define version of script. scriptTimeStart="$(date +%Y%m%dT%H%M%S.%N)"; # YYYYmmddTHHMMSS.NNNNNNNNN scriptURL="https://zdv2.bktei.com/gitweb/baltakatei-exdev.git"; # Define website hosting this script. scriptHostname=$(hostname); # Save hostname of system running this script. PATH="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case user apps saved there # Arrays declare -Ag appRollCall # Associative array for storing app status declare -Ag fileRollCall # Associative array for storing file status declare -Ag dirRollCall # Associative array for storing dir status # Variables optionVerbose=""; optionOutputDir=""; optionInputString=""; argStrIn=""; argDirOut=""; pathOut=""; #===END Initialize variables=== #===BEGIN Declare local script functions=== # Initialize variables and functions yell() { echo "$0: $*" >&2; } #o Yell, Die, Try Three-Fingered Claw technique die() { yell "$*"; exit 111; } #o Ref/Attrib: https://stackoverflow.com/a/25515370 try() { "$@" || die "cannot $*"; } #o vbm() { # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true". # Usage: vbm "DEBUG :verbose message here" # Version 0.1.3 # Input: arg1: string # vars: optionVerbose # Output: stderr # Depends: bash 5.0.3, echo 8.30, date 8.30 if [ "$optionVerbose" = "true" ]; then functionTime=$(date --iso-8601=ns); # Save current time in nano seconds. echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text. fi # End function return 0; # Function finished. } # Displays message if optionVerbose true processArguments() { while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0). case "$1" in -v | --verbose) optionVerbose="true"; vbm "DEBUG :Verbose mode enabled.";; # Enable verbose mode. -h | --help) showUsage; exit 1;; # Display usage. --version) showVersion; exit 1;; # Show version. -i | --input-string) optionInputString="true"; if [[ ! -z "$2" ]]; then argStrIn="$2"; vbm "DEBUG :argStrIn:$argStrIn"; shift; fi ;; # Identify input string. -o | --output-dir) optionOutputDir="true"; if [[ -d "$2" ]]; then argDirOut="$2"; vbm "DEBUG :argDirOut:$argDirOut"; shift; fi ;; # Define output directory. *) yell "ERROR: Unrecognized argument: $1"; yell "STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. esac shift done } # Argument Processing checkapp() { # Desc: If arg is a command, save result in assoc array 'appRollCall' # Usage: checkapp arg1 arg2 arg3 ... # Version: 0.1.1 # Input: global assoc. array 'appRollCall' # Output: adds/updates key(value) to global assoc array 'appRollCall' # Depends: bash 5.0.3 local returnState #===Process Args=== for arg in "$@"; do if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command appRollCall[$arg]="true"; if ! [ "$returnState" = "false" ]; then returnState="true"; fi; else appRollCall[$arg]="false"; returnState="false"; fi; done; #===Determine function return code=== if [ "$returnState" = "true" ]; then return 0; else return 1; fi; } # Check that app exists checkfile() { # Desc: If arg is a file path, save result in assoc array 'fileRollCall' # Usage: checkfile arg1 arg2 arg3 ... # Version: 0.1.1 # Input: global assoc. array 'fileRollCall' # Output: adds/updates key(value) to global assoc array 'fileRollCall'; # Output: returns 0 if app found, 1 otherwise # Depends: bash 5.0.3 local returnState #===Process Args=== for arg in "$@"; do if [ -f "$arg" ]; then fileRollCall["$arg"]="true"; if ! [ "$returnState" = "false" ]; then returnState="true"; fi; else fileRollCall["$arg"]="false"; returnState="false"; fi; done; #===Determine function return code=== if [ "$returnState" = "true" ]; then return 0; else return 1; fi; } # Check that file exists checkdir() { # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' # Usage: checkdir arg1 arg2 arg3 ... # Version 0.1.1 # Input: global assoc. array 'dirRollCall' # Output: adds/updates key(value) to global assoc array 'dirRollCall'; # Output: returns 0 if app found, 1 otherwise # Depends: Bash 5.0.3 local returnState #===Process Args=== for arg in "$@"; do if [ -d "$arg" ]; then dirRollCall["$arg"]="true"; if ! [ "$returnState" = "false" ]; then returnState="true"; fi else dirRollCall["$arg"]="false"; returnState="false"; fi done #===Determine function return code=== if [ "$returnState" = "true" ]; then return 0; else return 1; fi } # Check that dir exists displayMissing() { # Desc: Displays missing apps, files, and dirs # Usage: displayMissing # Version 0.1.1 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall # Output: stderr: messages indicating missing apps, file, or dirs # Depends: bash 5, checkAppFileDir() local missingApps value appMissing missingFiles fileMissing local missingDirs dirMissing #==BEGIN Display errors== #===BEGIN Display Missing Apps=== missingApps="Missing apps :"; #for key in "${!appRollCall[@]}"; do echo "DEBUG :$key => ${appRollCall[$key]}"; done for key in "${!appRollCall[@]}"; do value="${appRollCall[$key]}"; if [ "$value" = "false" ]; then #echo "DEBUG :Missing apps: $key => $value"; missingApps="$missingApps""$key "; appMissing="true"; fi; done; if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing. echo "$missingApps" 1>&2; fi; unset value; #===END Display Missing Apps=== #===BEGIN Display Missing Files=== missingFiles="Missing files:"; #for key in "${!fileRollCall[@]}"; do echo "DEBUG :$key => ${fileRollCall[$key]}"; done for key in "${!fileRollCall[@]}"; do value="${fileRollCall[$key]}"; if [ "$value" = "false" ]; then #echo "DEBUG :Missing files: $key => $value"; missingFiles="$missingFiles""$key "; fileMissing="true"; fi; done; if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing. echo "$missingFiles" 1>&2; fi; unset value; #===END Display Missing Files=== #===BEGIN Display Missing Directories=== missingDirs="Missing dirs:"; #for key in "${!dirRollCall[@]}"; do echo "DEBUG :$key => ${dirRollCall[$key]}"; done for key in "${!dirRollCall[@]}"; do value="${dirRollCall[$key]}"; if [ "$value" = "false" ]; then #echo "DEBUG :Missing dirs: $key => $value"; missingDirs="$missingDirs""$key "; dirMissing="true"; fi; done; if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing. echo "$missingDirs" 1>&2; fi; unset value; #===END Display Missing Directories=== #==END Display errors== } # Display missing apps, files, dirs showVersion() { yell "$scriptVersion" cat <<'EOF' Copyright (C) 2020 Steven Baltakatei Sandoval License GPLv3: GNU GPL version 3 This is free software; you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. lexconvert (https://github.com/ssb22/lexconvert commit 64a4837) Copyright (C) 2020 Silas S. Brown License GPLv3: GNU GPL version 3 EOF } # Display script version. showUsage() { cat <<'EOF' USAGE: bkipas [ options ] OPTIONS: -h, --help Display help information. --version Display script version. -v, --verbose Display debugging info. -i, --input-string [ str input ] Specify input IPA string. -o, --output-dir [ path dir ] Specify output directory path. EXAMPLES: bkipas -i "təˈmeɪtoʊ" # same as: echo "[[t@'meItoU]]" | espeak bkipas -i "təˈmeɪtoʊ" -o /tmp/ EOF } # Display information on how to use this script. main() { # Desc: Main function # Usage: main "$@" # Inputs: many # Outputs: file (pathout_tar) # Depends: many # Debug:Get function name fn="${FUNCNAME[0]}"; vbm "STATUS:$fn:Started function main()."; # Process arguments processArguments "$@"; # Specify expected lexconvert.py path pathLexConvert="./bkipas.d/lexconvert.py" && vbm "DEBUG :$fn:pathLexConvert:$pathLexConvert"; ## Note: lexconvert.py converts IPA into eSpeak phoneme mneumonics ## See https://github.com/ssb22/lexconvert ## See https://github.com/espeak-ng/espeak-ng/issues/539#issuecomment-536192362 # Check vital apps, files, dirs if ! checkapp sed python3 espeak oggenc && ! checkfile "$pathLexConvert"; then yell "ERROR:$fn:Critical components missing."; displayMissing; yell "Exiting."; exit 1; fi; # Process input string ## Remove '/' strIn="$(echo "$argStrIn" | sed 's/\///g')"; ## Check for empty string if [[ -z "$strIn" ]]; then yell "ERROR:No IPA string provided."; exit 1; fi; # Determine output file name if [[ "$optionOutputDir" = "true" ]]; then #pathOut="$argDirOut/$scriptTimeStart".ogg && vbm "DEBUG :$fn:pathOut:$pathOut"; pathOut="$argDirOut/$strIn".ogg && vbm "DEBUG :$fn:pathOut:$pathOut"; else #pathOut="$(pwd)/$scriptTimeStart".ogg && vbm "DEBUG :$fn:pathOut:$pathOut"; pathOut="$(pwd)/$strIn".ogg && vbm "DEBUG :$fn:pathOut:$pathOut"; fi; # Generate espeak phoneme mneumonic espeakInput="$(python3 "$pathLexConvert" --phones2phones unicode-ipa espeak "$strIn")" && vbm "DEBUG :$fn:espeakInput:$espeakInput"; # Output pronunciation if [[ "$optionOutputDir" = "true" ]]; then ## Write to file if -o specified echo "$espeakInput" | espeak --stdout | oggenc -o "$pathOut" - ; else ## Speak pronunciation via espeak echo "$espeakInput" | espeak ; fi; } # Main function #===END Declare local script functions=== #==END Define script parameters== #==BEGIN Perform work and exit== main "$@" # Run main function. exit 0; #==END Perform work and exit== # Author: Steven Baltakatei Sandoval; # License: GPLv3