mpvremote/mpvremote2.sh

359 lines
9.3 KiB
Bash
Raw Normal View History

2026-09-18 19:37:44 -04:00
#!/usr/bin/env bash
# ==============================================================================
# GPLv3: You better join us now and share the software, motherfucker.
# Keep the code free as in freedom.
# If you're gonna sell it, give 'em the code too.
#
# WTF IS THIS?
# A quick-and-dirty "TV style" remote control for mpv over a UNIX socket.
#
# ASSUMPTIONS:
# 1. mpv is already running.
# 2. mpv was launched with:
# --input-ipc-server=/tmp/mpv-socket
# or the socket is configured in mpv.conf.
# ==============================================================================
SOCKET="/tmp/mpv-socket"
MEDIA_DIR="$HOME/GDrive/Videos"
if [[ ! -S "$SOCKET" ]]; then
echo "Error: Socket $SOCKET not found or mpv is not running."
exit 1
fi
# ------------------------------------------------------------------------------
# Send an mpv IPC command.
#
# Usage:
# mpv-cmd "cycle" "pause"
# mpv-cmd "seek" "10"
# mpv-cmd "loadfile" "/path/to/file" "append-play"
# ------------------------------------------------------------------------------
mpv-cmd() {
local json_args=""
local arg
for arg in "$@"; do
# Escape JSON string characters.
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//$'\n'/\\n}"
arg="${arg//$'\r'/\\r}"
arg="${arg//$'\t'/\\t}"
if [[ -n "$json_args" ]]; then
json_args+=","
fi
json_args+="\"$arg\""
done
printf '{"command":[%s]}\n' "$json_args" |
socat -t 1 - "$SOCKET" >/dev/null 2>&1
}
# ------------------------------------------------------------------------------
# Get an mpv property.
#
# Requires jq. This avoids trying to parse JSON with sed.
# ------------------------------------------------------------------------------
mpv-get() {
local prop="$1"
if ! command -v jq >/dev/null 2>&1; then
echo "Error: jq is required for property queries." >&2
return 1
fi
printf '{"command":["get_property","%s"]}\n' "$prop" |
socat -t 1 - "$SOCKET" 2>/dev/null |
jq -r '.data // empty'
}
# ------------------------------------------------------------------------------
# Queue a video.
# ------------------------------------------------------------------------------
queue_picker() {
echo
echo "--- Queue Media from $MEDIA_DIR ---"
if [[ ! -d "$MEDIA_DIR" ]]; then
echo "Directory not found: $MEDIA_DIR"
return 1
fi
local clean_dir="${MEDIA_DIR%/}"
declare -A video_map
local exts=(
"mp4"
"mkv"
"avi"
"webm"
)
local find_args=()
for ext in "${exts[@]}"; do
if [[ ${#find_args[@]} -gt 0 ]]; then
find_args+=("-o")
fi
find_args+=("-iname" "*.$ext")
done
while IFS= read -r -d '' file; do
local rel_name="${file#"${clean_dir}/"}"
video_map["$rel_name"]="$file"
done < <(
find "$clean_dir" \
-type f \
\( "${find_args[@]}" \) \
-print0
)
if [[ ${#video_map[@]} -eq 0 ]]; then
echo "No video files found."
return 1
fi
local selected_key=""
if command -v fzf >/dev/null 2>&1; then
selected_key=$(
printf '%s\n' "${!video_map[@]}" |
sort |
fzf --prompt="Select video to queue: "
)
else
echo "Choose a file to queue (or Cancel):"
PS3="Enter number: "
local keys=("${!video_map[@]}")
select choice in "${keys[@]}" "Cancel"; do
if [[ "$choice" == "Cancel" || -z "$choice" ]]; then
echo "Cancelled."
return 0
fi
selected_key="$choice"
break
done
fi
if [[ -n "$selected_key" && -n "${video_map[$selected_key]:-}" ]]; then
local abs_path="${video_map[$selected_key]}"
echo "Queuing: $(basename "$abs_path")"
mpv-cmd "loadfile" "$abs_path" "append-play"
mpv-cmd "set_property" "pause" "no"
fi
}
cleanup() {
if [[ -n "${old_stty:-}" ]]; then
stty "$old_stty" 2>/dev/null
else
stty sane 2>/dev/null
fi
echo
}
# ------------------------------------------------------------------------------
# Edit the current playlist via fzf.
#
# Enter -> skip to selected item
# Del -> remove selected item
# q/Esc/Left -> return to remote
# ------------------------------------------------------------------------------
edit_playlist() {
local playlist_json fzf_input selected key_pressed idx
while true; do
playlist_json=$(mpv-get "playlist")
if [[ -z "$playlist_json" || "$playlist_json" == "[]" ]]; then
echo "Playlist is empty."
return
fi
fzf_input=$(printf '%s' "$playlist_json" | jq -r '
to_entries[] |
(if .value.current == true then "▶ " else "" end) as $prefix |
"\(.key)|\($prefix)\(.value.title // (.value.filename | split("/") | last))"
')
if [[ -z "$fzf_input" ]]; then
echo "Playlist is empty."
return
fi
readarray -t result < <(
printf '%s\n' "$fzf_input" | fzf \
--delimiter='|' \
--with-nth=2 \
--prompt="Playlist (Enter=skip, Del=remove, Esc/q/←=back): " \
--expect=del,left,esc,q \
--header="Current Playlist"
)
key_pressed="${result[0]:-}"
selected="${result[1]:-}"
if [[ -z "$selected" ]]; then
return
fi
idx="${selected%%|*}"
if [[ -z "$key_pressed" ]]; then
# Enter pressed - skip to index
mpv-cmd "set_property" "playlist-pos" "$idx"
return
elif [[ "$key_pressed" == "del" ]]; then
mpv-cmd "playlist-remove" "$idx"
# Refresh loop
continue
else
# q, esc, left
return
fi
done
}
remote() {
local old_stty
old_stty=$(stty -g)
trap 'exit 130' INT
trap 'exit 143' TERM
trap cleanup EXIT
stty -echo -icanon min 1 time 0
echo "=== MPV Terminal Remote ==="
echo "[Space] Pause | [h/l] Seek -10s/+10s | [j/k] Vol -/+ | [s] Subtitle"
echo "[o] Open/Queue Video | [e] Edit Playlist | [n] Next Track | [p] Prev Track"
echo "[r] Repeat Current Vid | [R] Repeat Playlist | [c] Controls | [q] Quit"
echo "Listening..."
while IFS= read -r -n1 key; do
case "$key" in
' ')
mpv-cmd "cycle" "pause"
is_paused=$(mpv-get "pause")
if [[ "$is_paused" == "true" ]]; then
echo ">> Video Paused..."
else
echo ">> Video Unpaused..."
fi
;;
c)
echo "=== MPV Terminal Remote ==="
echo "[Space] Pause | [h/l] Seek -10s/+10s | [j/k] Vol -/+ | [s] Subtitle"
echo "[o] Open/Queue Video | [e] Edit Playlist | [n] Next Track | [p] Prev Track"
echo "[r] Repeat Current Vid | [R] Repeat Playlist | [c] Controls | [q] Quit"
;;
h)
mpv-cmd "seek" "-10"
echo ">> Seeked -10s"
;;
l)
mpv-cmd "seek" "10"
echo ">> Seeked +10s"
;;
k)
mpv-cmd "add" "volume" "5"
vol=$(mpv-get "volume")
echo ">> Volume Level: ${vol%.*}"
;;
j)
mpv-cmd "add" "volume" "-5"
vol=$(mpv-get "volume")
echo ">> Volume Level: ${vol%.*}"
;;
s)
mpv-cmd "cycle" "sub"
sub_state=$(mpv-get "sub")
if [[ "$sub_state" == "false" ||
"$sub_state" == "no" ||
-z "$sub_state" ]]; then
echo ">> Subtitles Disabled..."
else
echo ">> Subtitles Enabled... ($sub_state)"
fi
;;
n)
mpv-cmd "playlist-next"
echo ">> Next Track"
;;
p)
mpv-cmd "playlist-prev"
echo ">> Prev Track"
;;
r)
f_loop=$(mpv-get "loop-file")
if [[ "$f_loop" == "no" || "$f_loop" == "false" || -z "$f_loop" ]]; then
mpv-cmd "set_property" "loop-file" "inf"
echo ">> Loop Current Vid: yes (inf)"
else
mpv-cmd "set_property" "loop-file" "no"
echo ">> Loop Current Vid: no"
fi
;;
R)
p_loop=$(mpv-get "loop-playlist")
if [[ "$p_loop" == "no" || "$p_loop" == "false" || -z "$p_loop" ]]; then
mpv-cmd "set_property" "loop-playlist" "inf"
echo ">> Loop Current Playlist: yes (inf)"
else
mpv-cmd "set_property" "loop-playlist" "no"
echo ">> Loop Current Playlist: no"
fi
;;
e)
stty "$old_stty"
edit_playlist
stty -echo -icanon min 1 time 0
echo "Listening..."
;;
o)
stty "$old_stty"
queue_picker
stty -echo -icanon min 1 time 0
echo "Listening..."
;;
q)
echo
echo "Exiting remote."
exit 0
;;
esac
done
}
remote "$@"