Skip to content

Instantly share code, notes, and snippets.

@pabloab
Last active September 23, 2024 23:38
Show Gist options
  • Save pabloab/0965faa8d8cff68418e2b6ae1081bd9e to your computer and use it in GitHub Desktop.
Save pabloab/0965faa8d8cff68418e2b6ae1081bd9e to your computer and use it in GitHub Desktop.
Simple pomodoro bash script that switch between sound outputs
#!/usr/bin/env bash
# Little script to play a music/sound on pomodoros through headphones and call to come back to work through HDMI speakers.
# set -euo pipefail # Unofficial bash strict mode
# Prerequsitres: gtts (`pip install --user --upgrade gtts`, mpv (`flatpak install io.mpv.Mpv`), vlc, libsox-fmt-mp3 and retry.
# Also use yt-dlp instead of yt-dl https://hund.tty1.se/2021/10/12/how-to-use-yt-dlp-instead-of-yt-dl-with-mpv.html
# TODO/Known bugs:
# - Fix issue with PipeWire: pacmd stat No PulseAudio daemon running, or not running as session daemon. https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/357
# - Use `pw-cli` if PipeWire https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Migrate-PulseAudio#pacmd
# - Avoid hiding output by using chronic (from moreutils)
# - Add a fadeout befor ending pomodoro
# - Add a counter/countdown on stdout
# - External sound module lower after midnight (25%, day hours 70%)
# - Option to disable audio autoswitch
# - Use `retry` also with gtts
# - Use set -euo pipefail. May conflict with SC2143
# DONE
# - Try to switch to external audio card profile only if exist
# - Make interumpible also the break (test)
stty -echoctl # hide ^C
function finish() {
local MSG_EXITING="Exiting..."
ps --pid "$pid" &>/dev/null && kill -INT "$pid"
echo "$MSG_EXITING"
exit 1
}
function handle_error() {
local MSG_ERRORS="Some errors encountered. Exiting..."
echo "$MSG_ERRORS"
exit 2
}
function handle_error() {
local MSG_ERRORS="Some errors encountered. Exiting..."
echo "$MSG_ERRORS"
exit 2
}
trap finish SIGINT
# Easily sets the volume of any app
function pacmd-set-app-volume() {
[[ $(pgrep pipewire) ]] && echo 'You are using PipeWire: This feature is not available.'
local player="$1"
local volume="$2"
local firstPlayerSinkIndex="$(pacmd list-sink-inputs | awk '/index:|application.name |application.process.binary / {print $0};' | grep -iB 1 "$player" | awk '/index:/ {print $2; exit}')" # get specific app sink
[[ $firstPlayerSinkIndex ]] && pacmd set-sink-input-volume "$firstPlayerSinkIndex" "$((volume*65536/100))" # 100% → 65536
}
function pomodoro() {
local LAPSE="25m"
local SHORT_BREAK="5m"
local LONG_BREAK="15m"
local POMODOROS="4"
local MPV_RUNTIME_PATH="flatpak run io.mpv.Mpv"
local MSG_END_OF_LAST_POMODORO="Fin del pomodoro $POMODOROS"
local MSG_END_OF_NORMAL_POMODORO="Fin del pomodoro"
local MSG_END_OF_BREAK="Let's go? "
local GTTS_LANG="es"
local COME_BACK_SOUND="/usr/share/sounds/freedesktop/stereo/complete.oga"
local SOUND="music-lofi-girl" # default
[[ $(pacmd stat 2>/dev/null) ]] && [[ $(pacmd list-cards | grep "output:hdmi") ]] && local CARDPROFILE_HDMI="output:hdmi-stereo-extra1+input:analog-stereo"
local CARDPROFILE_ANALOG="output:analog-stereo+input:analog-stereo"
# local CARDPROFILE_BLUETOOTH="output:bluez_sink.14_3F_A6_78_40_ED.a2dp_sink"
local GNOME_DNDMODE_PREVIOUS_STATE bold red green reset
GNOME_DNDMODE_PREVIOUS_STATE="$(gsettings get org.gnome.desktop.notifications show-banners)"
bold=$(tput bold)
red=$(tput setaf 1)
green=$(tput setaf 2)
reset=$(tput sgr0)
declare -A music_urls=(
["music-freecode"]="https://coderadio-admin.freecodecamp.org/radio/8010/radio.mp3"
["music-jazz"]="https://youtu.be/Dx5qFachd3A"
["music-lofi"]="https://youtu.be/kgx4WGK0oNU"
["music-lofi-girl"]="https://youtu.be/jfKfPfyJRdk" # https://fanlink.to/lofigirl-music ytdl://ytsearch:\"lofi+radio+girl\"
["music-coffee"]="https://youtu.be/fEvM-OUbaKs"
["music-piano"]="https://youtu.be/xWRHTpqQMGM"
["music-synthwave"]="https://youtu.be/oC0iVoNJwbE"
["music-spacesynth"]="https://youtu.be/5-anTj1QrWs"
["binaural"]="https://youtu.be/F5Tt3LoygCQ"
["rain"]="https://youtu.be/q76bMs-NwRk"
)
# CVLC_OPTIONS="--equalizer-bands "0 0 0 0 0 0 0 0 0 0" --headphone-dim 3 --equalizer-preset classical --equalizer-preamp -15 --compressor-rms-peak 0.5" # {flat,classical,club,dance,fullbass,fullbasstreble,fulltreble,headphones,largehall,live,party,pop,reggae,rock,ska,soft,softrock,techno}
[[ $(pgrep pipewire) ]] && echo 'You are using PipeWire: Some features will not be available.'
local OPTIND
while getopts "p:m:h" options; do
case "${options}" in
p) POMODOROS=${OPTARG};;
m) SOUND=${OPTARG};;
h) echo "Options: [-p pomodoros] [-m music-freecode|music-jazz|music-lofi|music-lofi-girl|music-coffee|music-piano|music-synthwave|music-spacesynth|binaural|rain|silence|noise-white|SOUND_FILE_OR_DIR]"; return 0;;
*) echo "Error: -${OPTARG} requires an argument."; return 1;;
esac
done
while true; do
for i in $(seq 1 "$POMODOROS"); do
echo -n "${reset}${bold}${green}Pomodoro $i! ${reset}"
[[ $(pacmd stat 2>/dev/null) ]] && pacmd set-card-profile 0 $CARDPROFILE_ANALOG
#pactl set-sink-volume "$(pactl list short sinks | grep RUNNING | cut -f1)" 25%
pactl set-sink-volume "$(pactl list short sinks | head -n1 | cut -f1)" 30%
gsettings set org.gnome.desktop.notifications show-banners false
if test -r "$SOUND" -a -f "$SOUND"; then
SILENCE_BETWEEN_TRACKS=3
[ -r "$SOUND" ] && timeout --foreground $LAPSE cvlc -q --loop "$SOUND" "vlc://pause:$SILENCE_BETWEEN_TRACKS" &> /dev/null
elif test -r "$SOUND" -a -d "$SOUND"; then
[ -r "$SOUND" ] && timeout --foreground $LAPSE cvlc -q --loop --random --volume-step 192 --gain 1 "$SOUND" "vlc://pause:$SILENCE_BETWEEN_TRACKS" &> /dev/null
else
case $SOUND in
music-freecode|music-jazz|music-lofi|music-lofi-girl|music-coffee|music-piano|music-synthwave|music-spacesynth|binaural|rain)
# timeout --foreground $LAPSE retry --until=success --times=5 --delay=5 -- streamlink --ipv4 --quiet --player="nice -n 19 mpv --no-video" ${music_urls[$SOUND]} "720p,480p,best" # &> /dev/null # |& tee #&> /dev/null
timeout $LAPSE retry --until=success --times=3 --delay=3 -- nice -n 19 $MPV_RUNTIME_PATH --no-config --quiet --no-video --ytdl-format=best "${music_urls[$SOUND]}" & # > /dev/null # |& tee
pid=$!
wait $pid
pid_exit_status=$?
[[ $pid_exit_status && $pid_exit_status -ne 124 ]] && handle_error # If timeout exit code is different of 0 or 124...
;;
silence)
sleep $LAPSE
;;
noise-white)
[ "m" == ${LAPSE: -1} ] || exit 1
LAPSE=${LAPSE%"m"}
play -q -n synth $LAPSE:00 whitenoise
;;
*)
play -q -n synth $LAPSE:00 pinknoise
;;
esac
fi
# play -q "/usr/share/sounds/freedesktop/stereo/bell.oga"
echo
if [ "$POMODOROS" != "$i" ]; then
echo "$MSG_END_OF_NORMAL_POMODORO" | gtts-cli - -l "$GTTS_LANG" | play -q -t mp3 - -t alsa |& tee /dev/null # If doesn't work: pip install gtts --upgrade
echo "Take a $SHORT_BREAK break"
sleep $SHORT_BREAK
else
echo "$MSG_END_OF_LAST_POMODORO" | gtts-cli - -l "$GTTS_LANG" | play -q -t mp3 - -t alsa |& tee /dev/null # If doesn't work: pip install gtts --upgrade
echo "Take a $LONG_BREAK break"
sleep $LONG_BREAK
fi
[[ $(pacmd stat 2>/dev/null) ]] && [[ $CARDPROFILE_HDMI ]] && pacmd set-card-profile 0 $CARDPROFILE_HDMI
#pactl set-sink-volume "$(pactl list short sinks | grep RUNNING | cut -f1)" 50%
pactl set-sink-volume "$(pactl list short sinks | head -n1 | cut -f1)" 30%
gsettings set org.gnome.desktop.notifications show-banners "$GNOME_DNDMODE_PREVIOUS_STATE"
echo -n "$MSG_END_OF_BREAK" # The \n is given by the user
SECONDS=0
while true; do
seq 2 | xargs -I{} play -q $COME_BACK_SOUND -t alsa &> /dev/null # Others: alarm-clock-elapsed.oga dialog-warning.oga message-new-instant.oga phone-incoming-call.oga service-login.oga service-logout.oga suspend-error.oga window-attention.oga
# seq 2 | xargs -I{} play -q -n -t alsa synth 2 pl G3 pl B3 pl D4 delay 0 .05 .1 remix - fade 0 1.1 .095 norm -1
# echo "$i" | cw # Morse code. xcwcp qrq cwcp cw
local LOOP_TIMEOUT_SECONDS="120"
if read -r -t "$LOOP_TIMEOUT_SECONDS"; then
#let "minutes=(SECONDS)/60"
(( minutes=(SECONDS)/60 ))
echo -ne "\033[A\033[${#MSG_END_OF_BREAK}C ${red}You took an extra $minutes'.${reset}\n"
break
fi
done
done
done
echo "Take a longer rest."
}
pomodoro "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment