more refactoring and made source file stable - WIP albums stored in source.txt not in repo
This commit is contained in:
@@ -4,135 +4,189 @@ import json
|
||||
import yt_dlp
|
||||
|
||||
|
||||
def download_playlist_for_cd(playlist_url: str, output_folder: str = "cd_audio"):
|
||||
"""Downloads YouTube audio directly into 44.1kHz 16-bit WAV files
|
||||
g_OUTPUT_FOLDER = "cd_audio"
|
||||
|
||||
ideal for burning directly to Audio CDs without secondary re-encoding loss.
|
||||
"""
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
ydl_opts = {
|
||||
# Grab best audio stream available
|
||||
"format": "bestaudio/best",
|
||||
# Prefix with track index (01, 02...) so CD track order matches playlist order
|
||||
"outtmpl": os.path.join(output_folder, "%(playlist_index)s - %(title)s.%(ext)s"),
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "wav", # Lossless format native to CD Burning
|
||||
}
|
||||
],
|
||||
# Enforce Red Book CD Standard audio resampling (44.1 kHz)
|
||||
"postprocessor_args": [
|
||||
"-ar", "44100", # Set sample rate to 44.1 kHz (CD Standard)
|
||||
"-ac", "2", # Force 2 channels (Stereo)
|
||||
],
|
||||
"ignoreerrors": True,
|
||||
}
|
||||
|
||||
print(f"Downloading audio optimized for CD burning: {playlist_url}")
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([playlist_url])
|
||||
def generate_ytdl_opts(outtmpl: str, metadata_artist: str = "", metadata_title: str = ""):
|
||||
return {
|
||||
"format": "bestaudio/best",
|
||||
"noplaylist": True,
|
||||
"outtmpl": outtmpl,
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "wav",
|
||||
}
|
||||
],
|
||||
"postprocessor_args": [
|
||||
"-ar", "44100",
|
||||
"-ac", "2",
|
||||
"-metadata", f"artist={metadata_artist}",
|
||||
"-metadata", f"title={metadata_title}",
|
||||
],
|
||||
"ignoreerrors": True,
|
||||
}
|
||||
|
||||
print("\nDownload complete! Import these .wav files directly into your CD burner.")
|
||||
|
||||
|
||||
def download_playlist_for_cd(playlist_url: str):
|
||||
ytdl_opts = generate_ytdl_opts(
|
||||
os.path.join(g_OUTPUT_FOLDER, "%(playlist_index)s - %(title)s.%(ext)s")
|
||||
)
|
||||
|
||||
print(f"Downloading audio optimized for CD burning: {playlist_url}")
|
||||
with yt_dlp.YoutubeDL(ytdl_opts) as ytdl:
|
||||
ytdl.download([playlist_url])
|
||||
|
||||
print("\nDownload complete! Import these .wav files directly into your CD burner.")
|
||||
|
||||
|
||||
|
||||
def download_single_video(url, song_num, artist, title):
|
||||
output_folder = "cd_audio"
|
||||
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
ytdl_opts = generate_ytdl_opts(
|
||||
os.path.join(g_OUTPUT_FOLDER, f"{song_num} - {artist} - {title}.%(ext)s"),
|
||||
artist, title
|
||||
)
|
||||
|
||||
ytdl_opts = {
|
||||
"format": "bestaudio/best",
|
||||
"noplaylist": True,
|
||||
"outtmpl": os.path.join(output_folder, f"{song_num} - {artist} - {title}.%(ext)s"),
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "wav",
|
||||
}
|
||||
],
|
||||
"postprocessor_args": [
|
||||
"-ar", "44100", # Set sample rate to 44.1 kHz (CD Standard)
|
||||
"-ac", "2", # Force 2 channels (Stereo)
|
||||
"-metadata", f"artist={artist}",
|
||||
"-metadata", f"title={title}",
|
||||
],
|
||||
"ignoreerrors": True,
|
||||
}
|
||||
with yt_dlp.YoutubeDL(ytdl_opts) as ytdl:
|
||||
ytdl.download([url])
|
||||
|
||||
with yt_dlp.YoutubeDL(ytdl_opts) as ytdl:
|
||||
ytdl.download([url])
|
||||
|
||||
|
||||
def parse_file(filename):
|
||||
|
||||
class Track:
|
||||
def __init__(self, track: int, title: str, url: str):
|
||||
self.track = track
|
||||
self.title = title
|
||||
self.url = url
|
||||
|
||||
class Album:
|
||||
def __init__(self, artist: str, album: str):
|
||||
self.artist = artist
|
||||
self.album = album
|
||||
self.tracks = []
|
||||
def add_track(self, track: Track):
|
||||
self.tracks.append(track)
|
||||
|
||||
with open(filename, "r") as file:
|
||||
data = json.load(file)
|
||||
class Track:
|
||||
def __init__(self, track: int, title: str, url: str):
|
||||
self.track = track
|
||||
self.title = title
|
||||
self.url = url
|
||||
|
||||
|
||||
albums = []
|
||||
class Album:
|
||||
def __init__(self, artist: str, album: str):
|
||||
self.artist = artist
|
||||
self.album = album
|
||||
self.tracks = []
|
||||
def add_track(self, track: Track):
|
||||
self.tracks.append(track)
|
||||
|
||||
for album in data:
|
||||
with open(filename, "r") as file:
|
||||
data = json.load(file)
|
||||
|
||||
try:
|
||||
new_album = Album(
|
||||
album["artist"],
|
||||
album["album"])
|
||||
for video in album["tracks"]:
|
||||
new_album.add_track(Track(
|
||||
video["track"],
|
||||
video["title"],
|
||||
video["url"]
|
||||
))
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
continue
|
||||
|
||||
albums.append(new_album)
|
||||
albums = []
|
||||
|
||||
return albums
|
||||
for album in data:
|
||||
|
||||
try:
|
||||
new_album = Album(
|
||||
album["artist"],
|
||||
album["album"])
|
||||
for video in album["tracks"]:
|
||||
new_album.add_track(Track(
|
||||
video["track"],
|
||||
video["title"],
|
||||
video["url"]
|
||||
))
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
continue
|
||||
|
||||
albums.append(new_album)
|
||||
|
||||
return albums
|
||||
|
||||
|
||||
|
||||
def print_help():
|
||||
print("-h print help and exit")
|
||||
print("-f <filename> select a file (required)")
|
||||
print("-a download all")
|
||||
print("-d <album> download a specific album")
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
|
||||
class Ret:
|
||||
file = None
|
||||
downloads = None
|
||||
ret = Ret()
|
||||
|
||||
cache_arg_count = len(argv)
|
||||
arg_idx = 1
|
||||
|
||||
while arg_idx < cache_arg_count:
|
||||
|
||||
if argv[arg_idx] == "-h":
|
||||
print_help()
|
||||
exit(1)
|
||||
|
||||
elif argv[arg_idx] == "-f":
|
||||
if arg_idx + 1 >= cache_arg_count:
|
||||
raise ValueError("-f <filename> - is missing the filename")
|
||||
|
||||
ret.file = argv[arg_idx + 1]
|
||||
|
||||
arg_idx += 2
|
||||
continue
|
||||
|
||||
elif argv[arg_idx] == "-a":
|
||||
|
||||
if ret.downloads != None:
|
||||
raise ValueError("Cannot use -a twice, or in combination with -d")
|
||||
|
||||
ret.downloads = ""
|
||||
|
||||
arg_idx += 1
|
||||
continue
|
||||
|
||||
elif argv[arg_idx] == "-d":
|
||||
|
||||
if ret.downloads != None:
|
||||
raise ValueError("Cannot use -d twice, or in combination with -a")
|
||||
|
||||
if arg_idx + 1 >= cache_arg_count:
|
||||
raise ValueError("-d <album> - is missing the album name")
|
||||
|
||||
ret.downloads = argv[arg_idx + 1]
|
||||
|
||||
arg_idx += 2
|
||||
continue
|
||||
|
||||
else:
|
||||
print_help()
|
||||
exit(2)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
url = input("Enter YouTube playlist URL: ").strip()
|
||||
if url:
|
||||
download_playlist_for_cd(url)
|
||||
config = parse_args(sys.argv)
|
||||
|
||||
else:
|
||||
data = parse_file(" ".join(sys.argv[1:]))
|
||||
for album in data:
|
||||
print(f"Band: {album.artist}")
|
||||
print(f"Album: {album.album}")
|
||||
if not os.path.exists(g_OUTPUT_FOLDER):
|
||||
os.makedirs(g_OUTPUT_FOLDER)
|
||||
|
||||
for video in album.tracks:
|
||||
print(f"\tTrack {video.track}: {video.title}")
|
||||
|
||||
if album.album == "Even In Arcadia":
|
||||
download_single_video(
|
||||
video.url,
|
||||
video.track,
|
||||
f"{album.artist} - {album.album}",
|
||||
video.title
|
||||
)
|
||||
data = parse_file(config.file)
|
||||
|
||||
|
||||
for album in data:
|
||||
|
||||
if not (config.downloads == "" or config.downloads == album.album):
|
||||
continue
|
||||
|
||||
print(f"Band: {album.artist}")
|
||||
print(f"Album: {album.album}")
|
||||
|
||||
for video in album.tracks:
|
||||
print(f"\tTrack {video.track}: {video.title}")
|
||||
download_single_video(
|
||||
video.url,
|
||||
video.track,
|
||||
f"{album.artist} - {album.album}",
|
||||
video.title
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user