Skip to content

Instantly share code, notes, and snippets.

@zoqaeski
Last active May 15, 2021 05:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zoqaeski/364c247d15dbc4441344a60b974f16c1 to your computer and use it in GitHub Desktop.
Save zoqaeski/364c247d15dbc4441344a60b974f16c1 to your computer and use it in GitHub Desktop.
MPV single instance controller
[Desktop Entry]
Type=Application
Name=mpv Media Player (Single-instance mode)
GenericName=Multimedia player
Comment=Play movies and songs without starting new instance of multimedia player
Icon=mpv
TryExec=mpvctl
Exec=mpvctl add -- %U
Terminal=false
Categories=AudioVideo;Audio;Video;Player;TV;
MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u;
X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb
#!/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
zparseopts -D d=debug -debug=debug \
l=playlist -list=playlist \
# 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
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 ipc
local errmsg
ipc-send() {
# Cycle through command and arguments
cmd="$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' "$cmd" "$args" | socat - $socket | jq)
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() {
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
# TODO: Fix this so that it sleeps until the socket and mpv instance are
# listening, because sometimes on a first run the file doesn't get added
if [[ -z $mpvpid ]] ; then
echo "Starting new instance…"
mpv --no-terminal --force-window --input-ipc-server=$socket --playlist-start=0 --idle=once & mpvpid=$!
sleep 1
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