- Updates
- 2024-02-26:
- Somehow, many people seems to be fed up with the YouTube web interface in recent time and so multiple people are posting about the same issue. I introduced the "Similar Articles by Others" section. I'm using my method for years already.
- Explanation on the semi-colon in the file names.
- Further minor improvements happened after the initial publication as well: screenshot, better invocation examples, …
- 2024-07-01: I had to change my script because YouTube no longer provides download format 22 (720).
- 2024-02-26:
There are many reasons why someone would not want to use https://YouTube.com in a web browser to search for and watch videos.
My most important reasons are:
- I never use an active Google account to log in to YouTube. This
would violate my personal data privacy.
- I happily accept the downside that I don't get personalized video recommendations. I would not use them anyway.
- I don't want to spend time clicking away all those useless cookie-windows - always reject all!
- I dislike the defaults for auto-play and such.
- I'm not interested in reading comments - most of the time. I never ever have written any YouTube comment myself.
- I prefer local video playback.
- Omits any streaming issue because of bad bandwidth or similar.
- My local video player has so much more cool features accessible by easy-to-learn keyboard shortcuts.
- I don't enjoy watching advertisements.
Therefore, I created a way to search for YouTube videos, download YouTube videos and watch them locally from my zsh command line interface.
If you don't want to use other solutions like Individous or FreeTube, you might want to check out my workflow.
You don't have to use a shell if you want to use my method. You can also wrap the shell scripts into easy to start temporary Terminal windows to interact with them. I didn't bother so far. I'm very fine with using the shell.
So here is my method which you can use and adapt to your personal taste in case you're familiar with basic shell scripting and how to invoke them.
My Basic Concept
I split up the method into several small shell scripts. This allows for easy maintenance and modular re-use.
Furthermore, I differ between two different video sizes: low and high resolutions. I use high resolutions when I want to watch the videos on my desktop and I use low resolution videos for synchronizing them via Syncthing to my mobile phone for watching when I'm away from my desktop. Therefore, most scripts are available in two versions that only differ with the output video resolution.
Since I'm using existing tools to do the heavy lifting, my shell script method has some dependencies:
yt-dlp is a fork of youtube-dl. This fork is better maintained and so it's recommended to migrate away from
youtube-dl
toyt-dlp
as a proper replacement.ytfzf offers a nice interface to find YouTube videos. As of 2024-02-24 it's no longer maintained but it's still working on my side so far.
guess-filename is one of my scripts from my file management method which renames downloaded files like I want them. Instead of
ayo why you runnin [gFh4dAX5g-U].mp4=
I get the file name
2023-06-28 youtube - ayo why you runnin - gFh4dAX5g-U 1;07.mp4
which contains the date of upload, the original title, the YouTube hash for re-finding it later and the duration of the video.
Searching for YouTube Videos
I search for videos via the wrapper scripts yth
and ytl
which stand for "YouTube high resolution" and "YouTube low resolution" in my head.
You invoke the scripts with a search string like in the following example invocations:
yth "trailer big lebowski" ytl "how to install newpipe on android" yth "review kaweco lilliput"
This will then show the videos matching this query:

