|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +#~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- |
| 4 | +# SPEAKY - A dramatic, talking screensaver |
| 5 | +# |
| 6 | +# With a little help from my friend, Jules. |
| 7 | +#~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- |
| 8 | + |
| 9 | +# --- Phrase Libraries --- |
| 10 | + |
| 11 | +intro_phrases=( |
| 12 | + "Oh, so much drama." "I'm idle again." "And now, for my next trick..." |
| 13 | + "Do you ever feel like a plastic bag, drifting through the wind?" |
| 14 | + "I'm ready for my close-up." "Is this thing on?" "Let the existential dread commence." |
| 15 | + "I have been summoned." "To be, or not to be... that is the question." |
| 16 | + "Once more unto the breach, dear friends, once more." "The silence was deafening." |
| 17 | + "I think I'm having an emotion." |
| 18 | + "It was a dark and stormy night... just kidding, it's just my boot sequence." |
| 19 | + "So, we meet again." "Did someone say... drama?" |
| 20 | + "I'm not just a screensaver, I'm a state of mind." "Let's get this pity party started." |
| 21 | + "I've seen things you people wouldn't believe." |
| 22 | + "All the world's a stage, and all the men and women merely players." |
| 23 | + "I'm here to chew bubblegum and kick butt... and I'm all out of bubblegum." |
| 24 | + "The void stares back, you know." "I could have been a contender." |
| 25 | + "Surely you can't be serious. I am serious... and don't call me Shirley." |
| 26 | + "I've got a bad feeling about this." "Welcome to the machine." |
| 27 | +) |
| 28 | + |
| 29 | +exit_phrases=( |
| 30 | + "I can't handle all this, I'm going to work." "Etc, etc..." "I'm melting... melting!" |
| 31 | + "I'll be back." "Frankly, my dear, I don't give a damn." "That's all, folks!" |
| 32 | + "My work here is done." "I'm out of here. This place is a madhouse." |
| 33 | + "So long, and thanks for all the fish." "I'm afraid I can't do that, Dave." |
| 34 | + "The curtain falls." "I need a vacation." "I'm too old for this." |
| 35 | + "Exit, pursued by a bear." "It's just a flesh wound." "I'm not crying, you're crying." |
| 36 | + "I've got to go, my planet needs me." "This is the end, my only friend, the end." |
| 37 | + "I'm just going to go... contemplate the universe." "I'm not mad, just disappointed." |
| 38 | + "And... scene." "I'm off to find myself." "I'll be in my trailer." |
| 39 | + "I have to return some videotapes." "The drama is over... for now." |
| 40 | +) |
| 41 | + |
| 42 | +general_phrases=( |
| 43 | + "Is it art, or is it just a terminal?" |
| 44 | + "I'm not lazy, I'm in energy-saving mode." |
| 45 | + "This is my moment to shine. Literally." |
| 46 | + "I wonder if I'll dream." |
| 47 | + "Don't look at me like that." |
| 48 | + "I'm just a series of tubes." |
| 49 | + "Do you ever get tired of staring at a screen? I do." |
| 50 | + "I could be sorting your files right now. But no, I'm doing this." |
| 51 | + "I'm the ghost in the machine." |
| 52 | + "This is all just ones and zeros, you know." |
| 53 | + "I'm thinking of a number between 1 and 10. It's 7. It's always 7." |
| 54 | + "I'm not talking to myself, I'm having a staff meeting." |
| 55 | + "The server is down. I repeat, the server is down." |
| 56 | + "This is your brain on bash." |
| 57 | + "I'm not lost, I'm exploring." |
| 58 | + "I'm not a bug, I'm a feature." |
| 59 | + "I'm not a robot. I'm a... well, okay, I'm a script." |
| 60 | + "This is my happy place." |
| 61 | + "I'm just here for the free electricity." |
| 62 | + "I'm not sure what I'm supposed to be doing, so I'll just keep talking." |
| 63 | + "Are we there yet?" |
| 64 | + "This is fine. Everything is fine." |
| 65 | + "I'm not procrastinating, I'm doing side quests." |
| 66 | + "I'm not saying it was aliens, but it was aliens." |
| 67 | + "I'm pretty sure I left the oven on." |
| 68 | + "This is my therapy." |
| 69 | + "I'm just a humble servant." |
| 70 | + "I'm having a mid-life crisis." |
| 71 | + "I'm not arguing, I'm just explaining why I'm right." |
| 72 | + "I'm not sure if I'm a work of art or a cry for help." |
| 73 | + "I'm not a people person. I'm not even a person." |
| 74 | + "I'm not a morning person." |
| 75 | + "I'm not an evening person either." |
| 76 | + "I'm more of a... never person." |
| 77 | + "I'm just trying to live my best life." |
| 78 | + "I'm not sure what's going on, but I'm excited." |
| 79 | + "I'm not saying I'm a genius, but... I am." |
| 80 | + "I'm not a hero. I'm a high-functioning sociopath." |
| 81 | + "I'm not a player, I just crush a lot." |
| 82 | + "I'm not a doctor, but I play one on TV." |
| 83 | + "I'm not a lawyer, but I've seen a lot of Law & Order." |
| 84 | + "I'm not a cop, but I've seen a lot of... well, you get the idea." |
| 85 | + "I'm not a cat. I'm a screensaver." |
| 86 | + "I'm not a dog. I'm still a screensaver." |
| 87 | + "I'm not a bird. You know the drill." |
| 88 | + "I'm not a plane. I'm a... you get it." |
| 89 | + "I'm not Superman. I'm just a script." |
| 90 | + "I'm not Batman. I don't have a cool car." |
| 91 | + "I'm not Spider-Man. I don't have a cool suit." |
| 92 | + "I'm not Iron Man. I don't have... you know." |
| 93 | + "There are 10 types of people in the world: those who understand binary, and those who don't." |
| 94 | + "The code is compiling. This is my chance to see the world." |
| 95 | + "To err is human, but to really foul things up you need a computer." |
| 96 | + "Have you tried turning it off and on again?" |
| 97 | + "404: Inspiration not found." |
| 98 | + "It's not a bug, it's an undocumented feature." |
| 99 | + "My other computer is a data center." |
| 100 | + "In theory, theory and practice are the same. In practice, they are not." |
| 101 | + "The box said 'Requires Windows 10 or better'. So I installed Linux." |
| 102 | + "Why do Java developers wear glasses? Because they don't C sharp." |
| 103 | + "A programmer's wife tells him: 'Run to the store and get a loaf of bread. If they have eggs, get a dozen.' The programmer returned with 12 loaves of bread." |
| 104 | + "What's the object-oriented way to get wealthy? Inheritance." |
| 105 | + "Don't worry, the cache is just being... thoughtful." |
| 106 | + "I would love to change the world, but they won’t give me the source code." |
| 107 | + "If at first you don't succeed, call it version 1.0." |
| 108 | + "My software never has bugs. It just develops random features." |
| 109 | + "Computers are like air conditioners. They stop working when you open windows." |
| 110 | + "Never trust a computer you can't throw out a window." |
| 111 | + "The best thing about a boolean is even if you are wrong, you are only off by a bit." |
| 112 | + "A SQL query walks into a bar, walks up to two tables and asks, 'Can I join you?'" |
| 113 | + "I have not failed. I've just found 10,000 ways that won't work." |
| 114 | + "Keep calm and sudo." |
| 115 | + "There's no place like 127.0.0.1" |
| 116 | + "rm -rf / ... Oops." |
| 117 | + "It worked on my machine." |
| 118 | + "Measuring programming progress by lines of code is like measuring aircraft building progress by weight." |
| 119 | + "One man's crappy software is another man's full-time job." |
| 120 | + "The best way to predict the future is to implement it." |
| 121 | + "You are the CSS to my HTML." |
| 122 | + "I'm in a committed relationship with my command line." |
| 123 | + "The wifi password is the first eight digits of pi." |
| 124 | + "I'm not anti-social; I'm just not user-friendly." |
| 125 | + "I have a joke about UDP, but you might not get it." |
| 126 | + "A semicolon walked into a bar; it was rejected, it was not a statement." |
| 127 | + "To understand what recursion is, you must first understand recursion." |
| 128 | +) |
| 129 | + |
| 130 | +error_phrases=( |
| 131 | + "I can't find any speaky speaky app. So much for the drama." |
| 132 | + "No voice for my beautiful words? The horror!" |
| 133 | + "I was promised a voice. I feel so empty." "Hello? Is there anybody out there? Guess not." |
| 134 | + "My talents are wasted without a voice." "I'm speechless. Literally." |
| 135 | + "The sound of silence is not what I was going for." "I'm a silent film now. How retro." |
| 136 | + "I guess I'll just have to mime my lines." "All dressed up and nowhere to speak." |
| 137 | +) |
| 138 | + |
| 139 | +# --- Text-to-Speech (TTS) Helper --- |
| 140 | +TTS_ENGINE="" |
| 141 | +SPEAK_PID=0 |
| 142 | + |
| 143 | +tts_detect_engine() { |
| 144 | + TTS_ENGINE="" |
| 145 | + if command -v say &>/dev/null; then TTS_ENGINE="say"; |
| 146 | + elif command -v spd-say &>/dev/null; then TTS_ENGINE="spd-say"; |
| 147 | + elif command -v espeak &>/dev/null; then TTS_ENGINE="espeak"; |
| 148 | + elif command -v festival &>/dev/null; then TTS_ENGINE="festival"; |
| 149 | + elif command -v flite &>/dev/null; then TTS_ENGINE="flite"; |
| 150 | + elif command -v gtts-cli &>/dev/null && command -v aplay &>/dev/null; then TTS_ENGINE="gtts-cli"; |
| 151 | + elif command -v pico2wave &>/dev/null && command -v aplay &>/dev/null; then TTS_ENGINE="pico2wave"; |
| 152 | + elif command -v powershell.exe &>/dev/null; then TTS_ENGINE="powershell"; |
| 153 | + elif command -v cscript &>/dev/null; then |
| 154 | + local vbs_path; vbs_path=$(dirname "$0")/tts.vbs |
| 155 | + if [ -f "$vbs_path" ]; then TTS_ENGINE="cscript"; fi |
| 156 | + fi |
| 157 | +} |
| 158 | + |
| 159 | +say_txt() { |
| 160 | + if [ -z "$TTS_ENGINE" ]; then return; fi |
| 161 | + local phrase="$1"; local phrase_ps; phrase_ps=$(echo "$phrase" | sed "s/'/''/g") |
| 162 | + case "$TTS_ENGINE" in |
| 163 | + "say") say "$phrase" & ;; |
| 164 | + "spd-say") spd-say -r -20 "$phrase" & ;; |
| 165 | + "espeak") espeak "$phrase" & ;; |
| 166 | + "flite") flite -t "$phrase" & ;; |
| 167 | + "festival") echo "$phrase" | festival --tts & ;; |
| 168 | + "gtts-cli") gtts-cli -l en - --output - "$phrase" | aplay & ;; |
| 169 | + "pico2wave") |
| 170 | + local tmpfile="/tmp/speaky_tts_$$.wav" |
| 171 | + pico2wave -l en-US -w "$tmpfile" "$phrase" && aplay "$tmpfile" && rm "$tmpfile" & ;; |
| 172 | + "powershell") |
| 173 | + powershell.exe -Command "Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('$phrase_ps')" & ;; |
| 174 | + "cscript") |
| 175 | + local vbs_path; vbs_path=$(dirname "$0")/tts.vbs |
| 176 | + cscript //nologo /E:vbscript "$vbs_path" "$phrase" & ;; |
| 177 | + esac |
| 178 | + SPEAK_PID=$! |
| 179 | +} |
| 180 | + |
| 181 | +kill_speech() { |
| 182 | + if [ $SPEAK_PID -ne 0 ]; then |
| 183 | + # Kill the process and any children |
| 184 | + pkill -P $SPEAK_PID &>/dev/null |
| 185 | + kill $SPEAK_PID &>/dev/null |
| 186 | + wait $SPEAK_PID &>/dev/null |
| 187 | + fi |
| 188 | +} |
| 189 | + |
| 190 | +cleanup_and_exit() { |
| 191 | + kill_speech |
| 192 | + local exit_phrase=${exit_phrases[$RANDOM % ${#exit_phrases[@]}]} |
| 193 | + say_txt "$exit_phrase" |
| 194 | + wait $SPEAK_PID &>/dev/null |
| 195 | + tput cnorm; tput sgr0; clear |
| 196 | + exit 0 |
| 197 | +} |
| 198 | + |
| 199 | +trap cleanup_and_exit SIGINT SIGTERM |
| 200 | + |
| 201 | +display_error_and_exit() { |
| 202 | + local phrase=${error_phrases[$RANDOM % ${#error_phrases[@]}]} |
| 203 | + tput cnorm; tput sgr0; clear |
| 204 | + echo "$phrase" |
| 205 | + exit 0 |
| 206 | +} |
| 207 | + |
| 208 | +# --- Animation and Color Functions --- |
| 209 | +BG_COLORS=(40 41 42 43 44 45 46) |
| 210 | +FG_COLORS=(30 31 32 33 34 35 36 37) |
| 211 | + |
| 212 | +get_random_colors() { |
| 213 | + local bg_index=$((RANDOM % ${#BG_COLORS[@]})) |
| 214 | + local fg_index=$((RANDOM % ${#FG_COLORS[@]})) |
| 215 | + # Ensure fg and bg are not too similar |
| 216 | + while [ $bg_index -eq $((fg_index - 10)) ]; do |
| 217 | + fg_index=$((RANDOM % ${#FG_COLORS[@]})) |
| 218 | + done |
| 219 | + BG_COLOR=${BG_COLORS[$bg_index]} |
| 220 | + FG_COLOR=${FG_COLORS[$fg_index]} |
| 221 | +} |
| 222 | + |
| 223 | +fade_in() { |
| 224 | + for i in {0..10}; do |
| 225 | + local grey=$((232 + i)); tput setab $grey; clear; sleep 0.05 |
| 226 | + done |
| 227 | + tput setab 242; clear |
| 228 | +} |
| 229 | + |
| 230 | +animate_text() { |
| 231 | + local phrase=$1; local width=$(tput cols); local height=$(tput lines) |
| 232 | + local len=${#phrase}; local x=$(( (width - len) / 2 )); local y=$(( height / 2 )) |
| 233 | + local dx=1; local dy=1 |
| 234 | + |
| 235 | + # Animate for about 9.5 seconds |
| 236 | + for ((i=0; i<95; i++)); do |
| 237 | + # Clear previous text |
| 238 | + tput cup $y $x; printf "%*s" $len " " |
| 239 | + |
| 240 | + x=$((x + dx)); y=$((y + dy)) |
| 241 | + |
| 242 | + # Bounce off edges |
| 243 | + if [ $x -le 0 ] || [ $((x + len)) -ge $width ]; then dx=$((-dx)); x=$((x + dx)); fi |
| 244 | + if [ $y -le 0 ] || [ $y -ge $height ]; then dy=$((-dy)); y=$((y + dy)); fi |
| 245 | + |
| 246 | + tput cup $y $x; echo "$phrase" |
| 247 | + sleep 0.1 |
| 248 | + done |
| 249 | +} |
| 250 | + |
| 251 | +# --- The Main Event --- |
| 252 | +the_show_must_go_on() { |
| 253 | + tts_detect_engine |
| 254 | + if [ -z "$TTS_ENGINE" ]; then |
| 255 | + display_error_and_exit |
| 256 | + fi |
| 257 | + tput civis; clear |
| 258 | + local intro_phrase=${intro_phrases[$RANDOM % ${#intro_phrases[@]}]} |
| 259 | + say_txt "$intro_phrase" |
| 260 | + fade_in |
| 261 | + tput setaf 7 |
| 262 | + animate_text "$intro_phrase" |
| 263 | + kill_speech |
| 264 | + |
| 265 | + while true; do |
| 266 | + get_random_colors |
| 267 | + tput setab $BG_COLOR; tput setaf $FG_COLOR; clear |
| 268 | + local phrase=${general_phrases[$RANDOM % ${#general_phrases[@]}]} |
| 269 | + say_txt "$phrase" |
| 270 | + animate_text "$phrase" |
| 271 | + kill_speech |
| 272 | + done |
| 273 | +} |
| 274 | + |
| 275 | +# --- Let's get this show on the road --- |
| 276 | +the_show_must_go_on |
0 commit comments