From 51fc669c0228dbb5f4082c5334fc004da6f27324 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Fri, 21 Aug 2026 02:47:45 -0400 Subject: [PATCH] feat(media): add play-media fzf picker for audio/video files Fuzzy-filters audio/video files under the current directory via fd, previews them with thumbnails pulled from the freedesktop thumbnail cache (or ffprobe metadata as a fallback), and plays the selection through the best available player: --player flag, $play_media_player, xdg-mime default, then a short mpv/vlc fallback list. Adds mpv/vlc as optional fish-deps entries. --- completions/play-media.fish | 2 + functions/_fish_deps_catalog.fish | 10 +-- functions/_fzf_preview_media.fish | 60 ++++++++++++++ functions/play-media.fish | 127 ++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 completions/play-media.fish create mode 100644 functions/_fzf_preview_media.fish create mode 100644 functions/play-media.fish diff --git a/completions/play-media.fish b/completions/play-media.fish new file mode 100644 index 0000000..daa415f --- /dev/null +++ b/completions/play-media.fish @@ -0,0 +1,2 @@ +complete --command play-media --short p --long player --description "Force a specific media player" --require-parameter +complete --command play-media --short h --long help --description "Print this help message" diff --git a/functions/_fish_deps_catalog.fish b/functions/_fish_deps_catalog.fish index 36ea98b..41aa526 100644 --- a/functions/_fish_deps_catalog.fish +++ b/functions/_fish_deps_catalog.fish @@ -36,27 +36,27 @@ function _fish_deps_catalog set -g _fdc_bins \ uv cargo fish starship fzf zoxide direnv paru yay \ wakatime tailscale \ - eza lsd bat btop dust duf prettyping go ov rg lazygit lazydocker docker trash kitty wezterm python3 yt-dlp screen + eza lsd bat btop dust duf prettyping go ov rg lazygit lazydocker docker trash kitty wezterm python3 yt-dlp screen mpv vlc set -g _fdc_tiers \ rec rec req rec req rec rec rec rec \ int int \ - rec rec rec opt opt opt opt opt rec rec opt opt opt rec term term rec opt opt + rec rec rec opt opt opt opt opt rec rec opt opt opt rec term term rec opt opt opt opt set -g _fdc_cargo \ "" "" "" starship "" zoxide "" "" "" \ "" "" \ - eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" + eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" set -g _fdc_pm \ uv cargo fish starship fzf zoxide direnv "" yay \ wakatime tailscale \ - eza lsd bat btop dust duf prettyping go ov ripgrep lazygit lazydocker docker trash kitty wezterm python yt-dlp screen + eza lsd bat btop dust duf prettyping go ov ripgrep lazygit lazydocker docker trash kitty wezterm python yt-dlp screen mpv vlc set -g _fdc_special \ curl-uv rustup-installer git-cargo-fish curl-installer fzf-update "" "" paru-build yay-build \ wakatime-binary "" \ - "" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" + "" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" end # SYNOPSIS diff --git a/functions/_fzf_preview_media.fish b/functions/_fzf_preview_media.fish new file mode 100644 index 0000000..375cdfd --- /dev/null +++ b/functions/_fzf_preview_media.fish @@ -0,0 +1,60 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# _fzf_preview_media +# +# DESCRIPTION +# Preview helper for play-media. Looks for a thumbnail already generated +# by a desktop file manager (Dolphin, Nautilus, GNOME Videos, ...) in the +# freedesktop thumbnail cache and renders it via _fzf_preview_image if +# found. Otherwise falls back to ffprobe-formatted metadata (duration, +# codec, resolution, tags) when ffprobe is installed, or plain `file` +# output as a last resort. Neither the thumbnail cache lookup nor ffprobe +# are tracked in fish-deps: both are best-effort, matching how the +# image-preview tool chain (kitten/chafa/viu/timg) is already handled. +# +# ARGUMENTS +# file_path Path to the audio/video file to preview +# +# EXIT STATUS +# 0 Always +# +# EXAMPLE +# _fzf_preview_media ./Videos/clip.mp4 +function _fzf_preview_media + set -f file_path $argv + if not test -e "$file_path" + echo "$file_path doesn't exist." >&2 + return 0 + end + + set -l abs_path (realpath -- "$file_path" 2>/dev/null) + + # freedesktop.org thumbnail spec: md5 of the file:// URI names the + # cached thumbnail. Dolphin/Nautilus/GNOME Videos already populate this + # cache, so reuse it instead of generating a new thumbnail ourselves. + set -l uri "file://"(string escape --style=url -- "$abs_path") + set -l hash (echo -n "$uri" | md5sum | string split ' ')[1] + set -l cache_home (set --query XDG_CACHE_HOME; and echo $XDG_CACHE_HOME; or echo "$HOME/.cache") + + for size in normal large x-large xx-large + set -l thumb "$cache_home/thumbnails/$size/$hash.png" + if test -f "$thumb" + _fzf_preview_image "$thumb" + return 0 + end + end + + if command -q ffprobe + set -l info (command ffprobe -v error -show_entries \ + format=duration,bit_rate:format_tags=title,artist,album:stream=codec_name,codec_type,width,height \ + -of default=noprint_wrappers=1 -- "$abs_path" 2>/dev/null) + if test -n "$info" + printf '%s\n' $info + return 0 + end + end + + command file --brief -- "$abs_path" +end diff --git a/functions/play-media.fish b/functions/play-media.fish new file mode 100644 index 0000000..f6b6f03 --- /dev/null +++ b/functions/play-media.fish @@ -0,0 +1,127 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# CATEGORY +# 13-media-and-utilities +# +# SYNOPSIS +# play-media [-p|--player ] +# play-media --help +# +# DESCRIPTION +# Opens an fzf picker (with thumbnail/metadata preview via +# _fzf_preview_media) listing audio and video files under the current +# directory, and plays the selection(s) in the best available media +# player. Supports multi-select (Tab) to queue several files at once. +# +# Player resolution order: +# 1. -p/--player (explicit override, validated as a command) +# 2. $play_media_player (explicit override, validated as a command) +# 3. xdg-mime default handler for the first selected file's mimetype +# 4. First known player binary found in a built-in list (mpv, vlc) +# +# The player is launched backgrounded and detached, mirroring open-url, +# so the shell is never blocked. +# +# ARGUMENTS +# -p, --player Force a specific player command +# -h, --help Print usage and exit +# +# EXIT STATUS +# 0 Player launched (or the picker was cancelled) +# 1 No media files found, invalid --player/$play_media_player, or no +# player found +# +# EXAMPLE +# play-media +# play-media --player mpv +function play-media --description 'Pick audio/video files with fzf and play them' + argparse h/help p/player= -- $argv + or return 1 + + if set -q _flag_help + echo "Usage: play-media [-p|--player ]" + echo "Pick audio/video files under the current directory with fzf and play them." + echo + echo " -p, --player Force a specific player command" + echo " -h, --help Show this help" + return 0 + end + + set -l audio_exts mp3 flac wav ogg oga opus m4a aac wma alac aiff + set -l video_exts mp4 mkv webm avi mov wmv flv m4v mpg mpeg ts + + # Directly use fd binary to avoid output buffering delay caused by a fd + # alias, if any. Debian-based distros install fd as fdfind. + set -f fd_cmd (command -v fdfind || command -v fd || echo "fd") + set -f --append fd_cmd --color=always $fzf_fd_opts + for ext in $audio_exts $video_exts + set -f --append fd_cmd -e $ext + end + + set -l selection ($fd_cmd 2>/dev/null | _fzf_wrapper --ansi --multi \ + --height=90% \ + --layout=reverse \ + --border=rounded \ + --border-label=' Play Media ' \ + --prompt='Play> ' \ + --header='Enter: Play Tab: Select Ctrl-/: Toggle Preview Esc: Cancel' \ + --bind='ctrl-/:toggle-preview' \ + --preview='_fzf_preview_media {}' \ + --preview-window='right:50%:wrap:border-left') + + if test -z "$selection" + return 0 + end + + set -l player + if set -q _flag_player + set player $_flag_player + if not type -q $player[1] + set_color red + echo "error: --player '$player[1]' is not a valid command" >&2 + set_color normal + return 1 + end + else if set -q play_media_player + echo $play_media_player | read -at player + if not type -q $player[1] + set_color red + echo "error: \$play_media_player '$player[1]' is not a valid command" >&2 + set_color normal + return 1 + end + else + if type -q xdg-mime; and command -q file + set -l mime (command file --brief --mime-type -- "$selection[1]" 2>/dev/null) + set -l desk (xdg-mime query default "$mime" 2>/dev/null) + if test -n "$desk" + set -l candidate (string replace -r '\.desktop$' '' -- $desk) + if type -q $candidate + set player $candidate + end + end + end + + if not set -q player[1] + for p in mpv vlc + if type -q -f $p + set player $p + break + end + end + end + end + + if not set -q player[1] + set_color red + echo "error: could not find a media player — set \$play_media_player, pass --player, or install mpv/vlc" >&2 + set_color normal + return 1 + end + + # Background the player so it doesn't block the terminal, and discard + # its own console chatter, mirroring open-url. + sh -c '("$@") >/dev/null 2>&1 &' -- $player $selection + return 0 +end