443 lines
14 KiB
Bash
Executable file
443 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
# Text formatting variables
|
|
text_bold="\e[1m"
|
|
text_reset="\e[0m"
|
|
|
|
function print_usage {
|
|
echo "Usage: strm [OPTIONS] QUERIES ... [OPTIONS]"
|
|
echo
|
|
echo "Stream media files over SSH in a convenient way."
|
|
echo
|
|
echo "OPTIONS"
|
|
echo " -h, --help Show this help message"
|
|
echo " -c, --config CONFIG_FILE Path to config file (default: ~/.config/strm/strm.config)"
|
|
echo " -f, --fullscreen Play video files in fullscreen"
|
|
echo " -i, --ignore-files IGNORE_FILES Ignore given filenames"
|
|
echo " -l, --list List files instead of playing"
|
|
echo " -m, --media-directories MEDIA_DIRECTORIES Use given media directories, config is ignored"
|
|
echo " -o, --or Combine queries with a logical OR (default: AND)"
|
|
echo " -p, --playback-directory DIRECTORY Use given playback directory"
|
|
echo " -q, --quit TIME_IN_MINUTES Quit after a given time"
|
|
echo " -r, --remote SSH_CONNECTION_STRING Execute strm with other given arguments on remote machine (-f is set by default)"
|
|
echo " -s, --shuffle Play files in random order"
|
|
echo " -t, --tidy Don't resume playback"
|
|
echo
|
|
echo "EXAMPLES"
|
|
echo " strm -l . # List all available files"
|
|
echo " strm Elephants Dream # Play files whose path contain 'elephants' and 'dream'"
|
|
echo " strm e*phants # Play files whose path matches the glob pattern 'e*phants'"
|
|
exit
|
|
}
|
|
|
|
function print_controls {
|
|
echo -ne "\n[${text_bold}p${text_reset}] Play/Pause, [${text_bold}<${text_reset}/${text_bold}>${text_reset}] Previous/Next, [${text_bold}q${text_reset}] Quit\n"
|
|
}
|
|
|
|
function print_remote_controls {
|
|
echo -ne "\n[${text_bold}p${text_reset}] Play/Pause, [${text_bold}<${text_reset}/${text_bold}>${text_reset}] Previous/Next, [${text_bold}q${text_reset}] Quit, [${text_bold}C-b d${text_reset}] Detach from session\n"
|
|
}
|
|
|
|
function error {
|
|
echo -ne "${text_bold}ERROR${text_reset} $1\n" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Set default values
|
|
config="$HOME/.config/strm/strm.config"
|
|
fullscreen=false
|
|
is_remote_call=false
|
|
list=false
|
|
media_directories=""
|
|
tidy=false
|
|
or=false
|
|
queries=()
|
|
quit=""
|
|
remote=""
|
|
remote_arguments=()
|
|
tmp_ignore_files=""
|
|
tmp_playback_directory=""
|
|
shuffle=false
|
|
|
|
# Parse arguments
|
|
while (( "$#" )); do
|
|
case "$1" in
|
|
-c|--config)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
config="$2"
|
|
remote_arguments+=("$1" "$2")
|
|
shift 2
|
|
else
|
|
error "Argument for '$1' is missing"
|
|
fi
|
|
;;
|
|
-f|--fullscreen)
|
|
fullscreen=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
print_usage
|
|
;;
|
|
-i|--ignore-files)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
tmp_ignore_files="$2"
|
|
remote_arguments+=("$1" "$2")
|
|
shift 2
|
|
else
|
|
error "Argument for '$1' is missing"
|
|
fi
|
|
;;
|
|
--is-remote-call)
|
|
is_remote_call=true
|
|
shift
|
|
;;
|
|
-l|--list)
|
|
list=true
|
|
remote_arguments+=("$1")
|
|
shift
|
|
;;
|
|
-m|--media-directories)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
media_directories="$2"
|
|
remote_arguments+=("$1" "$2")
|
|
shift 2
|
|
else
|
|
error "Argument for '$1' is missing"
|
|
fi
|
|
;;
|
|
-o|--or)
|
|
or=true
|
|
remote_arguments+=("$1")
|
|
shift
|
|
;;
|
|
-p|--playback-directory)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
tmp_playback_directory="$2"
|
|
remote_arguments+=("$1" "$2")
|
|
shift 2
|
|
else
|
|
error "Argument for '$1' is missing"
|
|
fi
|
|
;;
|
|
-q|--quit)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
quit="$2"
|
|
remote_arguments+=("$1" "$2")
|
|
shift 2
|
|
else
|
|
error "Argument for '$1' is missing"
|
|
fi
|
|
;;
|
|
-r|--remote)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
remote="$2"
|
|
shift 2
|
|
else
|
|
error "Argument for '$1' is missing"
|
|
fi
|
|
;;
|
|
-s|--shuffle)
|
|
shuffle=true
|
|
remote_arguments+=("$1")
|
|
shift
|
|
;;
|
|
-t|--tidy)
|
|
tidy=true
|
|
remote_arguments+=("$1")
|
|
shift
|
|
;;
|
|
-*|--*=)
|
|
error "Unsupported flag: $1"
|
|
;;
|
|
*)
|
|
queries+=("$1")
|
|
remote_arguments+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Execute strm on remote if argument set
|
|
if [ "$remote" != "" ]; then
|
|
|
|
# Check if dependencies are fulfilled on remote
|
|
if ! ssh "$remote" "command -v mpv strm tmux &>/dev/null"; then
|
|
error "make sure ${text_bold}$remote${text_reset} is accessible and mpv, strm and tmux are installed"
|
|
fi
|
|
|
|
# Check if strm tmux session is already running
|
|
if ssh -o ConnectTimeout=10 "$remote" "tmux has-session -t strm &>/dev/null"; then
|
|
|
|
# Attach to tmux session
|
|
ssh -o ConnectTimeout=10 -t "$remote" "tmux attach-session -t strm &>/dev/null"
|
|
|
|
else
|
|
|
|
# Invert fullscreen argument
|
|
if [ "$fullscreen" == false ]; then
|
|
remote_arguments+=("-f")
|
|
fi
|
|
|
|
# Notify strm about remote session
|
|
remote_arguments+=("--is-remote-call")
|
|
|
|
# Execute strm on remote machine
|
|
ssh -o ConnectTimeout=10 -t "$remote" "DISPLAY=:0 tmux new-session -s strm 'tmux set-option status off; strm ${remote_arguments[@]}' &>/dev/null"
|
|
fi
|
|
exit
|
|
fi
|
|
|
|
# Check if mpv is installed
|
|
if ! command -v mpv &>/dev/null; then
|
|
error "mpv was not found, please install it"
|
|
fi
|
|
|
|
# If no media directory was set load config file
|
|
if [ "$media_directories" == "" ]; then
|
|
|
|
# Read config file
|
|
if test -f "$config"; then
|
|
source "$config"
|
|
else
|
|
error "Config file not found ($config)"
|
|
fi
|
|
|
|
# Throws error if still no media directory set
|
|
if [ "$media_directories" == "" ]; then
|
|
error "No media directories specified"
|
|
fi
|
|
fi
|
|
|
|
# Override playback_directory if argument set
|
|
if [ "$tmp_playback_directory" != "" ]; then
|
|
playback_directory="$tmp_playback_directory"
|
|
fi
|
|
|
|
# Override ignore files if argument set
|
|
if [ "$tmp_ignore_files" != "" ]; then
|
|
ignore_files="$tmp_ignore_files"
|
|
fi
|
|
|
|
# Synchronize playback directory
|
|
if [ "$list" == false ] && [ "$playback_directory" != "" ]; then
|
|
|
|
# Check if rsync is installed
|
|
if ! command -v rsync &>/dev/null; then
|
|
error "rsync was not found, please install it"
|
|
fi
|
|
|
|
# Get connection string and remote directory
|
|
IFS="/" read -r connection_string directory <<< "$playback_directory"
|
|
|
|
# Correct empty connection string
|
|
if [ "$connection_string" == "" ]; then
|
|
connection_string="localhost"
|
|
fi
|
|
|
|
# Check validity of directory
|
|
if [ "$directory" == "" ]; then
|
|
error "Not a valid playback directory ($playback_directory)"
|
|
fi
|
|
|
|
# Make local playback directory if not existent
|
|
mkdir -p "$HOME/.cache/strm"
|
|
|
|
# Add leading and trailing slash to directory if missing
|
|
[[ "$directory" != /*/ ]] && directory="/$directory/"
|
|
|
|
echo -ne "Synchronizing playback directory with $text_bold$(basename $connection_string)$text_reset\n"
|
|
|
|
# Make remote directory if not existent
|
|
ssh -o ConnectTimeout=10 "$connection_string" "mkdir -p $directory"
|
|
|
|
# Synchronize remote to local
|
|
rsync -az --delete "$connection_string:$directory" "$HOME/.cache/strm/"
|
|
fi
|
|
|
|
# Check queries
|
|
if [ "${#queries[@]}" == 0 ]; then
|
|
if [ "$playback_directory" != "" ] && test -f "$HOME/.cache/strm/strm_later"; then
|
|
source "$HOME/.cache/strm/strm_later"
|
|
else
|
|
print_usage
|
|
fi
|
|
fi
|
|
|
|
# Read media directories
|
|
IFS="," read -a media_directories <<< "$media_directories"
|
|
|
|
# Read ignore files
|
|
IFS="," read -a ignore_files <<< "$ignore_files"
|
|
|
|
# Construct find argument array
|
|
|
|
# List only files and symlinks
|
|
find_arguments=("-type" "f,l")
|
|
|
|
# Ignore hidden files and directories
|
|
find_arguments+=("!" "-path" "'*/\.*'")
|
|
|
|
# Ignore additional given filenames case-insensitive
|
|
if [ "${#ignore_files[@]}" -ge 1 ]; then
|
|
find_arguments+=("-and" "!" "\(")
|
|
for i in "${!ignore_files[@]}"; do
|
|
if [ "$i" -ge 1 ]; then
|
|
find_arguments+=("-or")
|
|
fi
|
|
find_arguments+=("-iname" "'${ignore_files[$i]}'")
|
|
done
|
|
find_arguments+=("\)")
|
|
fi
|
|
|
|
# Add queries
|
|
find_arguments+=("-and" "\(")
|
|
for i in "${!queries[@]}"; do
|
|
|
|
# If -o flag is set and more than one query is given, add a logical OR
|
|
if [ "$or" == true ] && [ "$i" -ge 1 ]; then
|
|
find_arguments+=("-or")
|
|
fi
|
|
|
|
# Use the ipath argument to search case-insensitive and surround query with wildcards
|
|
find_arguments+=("-ipath" "'*${queries[$i]}*'")
|
|
done
|
|
find_arguments+=("\)")
|
|
|
|
# Initialize result arrays
|
|
sftp_results=()
|
|
print_results=()
|
|
|
|
# Get results from every media directory
|
|
for media_directory in "${media_directories[@]}"; do
|
|
|
|
tmp_sftp_results=()
|
|
tmp_print_results=()
|
|
|
|
# Get connection string and remote directory
|
|
IFS="/" read -r connection_string directory <<< "$media_directory"
|
|
|
|
# Correct empty connection string
|
|
if [ "$connection_string" == "" ]; then
|
|
connection_string="localhost"
|
|
fi
|
|
|
|
# Check validity of directory
|
|
if [ "$directory" == "" ]; then
|
|
error "Not a valid media directory ($media_directory)"
|
|
fi
|
|
|
|
# Add leading and trailing slash to directory if missing
|
|
[[ "$directory" != /*/ ]] && directory="/$directory/"
|
|
|
|
echo -ne "Fetching results from $text_bold$(basename $directory)$text_reset on $text_bold$connection_string$text_reset\n"
|
|
|
|
# Get search results from remote
|
|
# Look for paths matching given queries in visible directories, listing only filenames and links
|
|
mapfile -t tmp_results < <(ssh -o ConnectTimeout=10 "$connection_string" find "'$directory'" "${find_arguments[@]}" | sort)
|
|
|
|
# Build SFTP strings and printable strings
|
|
for i in "${!tmp_results[@]}"; do
|
|
tmp_sftp_results["$i"]="sftp://$connection_string${tmp_results[$i]}"
|
|
tmp_print_result="$text_bold$connection_string$text_reset ${tmp_results[$i]}"
|
|
tmp_print_results["$i"]="${tmp_print_result/$directory/}"
|
|
done
|
|
sftp_results=("${sftp_results[@]}" "${tmp_sftp_results[@]}")
|
|
print_results=("${print_results[@]}" "${tmp_print_results[@]}")
|
|
done
|
|
|
|
echo
|
|
|
|
# Exit if no results found
|
|
if [ "${#sftp_results[@]}" == 0 ]; then
|
|
if [ "$is_remote_call" == true ]; then
|
|
echo "No files found, press something to quit"
|
|
read -n 1
|
|
else
|
|
echo "No files found"
|
|
fi
|
|
exit
|
|
fi
|
|
|
|
# Print result header
|
|
if [ "$list" == true ]; then
|
|
echo "Found the following files:"
|
|
else
|
|
if [ "$shuffle" == true ]; then
|
|
echo "Playing the following files in random order:"
|
|
else
|
|
echo "Playing the following files:"
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
|
|
# Print results
|
|
for result in "${print_results[@]}"; do
|
|
echo -ne "$result\n"
|
|
done
|
|
|
|
# Play results if --list flag not set
|
|
if [ "$list" == false ]; then
|
|
|
|
# Save arguments for later call
|
|
if [ "$playback_directory" != "" ]; then
|
|
echo "queries=(${queries[@]})" > "$HOME/.cache/strm/strm_later"
|
|
echo "fullscreen=$fullscreen" >> "$HOME/.cache/strm/strm_later"
|
|
echo "or=$or" >> "$HOME/.cache/strm/strm_later"
|
|
echo "shuffle=$shuffle" >> "$HOME/.cache/strm/strm_later"
|
|
fi
|
|
|
|
# Print controls
|
|
if [ "$is_remote_call" == true ]; then
|
|
print_remote_controls
|
|
else
|
|
print_controls
|
|
fi
|
|
|
|
# Construct addtitional mpv arguments
|
|
mpv_arguments=()
|
|
if [ "$fullscreen" == true ]; then
|
|
mpv_arguments+=("--fullscreen")
|
|
fi
|
|
if [ "$shuffle" == true ]; then
|
|
mpv_arguments+=("--shuffle")
|
|
fi
|
|
if [ "$tidy" == true ]; then
|
|
mpv_arguments+=("--no-resume-playback")
|
|
fi
|
|
if [ "$playback_directory" != "" ]; then
|
|
mpv_arguments+=("--save-position-on-quit")
|
|
fi
|
|
|
|
# Play all remote files
|
|
if [ "$quit" != "" ]; then
|
|
|
|
# Convert minutes to seconds
|
|
((quit=quit*60))
|
|
|
|
timeout --foreground --signal QUIT "$quit" mpv --msg-level=all=error,statusline=status --watch-later-directory="$HOME/.cache/strm" --term-status-msg='${playlist-pos-1}/${playlist-count} - ${time-pos}/${duration} - \e[1m${metadata/artist:}${?metadata/artist: - }${metadata/album:}${?metadata/album: - }${metadata/title:}${!metadata/title:${filename/no-ext}}\e[0m' "${mpv_arguments[@]}" "${sftp_results[@]}"
|
|
else
|
|
mpv --msg-level=all=error,statusline=status --watch-later-directory="$HOME/.cache/strm" --term-status-msg='${playlist-pos-1}/${playlist-count} - ${time-pos}/${duration} - \e[1m${metadata/artist:}${?metadata/artist: - }${metadata/album:}${?metadata/album: - }${metadata/title:}${!metadata/title:${filename/no-ext}}\e[0m' "${mpv_arguments[@]}" "${sftp_results[@]}"
|
|
fi
|
|
|
|
# Synchronize playback directory back if directory is set
|
|
if [ "$playback_directory" != "" ]; then
|
|
|
|
# Get connection string and remote directory
|
|
IFS="/" read -r connection_string directory <<< "$playback_directory"
|
|
|
|
# Correct empty connection string
|
|
if [ "$connection_string" == "" ]; then
|
|
connection_string="localhost"
|
|
fi
|
|
|
|
# Add leading and trailing slash to directory if missing
|
|
[[ "$directory" != /*/ ]] && directory="/$directory/"
|
|
|
|
echo -ne "\nSynchronizing playback directory with $text_bold$(basename $connection_string)$text_reset"
|
|
|
|
# Synchronize local to remote
|
|
rsync -az --delete "$HOME/.cache/strm/" "$connection_string:$directory"
|
|
fi
|
|
|
|
fi
|