On Mac Power Users #493 I made an off-hand reference to a script-and-shortcut that I use to save YouTube videos from my iOS device into a Dropbox folder that I later view on my Apple TV using Infuse.
There has been approximately 4,371% more interest in this script than anything I have written on the Internet in the past 20 years, so I thought I should publish it somewhere I can point people to.
Here’s the overview
I always like to explain the “concept” before I get into the nitty-gritty details of how it’s done.
-
On my iPhone or iPad, send a URL to a Shortcut.
-
The Shortcut saves the URL to a text file in Dropbox.
-
Somewhere, a Mac notices that the file has been created/modified.
-
A shell script is triggered which looks for URLs in the text file.
-
Those URLs are passed to Downie which has been previously configured to save videos to a different Dropbox folder.
-
When the script is finished, it deletes the original text file and waits for it to be re-created.
-
A push notification is sent to tell me the video has been downloaded
There’s more than one way to do it.
I’m not going to describe the only way to do this, because there are lots of ways to do this.
In step #2, you could use iCloud instead of Dropbox. If you use ShellFish you could probably use Shortcuts to save the file directly to your Mac via SFTP, rather than using any cloud storage at all. Heck, you could probably use Shortcuts’ built-in “Run SSH Command” to send the URL to a text file directly. I chose Dropbox because that was the easiest way for me to do it, and I know that even if my Mac is offline for some reason (which is almost never is) or if Dropbox isn’t running on my Mac (which it almost always is), this will still work.
In step #3 you could use launchd
or Hazel.
In step #5, you could use youtube-dl instead of Downie.
It’s not foolproof
If you ask Downie to download something that isn’t a video, it probably won’t work right. I make no attempt to determine whether or not the URL is actually a video URL or not.
If you run the shortcut twice in rapid succession, it’s possible that your second URL might get added to the text file in the split-second after it is processed and before it is deleted, and your URL would be lost. That’s why I have the push notification in step #7, so I know when it’s safe to send another URL.
Here are the pieces. Some assembly required.
Shortcut on iPhone/iPad
This is pretty straight-forward. I’ll include a screenshot because it might help some folks understand how to make it, but it’s about as simple as shortcuts get:
-
The shortcut accepts anything (note: I should probably make this less “receptive” because in iOS 13 shortcuts appear in a lot of places they don’t always belong, but I’m not re-writing anything until iOS 13 officially ships.) Right away this is a big assumption because we are trusting the user to give us the correct kind of data (specifically, a URL).
-
The shortcut “expands” the URL. This is not strictly necessary, but I want to save the actual URL rather than a
bit.ly
URL. -
The user is “Ask”-ed “Is this what you want to add?” This presents the “expanded” URL to the user before it is saved. Tip: If what you really wanted to do was to save a video URL that you have on the clipboard, this is a good time to do that. You can just tap on the alert and paste the clipboard into it. This URL will not be expanded, which is a drawback. A better shortcut would ask the user if they want to use the shortcut input or the clipboard right from the start. Maybe I’ll improve it someday.
-
Will take URLs from the input the user has given us. I use this step because it would theoretically let me throw a big block of text with a bunch of URLs at the shortcut and have all of them parsed out properly, but in actual practice I use this to send one URL about 99% of the time.
-
The URLs are “appended” to a file in Dropbox. In reality, that file won’t exist 99.99% of the time, but it might – for example, if the Mac has crashed, or one of the “pieces” on the Mac has failed to run properly. Worst-case scenario you will end up with a text file with a bunch of URLs that you need to process. Note the “Make New Line” which should ensure that every URL is on its own line in the text file. That makes processing on the Mac easier. Finally the file path is relative to ~/Dropbox/ on your Mac. So
~/Dropbox/Action/downie-get.txt
on my Mac becomes/Action/downie-get.txt
for the purposes of this shortcut.
I think I originally borrowed the idea of an “Action” folder from David. I actually have two, one is ~/Action/ for things I want to do locally on my Mac, and the other is ~/Dropbox/Action/ for things that I want to do via Dropbox. This folder should always be empty. If there is something in it, the Mac should be trying to take some “action” on it. When that action is finished, the file should end up somewhere else (either filed or deleted).
One of the nice things about using Dropbox for this is that you can go back at least 30 days and “un-delete” previous versions of the file, if you need to for some reason. I’ve never needed that, but I like know it is there.
The shell script on the Mac
All of the hard work is done with a shell script on my Mac which is called /usr/local/bin/downie-get.sh
. You can call it whatever you want on your Mac, just make sure you edit the launchd
plist below to use the proper path.
My script should work for most people in most cases without (m)any changes, but I’ll try to describe what each step does in case you want to modify it.
Note that you may need to scroll the box to see the whole script.
#!/usr/bin/env zsh -f
# This tells the script to get the $PATH from the ~/.path file if it exists
# otherwise it will use the second $PATH in the "else" clause
if [[ -e "$HOME/.path" ]]
then
source "$HOME/.path"
else
PATH='/usr/local/scripts:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin'
fi
# This is the same file that we created in the Shortcut,
# except that we need to make sure we include the "$HOME/Dropbox" part of it
# if you change the filename in the Shortcut, change it here too
INPUT="$HOME/Dropbox/Action/downie-get.txt"
if [[ ! -e "$INPUT" ]]
then
# What happens if the script is run but the file does not exist?
# We should just quit immediately
echo "$NAME: The input file does not exist."
exit 0
fi
if [[ ! -s "$INPUT" ]]
then
# What happens if the script is run and the file exists but it is
# zero bytes? We should just quit immediately
echo "$NAME: The input file is empty."
exit 0
fi
## Ok, so if we get here, the file exists and is not empty.
## Now, is Downie installed? We'll check two places:
##
## '/Applications/Downie 3.app'
##
## and
##
##
######## DOWNIE-CHECK-BEGIN
if [[ -d '/Applications/Setapp/Downie.app' ]]
then
APPNAME='Downie'
elif [[ -d '/Applications/Downie 3.app' ]]
then
APPNAME='Downie 3'
else
# If Downie isn't installed, there's really no point in going on, is there?
echo "$NAME: I did not find Downie at '/Applications/Downie 3.app' or at '/Applications/Setapp/Downie.app'!"
exit 0
fi
######## DOWNIE-CHECK-END
# we're going to use this so we can remove the original file ASAP
# in case another URL gets sent by another invocation of the Shortcut
TEMPFILE="$HOME/.Trash/downie-get.$$.$RANDOM.txt"
# This will rename the original file to this temp file in the trash
mv -vf "$INPUT" "$TEMPFILE"
# the 'egrep' line will look for any line that starts with 'http'
# which will also match 'https' URLs, of course
# and then it will process each line that starts with https
# and ignore all of the other lines, including any blank lines.
egrep '^http' "$TEMPFILE" | while read line
do
## This will tell "Downie" or "Downie 3" to open the "$line" (which is a variable
## containing a URL, assuming any were found)
## The `-g` and the `-j` tell Downie to stay in the background and
## stay hidden, so the app won't jump up in front of me if I happen
## to be using it while it is trying to do this
open -g -j -a "$APPNAME" "$line"
done
exit 0
That’s pretty much it. I have previously configured Downie’s “General” preferences so it will:
- Save videos to ~/Dropbox/Video/Infuse/
- “Add .part extension to incomplete downloads”
- “Automatically start download when added”
- when multiple qualities are available: “Automatically select best quality” and “Prefer mp4 downloads”
If you want to test this on your own before trying the script, try this in Terminal:
open -a 'Downie 3' "https://www.youtube.com/watch?v=289v_gKJjWU"
or if you use Downie via Setapp:
open -a 'Downie' "https://www.youtube.com/watch?v=289v_gKJjWU"
If the video immediately starts downloading, you’re good to go. Add in the -g and -j
before the -a
if you want Downie to stay in the background.
"But I want to use youtube-dl
not Downie!
Ok, well, as I said, there are lots of ways to do this, but if you want to use youtube-dl
then:
-
Delete the lines from “######## DOWNIE-CHECK-BEGIN” to “######## DOWNIE-CHECK-END”
-
replace
open -g -j -a "$APPNAME" "$line"
with these lines:
cd ~/Dropbox/Video/Infuse/
youtube-dl --add-metadata --restrict-filenames --continue --format mp4 \
--output "%(title)s-(%(id)s).%(ext)s" "$line"
The cd
line in cd ~/Dropbox/Video/Infuse/
means “save the files in the folder ~/Dropbox/Video/Infuse/” which must already exist. Change that to wherever you want the videos to be saved.
Obviously you can use whatever arguments to youtube-dl
that you want, I just used the ones that I most often use.
Other than that, the script should work just the same.
launchd
or Hazel on the Mac.
As I mentioned above, you could use Hazel or launchd
for this part. There’s really not a “right” choice. Hazel is definitely more user-friendly, but launchd
plists are easier to back up than Hazel rules. Plus I have LaunchControl which makes writing launchd
plists very easy. YMMV. Use what you like. I’m going to describe the launchd
way because that’s what I use. If you want to use Hazel and don’t know how, leave a reply and I’ll help if I can.
Here is the .plist file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.tjluoma.downie-get</string>
<key>Program</key>
<string>/usr/local/bin/downie-get.sh</string>
<key>RunAtLoad</key>
<true/>
<key>WatchPaths</key>
<array>
<string>/Users/luomat/Dropbox/Action/downie-get.txt</string>
</array>
</dict>
</plist>
Save that to ~/Library/LaunchAgents/com.tjluoma.downie-get.plist
-
Change the
/usr/local/bin/downie-get.sh
to point to wherever you saved the shell script we created above. -
Change the
/Users/luomat/Dropbox/Action/downie-get.txt
path to the same path that you used in the Shortcut. Make sure that you include the path to the Dropbox folder, so, for example: -
/Action/downie-get.txt
in the Dropbox Shortcut becomes -
~/Dropbox/Action/downie-get.txt
Except that launchd
doesn’t always like it when you use ~
(I can never remember when it’s OK to use and when it isn’t, so I just try to avoid it altogether) so use /Users/YourShortNameHere
instead of /Users/luomat
.
Once you have the file edited properly, you need to tell launchd
to “load” it. If you aren’t using LaunchControl you can do this in Terminal.app:
launchctl load ~/Library/LaunchAgents/com.tjluoma.downie-get.plist
If you ever want to stop using this, just delete the ~/Library/LaunchAgents/com.tjluoma.downie-get.plist
file and reboot your Mac.
“What about that push notification?”
Yeah, I mentioned that I also get a push notification when the file is finished. That’s done with Hazel looking in ~/Dropbox/Video/Infuse/
for new video files and then it uses Pushover to send me a push notification.
This uses po.sh which I’ve written about separately, so I won’t repeat it here, but I will say that po.sh
is super useful and I use it for dozens of things. If you want to get notifications on your iPhone/iPad for things that happen on your Mac, checkout Pushover.
(I think the app is $5 but the free service tier is all I’ve ever needed, and I paid them that $5 about 6 years ago.)
Infuse on the AppleTV
I use Infuse on the AppleTV to play videos from that Dropbox folder on the Apple TV, which, as Stephen pointed out, also has the benefit of keeping me out of the YouTube app on the Apple TV, which is Not Good.™
Questions? Comments? Concerns?
Let me know if there are parts of this that I need to explain better.