In this interface, you can filter the results by typing your filter strings. With the cursor keys, you can move among the result hits. With the return key you invoke the download and close the window.
My yth
and ytl
scripts below are currently using the "view-count" for sorting the results. You might as well change this to different settings.
If you play around with ytfzf
you could even get preview images. I tried it for a couple of minutes, failed and did not care to re-try so far. ytfzf
does offer more stuff - just read its documentation.
This is my yth
:
#!/usr/bin/env sh URL=$(ytfzf -l -L --upload-sort=view-count "${1}") [ "x${URL}" != "x" ] && yd "${URL}" 720 #end
Here is my ytl
:
#!/usr/bin/env sh URL=$(ytfzf -l -L --upload-sort=view-count "${1}") [ "x${URL}" != "x" ] && yd "${URL}" 640 #end
The numbers 720 and 640 stand for the video width which then gets translated into YouTube-specific download quality indicators which you can see when you execute:
yt-dlp -F 'https://www.youtube.com/watch?v=gFh4dAX5g-U'
At the moment, 720 is 720×720 mp4 using avc1.64001F codec and 640 is 6400×360 mp4 using the avc1.42001E codec.
If you want to use the same method for other video platforms supported by yt-dlp
, you only need to add functionality that chooses the different quality indicators because they differ among different video providers.
Please note that on YouTube not all videos are available in all qualities. Especially older content might only be available in lower resolutions.
Both wrapper scripts are using yd
which is my general download script:
#!/usr/bin/env bash URL="${1}" FORMAT="${2}" if hash yt-dlp 2>/dev/null; then YDBIN="yt-dlp" Y_QUERY_OPTIONS="--no-check-certificate --compat-options list-formats" Y_DL_OPTIONS="--no-mtime --write-info-json --no-check-certificate" elif hash youtube-dl 2>/dev/null; then echo "WARNING: \"yt-dlp\" not found, using \"youtube-dl\" instead" YDBIN="youtube-dl" else echo "ERROR: no youtube downloader tool found." exit 1 fi if [ "${YDBIN}" = "youtube-dl" ]; then Y_QUERY_OPTIONS="--no-check-certificate -F" Y_DL_OPTIONS="--write-info-json --no-check-certificate" fi ## FIXXME: doesn't work when URL is not lowercase or when URL contains "youtube" not in the domain part: if [ -z ${FORMAT} ] || [[ "${URL}" != *"youtube"* ]]; then "${YDBIN}" ${Y_QUERY_OPTIONS} "${URL}" | grep -v only echo read -p 'Please enter the desired version to download: ' FORMAT echo fi ## YouTube: translate download wish to actual formats: if [ "x${FORMAT}" = "x720" ]; then FORMAT="-S vcodec:h264,fps,res:720,acodec:m4a" elif [ "x${FORMAT}" = "x640" ]; then FORMAT="-f 18" else FORMAT="-f ${FORMAT}" fi "${YDBIN}" ${Y_DL_OPTIONS} ${FORMAT} "${URL}" # Optional, if you want to get file names like: # "2023-07-04 youtube - Nix flakes explained - S3VBi6kHw5c 7;21.mp4" guess-filename-for-info-json-mp4-files.sh #end
Of course, if you don't want to use yth
and ytl
because you only want high resolution videos, you could modify yd
by replacing ${FORMAT}
with the appropriate format string and invoke it instead of yth
and ytl
.
The yd
script could be further simplified by removing the youtube-dl
support. However, some people might find it useful when it works for yt-dlp
as well as with youtube-dl
. I personally don't use the latter any more.
Here is my wrapper script for invoking guess-filename
after the download: guess-filename-for-info-json-mp4-files.sh
#!/usr/bin/env sh #- process # 1. find latest (all?) file(s) in directory (with extension .info.json) # 2. generate video file name by removing .info.json and replace with .mp4 # 3. invoke guess-filename on video file # 4. remove json file for jsonfile in *.info.json; do mp4name=$( echo ${jsonfile} | sed 's/.info.json/.mp4/') m4aname=$( echo ${jsonfile} | sed 's/.info.json/.m4a/') if [ -f "${mp4name}" ]; then guess-filename "${mp4name}" rm "${jsonfile}" elif [ -f "${m4aname}" ]; then guess-filename "${m4aname}" rm "${jsonfile}" else echo "I could not locate \"${mp4name}\" for the given \"${jsonfile}\" ... ignoring it." fi done #end
If you are fine with the default file names, you could remove guess-filename-for-info-json-mp4-files.sh
from yd
. This would also get rid of the guess-filename
dependency.
Downloading YouTube Videos
If you do have a YouTube video URL from anywhere on the web, you can use my wrapper scripts ydh
and ydl
just for downloading the videos without the necessity for searching like above.
The script names ydh
and ydl
stand for "YouTube download high quality" and "YouTube download low quality" in my head.
With the yd
script from the previous section, those wrapper scripts are pretty simple. Here is ydh
:
#!/usr/bin/env sh yd "${1}" 720 #end
And here is ydl
:
#!/usr/bin/env sh yd "${1}" 640 #end
You do invoke them like:
ydh 'https://www.youtube.com/watch?v=gFh4dAX5g-U'
Alternatively, you can also invoke it with the YouTube hash only in you want:
ydh gFh4dAX5g-U
Both methods lead to a local video file like:
2023-06-28 youtube - ayo why you runnin - gFh4dAX5g-U 1;07.mp4
Don't you wonder about the ";" to separate minutes from seconds in the last part of the file name. I had to switch from ":" because somehow, Google decided that the Android file system of my Pixel 4a should inherit the file name restrictions of MS-DOS/FAT by Microsoft. This conflicts with many characters in cluding the colon. The semi-colon worked somehow.
My Personal Usage Patterns
In practice, I rarely use yth
or ytl
for searching.
Most of the time, I come across a YouTube URL on Mastodon or on the web. I copy the URL into my clipboard, switch to my tmux
shell, switch to my default video download directory and invoke ydh
or ydl
for downloading and renaming the video.
Using the zsh
command mpv *(.om[1])
from my command history, I open the most recently downloaded video in my favorite movie player.
I can think of an automation method that watches for changes in the clipboard and invokes ydh
to my default download directory in case the clipboard matches a YouTube URL. So far, I prefer the flexibility to decide on the quality I want to download.
By the way, for Android YouTube consumption, I do recommend NewPipe as a full replacement for the YouTube app by Google. It's a really good mobile app that deserves all support you can give.