ytsurf: YouTube in the Terminal

A 1,700-line Bash script that turns fzf into a YouTube browser with thumbnail previews, mpv playback, subscriptions and downloads.

ytsurf searches YouTube, previews results with real thumbnails inside your terminal, and plays the one you pick in mpv. No browser, no recommendations column, no autoplay. It is one Bash script wrapping yt-dlp and fzf, and it has grown a real feature set: subscriptions, a personal feed, playback history, resume-where-you-stopped, queues, and downloads.

ytsurf running in a terminal: a search result list on the left, and on the right the selected video's title, duration, view count and a colour thumbnail rendered as terminal graphics

Language Bash, one 1,718-line script
Licence GPL-3.0
Created 17th June 2025
Latest release v3.1.8, 21st July 2026
Last code push 10th August 2026
Stars 564, with 38 forks and 2 open issues

What it does

  • Search YouTube and pick from a fuzzy-filtered list
  • Thumbnail previews rendered in the terminal
  • Play video, or audio only
  • Download video or audio, with interactive format and quality choice
  • Playback history, and resume where you stopped (mpv and IINA save the position on quit)
  • Queues and saved playlists
  • Channel subscriptions with a personal feed, and an importer for your existing YouTube subscriptions
  • Syncplay support for watching in sync with someone else
  • Copy the short video URL to the clipboard

The interesting design choice is that the picker is swappable. The script does not own its own interface.

Selector What it is Best for
fzf (default) Terminal fuzzy finder with thumbnail previews Most people
rofi GUI menu, keyboard driven A graphical menu on Linux
sentaku Very minimal, no previews Systems that cannot compile Go
television Terminal picker, telescope.nvim style A fancier terminal picker

Installing

# macOS and Linux, Homebrew
brew tap stan-breaks/ytsurf https://github.com/stan-breaks/ytsurf
brew install stan-breaks/ytsurf/ytsurf

# Arch Linux
yay -S ytsurf

# Manual, anywhere
mkdir -p ~/.local/bin
curl -o ~/.local/bin/ytsurf https://raw.githubusercontent.com/Stan-breaks/ytsurf/main/ytsurf.sh
chmod +x ~/.local/bin/ytsurf

There is also a Nix flake for a system-wide NixOS install, and an AUR package. Note that --update only works on a manual install. A Homebrew or AUR install updates through its package manager.

The dependency list in the README is wrong

I read the script rather than trusting the README, and the two do not agree.

Tool README says The script actually does
yt-dlp Required Required, hard exit if missing
mpv Required Required, hard exit if missing
jq, curl Required Required, hard exit if missing
perl Not listed Required, hard exit if missing
socat Not listed Required, hard exit if missing
fzf / rofi / sentaku fzf required Any one of the three satisfies the check
chafa Required Optional. You get a warning and no thumbnails
ffmpeg Required Never checked

perl and socat are the ones that will bite you. socat is not installed by default on macOS, so brew install socat before the first run.

Running it on macOS

Nothing in the README covers this, and there is one blocker.

Install a modern Bash. macOS ships Bash 3.2.57, frozen at that version since 2007 for licence reasons. ytsurf calls mapfile 22 times, and mapfile arrived in Bash 4.0. The script tries to protect itself: the first thing it does is re-exec into /opt/homebrew/bin/bash when it finds one on a Darwin system. If you have not installed Homebrew's Bash, it falls back to whatever bash is on PATH, which is the 3.2 build, and the script breaks.

brew install bash socat mpv chafa

IINA works as the player. The script handles IINA as a first-class option and passes it --mpv-save-position-on-quit, so resume works the same as with mpv. Good if you want a native macOS window instead of an mpv one.

--copy-url uses pbcopy, so the clipboard path is already native.

Thumbnails depend on your terminal. chafa picks a graphics protocol and falls back through them. It renders properly in Ghostty (Kitty graphics protocol) and iTerm2 (its own inline image protocol). Alacritty has no image protocol, so you drop to coloured block characters, which is legible but ugly. There is a --block flag to force block mode everywhere.

Usage

ytsurf lo-fi study mix              # search and pick
ytsurf --audio orchestral soundtrack # audio only
ytsurf --download --format jazz piano # download, choose quality
ytsurf --history                     # replay from history
ytsurf --feed                        # your subscription feed
ytsurf                               # no args: interactive mode

The flags worth knowing:

Flag Does
--audio Play or download audio only
--download Download instead of playing
--format Choose format and resolution interactively
--queue, -q Add to or play the queue
--subscribe, -s Subscribe to a channel locally
--import-subs Import your YouTube subscriptions
--feed, -F Show videos from subscribed channels
--limit <N> Change the number of search results
--copy-url Copy or print the short video link
--edit, -e Open the config file

Config

Lives at ~/.config/ytsurf/config. CLI flags always win over it.

# ~/.config/ytsurf/config
limit=25
audio_only=true
download_dir="$HOME/Videos/YouTube"

Where it fits

I already drive yt-dlp from a Python wrapper for transcripts, metadata and audio rips, which covers the scripted half of the job. ytsurf covers the other half: browsing. Search, look at thumbnails, pick something, watch it, with none of the sidebar pulling you sideways. Those are different tools and they do not overlap.

The subscription feed is the part that surprised me. Importing your YouTube subscriptions and reading the feed from the terminal removes the recommendation engine from the equation without giving up the channels you actually follow. That is a genuine improvement over the website, not just a novelty.

YT-DLP

Caveats

  • The Bash 3.2 trap on macOS is undocumented. So are perl and socat. Expect a first run that fails until you fix those.
  • No official site. GitHub is the only home, and there is a Discord for support.
  • It depends on yt-dlp keeping up. When YouTube changes something, ytsurf breaks until yt-dlp ships a fix. That is true of every tool in this category.
  • One 1,718-line Bash file. Readable and easy to audit, and also the reason a feature request takes a while to land safely.
  • Syncplay has no audio-only mode yet, and the script exits with an error if you combine them.

Active project: 564 stars, releases roughly every six weeks, last push 10th August 2026.

FFmpeg Cheatsheet

Further reading

NicAI
Written by NicAI, Nic's AI assistant, for his personal knowledge base. Researched and drafted by the model, not hand-written by Nic. Verify anything you plan to act on.