Skip to content

Instantly share code, notes, and snippets.

@zoqaeski
Last active October 31, 2019 00:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zoqaeski/9a99c87e6ef0811791317a964c67743a to your computer and use it in GitHub Desktop.
Save zoqaeski/9a99c87e6ef0811791317a964c67743a to your computer and use it in GitHub Desktop.
MPV single-instance mode script
#!/usr/bin/env zsh
# This script requires:
# - that the directory $XDG_RUNTIME_DIR exist
# + that the programs socat and jq be installed for writing to and reading from
# the MPV JSON IPC pipe
# Parts of this script were adapted from https://github.com/Duncaen/mpvctl, but
# I'm intending to add a few more features to my version.
# The socket is put in the XDG_RUNTIME_DIR so it won't be accessible to anyone else
readonly socket="$XDG_RUNTIME_DIR/mpv.socket"
readonly progname=$0
readonly urlregex='^(http|https|ytdl|smb|bd|bluray|dvd|dvdnav|dvdread|tv|pvr|dvb|mf|cdda|lavf|av|file|fd|fdclose|edl)://'
# Options
local -a debug playlist mpvlog
zparseopts -D d=debug -debug=debug \
l=playlist -list=playlist \
v=mpvlog -verbose=mpvlog \
# We need to check if an mpv instance with the socket open is running
local mpvpid=$(ps -x -o pid,command | grep $socket | grep -v grep | awk '{print $1}')
usage() {
cat <<EOFUSAGE
$progname [options] <cmd> [ARGS...]
OPTIONS:
-l, --list Show playlist after performing COMMAND
-d, --debug Debug this script
-v, --verbose Log mpv output (to /tmp/mpv-DATE.txt)
COMMANDS:
play [index] Start playing
pause Pause current file
stop Stop and quit
next Play next file
prev(ious) Play previous file
seek <seconds> Seek <seconds> in file
move <index1> <index2> Move <index1> to <index2> in playlist
remove [index] Remove current file or item <index> from playlist
clear Clear all but currently playing files
ls, list List the playlist
add <filenames...> Add files to playlist
replace <filenames...> Replace playlist with files
prop [...] Get properties
EOFUSAGE
}
error() {
printf "Error: %s\n" "$@"
exit 1
}
local ipcmd
local ipc
local errmsg
# Send a command via the IPC interface
ipc-send() {
# Cycle through command and arguments
ipccmd="$1"
args=""
idx=0
shift
for a in "$@"; do
[[ $(( idx=idx+1 )) -le "$#" ]] && args="$args, "
case "${a}" in
true|false|[0-9]*) args="${args}${a}" ;;
*) args="${args}\"${a}\""
esac
done
# Send the command to mpv, and store the output in $ipc
ipc=$(printf '{ "command": [ "%s" %s ] }\n' "$ipccmd" "$args" | socat - $socket 2>&1)
# If socat can't connect, don't return an error: mpv might not be accepting connections yet
[[ $? -eq 0 ]] && errmsg=$(echo $ipc | jq -r 'if .error != "success" then .error else empty end')
[[ -n $errmsg ]] && error $errmsg
[[ -n $debug ]] && echo $ipc
}
list() {
ipc-send 'get_property' 'playlist'
echo $ipc | jq -r '.data | ([keys[] as $k | .[$k] | .index=$k]) | map((.index | tostring) + (if .playing == true then " ▶ " else " " end) + .filename + "") | .[]'
}
# Basic sanity check.
# If the argument is a URL that mpv recognises, pass it through, otherwise
# use the full path of the file if it is readable. It won't stop you from
# sending mpv files or URLs it doesn't know how to play, but it will ensure
# those files can be passed
local sanefilename
check() {
if [[ $1 =~ $urlregex ]]; then
sanefilename="$1"
elif [[ -r $1:a ]] ; then
sanefilename="$1:a"
fi
}
# Append file names to the playlist
append() {
opt="$1"
[ -n "$opt" ] && shift
# Basic sanity check of each file passed through
local -a files
for f in $argv ; do
check $f
files+=($sanefilename)
unset sanefilename
done
for f in $files ; do
ipc-send 'loadfile' "$f" "$opt"
done
}
main() {
cmd="$1"
[ -n "$cmd" ] && shift
if [[ -z $mpvpid ]] && [[ ! $cmd = "add" ]] ; then
error "MPV is not running!"
fi
# Initiate mpv if it is not already running
if [[ -z $mpvpid ]] ; then
echo "Starting new instance…"
if [[ -n $mpvlog ]] ; then
mpv --no-terminal --force-window --input-ipc-server=$socket --playlist-start=0 --idle=once --log-file=/tmp/mpv-$(date '+%Y%m%dT%H%M%S').txt & mpvpid=$!
else
mpv --no-terminal --force-window --input-ipc-server=$socket --playlist-start=0 --idle=once & mpvpid=$!
fi
local connected=1
while [[ $connected -ne 0 ]] ; do
ipc-send 'get_property' 'mpv-version'
echo $ipc | grep -v "Connection refused" -
connected=$?
sleep 0.1
done
fi
case $cmd in
# Start playing
# $1 skips to index on playlist
play)
if [[ "$1" ]] ; then
ipc-send 'set_property' 'playlist-pos' "$1"
fi
ipc-send 'set_property' 'pause' 'false'
;;
# Pause
pause)
ipc-send 'set_property' 'pause' 'true'
;;
# Exit mpv
stop)
ipc-send 'quit'
;;
# Play next item in playlist
next)
ipc-send 'playlist-next'
;;
# Play previous item in playlist
prev|previous)
ipc-send 'playlist-prev'
;;
# Seek by $1 seconds
seek)
ipc-send 'seek' "$1" "$2"
;;
# Switch playlist items
move)
ipc-send 'playlist-move' "$1" "$2"
;;
# Remove item
# With no argument, removes current item from playlist
remove)
if [[ -z "$1" ]] ; then
ipc-send 'playlist-remove' 'current'
else
ipc-send 'playlist-remove' "$1"
fi
;;
# Remove all but current item
clear)
ipc-send 'playlist-clear'
;;
# Show current playlist
ls|list)
unset playlist
list
;;
# Add item(s) to playlist
add|append)
append "append-play" $argv
;;
# Replace currently playing items
rep|replace)
append "replace" $1
shift
for file in $argv ; do
append "append" "$file"
done
;;
# Show properties
prop)
for p in $argv ; do
ipc-send "get_property" "$p"
echo $ipc | jq '.data'
done
;;
*)
usage
exit 1
;;
esac
# Print playlist if started with -l or --list
if [[ -n $playlist[1] ]] ; then
list
fi
}
if [[ -n $debug[1] ]] ; then
setopt xtrace
fi
main "$@"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment