|
#!/bin/python3 |
|
""" |
|
Download videos from a YouTube playlist with appropriate filenames and metadata. |
|
|
|
Download YouTube Playlist Script |
|
|
|
This script downloads videos from a YouTube playlist, either the full video or audio-only in MP3 format. |
|
The produced files' names include the track number and title, and the following media info is stored in the files' metadata: |
|
- title |
|
- track number |
|
- album (YouTube playlist title) |
|
- publisher (name of the YouTube channel) |
|
|
|
Usage: |
|
python download_youtube_playlist.py <playlist_url> <download_directory> [--audio-only] |
|
|
|
Arguments: |
|
playlist_url URL of the YouTube playlist to download. |
|
download_directory Directory where the downloaded files will be saved. |
|
--audio-only Optional flag to download audio-only files. |
|
|
|
Requirements: |
|
- pytube |
|
- yt-dlp |
|
- ffmpeg-python |
|
|
|
Ensure the following Python packages are installed: |
|
pip install pytube yt-dlp ffmpeg-python |
|
""" |
|
import os |
|
import sys |
|
from pytube import Playlist |
|
import yt_dlp |
|
import ffmpeg |
|
import tempfile |
|
import shutil |
|
|
|
|
|
def download_videos_from_playlist( |
|
playlist_url, download_directory, audio_only=False |
|
): |
|
"""Download videos from a YouTube playlist. |
|
|
|
Args: |
|
playlist_url (str): URL of the YouTube playlist. |
|
download_directory (str): Directory where the downloaded files will be saved. |
|
audio_only (bool): If True, download audio-only files. |
|
|
|
Returns: |
|
None |
|
""" |
|
tempdir = tempfile.mkdtemp() |
|
# Create the directory if it doesn't exist |
|
if not os.path.exists(download_directory): |
|
os.makedirs(download_directory) |
|
|
|
# Fetch playlist details using pytube |
|
playlist = Playlist(playlist_url) |
|
videos = playlist.videos |
|
|
|
# Prepare yt-dlp options |
|
ydl_opts = { |
|
'format': 'bestaudio' if audio_only else 'best', |
|
'postprocessors': [{ |
|
'key': 'FFmpegExtractAudio', |
|
'preferredcodec': 'mp3', |
|
'preferredquality': '192', |
|
}] if audio_only else [], |
|
} |
|
|
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl: |
|
for index, video in enumerate(videos, start=1): |
|
print(f'Downloading {index}. {video.title}') |
|
info_dict = ydl.extract_info(video.watch_url, download=False) |
|
|
|
# filename without extension |
|
filename = f'{index}. {video.title}' |
|
|
|
ext = 'mp3' if audio_only else info_dict.get('ext', 'mp4') |
|
output_template = os.path.join(tempdir, f'{filename}.%(ext)s') |
|
|
|
temp_path = os.path.join(tempdir, filename) |
|
ydl_opts['outtmpl'] = output_template |
|
ydl = yt_dlp.YoutubeDL(ydl_opts) |
|
ydl.download([video.watch_url]) |
|
|
|
temp_path = output_template % {'ext': ext} |
|
|
|
# filename with extension |
|
filename = os.path.basename(temp_path) |
|
output_path = os.path.join(download_directory, filename) |
|
|
|
# Set metadata using ffmpeg |
|
print(f'Setting metadata for {index}. {video.title}') |
|
metadata = { |
|
"title": video.title, |
|
"track": index, |
|
"album": playlist.title, |
|
"publisher": video.author |
|
} |
|
metadata_list = [f"{key}={value}" for key, |
|
value in list(metadata.items())] |
|
metadata_dict = {f"metadata:g:{i}": e for i, |
|
e in enumerate(metadata_list)} |
|
|
|
# Use `ffmpeg` to duplicate the file, |
|
# setting metadata and copying streams without re-encoding |
|
ffmpeg.input(temp_path).output( |
|
output_path, |
|
**metadata_dict, |
|
codec='copy' |
|
).overwrite_output().run() |
|
os.remove(temp_path) |
|
shutil.rmtree(tempdir) |
|
|
|
|
|
if __name__ == '__main__': |
|
if len(sys.argv) not in [3, 4]: |
|
print( |
|
"Usage: python download_youtube_playlist.py <playlist_url> <download_directory> [--audio-only]") |
|
sys.exit(1) |
|
|
|
playlist_url = sys.argv[1] |
|
download_directory = sys.argv[2] |
|
audio_only = '--audio-only' in sys.argv |
|
|
|
download_videos_from_playlist(playlist_url, download_directory, audio_only) |