Last active
September 10, 2020 00:16
-
-
Save sammdu/59785713c336a6c312b5880f6ed6ffb0 to your computer and use it in GitHub Desktop.
[ytdl.py] Download a list of songs from YouTube, at the best audio quality. Can optionally download videos as well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import subprocess | |
from argparse import ArgumentParser | |
from pathlib import Path | |
from sys import exit, stderr | |
# [1/3] COMMAND LINE ARGUMENTS | |
parser = ArgumentParser() | |
parser.add_argument( | |
"playlist", | |
metavar="PLAYLIST_FILE", | |
nargs="?", | |
default="list.txt", | |
help='text file containing a list of URLs to download; default to "list.txt"', | |
) | |
parser.add_argument( | |
"-v", | |
"--video", | |
action="store_true", # true if provided, false if omitted | |
help="download video instead of audio", | |
) | |
parser.add_argument( | |
"-k", | |
"--keep-original", | |
action="store_true", # true if provided, false if omitted | |
help="keep the original file name and don't do any cleanups, " | |
+ "such as removing dots, spaces, single-quotes, and ampersands", | |
) | |
parser.add_argument( | |
"-n", | |
"--no-metadata", | |
action="store_true", # true if provided, false if omitted | |
help="prevent writing the full title information to the file's metadata using ffmpeg", | |
) | |
parser.add_argument( | |
"-o", | |
"--output", | |
metavar="OUTPUT_DIRECTORY", | |
default="./", | |
help="where to store the downloaded files; default to the current directory", | |
) | |
args = parser.parse_args() | |
# [2/3] FUNCTION AND CLASS DEFINITIONS | |
class ansicolors: | |
# put one of these sequences at the beginning of a string | |
RED = "\033[91m" | |
GREEN = "\033[92m" | |
BLUE = "\033[94m" | |
YELLOW = "\033[93m" | |
# put this sequence after a string to end the colored area | |
END = "\033[0m" | |
def filename_cleanup(filename): | |
""" | |
removes undesired characters by applying the defined | |
replacement table to the string; also makes letters | |
lower-case | |
""" | |
# define replacement table | |
rtable = { | |
".": "", | |
",": "", | |
" ": "-", | |
"‘": "", | |
"’": "", | |
"(": "", | |
")": "", | |
"[": "", | |
"]": "", | |
"&": "and", | |
"---": "-", | |
} | |
# apply replacement table | |
for key in rtable: | |
filename = filename.replace(key, rtable[key]) | |
# make all letters lower-case | |
filename = filename.lower() | |
return filename | |
def get_title(link): | |
""" | |
fetch the title of the linked YouTube video; | |
include the title text, artist, uploader, and video ID | |
""" | |
title = subprocess.check_output( | |
[ | |
"youtube-dl", | |
"--get-filename", | |
"-o", | |
"%(title)s-%(artist)s-%(uploader)s_%(id)s", | |
link, | |
], | |
text=True, | |
) | |
return title.strip() | |
def download_file(url, type, path, title=""): | |
""" | |
execute youtube-dl command to download the file at the URL; | |
- type can either be 'audio' or 'video'; | |
- if title is supplied, reconstruct the file name with the title, | |
otherwise leave it default | |
""" | |
# enforce that the type is either "audio" or "video" | |
assert type in ["audio", "video"] | |
# initialize the command to be populated | |
command = ["youtube-dl"] | |
# supply media type | |
if type == "audio": | |
command.append("-f bestaudio") | |
else: | |
command.append("-f best") | |
# supply title if given | |
if title: | |
command.append("-o") | |
command.append(f"{title}.%(ext)s") | |
# append URL | |
command.append(url) | |
# execute 'command' in 'path' | |
subprocess.run(command, cwd=path) | |
def write_metadata(title, filename, path): | |
""" | |
write the title to the output file using ffmpeg | |
""" | |
# find the exact path of the file | |
file = list(Path(path).glob(f"{filename}*"))[0] | |
# rename the file to file.tmp | |
file_tmp = Path(file).rename(f"{file}.tmp") | |
# write metadata to a new file | |
subprocess.run( | |
[ | |
"ffmpeg", | |
"-i", | |
f"{file_tmp}", | |
"-metadata", | |
f"title={title}", | |
"-c", | |
"copy", | |
f"{file}", | |
] | |
) | |
# delete the temporary file | |
Path(file_tmp).unlink() | |
# [3/3] MAIN LOGIC | |
try: | |
with open(args.playlist, "r") as playlist: # open the playlist file in read mode | |
for line in playlist: # go over the playlist line by line | |
if line != "\n": # skip empty lines | |
# split URL line at the first '&' character to truncate extra parameters | |
line_stripped = line.split("&")[0].strip() | |
# print the current URL in red (YouTube) color | |
print(f"\n● {ansicolors.RED}{line_stripped}{ansicolors.END}") | |
# if not prevented, clean up title | |
if not args.keep_original: | |
title_raw = get_title(line_stripped) # prefetch the title | |
title = filename_cleanup(title_raw) # apply the cleanup functoin | |
else: | |
title = "" | |
# download audio by default, otherwise download video | |
if not args.video: | |
download_file( | |
url=line_stripped, type="audio", path=args.output, title=title | |
) | |
else: | |
download_file( | |
url=line_stripped, type="video", path=args.output, title=title | |
) | |
# if not prevented, write metadata information to the file | |
if not args.no_metadata: | |
write_metadata(title_raw, title, args.output) | |
except FileNotFoundError: | |
print( | |
f""" | |
{ansicolors.YELLOW}{args.playlist}{ansicolors.END} not found. | |
Please check that you supplied the correct file name for the playlist. | |
For more information, see "ytdl.py --help" | |
""", | |
file=stderr, # write to STDERR | |
) | |
exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Requirements:
Usage:
Instructions:
list.txt
is used. You may supply the file name later if you named it something else:Options:
-o
option, followed by the intended output directory, as such: The example above will output the downloaded files into thedownload
folder next to the script.-v
or--video
option, as such: it will download videos in every URL at the best quality available.-k
or--keep-original
option, as such:-n
or--no-metadata
option, as such: