Skip to content

Instantly share code, notes, and snippets.

@fa7ad

fa7ad/mpvc.sh

Created Mar 30, 2020
Embed
What would you like to do?
Cast Youtube to MPV
#!/bin/sh
#
# fyr - 2019 (c) MIT | /bin/sh mpvc
# control mpv remotely using JSON ipc
# https://mpv.io/manual/master/#json-ipc
SOCKET=${MPVC_SOCKET:-/tmp/mpvsocket}
MPVOPTIONS="--no-audio-display"
usage() {
cat >&2 << EOF
Usage: $(basename $0) [-S "socket"] [-a "filenames"] [-o "path"] [-f "format string"]
-p | --toggle : Toggle playback.
-s | --stop : Always stop playback.
-P | --play : Always start playback.
-f | --format : Enter a formatting string.
-a | --add : Add files to playlist.
-i | --playlist : Print filenames of tracks to fit within terminal.
-I | --fullplaylist : Print all filenames of tracks in current playlist.
-o | --save : Save current playlist to given path.
-j | --track : Go forwards/backwards through the playlist queue.
-J | --tracknum : Jump to playlist item number.
-z | --shuffle : Shuffle the current playlist.
-l | --loop : Loop currently playing playlist.
-L | --loopfile : Loop currently playing file.
-v | --vol : Increase/decrease volume relative to current volume.
-V | --volume : Set absolute volume.
-m | --mute : Toggle sound.
-t | --seek : Increases/decreases time relatively, accepts % values.
-T | --time : Set absolute time.
-x | --speed : Increase/decrease speed relative to the current speed.
-X | --speedval : Set absolute speed.
-I | --image : Enable adding of images to the queue.
-k | --kill : Kill the mpv process controlling the given socket.
-K | --killall : Kill all mpv processes indiscriminately.
-S | --socket : Set socket location [default: $SOCKET].
-q | --quiet : Surpress all text output.
-Q | --vid=no : Start mpv with video output disabled.
-- | : After adding files options after -- are passed to mpv.
-h | --help : Print this help.
Formatting:
\`$(basename $0) --format\` will interpret the following delimiters if they are found:
%name%, %path%, %dir%, %title%, %artist%, %album%, %albumartist%, comment%,
%genre%, %year%, %percentage%, %playlistlength%, %position%, %repeat%,
%single, %status%, %time%, %precisetime%, %speed%, %length%, %remaining%
%volume%, %muted%, %frame%, %width%, %height%, %ab-loop-a%, %ab-loop-b%
MPC compatibility layer:
mpvc features a nearly complete compatibility layer for mpc commands in
addition to GNU style arguments. http://linux.die.net/man/1/mpc
Exit codes:
0: Program ran succesfully.
1: Input Argument error.
2: Socket does not exist.
3: Socket is not currently open.
4: Dependency error.
EOF
test -z "$1" && exit || exit "$1"
}
# Retrieval Functions
###############################################################################
# match given filename string to appropriate output
# accepted properties: filename
cleanFilename() {
filename="$1"
case "$filename" in
*.googlevideo.com/*)
filename="mps-yt stream"
printf '%s\n' "$filename"
;;
*youtu*|watch*)
# no other option but to use jq
type jq > /dev/null 2>&1 && {
# this is fucking quick
id="$(printf '%s\n' "$filename" | cut -d'=' -f 2)"
title="$(curl -sL \
"https://youtube.com/oembed?url=https://youtube.com/watch?v=$id" | \
jq -r '.title')"
} || {
title="$filename"
}
printf '%s\n' "$title"
;;
*)
# return filename to relative path
# this will probably look funky if there's a '/' in the filename
filename=$(basename "$filename")
printf '%s\n' "$filename"
;;
esac
}
# accepted properties: media-title, path
getMediaProperties() {
property="$1"
mediaValue=$(printf '%s\n' "{ \"command\": [\"get_property\", \
\"${property}\" ] }" | $SOCKETCOMMAND 2> /dev/null | awk -F '"' '{print $4}')
printf '%s\n' "$mediaValue" | escapeSedChar '&' | escapeSedChar '#'
}
# accepted properties: date, genre, title, album, artist, album_artist
getMetadata() {
property=$1
metadata=$(printf '%s\n' "{ \"command\": [\"get_property\", \
\"metadata/by-key/${property}\" ] }" | $SOCKETCOMMAND 2> /dev/null | \
tr -d "{}" | awk -F '"' '{print $4}')
# test for no result
test "$metadata" = "property not found" && {
result="N/A"
} || {
result="$metadata"
}
# test if title has no property and return filename instead
test "$property" = "title" && {
test "$metadata" = "property not found" && {
result=$(getPropertyString filename)
}
test "$metadata" = "error" && {
result=$(getPropertyString filename)
}
}
test "$property" = "title" && {
title=$(cleanFilename "$result")
printf '%s' "$title" | escapeSedChar '&' | escapeSedChar '#'
return 0
}
printf '%s' "$result" | escapeSedChar '&' | escapeSedChar '#'
}
# retrieve integer/boolean property
# accepted properties: mute, pause, loop-file, estimated-frame-number, width, height
getProperty() {
property=$1
result=$(printf '%s\n' "{ \"command\": [\"get_property\", \
\"${property}\"] }" | $SOCKETCOMMAND 2> /dev/null | awk -F '[":,]' '{print $4}')
printf '%s' "$result"
}
# accepted properties: idle-active, playlist-count, playlist-pos, playback-time,
# playtime-remaining, time-remaining, percent-pos, duration volume
getPropertyString() {
property=$1
result=$(printf '%s\n' "{ \"command\": [\"get_property_string\", \
\"${property}\"] }" | $SOCKETCOMMAND 2> /dev/null | awk -F '[".]' '{print $4}')
test "$result" = "error" && {
test $# -ge 2 && printf '%s' "$2" || printf '%s' "N/A"
return 1
} || {
printf '%s' "$result"
return 0
}
}
# accepted properties: speed
getSpeed() {
speed=$(printf '%s\n' "{ \"command\": [\"get_property_string\", \
\"speed\"] }" | $SOCKETCOMMAND 2> /dev/null | awk -F '"' '{print substr ($4, 0, 4)}')
printf '%s' "$speed"
}
# accepted properties: loop-file, loop-playlist
getLoopStatus() {
property=$1
case "$property" in
loop-file)
status=$(getProperty loop-file)
;;
loop-playlist)
status=$(printf '%s\n' "{ \"command\": [\"get_property\", \
\"loop-playlist\"] }" | $SOCKETCOMMAND 2> /dev/null)
printf '%s\n' "$status" | grep "inf" > /dev/null 2>&1 && {
status="$(printf '%s\n' "$status" | cut -d\" -f 4)"
} || {
status="$(printf '%s\n' "$status" | awk -F '[":,]' '{print $4}')"
}
;;
esac
test "$status" -ne 0 2> /dev/null
test $? -ne 2 && {
loop="$status"
} || {
case "$status" in
true | inf) loop="yes" ;;
false) loop="no" ;;
*) loop="N/A" ;;
esac
}
printf '%s' "$loop"
}
getMuteStatus() {
muted="$(getProperty mute)"
case "$muted" in
true) muted="yes" ;;
false) muted="no" ;;
*) muted="N/A" ;;
esac
printf '%s' "$muted"
}
getPauseStatus() {
status="$(getProperty pause)"
case "$status" in
true) status="paused" ;;
false) status="playing" ;;
*) status="N/A" ;;
esac
printf '%s' "$status"
}
# accepted properties: $1: filename $2: full path flag to skip basename
getPlaylistFilename() {
track="$1"
fullpathFlag="$2"
filename=$(printf '%s\n' "{ \"command\": [\"get_property_string\", \
\"playlist/$track/filename\"] }" | $SOCKETCOMMAND 2> /dev/null | \
cut -d\" -f 4)
test "$fullpathFlag" != "fullpath" && filename=$(cleanFilename "$filename")
printf '%s\n' "$filename"
}
# print all filenames in current playlist
getPlaylist() {
noColour="$1"
tracks=$(getPropertyString playlist-count)
currentTrack=$(getPropertyString playlist-pos)
test "$tracks" -eq 0 -o "$currentTrack" = "N/A" && {
printf '%s\n' "MPV instance on ${SOCKET} is currently idle." >&2
exit 1
}
calculateTerminalHeight
test "${SEQCOMMAND}" = "jot" && REPS=$((LAST - FIRST + 1))
for i in $("$SEQCOMMAND" $REPS "$FIRST" "$LAST"); do
filename=$(getPlaylistFilename $((i - 1)))
test "$noColour" != "nocolour" && {
test "$currentTrack" -eq $((i - 1)) && {
printf '%d  %s\n' "$i" "${filename} "
} || {
printf '%d %s\n' "$i" "${filename}"
}
} || {
printf '%d %s\n' "$i" "${filename}"
}
done
}
getFullPlaylist() {
noColour="$1"
tracks=$(getPropertyString playlist-count)
currentTrack=$(getPropertyString playlist-pos)
test "$tracks" -eq 0 -o "$currentTrack" = "N/A" && {
printf '%s\n' "MPV instance on ${SOCKET} is currently idle." >&2
exit 1
}
FIRST=1
LAST=$(getPropertyString playlist-count)
test "${SEQCOMMAND}" = "jot" && REPS=$((LAST - FIRST + 1))
for i in $("$SEQCOMMAND" $REPS "$FIRST" "$LAST"); do
filename=$(getPlaylistFilename $((i - 1)))
test "$noColour" != "nocolour" && {
test "$currentTrack" -eq $((i - 1)) && {
printf '%d  %s\n' "$i" "${filename} "
} || {
printf '%d %s\n' "$i" "${filename}"
}
} || {
printf '%d %s\n' "$i" "${filename}"
}
done
}
# open mpvc to retrieve all metadata from a playlist file
getFilenameMetadata() {
intCheck "$1" || exit 1
trackToMetadata="$1"
filename=$(getPlaylistFilename $trackToMetadata fullpath)
echo $filename
# mpvc -S /tmp/tempsock -m -a "$filename" -f \
# "Artist: %artist%"
}
# saves playlist to file but with no path checking. we live dangerously
savePlaylist() {
saveLocation="$1"
test -e "$saveLocation.m3u" && {
printf '%s\n' "Playlist exists! Overwrite? (y for yes, anything else no)"
oldstty=$(stty -g)
stty raw -echo; key="$(head -c 1)"; stty $oldstty
case $key in
y) rm $saveLocation.m3u ;;
*) return ;;
esac
}
printf '%s\n' "Adding files to $saveLocation.m3u..."
FIRST=1
LAST=$(getPropertyString playlist-count)
test "${SEQCOMMAND}" = "jot" && REPS=$((LAST - FIRST + 1))
for i in $("$SEQCOMMAND" $REPS "$FIRST" "$LAST"); do
printf '%s\n' "$(getPlaylistFilename $((i - 1)) fullpath)" >> "$saveLocation".m3u
done
QUIETFLAG=true
}
# Control Functions
###############################################################################
appendTrack() {
filename="$@"
# require absolute paths
test -e "$filename" -a "$(printf "%s" "$filename" | cut -c 1)" != '/' && {
filename="$(pwd)/$filename"
}
# don't add images or bad files to the queue by default
# this doesn't stop mpv from resolving directories and adding them anyway
test "$IMAGEFLAG" != "true" && {
case $filename in
*.png|*.jpg|*.jpeg|*.gif|*.psd|*.pdf)
return
;;
esac
}
pgrep -f "mpv .*$SOCKET" > /dev/null 2>&1 && {
printf '%s\n' "{ \"command\": [\"loadfile\", \"$filename\", \
\"append-play\" ] }" | $SOCKETCOMMAND > /dev/null 2>&1
} || {
exec mpv --really-quiet --idle=once --input-ipc-server="${SOCKET}" \
$MPVOPTIONS "$filename" &
# wait up to 5 seconds for mpv to start on $SOCKET
for i in $($SEQCOMMAND 50); do
idlestatus=$(getPropertyString idle-active)
test "$idlestatus" && break
sleep 0.01
done
}
filename=$(cleanFilename "$filename")
printf '%s\n' "Adding: ${filename}"
}
setTimeRelative() {
time=$(getPropertyString playback-time)
printf '%s\n' "$1" | grep "%" > /dev/null 2>&1 && {
percentageValue=$(printf '%s\n' "$1" | rev | cut -c 2- | rev)
printf '%s\n' "{ \"command\": [\"set_property\", \"percent-pos\", \
$percentageValue ] }" | $SOCKETCOMMAND > /dev/null
return
}
sign=$(printf '%s' "$1" | cut -c 1)
case "$sign" in
-)
minusFlag=true
timeArg=$(printf '%s' "$1" | cut -c 2-)
;;
+)
timeArg=$(printf '%s' "$1" | cut -c 2-)
;;
*)
timeArg=$1
;;
esac
timeSec=$(parseTimeString "$timeArg") || exit $?
test "$minusFlag" = "true" && {
time=$((time - timeSec))
} || {
time=$((time + timeSec))
}
printf '%s\n' "{ \"command\": [\"set_property\", \"playback-time\", \
$time ] }" | $SOCKETCOMMAND > /dev/null
}
setTimeAbsolute() {
time=$(parseTimeString "$1") || exit $?
trackTime=$(getPropertyString duration)
test "$time" -ge "$trackTime" && {
printf '%s\n' "Given time is greater than track length! ($(trackLength))"
QUIETFLAG=true
return 1
}
printf '%s\n' "{ \"command\": [\"set_property\", \"playback-time\", \
$time ] }" | $SOCKETCOMMAND > /dev/null
}
setVolumeRelative() {
intCheck "$1" || exit 1
volume=$(getPropertyString volume)
test "$volume" = "error" && {
printf '%s\n' "Currently playing media does not have sound." >&2
exit 1
}
volume=$((volume + $1))
test $volume -lt 0 && {
printf '%s\n' "Volume cannot be set lower than 0%" >&2
exit 1
}
test $volume -gt 130 && {
printf '%s\n' "Volume cannot be set great than 130%" >&2
exit 1
}
printf '%s\n' "{ \"command\": [\"set_property\", \"volume\", $volume ] }" | \
$SOCKETCOMMAND > /dev/null
}
setVolumeAbsolute() {
# test if media has sound
volume=$(getPropertyString volume)
test "$volume" = "error" && {
printf '%s\n' "Currently playing media does not have sound." >&2
return 1
}
intCheck "$1" || exit 1
volume=$1
test "$volume" -lt 0 && {
printf '%s\n' "Volume cannot be set lower than 0%" >&2
return 1
}
test "$volume" -gt 130 && {
printf '%s\n' "Volume cannot be set great than 130%" >&2
return 1
}
printf '%s\n' "{ \"command\": [\"set_property\", \"volume\", $volume ] }" | \
$SOCKETCOMMAND > /dev/null
}
setSpeedRelative() {
validateBC
speed=$(getSpeed)
fltCheck "$1" || exit 1
speed=$(printf '%s\n' "$speed+$1" | bc)
printf '%s\n' "{ \"command\": [\"set_property_string\", \"speed\", \
\"$speed\" ] }" | $SOCKETCOMMAND > /dev/null
}
setSpeedAbsolute() {
validateBC
fltCheck "$1" || exit 1
speed=$1
printf '%s\n' "{ \"command\": [\"set_property_string\", \"speed\", \
\"$speed\" ] }" | $SOCKETCOMMAND > /dev/null
}
setTrackRelative() {
intCheck "$1" || exit 1
currentTrack=$(getPropertyString playlist-pos)
desiredTrack=$((currentTrack + $1))
trackCount=$(getPropertyString playlist-count)
# if time is greater than 10 seconds, set time to 0
test "$desiredTrack" -lt "$currentTrack" && {
seconds=$(getPropertyString playback-time)
test "$seconds" -ge 10 && {
setTimeAbsolute 0
return
}
}
test "$desiredTrack" -ge "$trackCount" && {
repeat=$(getLoopStatus loop-playlist)
test "$repeat" = "yes" && {
desiredTrack=0
}
}
test "$desiredTrack" -lt 0 && {
repeat=$(getLoopStatus loop-file)
test "$repeat" = "yes" && {
setTrackAbsolute "$trackCount"
return
} || {
desiredTrack=0
}
}
printf '%s\n' "{ \"command\": [\"set_property\", \"playlist-pos\", \
$desiredTrack ] }" | $SOCKETCOMMAND > /dev/null
# tiny delay so printFinalOutput can catch up
sleep 0.5
}
setTrackAbsolute() {
intCheck "$1" || exit 1
currentTrack=$1
currentTrack=$((currentTrack - 1))
trackCount=$(getPropertyString playlist-count)
test "$currentTrack" -lt 0 || test "$currentTrack" -ge "$trackCount" && {
printf '%s\n' "Item $currentTrack is out of range of playlist." >&2
exit 1
}
printf '%s\n' "{ \"command\": [\"set_property\", \
\"playlist-pos\", $currentTrack ] }" | $SOCKETCOMMAND > /dev/null
# tiny delay so printFinalOutput can catch up
sleep 0.5
}
moveTrack() {
intCheck "$1" || exit 1
test -z "$2" && {
trackToMove=$(getPropertyString playlist-pos)
newTrackPosition=$1
} || {
trackToMove=$1
trackToMove=$((trackToMove - 1))
newTrackPosition=$2
trackCount=$(getPropertyString playlist-count)
test "$trackToMove" -lt 0 || test "$trackToMove" -ge "$trackCount" && {
printf '%s\n' "Item $trackToMove is out of range of playlist." >&2
exit 1
}
}
test "$newTrackPosition" -lt 0 && {
printf '%s\n' "Position $newTrackPosition is out of range of playlist." >&2
exit 1
}
test "$newTrackPosition" -eq 1 && newTrackPosition=0
printf '%s\n' "{ \"command\": [\"playlist-move\", \"$trackToMove\", \
\"$newTrackPosition\" ] }" | $SOCKETCOMMAND > /dev/null
test "$QUIETFLAG" != "true" && {
getPlaylist
QUIETFLAG=true
}
}
removeTrack() {
trackToRemove=$1
test "$trackToRemove" = "current" && {
printf '%s\n' "{ \"command\": [\"playlist-remove\", \
\"$trackToRemove\" ] }" | $SOCKETCOMMAND > /dev/null
} || {
intCheck "$1" || exit 1
trackToRemove=$((trackToRemove - 1))
trackCount=$(getPropertyString playlist-count)
test "$trackToRemove" -lt 0 || test "$trackToRemove" -ge "$trackCount" && {
printf '%s\n' "Item $trackToRemove is out of range of playlist." >&2
exit 1
}
filename="$(getPlaylistFilename $trackToRemove)"
printf '%s\n' "{ \"command\": [\"playlist-remove\", \
\"$trackToRemove\" ] }" | $SOCKETCOMMAND > /dev/null
}
test "$QUIETFLAG" != "true" && {
printf '%s\n' "$filename has been removed from the playlist."
exit
}
}
alwaysPlay() {
currentTrack=$(getPropertyString playlist-pos)
test "$currentTrack" = "N/A" && {
setTrackAbsolute 1
return
}
printf '%s\n' "{ \"command\": [\"set_property\", \"pause\", false ] }" | \
$SOCKETCOMMAND > /dev/null
}
alwaysPause() {
status="true"
printf '%s\n' "{ \"command\": [\"set_property\", \"pause\", $status ] }" | \
$SOCKETCOMMAND > /dev/null
}
togglePause() {
currentTrack=$(getPropertyString playlist-pos)
test "$currentTrack" = "N/A" && {
setTrackAbsolute 1
return
}
status=$(getPauseStatus)
test "$status" = "playing" && status="true" || status="false"
printf '%s\n' "{ \"command\": [\"set_property\", \"pause\", $status ] }" | \
$SOCKETCOMMAND > /dev/null
}
toggleMute() {
test -z "$1" && {
muted=$(getMuteStatus)
test "$muted" = "no" && muted="true" || muted="false"
} || {
muted=$1
}
printf '%s\n' "{ \"command\": [\"set_property\", \"mute\", $muted ] }" | \
$SOCKETCOMMAND > /dev/null
}
toggleLoopFile() {
test -z "$1" && {
loopFile=$(getLoopStatus loop-file)
test "$loopFile" = "no" && loopFile="inf" || loopFile="no"
} || {
loopFile=$1
}
printf '%s\n' "{ \"command\": [\"set_property_string\", \"loop-file\", \
\"$loopFile\" ] }" | $SOCKETCOMMAND > /dev/null
}
toggleLoopPlaylist() {
test -z "$1" && {
loopPlaylist=$(getLoopStatus loop-playlist)
test "$loopPlaylist" = "no" && loopPlaylist="inf" || loopPlaylist="no"
} || {
loopPlaylist=$1
}
printf '%s\n' "{ \"command\": [\"set_property_string\", \"loop-playlist\", \
\"$loopPlaylist\" ] }" | $SOCKETCOMMAND > /dev/null
}
shufflePlaylist() {
printf '%s\n' "{ \"command\": [\"playlist-shuffle\" ] }" | \
$SOCKETCOMMAND > /dev/null
test "$QUIETFLAG" != "true" && {
printf '%s\n' "Playlist shuffled."
QUIETFLAG=true
}
test "$(getLoopStatus loop-playlist)" != "yes" && {
toggleLoopPlaylist inf
}
}
cropPlaylist() {
printf '%s\n' "{ \"command\": [\"playlist-clear\" ] }" | \
$SOCKETCOMMAND > /dev/null
test "$QUIETFLAG" != "true" && {
getPlaylist
QUIETFLAG=true
}
}
clearPlaylist() {
QUIETFLAG=true
# kill any running process of mpvc add
pgrep -f "mpvc add" > /dev/null 2>&1 && {
pkill -f "mpvc add"
}
cropPlaylist
removeTrack 1
printf '%s\n' "Playlist cleared."
}
# quit mpv process on given socket
killSocket() {
printf '%s\n' "{ \"command\": [\"quit\"] }" | $SOCKETCOMMAND > /dev/null
exit
}
# kill all instances of mpv running under your user
killAllMpv() {
pkill "mpv" /dev/null 2>&1
exit
}
# Time Functions
###############################################################################
elapsedTime() {
time=$(getPropertyString playback-time)
formatTime "$time"
}
preciseElapsedTime() {
time=$(getPropertyString playback-time 0.0)
bigTime=$(printf '%s\n' "$time" | cut -d. -f 1)
tinyTime=$(printf '%s\n' "$time" | cut -d. -f 2)
formatTime "$bigTime" "$tinyTime"
}
trackLength() {
duration=$(getPropertyString duration)
formatTime "$duration"
}
playtimeRemaining() {
playtime=$(getPropertyString playtime-remaining)
formatTime "$playtime"
}
# format seconds into HH:MM:SS format
formatTime() {
test "$1" = "N/A" && return 1
rawSeconds=$1
seconds=$((rawSeconds % 60))
minutes=$((rawSeconds / 60))
hours=$((minutes / 60))
test $seconds -lt 10 && seconds="0$seconds"
test $minutes -ge 60 && minutes=$((minutes - hours*60))
test $minutes -lt 10 && minutes="0$minutes"
test $hours -lt 10 && hours="0$hours"
test -z "$2" && {
printf '%s\n' "$hours:$minutes:$seconds"
} || {
milleSeconds=$2
printf '%s\n' "$hours:$minutes:$seconds.$milleSeconds"
}
}
# Formatting and Printing Functions
###############################################################################
# formats and prints according to $FORMATSTRING
formatPrint() {
# modified format string
FORMATSTRING=$(printf '%s\n' "$FORMATSTRING" | sed "
s#%status%#$(getPauseStatus)#g
s#%year%#$(getMetadata date)#g
s#%genre%#$(getMetadata genre)#g
s#%title%#$(getMetadata title)#g
s#%album%#$(getMetadata album)#g
s#%artist%#$(getMetadata artist)#g
s#%comment%#$(getMetadata comment)#g
s#%albumartist%#$(getMetadata album_artist)#g
s#%speed%#$(getSpeed)#g
s#%time%#$(elapsedTime)#g
s#%precisetime%#$(preciseElapsedTime)#g
s#%volume%#$(getPropertyString volume)#g
s#%length%#$(trackLength)#g
s#%remaining%#$(playtimeRemaining)#g
s#%muted%#$(getMuteStatus)#g
s#%percentage%#$(getPropertyString percent-pos)#g
s#%name%#$(getMediaProperties filename)#g
s#%path%#$(getMediaProperties path)#g
s#%dir%#$(getPropertyString working-directory)#g
s#%repeat%#$(getLoopStatus loop-playlist)#g
s#%single%#$(getLoopStatus loop-file)#g
s#%playlistlength%#$(getPropertyString playlist-count)#g
s#%position%#$(($(getPropertyString playlist-pos) + 1))#g
s#%frame%#$(getProperty estimated-frame-number)#g
s#%width%#$(getProperty width)#g
s#%height%#$(getProperty height)#g
s#%ab-loop-a%#$(getProperty ab-loop-a)#g
s#%ab-loop-b%#$(getProperty ab-loop-b)#g
")
printf '%s\n' "${FORMATSTRING}"
exit
}
# print default status of mpv instance
printDefaultStatus() {
artist=$(getMetadata artist)
test "$artist" != "N/A" && {
FORMATSTRING="\
${artist} - %title%
[%status%] #%position%/%playlistlength% %time%/%length% (%percentage%%)
speed: %speed%x volume: %volume%% muted: %muted% repeat: %repeat% single: %single%"
} || {
FORMATSTRING="\
%title%
[%status%] #%position%/%playlistlength% %time%/%length% (%percentage%%)
speed: %speed%x volume: %volume%% muted: %muted% repeat: %repeat% single: %single%"
}
formatPrint
}
printPrettyOutput() {
exit 0
}
# catches if mpv is idle or not
printFinalOutput() {
test "$QUIETFLAG" != "true" && {
test "$(getPropertyString idle-active)" = "yes" && {
printf '%s\n' "MPV instance on ${SOCKET} is currently idle." >&2
} || {
printDefaultStatus
}
}
exit
}
# Misc. Functions
###############################################################################
escapeSedChar() {
awk -F "$1" '{
for (x=1; x<NF; x++) printf "%s\\'"$1"'", $x
printf "%s", $NF
exit }'
}
intCheck() {
test "$1" -ne 0 2> /dev/null
test "$?" -ne 2 || return 1
}
fltCheck() {
intCheck "$1" && return 0
case "$1" in
*.*.*|*[!-.0-9]*) ;;
*[0-9].[0-9]*) return 0 ;;
esac
return 1
}
calculateTerminalHeight() {
rows=$(($(tput lines) - 2))
halfrows=$((rows / 2))
test "$tracks" -gt $rows && {
test "$currentTrack" -le $halfrows && {
FIRST=1
LAST=$rows
return
}
test $((currentTrack + halfrows)) -ge "$tracks" && {
FIRST=$((tracks - rows + 1))
LAST=$tracks
return
}
test $((currentTrack + halfrows)) -lt "$tracks" && {
FIRST=$((currentTrack - halfrows + 1))
LAST=$((currentTrack + halfrows))
return
}
}
FIRST=1
LAST=$tracks
}
parseTimeString() {
printf '%s' "$1" | grep -q -e "^\([0-9.]*:\)\{0,2\}[0-9.]*$" -e "^[0-9]*[sSmMhH]$" || {
cat >&2 << EOF
Timestamp formats must match either H:M:S with hour and minute fields optional,
or a single integer number with a unit of time appended: h, m, s.
EOF
exit 1
}
lastChar=$(printf '%s\n' "$1" | rev | cut -c 1)
case "$lastChar" in
s|S|m|M|h|H)
timeInt=$(printf '%s' "$1" | rev | cut -c 2- | rev)
intCheck "$timeInt" || exit 1
case "$lastChar" in
h|H) timeInt=$((timeInt * 60 * 60)) ;;
m|M) timeInt=$((timeInt * 60)) ;;
esac
;;
*)
timeInt=$(printf '%s' "$1" | awk -F ':' '{
time=0
for (x=1; x<=NF; x++) time=time*60+$x
printf "%d\n", time
exit
}')
;;
esac
printf "%d" "$timeInt"
}
validateDeps() {
type mpv > /dev/null 2>&1 || {
printf '%s\n' "Cannot find mpv on your \$PATH." >&2
exit 4
}
type nc > /dev/null 2>&1 && SOCKETCOMMAND="nc -U -N $SOCKET"
type socat > /dev/null 2>&1 && SOCKETCOMMAND="socat - $SOCKET"
test "$SOCKETCOMMAND" || {
printf '%s\n' "Cannot find socat or nc on your \$PATH." >&2
exit 4
}
type jot > /dev/null 2>&1 && SEQCOMMAND="jot"
type seq > /dev/null 2>&1 && SEQCOMMAND="seq"
test "$SEQCOMMAND" || {
printf '%s\n' "Cannot find seq or jot on your \$PATH." >&2
exit 4
}
}
validateBC() {
type bc > /dev/null 2>&1 || {
printf '%s\n' "Cannot find bc on your \$PATH."
printf '%s\n' "Please install for speed control."
exit 4
}
}
validateSocket() {
test "$PLAYFLAG" != "true" && {
# test if socket exists
test -S "$SOCKET" || {
printf '%s\n' "$SOCKET does not exist. Use mpv --input-ipc-server to start one." >&2
exit 2
}
# test if socket is open
status="$(getPauseStatus)"
test "$status" = "N/A" && {
printf '%s\n' "No files added to $SOCKET."
exit 3
}
} || {
return
}
}
getVersion() {
mpv --version
printf '%s\n' "MPVC Release 1.3 (c) Laurence Willetts MIT"
exit
}
main() {
validateDeps
# grab mpv options first if any
for arg in "$@"; do
test "$MPVFLAG" = "true" && {
MPVOPTIONS="$MPVOPTIONS $arg"
} || {
test "$arg" = "--" && MPVFLAG=true
}
done
# grab input files next
for arg in "$@"; do
case "$arg" in
--) break ;;
-?|--*) APPENDFLAG=false ;;
esac
test "$APPENDFLAG" = "true" && {
APPENDED=true
QUIETFLAG=true
appendTrack "$arg"
}
case "$arg" in
add|-a|--append) APPENDFLAG=true ;;
esac
done
# grab piped input if no files were passed and we're expecting data on stdin
test -z "$APPENDED" &&
{ test "$APPENDFLAG" = "true" || test -p /dev/stdin ; } && {
QUIETFLAG=true
while read -r line; do
appendTrack "${line}"
done
}
validateSocket
# mpc compatibility layer
case "$1" in
play|start|resume)
case "$2" in
0)
setTrackAbsolute 1
;;
$)
setTrackAbsolute $(getPropertyString playlist-count)
;;
*)
intCheck "$2" && setTrackAbsolute "$2"
;;
esac
alwaysPlay
printFinalOutput
;;
vol|volume)
firstChar=$(printf '%s\n' "$2" | cut -c 1)
case "$firstChar" in
"+")
volume=$(printf '%s\n' "$2" | cut -d+ -f2)
setVolumeRelative "$volume"
;;
"-")
volume=$(printf '%s\n' "$2" | cut -d- -f2)
setVolumeRelative "-${volume}"
;;
*)
test ! -z "$2" && {
setVolumeAbsolute "$2"
} || {
printf '%s\n' "Specify volume in/decrease amount or absolute amount."
return 1
}
;;
esac
printFinalOutput
;;
repeat)
case "$2" in
"on") toggleLoopPlaylist inf ;;
"off") toggleLoopPlaylist no ;;
*) toggleLoopPlaylist ;;
esac
printFinalOutput
;;
single)
case "$2" in
"on") toggleLoopFile yes ;;
"off") toggleLoopFile no ;;
*) toggleLoopFile ;;
esac
printFinalOutput
;;
metadata) getFilenameMetadata "$2" ;;
pause) alwaysPause ; printFinalOutput ;;
next) setTrackRelative 1 ; printFinalOutput ;;
prev) setTrackRelative -1 ; printFinalOutput ;;
move) moveTrack "$2" "$3" ; printFinalOutput ;;
mute) toggleMute true ; printFinalOutput ;;
unmute) toggleMute false ; printFinalOutput ;;
pretty) printPrettyOutput ;;
# find) do fuzzy search
# idleloop) observe_property
# consume) to implement # add on|off toggle
# random) MPV doesn't have this control option!
# create an issue or implement puesdo-random tracks
esac
# GNU-style options
for arg in "$@"; do
test "$SEEKFLAG" = "true" && setTimeRelative "$arg"; SEEKFLAG=false
test "$TIMEFLAG" = "true" && setTimeAbsolute "$arg"; TIMEFLAG=false
test "$VOLFLAG" = "true" && setVolumeRelative "$arg"; VOLFLAG=false
test "$VOLUMEFLAG" = "true" && setVolumeAbsolute "$arg"; VOLUMEFLAG=false
test "$SPEEDFLAG" = "true" && setSpeedRelative "$arg"; SPEEDFLAG=false
test "$SPEEDVALFLAG" = "true" && setSpeedAbsolute "$arg"; SPEEDVALFLAG=false
test "$TRACKFLAG" = "true" && setTrackRelative "$arg"; TRACKFLAG=false
test "$TRACKVALFLAG" = "true" && setTrackAbsolute "$arg"; TRACKVALFLAG=false
test "$REMOVEFLAG" = "true" && removeTrack "$arg"; REMOVEFLAG=false
test "$SAVEFLAG" = "true" && savePlaylist "$arg"; SAVEFLAG=false
case "$arg" in
-t|--seek|seek) SEEKFLAG=true ;;
-T|--time) TIMEFLAG=true ;;
-v|--vol) VOLFLAG=true ;;
-V|--volume) VOLUMEFLAG=true ;;
-x|--speed|speed) SPEEDFLAG=true ;;
-X|--speedval) SPEEDVALFLAG=true ;;
-j|--track) TRACKFLAG=true ;;
-J|--tracknum) TRACKVALFLAG=true ;;
-r|--remove|rm|remove|del) REMOVEFLAG=true ;;
--save|save) SAVEFLAG=true ;;
-s|--stop|stop) alwaysPause; setTimeAbsolute 0 ;;
-P|--play|play) alwaysPlay ;;
-p|--toggle|toggle) togglePause ;;
-m|--mute) toggleMute ;;
mute) toggleMute true ;;
unmute) toggleMute false ;;
-i|--playlist|playlist) getPlaylist; exit ;;
-I|--fullplaylist|fullplaylist) getFullPlaylist; exit ;;
-L|--loop|loop) toggleLoopPlaylist ;;
-l|--loopfile|loopfile) toggleLoopFile ;;
-z|--shuffle|shuffle) shufflePlaylist ;;
-c|--crop|crop) cropPlaylist ;;
-C|--clear|clear) clearPlaylist ;;
-k|--kill|kill) killSocket ;;
--version|version) getVersion ;;
-f|--format) continue ;;
-a|--append) continue ;;
-S|--socket) continue ;;
-[1-9][0-9][0-9][0-9][0-9][0-9]) continue ;;
--|---|----) continue ;;
-?) usage 1 ;;
esac
done
# produce format strings last
test "$QUIETFLAG" != "true" && {
test ! -z "$FORMATSTRING" && formatPrint
printFinalOutput
}
}
# more global argument parsing
for arg in "$@"; do
test "$SOCKETFLAG" = "true" && SOCKET=$arg && SOCKETFLAG=false
test "$FORMATFLAG" = "true" && FORMATSTRING=$arg && FORMATFLAG=false
case $arg in
--version) getVersion ;;
-q|--quiet) QUIETFLAG=true ;;
-I|--image) IMAGEFLAG=true ;;
-S|--socket) SOCKETFLAG=true ;;
-f|--format) FORMATFLAG=true ;;
-Q|--vid=no) MPVOPTIONS="$MPVOPTIONS --vid=no" ;;
-K|--killall) killAllMpv ;;
--list-options) usage 0 ;;
-h|--help|h|help) usage 0 ;;
esac
done
test "$QUIETFLAG" = "true" && {
main "$@" >/dev/null 2>&1
} || {
main "$@"
}
#!/bin/bash
# simple YouTube TV
# kills any currently playing video as soon as a new video is queued
export SCREEN_ID="8ndhpemp4aq6750bftr7kuo4b4"
export SCREEN_NAME="YCast"
export SCREEN_APP="ycast-v1"
gotubecast -s "$SCREEN_ID" -n "$SCREEN_NAME" -i "$SCREEN_APP" | while read line
do
cmd="`cut -d ' ' -f1 <<< "$line"`"
arg="`cut -d ' ' -f2 <<< "$line"`"
case "$cmd" in
pairing_code)
echo "Your pairing code: $arg"
;;
remote_join)
cut -d ' ' -f3- <<< "$line connected"
;;
video_id)
mpvc -k >/dev/null &
mpvc -a "ytdl://$arg" >/dev/null &
;;
play | pause)
mpvc -p >/dev/null &
;;
stop)
mpvc -k >/dev/null &
;;
seek_to)
mpvc -T $arg >/dev/null &
;;
set_volume)
mpvc -V $arg >/dev/null &
;;
esac
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment