Created
March 30, 2020 14:07
-
-
Save fa7ad/5b2aafc5722e61de41be4322a731bf4b to your computer and use it in GitHub Desktop.
Cast Youtube to MPV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 [7m %s[0m\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 [7m %s[0m\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 "$@" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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