Skip to content

Instantly share code, notes, and snippets.

@loretoparisi
Created February 26, 2025 21:00
Show Gist options
  • Save loretoparisi/e01f09a9b4de27e262bedc059012bf5c to your computer and use it in GitHub Desktop.
Save loretoparisi/e01f09a9b4de27e262bedc059012bf5c to your computer and use it in GitHub Desktop.
saymp3: TSS to mp3
#!/bin/bash
# Function to display usage instructions
usage() {
echo "Usage: $0 [options] [\"Text to speak\"]"
echo ""
echo "Options:"
echo " -o, --output FILE Output file name (default: output.mp3)"
echo " -l, --language CODE Language code (en, es, fr, de, it, ja, zh, etc.)"
echo " -h, --help Display this help message"
echo ""
echo "Examples:"
echo " $0 \"Hello World\" # Basic usage"
echo " $0 \"Hola Mundo\" -o greeting.mp3 -l es # Spanish with custom filename"
echo " echo \"Hello\" | $0 # Pipe input"
echo " cat file.txt | $0 -l fr # Pipe from file with French language"
echo " $0 -l list # List all available voices"
exit 1
}
# Default values
OUTPUT_FILE="output.mp3"
LANGUAGE=""
VOICE=""
TEXT=""
# Check if we have stdin input (piped)
if [ -p /dev/stdin ]; then
# Read from stdin
TEXT=$(cat)
PIPE_MODE=true
else
PIPE_MODE=false
fi
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-l|--language)
LANGUAGE="$2"
shift 2
;;
-h|--help)
usage
;;
*)
# If we didn't get text from pipe, use this argument
if [ "$PIPE_MODE" = false ]; then
TEXT="$1"
fi
shift
;;
esac
done
# Check if we have text to speak
if [ -z "$TEXT" ]; then
echo "Error: No text provided to speak. Use piped input or provide text as an argument."
usage
fi
# Select voice based on language code
if [ -n "$LANGUAGE" ]; then
case "$LANGUAGE" in
"en") VOICE="Alex" ;; # English
"es") VOICE="Juan" ;; # Spanish
"fr") VOICE="Thomas" ;; # French
"de") VOICE="Anna" ;; # German
"it") VOICE="Luca" ;; # Italian
"ja") VOICE="Kyoko" ;; # Japanese
"zh") VOICE="Tingting" ;; # Chinese
"pt") VOICE="Joana" ;; # Portuguese
"ru") VOICE="Milena" ;; # Russian
"ar") VOICE="Maged" ;; # Arabic
"nl") VOICE="Claire" ;; # Dutch
"sv") VOICE="Alva" ;; # Swedish
"da") VOICE="Magnus" ;; # Danish
"fi") VOICE="Satu" ;; # Finnish
"no") VOICE="Nora" ;; # Norwegian
"ko") VOICE="Yuna" ;; # Korean
"pl") VOICE="Zosia" ;; # Polish
"cs") VOICE="Zuzana" ;; # Czech
"el") VOICE="Melina" ;; # Greek
"tr") VOICE="Yelda" ;; # Turkish
"hu") VOICE="Mariska" ;; # Hungarian
"list")
echo "Available voices on your system:"
say -v "?"
exit 0
;;
*) echo "Warning: Unknown language code '$LANGUAGE'. Using system default voice."
VOICE="" ;;
esac
if [ -n "$VOICE" ]; then
VOICE_PARAM="-v $VOICE"
echo "Using voice: $VOICE for language: $LANGUAGE"
fi
fi
# Remove .mp3 extension if present to avoid issues
BASE_NAME="${OUTPUT_FILE%.mp3}"
TEMP_AIFF="${BASE_NAME}_temp.aiff"
# Check if ffmpeg is installed
if ! command -v ffmpeg &> /dev/null; then
echo "Error: ffmpeg is not installed. Please install it first."
echo "You can install it with: brew install ffmpeg"
exit 1
fi
# Use the say command to save the text as temporary aiff audio
if [ -n "$VOICE_PARAM" ]; then
say $VOICE_PARAM -o "$TEMP_AIFF" "$TEXT"
else
say -o "$TEMP_AIFF" "$TEXT"
fi
# Check if the say command was successful
if [ $? -ne 0 ]; then
echo "Error: Failed to create audio file with say command"
exit 1
fi
# Convert the aiff to mp3 using ffmpeg
ffmpeg -i "$TEMP_AIFF" -y -acodec libmp3lame -ab 128k "${BASE_NAME}.mp3" -loglevel error
# Check if the conversion was successful
if [ $? -eq 0 ]; then
# Remove the temporary aiff file
rm "$TEMP_AIFF"
echo "Audio successfully saved to: ${BASE_NAME}.mp3"
else
echo "Error: Failed to convert to mp3"
# Keep the aiff file in case user wants it
echo "Temporary AIFF file is available at: $TEMP_AIFF"
exit 1
fi
@loretoparisi
Copy link
Author

Basic usage

./sayit.sh "Hello World"

Pipe input

echo "Ciao" | ./sayit.sh

Pipe input with language specification

echo "Hola mundo" | ./sayit.sh --language es

Pipe from a file with custom output name

cat message.txt | ./sayit.sh --output message.mp3

List available voices

./sayit.sh --language list

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment