From 01df69e698cefde19c80b52e012b3040911cef41 Mon Sep 17 00:00:00 2001 From: Subhamoy Biswas Date: Mon, 21 Oct 2024 21:56:26 +0530 Subject: [PATCH] (refactor): updated project structure with more categorized files --- .gitignore | 3 +- pytubepp/config.py | 53 +++ pytubepp/download.py | 68 ++++ pytubepp/main.py | 740 +--------------------------------------- pytubepp/postprocess.py | 76 +++++ pytubepp/utils.py | 56 +++ pytubepp/video.py | 509 +++++++++++++++++++++++++++ 7 files changed, 769 insertions(+), 736 deletions(-) create mode 100644 pytubepp/config.py create mode 100644 pytubepp/download.py create mode 100644 pytubepp/postprocess.py create mode 100644 pytubepp/utils.py create mode 100644 pytubepp/video.py diff --git a/.gitignore b/.gitignore index ce8633e..abf8208 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -# Compiled python modules. +# Compiled python modules and cache. +__pycache__/ *.pyc # Setuptools distribution folder. diff --git a/pytubepp/config.py b/pytubepp/config.py new file mode 100644 index 0000000..8083b66 --- /dev/null +++ b/pytubepp/config.py @@ -0,0 +1,53 @@ +import os, json, platform, appdirs, tempfile + +def get_download_folder(): + system = platform.system() + if system in ["Windows", "Darwin", "Linux"]: + cli_download_dir = os.path.join(os.path.expanduser("~"), "Downloads", "PytubePP Downloads") + os.makedirs(cli_download_dir, exist_ok=True) + return cli_download_dir + else: + cli_download_dir = os.path.join(appdirs.user_download_dir(), "PytubePP Downloads") + os.makedirs(cli_download_dir, exist_ok=True) + return cli_download_dir + +DEFAULT_CONFIG = { + 'downloadDIR': get_download_folder(), + 'defaultStream': 'max', +} + +def get_temporary_directory(): + temp_dir = tempfile.gettempdir() + cli_temp_dir = os.path.join(temp_dir, 'pytubepp') + os.makedirs(cli_temp_dir, exist_ok=True) + return cli_temp_dir + +def load_config(): + config_dir = appdirs.user_config_dir('pytubepp') + config_path = os.path.join(config_dir, 'config.json') + if os.path.exists(config_path): + with open(config_path, 'r') as f: + return json.load(f) + else: + return DEFAULT_CONFIG + +def save_config(config): + config_dir = appdirs.user_config_dir('pytubepp') + os.makedirs(config_dir, exist_ok=True) + config_path = os.path.join(config_dir, 'config.json') + with open(config_path, 'w') as f: + json.dump(config, f, indent=4) + +def update_config(key, value): + config = load_config() + config[key] = value + save_config(config) + +def reset_config(): + config_dir = appdirs.user_config_dir('pytubepp') + config_path = os.path.join(config_dir, 'config.json') + if os.path.exists(config_path): + os.remove(config_path) + print('\nConfig reset successful!') + else: + print('\nAlready using the default configs! Not resetting...!') \ No newline at end of file diff --git a/pytubepp/download.py b/pytubepp/download.py new file mode 100644 index 0000000..2ada86c --- /dev/null +++ b/pytubepp/download.py @@ -0,0 +1,68 @@ +from tqdm import tqdm +from .config import get_temporary_directory, load_config +from .utils import get_unique_filename +import os, re, requests, shutil, sys, random + +userConfig = load_config() +downloadDIR = userConfig['downloadDIR'] +tempDIR = get_temporary_directory() + +def download_progressive(stream, itag, title, resolution, file_extention, tempDIR=tempDIR, downloadDIR=downloadDIR): + global total_filesize, progress_bar + selected_vdo = stream.get_by_itag(itag) + total_filesize = selected_vdo.filesize + progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Video+Audio") + random_filename = str(random.randint(1000000000, 9999999999)) + filename = random_filename + '_vdo.' + file_extention + output_temp_file = os.path.join(tempDIR, filename) + output_file = os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '.' + file_extention)) + selected_vdo.download(output_path=tempDIR, filename=filename) + print('Processing...') + shutil.move(output_temp_file, output_file) + print('Done! 🎉') + +def download_nonprogressive(stream, itag_vdo, itag_ado, file_extention, output_path): + global total_filesize, progress_bar + selected_vdo = stream.get_by_itag(itag_vdo) + selected_ado = stream.get_by_itag(itag_ado) + random_filename = str(random.randint(1000000000, 9999999999)) + total_filesize = selected_vdo.filesize + progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Video") + selected_vdo.download(output_path=output_path, filename=random_filename + '_vdo.' + file_extention) + total_filesize = selected_ado.filesize + progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Audio") + selected_ado.download(output_path=output_path, filename=random_filename + '_ado.' + file_extention) + return random_filename + +def download_audio(stream, itag, output_path): + global total_filesize, progress_bar + selected_ado = stream.get_by_itag(itag) + total_filesize = selected_ado.filesize + progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Audio") + random_filename = str(random.randint(1000000000, 9999999999)) + selected_ado.download(output_path=output_path, filename=random_filename + '_ado.mp4') + return random_filename + +def download_thumbnail(url, file_path): + print('Downloading thumbnail...') + maxres_url = re.sub(r'/[^/]*\.jpg.*$', '/maxresdefault.jpg', url) + hq_url = re.sub(r'/[^/]*\.jpg.*$', '/hqdefault.jpg', url) + + response = requests.get(maxres_url, stream=True) + if response.status_code != 200: + response = requests.get(hq_url, stream=True) + + if response.status_code == 200: + with open(file_path, 'wb') as file: + response.raw.decode_content = True + shutil.copyfileobj(response.raw, file) + else: + print('Failed to download thumbnail...!') + sys.exit() + +def progress(chunk, file_handle, bytes_remaining): + chunk_size = total_filesize - bytes_remaining + progress_bar.update(chunk_size - progress_bar.n) + + if bytes_remaining == 0: + progress_bar.close() \ No newline at end of file diff --git a/pytubepp/main.py b/pytubepp/main.py index d0755c7..254af24 100644 --- a/pytubepp/main.py +++ b/pytubepp/main.py @@ -1,746 +1,16 @@ -from pytubefix import YouTube -from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB -from tabulate import tabulate -from tqdm import tqdm -from importlib.metadata import version -import appdirs, ffmpy, requests, re, os, sys, random, shutil, platform, json, argparse, tempfile, subprocess - -def network_available(): - try: - param = '-n' if platform.system().lower() == 'windows' else '-c' - command = ['ping', param, '1', 'youtube.com'] - subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) - return True - except subprocess.CalledProcessError: - return False - -def get_version(): - try: - return version('pytubepp') - except Exception as e: - return "Unknown" - -def get_download_folder(): - system = platform.system() - if system in ["Windows", "Darwin", "Linux"]: - cli_download_dir = os.path.join(os.path.expanduser("~"), "Downloads", "PytubePP Downloads") - os.makedirs(cli_download_dir, exist_ok=True) - return cli_download_dir - else: - cli_download_dir = os.path.join(appdirs.user_download_dir(), "PytubePP Downloads") - os.makedirs(cli_download_dir, exist_ok=True) - return cli_download_dir - -def get_temporary_directory(): - temp_dir = tempfile.gettempdir() - cli_temp_dir = os.path.join(temp_dir, 'pytubepp') - os.makedirs(cli_temp_dir, exist_ok=True) - return cli_temp_dir - -DEFAULT_CONFIG = { - 'downloadDIR': get_download_folder(), - 'defaultStream': 'max', -} - -def load_config(): - config_dir = appdirs.user_config_dir('pytubepp') - config_path = os.path.join(config_dir, 'config.json') - if os.path.exists(config_path): - with open(config_path, 'r') as f: - return json.load(f) - else: - return DEFAULT_CONFIG - -def save_config(config): - config_dir = appdirs.user_config_dir('pytubepp') - os.makedirs(config_dir, exist_ok=True) - config_path = os.path.join(config_dir, 'config.json') - with open(config_path, 'w') as f: - json.dump(config, f, indent=4) - -def update_config(key, value): - config = load_config() - config[key] = value - save_config(config) - -def reset_config(): - config_dir = appdirs.user_config_dir('pytubepp') - config_path = os.path.join(config_dir, 'config.json') - if os.path.exists(config_path): - os.remove(config_path) - print('\nConfig reset successful!') - else: - print('\nAlready using the default configs! Not resetting...!') +from .config import get_temporary_directory, load_config, update_config, reset_config +from .utils import get_version, clear_temp_files +from .video import set_global_video_info, show_video_info, show_raw_info, download_stream +import appdirs, os, sys, argparse +global stream, maxres userConfig = load_config() downloadDIR = userConfig['downloadDIR'] tempDIR = get_temporary_directory() configDIR = appdirs.user_config_dir('pytubepp') defaultStream = userConfig['defaultStream'] version = get_version() - -def merge_audio_video(title, resolution, file_extention, random_filename, tempDIR=tempDIR, downloadDIR=downloadDIR): - video_file = os.path.join(tempDIR, random_filename + '_vdo.' + file_extention) - audio_file = os.path.join(tempDIR, random_filename + '_ado.' + file_extention) - output_temp_file = os.path.join(tempDIR, random_filename + '_merged.' + file_extention) - output_file = os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '.' + file_extention)) - - input_params = {video_file: None, audio_file: None} - output_params = {output_temp_file: ['-c:v', 'copy', '-c:a', 'copy']} - - print('Processing...') - devnull = open(os.devnull, 'w') - ff = ffmpy.FFmpeg(inputs=input_params, outputs=output_params) - ff.run(stdout=devnull, stderr=devnull) - devnull.close() - - shutil.move(output_temp_file, output_file) - postprocess_cleanup(tempDIR, ['_vdo.' + file_extention, '_ado.' + file_extention, '_merged.' + file_extention], random_filename) - print('Done! 🎉') - -def convert_to_mp3(title, thumbnail_url, random_filename, mp3_artist='Unknown', mp3_title='Unknown', mp3_album='Unknown', tempDIR=tempDIR, downloadDIR=downloadDIR): - image_file = os.path.join(tempDIR, random_filename + '_thumbnail.jpg') - download_thumbnail(thumbnail_url, image_file) - audio_file = os.path.join(tempDIR, random_filename + '_ado.mp4') - output_file = os.path.join(downloadDIR, get_unique_filename(title + '_audio.mp3')) - - print('Processing...') - devnull = open(os.devnull, 'w') - video_file = os.path.join(tempDIR, random_filename + '_thumbnail.mp4') - ff1 = ffmpy.FFmpeg( - inputs={image_file: '-loop 1 -t 1'}, - outputs={video_file: '-vf "scale=1280:720" -r 1 -c:v libx264 -t 1'} - ) - ff1.run(stdout=devnull, stderr=devnull) - - merged_file = os.path.join(tempDIR, random_filename + '_merged.mp4') - ff2 = ffmpy.FFmpeg( - inputs={video_file: None, audio_file: None}, - outputs={merged_file: '-c:v copy -c:a copy'} - ) - ff2.run(stdout=devnull, stderr=devnull) - - output_temp_file = os.path.join(tempDIR, random_filename + '_merged.mp3') - ff3 = ffmpy.FFmpeg( - inputs={merged_file: None}, - outputs={output_temp_file: '-vn -c:a libmp3lame -q:a 2'} - ) - ff3.run(stdout=devnull, stderr=devnull) - devnull.close() - - audio = ID3(output_temp_file) - audio.add(TIT2(encoding=3, text=mp3_title)) - audio.add(TPE1(encoding=3, text=mp3_artist)) - audio.add(TALB(encoding=3, text=mp3_album)) - with open(image_file, 'rb') as img: - audio.add(APIC( - encoding=3, - mime='image/jpeg', - type=3, - desc=u'Cover', - data=img.read() - )) - audio.save() - - shutil.move(output_temp_file, output_file) - postprocess_cleanup(tempDIR, ['_thumbnail.jpg', '_thumbnail.mp4', '_ado.mp4', '_merged.mp4'], random_filename) - print('Done! 🎉') - -def download_progressive(stream, itag, title, resolution, file_extention, tempDIR=tempDIR, downloadDIR=downloadDIR): - global total_filesize, progress_bar - selected_vdo = stream.get_by_itag(itag) - total_filesize = selected_vdo.filesize - progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Video+Audio") - random_filename = str(random.randint(1000000000, 9999999999)) - filename = random_filename + '_vdo.' + file_extention - output_temp_file = os.path.join(tempDIR, filename) - output_file = os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '.' + file_extention)) - selected_vdo.download(output_path=tempDIR, filename=filename) - print('Processing...') - shutil.move(output_temp_file, output_file) - print('Done! 🎉') - -def download_nonprogressive(stream, itag_vdo, itag_ado, file_extention, output_path): - global total_filesize, progress_bar - selected_vdo = stream.get_by_itag(itag_vdo) - selected_ado = stream.get_by_itag(itag_ado) - random_filename = str(random.randint(1000000000, 9999999999)) - total_filesize = selected_vdo.filesize - progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Video") - selected_vdo.download(output_path=output_path, filename=random_filename + '_vdo.' + file_extention) - total_filesize = selected_ado.filesize - progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Audio") - selected_ado.download(output_path=output_path, filename=random_filename + '_ado.' + file_extention) - return random_filename - -def download_audio(stream, itag, output_path): - global total_filesize, progress_bar - selected_ado = stream.get_by_itag(itag) - total_filesize = selected_ado.filesize - progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Audio") - random_filename = str(random.randint(1000000000, 9999999999)) - selected_ado.download(output_path=output_path, filename=random_filename + '_ado.mp4') - return random_filename - -def download_thumbnail(url, file_path): - print('Downloading thumbnail...') - maxres_url = re.sub(r'/[^/]*\.jpg.*$', '/maxresdefault.jpg', url) - hq_url = re.sub(r'/[^/]*\.jpg.*$', '/hqdefault.jpg', url) - - response = requests.get(maxres_url, stream=True) - if response.status_code != 200: - response = requests.get(hq_url, stream=True) - - if response.status_code == 200: - with open(file_path, 'wb') as file: - response.raw.decode_content = True - shutil.copyfileobj(response.raw, file) - else: - print('Failed to download thumbnail...!') - sys.exit() - -def progress(chunk, file_handle, bytes_remaining): - chunk_size = total_filesize - bytes_remaining - progress_bar.update(chunk_size - progress_bar.n) - - if bytes_remaining == 0: - progress_bar.close() - -def postprocess_cleanup(dir, files, random_filename): - for file in files: - file_path = os.path.join(dir, random_filename + file) - try: - if os.path.isfile(file_path): - os.remove(file_path) - except Exception as e: - print(e) - -def clear_temp_files(): - if os.listdir(tempDIR) != []: - for file in os.listdir(tempDIR): - file_path = os.path.join(tempDIR, file) - try: - if os.path.isfile(file_path): - os.remove(file_path) - print(f'Removed: {file}') - except Exception as e: - print(e) - else: - print('No temporary files found to clear...!') - -def get_unique_filename(filename, directory=downloadDIR): - base_name, extension = os.path.splitext(filename) - counter = 1 - while os.path.exists(os.path.join(directory, filename)): - filename = f"{base_name} ({counter}){extension}" - counter += 1 - return filename - -def is_valid_url(url): - match = re.search(r"(https?://(?:www\.|music\.)?youtube\.com/watch\?v=[^&]{11}|https?://youtu\.be/[^?&]*(\?si=[^&]*)?)", url) - return match - -def set_global_video_info(link): - if not network_available(): - print('\nRequest timeout! Please check your network and try again...!!') - sys.exit() - - if is_valid_url(link): - global video, author, title, thumbnail, views, stream, stream_resolutions, maxres - link = is_valid_url(link).group(1) - video = YouTube(link, on_progress_callback=progress) - author = video.author - title = re.sub(r'[\\/*?:"<>|]', '_', author + ' - ' + video.title) - thumbnail = video.thumbnail_url - views = str(video.views) - stream = video.streams - stream_resolutions = { - '4320p': { - 'allowed_streams': ['8k', '4320', '4320p'], - 'message': ['4320p', '[8k, 4320, 4320p]'] - }, - '2160p': { - 'allowed_streams': ['4k', '2160', '2160p'], - 'message': ['2160p', '[4k, 2160, 2160p]'] - }, - '1440p': { - 'allowed_streams': ['2k', '1440', '1440p'], - 'message': ['1440p', '[2k, 1440, 1440p]'] - }, - '1080p': { - 'allowed_streams': ['fhd', '1080', '1080p'], - 'message': ['1080p', '[fhd, 1080, 1080p]'] - }, - '720p': { - 'allowed_streams': ['hd', '720', '720p'], - 'message': ['720p', '[hd, 720, 720p]'] - }, - '480p': { - 'allowed_streams': ['480', '480p'], - 'message': ['480p', '[480, 480p]'] - }, - '360p': { - 'allowed_streams': ['360', '360p'], - 'message': ['360p', '[360, 360p]'] - }, - '240p': { - 'allowed_streams': ['240', '240p'], - 'message': ['240p', '[240, 240p]'] - }, - '144p': { - 'allowed_streams': ['144', '144p'], - 'message': ['144p', '[144, 144p]'] - }, - 'mp3': { - 'allowed_streams': ['mp3'], - 'message': ['mp3', '[mp3]'] - } - } - for res in stream_resolutions.keys(): - if res != 'mp3' and stream.filter(res=res): - maxres = res - break - return True - else: - return False - -def show_video_info(link): - if set_global_video_info(link): - table = [] - found = False - - for res in stream_resolutions.keys(): - if found or (res not in ['mp3'] and stream.filter(res=res)) or (res == 'mp3' and stream.get_by_itag(140)): - found = True - if res == 'mp3': - matching_stream = stream.get_by_itag(140) - else: - matching_stream = next((s for s in stream if s.resolution == res), None) - if matching_stream is not None: - if res == '4320p': - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - if res == '2160p': - if stream.get_by_itag(701): - type = stream.get_by_itag(701).mime_type - filesize = f"{(stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{stream.get_by_itag(701).fps}fps" - vdo_codec = stream.get_by_itag(701).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(701).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - else: - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(251).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(251).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(251).abr - elif res == '1440p': - if stream.get_by_itag(700): - type = stream.get_by_itag(700).mime_type - filesize = f"{(stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{stream.get_by_itag(700).fps}fps" - vdo_codec = stream.get_by_itag(700).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(700).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - else: - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(251).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(251).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(251).abr - elif res == '1080p': - if stream.get_by_itag(699): - type = stream.get_by_itag(699).mime_type - filesize = f"{(stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{stream.get_by_itag(699).fps}fps" - vdo_codec = stream.get_by_itag(699).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(699).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - else: - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - elif res == '720p': - if stream.get_by_itag(698): - type = stream.get_by_itag(698).mime_type - filesize = f"{(stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{stream.get_by_itag(698).fps}fps" - vdo_codec = stream.get_by_itag(698).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(698).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - else: - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - elif res == '480p': - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - elif res == '360p': - type = matching_stream.mime_type - filesize = f"{matching_stream.filesize / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize >= 1073741824 else f"{matching_stream.filesize / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = matching_stream.audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = matching_stream.abr - elif res in ['240p', '144p']: - type = matching_stream.mime_type - filesize = f"{(matching_stream.filesize + stream.get_by_itag(139).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(139).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(139).filesize) / (1024 * 1024):.2f} MB" - fps = f"{matching_stream.fps}fps" - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(139).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(139).abr - elif res == 'mp3': - type = "audio/mp3" - filesize = f"{matching_stream.filesize / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize >= 1073741824 else f"{matching_stream.filesize / (1024 * 1024):.2f} MB" - fps = "none" - vdo_codec = "none" - ado_codec = matching_stream.audio_codec - vdo_bitrate = "none" - ado_bitrate = matching_stream.abr - - else: - filesize = "N/A" - message = stream_resolutions[res]['message'] + [type] + [filesize] + [fps] + [vdo_codec] + [ado_codec] + [vdo_bitrate] + [ado_bitrate] - table.append(message) - - if not found: - print('Sorry, No video streams found....!!!') - sys.exit() - - print(f'\nTitle: {video.title}\nAuthor: {author}\nPublished On: {video.publish_date.strftime("%d/%m/%Y")}\nDuration: {video.length}\nViews: {views}\n') - print(tabulate(table, headers=['Stream', 'Alias (for -s flag)', 'Format', 'Size', 'FrameRate', 'V-Codec', 'A-Codec', 'V-BitRate', 'A-BitRate'])) - print('\n') - else: - print('\nInvalid video link! Please enter a valid video url...!!') - -def show_raw_info(link, prettify=False): - if set_global_video_info(link): - streams_list = [] - found = False - - for res in stream_resolutions.keys(): - if found or (res not in ['mp3'] and stream.filter(res=res)) or (res == 'mp3' and stream.get_by_itag(140)): - found = True - if res == 'mp3': - matching_stream = stream.get_by_itag(140) - else: - matching_stream = next((s for s in stream if s.resolution == res), None) - if matching_stream is not None: - if res == '4320p': - itag = matching_stream.itag - resolution = '4320p' - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(140).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = True if matching_stream.itag == 702 else False - if res == '2160p': - resolution = '2160p' - if stream.get_by_itag(701): - itag = 701 - type = stream.get_by_itag(701).mime_type - filesize = stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize - fps = stream.get_by_itag(701).fps - vdo_codec = stream.get_by_itag(701).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(701).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = True - else: - itag = matching_stream.itag - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(251).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(251).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(251).abr - is_hdr = False - elif res == '1440p': - resolution = '1440p' - if stream.get_by_itag(700): - itag = 700 - type = stream.get_by_itag(700).mime_type - filesize = stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize - fps = stream.get_by_itag(700).fps - vdo_codec = stream.get_by_itag(700).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(700).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = True - else: - itag = matching_stream.itag - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(251).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(251).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(251).abr - is_hdr = False - elif res == '1080p': - resolution = '1080p' - if stream.get_by_itag(699): - itag = 699 - type = stream.get_by_itag(699).mime_type - filesize = stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize - fps = stream.get_by_itag(699).fps - vdo_codec = stream.get_by_itag(699).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(699).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = True - else: - itag = matching_stream.itag - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(140).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = False - elif res == '720p': - resolution = '720p' - if stream.get_by_itag(698): - itag = 698 - type = stream.get_by_itag(698).mime_type - filesize = stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize - fps = stream.get_by_itag(698).fps - vdo_codec = stream.get_by_itag(698).video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{stream.get_by_itag(698).bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = True - else: - itag = matching_stream.itag - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(140).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = False - elif res == '480p': - itag = matching_stream.itag - resolution = '480p' - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(140).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(140).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(140).abr - is_hdr = False - elif res == '360p': - itag = matching_stream.itag - resolution = '360p' - type = matching_stream.mime_type - filesize = matching_stream.filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = matching_stream.audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = matching_stream.abr - is_hdr = False - elif res in ['240p', '144p']: - itag = matching_stream.itag - resolution = res - type = matching_stream.mime_type - filesize = matching_stream.filesize + stream.get_by_itag(139).filesize - fps = matching_stream.fps - vdo_codec = matching_stream.video_codec - ado_codec = stream.get_by_itag(139).audio_codec - vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" - ado_bitrate = stream.get_by_itag(139).abr - is_hdr = False - elif res == 'mp3': - itag = matching_stream.itag - resolution = 'mp3' - type = "audio/mp3" - filesize = matching_stream.filesize - fps = None - vdo_codec = None - ado_codec = matching_stream.audio_codec - vdo_bitrate = None - ado_bitrate = matching_stream.abr - is_hdr = False - - else: - filesize = "N/A" - current_stream = { - 'itag': itag, - 'res': resolution, - 'mime_type': type, - 'file_size': filesize, - 'fps': fps, - 'vcodec': vdo_codec, - 'acodec': ado_codec, - 'vbitrate': vdo_bitrate, - 'abitrate': ado_bitrate, - 'is_hdr': is_hdr - } - streams_list.append(current_stream) - - if not found: - print('Sorry, No video streams found....!!!') - sys.exit() - - if prettify: - print(json.dumps({ - 'id': video.video_id, - 'title': video.title, - 'author': author, - 'thumbnail_url': thumbnail, - 'views': video.views, - 'published_on': video.publish_date.strftime('%d/%m/%Y'), - 'duration': video.length, - 'streams': streams_list, - }, indent=4)) - else: - print(json.dumps({ - 'id': video.video_id, - 'title': video.title, - 'author': author, - 'thumbnail_url': thumbnail, - 'views': video.views, - 'published_on': video.publish_date.strftime('%d/%m/%Y'), - 'duration': video.length, - 'streams': streams_list, - })) - else: - print('\nInvalid video link! Please enter a valid video url...!!') - -def get_allowed_streams(link): - if set_global_video_info(link): - allowed_streams = [] - found = False - for res in stream_resolutions.keys(): - if found or (res not in ['mp3'] and stream.filter(res=res)) or (res == 'mp3' and stream.get_by_itag(140)): - found = True - allowed_streams.extend(stream_resolutions[res]['allowed_streams']) - return allowed_streams - else: - print('\nInvalid video link! Please enter a valid video url...!!') - return [] - -def print_short_info(chosen_stream): - if chosen_stream in ['720', '720p', 'hd']: - print(f'\nVideo: {title}\nSelected Stream: 720p (HD)\n') - elif chosen_stream in ['360', '360p']: - print(f'\nVideo: {title}\nSelected Stream: 360p (SD)\n') - elif chosen_stream in ['1080', '1080p', 'fhd']: - print(f'\nVideo: {title}\nSelected Stream: 1080p (FHD)\n') - elif chosen_stream in ['480', '480p']: - print(f'\nVideo: {title}\nSelected Stream: 480p (SD)\n') - elif chosen_stream in ['240', '240p']: - print(f'\nVideo: {title}\nSelected Stream: 240p (LD)\n') - elif chosen_stream in ['144', '144p']: - print(f'\nVideo: {title}\nSelected Stream: 144p (LD)\n') - elif chosen_stream in ['4320', '4320p', '8k']: - print(f'\nVideo: {title}\nSelected Stream: 4320p (8K)\n') - elif chosen_stream in ['2160', '2160p', '4k']: - print(f'\nVideo: {title}\nSelected Stream: 2160p (4K)\n') - elif chosen_stream in ['1440', '1440p', '2k']: - print(f'\nVideo: {title}\nSelected Stream: 1440p (2K)\n') - elif chosen_stream == 'mp3': - print(f'\nVideo: {title}\nSelected Stream: mp3 (Audio)\n') - -def download_stream(link, chosen_stream): - if set_global_video_info(link): - print_short_info(chosen_stream) - allowed_streams = get_allowed_streams(link) - if chosen_stream in allowed_streams: - if chosen_stream in ['360', '360p']: - download_progressive(stream, 18, title, '360p', 'mp4') - - elif chosen_stream in ['1080', '1080p', 'fhd']: - if stream.get_by_itag(699): - merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 699, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(299): - merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 299, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(137): - merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 137, 140, 'mp4', tempDIR)) - - elif chosen_stream in ['720', '720p', 'hd']: - if stream.get_by_itag(698): - merge_audio_video(title, '720p', 'mp4', download_nonprogressive(stream, 698, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(298): - merge_audio_video(title, '720p', 'mp4', download_nonprogressive(stream, 298, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(136): - merge_audio_video(title, '720p', 'mp4', download_nonprogressive(stream, 136, 140, 'mp4', tempDIR)) - - elif chosen_stream in ['480', '480p']: - merge_audio_video(title, '480p', 'mp4', download_nonprogressive(stream, 135, 140, 'mp4', tempDIR)) - - elif chosen_stream in ['240', '240p']: - merge_audio_video(title, '240p', 'mp4', download_nonprogressive(stream, 133, 139, 'mp4', tempDIR)) - - elif chosen_stream in ['144', '144p']: - merge_audio_video(title, '144p', 'mp4', download_nonprogressive(stream, 160, 139, 'mp4', tempDIR)) - - elif chosen_stream in ['4320', '4320p', '8k']: - if stream.get_by_itag(702): - merge_audio_video(title, '8k', 'mp4', download_nonprogressive(stream, 702, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(571): - merge_audio_video(title, '8k', 'mp4', download_nonprogressive(stream, 571, 140, 'mp4', tempDIR)) - - elif chosen_stream in ['2160', '2160p', '4k']: - if stream.get_by_itag(701): - merge_audio_video(title, '4k', 'mp4', download_nonprogressive(stream, 701, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(315): - merge_audio_video(title, '4k', 'webm', download_nonprogressive(stream, 315, 251, 'webm', tempDIR)) - elif stream.get_by_itag(313): - merge_audio_video(title, '4k', 'webm', download_nonprogressive(stream, 313, 251, 'webm', tempDIR)) - - elif chosen_stream in ['1440', '1440p', '2k']: - if stream.get_by_itag(700): - merge_audio_video(title, '2k', 'mp4', download_nonprogressive(stream, 700, 140, 'mp4', tempDIR)) - elif stream.get_by_itag(308): - merge_audio_video(title, '2k', 'webm', download_nonprogressive(stream, 308, 251, 'webm', tempDIR)) - elif stream.get_by_itag(271): - merge_audio_video(title, '2k', 'webm', download_nonprogressive(stream, 271, 251, 'webm', tempDIR)) - - elif chosen_stream == 'mp3': - convert_to_mp3(title, thumbnail, download_audio(stream, 140, tempDIR), author, video.title, author) - else: - print('\nInvalid download stream or stream not available! Please choose a different stream...!! (use -i to see available streams)') - else: - print('\nInvalid video link! Please enter a valid video url...!!') - def main(): parser = argparse.ArgumentParser(description=f'PytubePP (Pytube Post Processor) v{version} - A Simple CLI Tool to Download Your Favorite YouTube Videos Effortlessly!') parser.add_argument('url', nargs='?', default=None, help='url of the youtube video') diff --git a/pytubepp/postprocess.py b/pytubepp/postprocess.py new file mode 100644 index 0000000..5d6e02d --- /dev/null +++ b/pytubepp/postprocess.py @@ -0,0 +1,76 @@ +from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB +from .config import get_temporary_directory, load_config +from .utils import get_unique_filename, postprocess_cleanup +from .download import download_thumbnail +import os, shutil, ffmpy + +userConfig = load_config() +downloadDIR = userConfig['downloadDIR'] +tempDIR = get_temporary_directory() + +def merge_audio_video(title, resolution, file_extention, random_filename, tempDIR=tempDIR, downloadDIR=downloadDIR): + video_file = os.path.join(tempDIR, random_filename + '_vdo.' + file_extention) + audio_file = os.path.join(tempDIR, random_filename + '_ado.' + file_extention) + output_temp_file = os.path.join(tempDIR, random_filename + '_merged.' + file_extention) + output_file = os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '.' + file_extention)) + + input_params = {video_file: None, audio_file: None} + output_params = {output_temp_file: ['-c:v', 'copy', '-c:a', 'copy']} + + print('Processing...') + devnull = open(os.devnull, 'w') + ff = ffmpy.FFmpeg(inputs=input_params, outputs=output_params) + ff.run(stdout=devnull, stderr=devnull) + devnull.close() + + shutil.move(output_temp_file, output_file) + postprocess_cleanup(tempDIR, ['_vdo.' + file_extention, '_ado.' + file_extention, '_merged.' + file_extention], random_filename) + print('Done! 🎉') + +def convert_to_mp3(title, thumbnail_url, random_filename, mp3_artist='Unknown', mp3_title='Unknown', mp3_album='Unknown', tempDIR=tempDIR, downloadDIR=downloadDIR): + image_file = os.path.join(tempDIR, random_filename + '_thumbnail.jpg') + download_thumbnail(thumbnail_url, image_file) + audio_file = os.path.join(tempDIR, random_filename + '_ado.mp4') + output_file = os.path.join(downloadDIR, get_unique_filename(title + '_audio.mp3')) + + print('Processing...') + devnull = open(os.devnull, 'w') + video_file = os.path.join(tempDIR, random_filename + '_thumbnail.mp4') + ff1 = ffmpy.FFmpeg( + inputs={image_file: '-loop 1 -t 1'}, + outputs={video_file: '-vf "scale=1280:720" -r 1 -c:v libx264 -t 1'} + ) + ff1.run(stdout=devnull, stderr=devnull) + + merged_file = os.path.join(tempDIR, random_filename + '_merged.mp4') + ff2 = ffmpy.FFmpeg( + inputs={video_file: None, audio_file: None}, + outputs={merged_file: '-c:v copy -c:a copy'} + ) + ff2.run(stdout=devnull, stderr=devnull) + + output_temp_file = os.path.join(tempDIR, random_filename + '_merged.mp3') + ff3 = ffmpy.FFmpeg( + inputs={merged_file: None}, + outputs={output_temp_file: '-vn -c:a libmp3lame -q:a 2'} + ) + ff3.run(stdout=devnull, stderr=devnull) + devnull.close() + + audio = ID3(output_temp_file) + audio.add(TIT2(encoding=3, text=mp3_title)) + audio.add(TPE1(encoding=3, text=mp3_artist)) + audio.add(TALB(encoding=3, text=mp3_album)) + with open(image_file, 'rb') as img: + audio.add(APIC( + encoding=3, + mime='image/jpeg', + type=3, + desc=u'Cover', + data=img.read() + )) + audio.save() + + shutil.move(output_temp_file, output_file) + postprocess_cleanup(tempDIR, ['_thumbnail.jpg', '_thumbnail.mp4', '_ado.mp4', '_merged.mp4'], random_filename) + print('Done! 🎉') \ No newline at end of file diff --git a/pytubepp/utils.py b/pytubepp/utils.py new file mode 100644 index 0000000..c0f951c --- /dev/null +++ b/pytubepp/utils.py @@ -0,0 +1,56 @@ +from importlib.metadata import version +from .config import load_config, get_temporary_directory +import os, re, subprocess, platform + +userConfig = load_config() +downloadDIR = userConfig['downloadDIR'] +tempDIR = get_temporary_directory() + +def network_available(): + try: + param = '-n' if platform.system().lower() == 'windows' else '-c' + command = ['ping', param, '1', 'youtube.com'] + subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + return True + except subprocess.CalledProcessError: + return False + +def get_version(): + try: + return version('pytubepp') + except Exception as e: + return "Unknown" + +def is_valid_url(url): + match = re.search(r"(https?://(?:www\.|music\.)?youtube\.com/watch\?v=[^&]{11}|https?://youtu\.be/[^?&]*(\?si=[^&]*)?)", url) + return match + +def get_unique_filename(filename, directory=downloadDIR): + base_name, extension = os.path.splitext(filename) + counter = 1 + while os.path.exists(os.path.join(directory, filename)): + filename = f"{base_name} ({counter}){extension}" + counter += 1 + return filename + +def postprocess_cleanup(dir, files, random_filename): + for file in files: + file_path = os.path.join(dir, random_filename + file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + except Exception as e: + print(e) + +def clear_temp_files(): + if os.listdir(tempDIR) != []: + for file in os.listdir(tempDIR): + file_path = os.path.join(tempDIR, file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + print(f'Removed: {file}') + except Exception as e: + print(e) + else: + print('No temporary files found to clear...!') \ No newline at end of file diff --git a/pytubepp/video.py b/pytubepp/video.py new file mode 100644 index 0000000..059f7da --- /dev/null +++ b/pytubepp/video.py @@ -0,0 +1,509 @@ +from pytubefix import YouTube +from tabulate import tabulate +from .config import get_temporary_directory +from .download import download_progressive, download_nonprogressive, download_audio, progress +from .postprocess import merge_audio_video, convert_to_mp3 +from .utils import network_available, is_valid_url +import re, sys, json + +tempDIR = get_temporary_directory() + +def set_global_video_info(link): + if not network_available(): + print('\nRequest timeout! Please check your network and try again...!!') + sys.exit() + + if is_valid_url(link): + global video, author, title, thumbnail, views, stream, stream_resolutions, maxres + link = is_valid_url(link).group(1) + video = YouTube(link, on_progress_callback=progress) + author = video.author + title = re.sub(r'[\\/*?:"<>|]', '_', author + ' - ' + video.title) + thumbnail = video.thumbnail_url + views = str(video.views) + stream = video.streams + stream_resolutions = { + '4320p': { + 'allowed_streams': ['8k', '4320', '4320p'], + 'message': ['4320p', '[8k, 4320, 4320p]'] + }, + '2160p': { + 'allowed_streams': ['4k', '2160', '2160p'], + 'message': ['2160p', '[4k, 2160, 2160p]'] + }, + '1440p': { + 'allowed_streams': ['2k', '1440', '1440p'], + 'message': ['1440p', '[2k, 1440, 1440p]'] + }, + '1080p': { + 'allowed_streams': ['fhd', '1080', '1080p'], + 'message': ['1080p', '[fhd, 1080, 1080p]'] + }, + '720p': { + 'allowed_streams': ['hd', '720', '720p'], + 'message': ['720p', '[hd, 720, 720p]'] + }, + '480p': { + 'allowed_streams': ['480', '480p'], + 'message': ['480p', '[480, 480p]'] + }, + '360p': { + 'allowed_streams': ['360', '360p'], + 'message': ['360p', '[360, 360p]'] + }, + '240p': { + 'allowed_streams': ['240', '240p'], + 'message': ['240p', '[240, 240p]'] + }, + '144p': { + 'allowed_streams': ['144', '144p'], + 'message': ['144p', '[144, 144p]'] + }, + 'mp3': { + 'allowed_streams': ['mp3'], + 'message': ['mp3', '[mp3]'] + } + } + for res in stream_resolutions.keys(): + if res != 'mp3' and stream.filter(res=res): + maxres = res + break + return True + else: + return False + +def show_video_info(link): + if set_global_video_info(link): + table = [] + found = False + + for res in stream_resolutions.keys(): + if found or (res not in ['mp3'] and stream.filter(res=res)) or (res == 'mp3' and stream.get_by_itag(140)): + found = True + if res == 'mp3': + matching_stream = stream.get_by_itag(140) + else: + matching_stream = next((s for s in stream if s.resolution == res), None) + if matching_stream is not None: + if res == '4320p': + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + if res == '2160p': + if stream.get_by_itag(701): + type = stream.get_by_itag(701).mime_type + filesize = f"{(stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{stream.get_by_itag(701).fps}fps" + vdo_codec = stream.get_by_itag(701).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(701).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + else: + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(251).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(251).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(251).abr + elif res == '1440p': + if stream.get_by_itag(700): + type = stream.get_by_itag(700).mime_type + filesize = f"{(stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{stream.get_by_itag(700).fps}fps" + vdo_codec = stream.get_by_itag(700).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(700).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + else: + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(251).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(251).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(251).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(251).abr + elif res == '1080p': + if stream.get_by_itag(699): + type = stream.get_by_itag(699).mime_type + filesize = f"{(stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{stream.get_by_itag(699).fps}fps" + vdo_codec = stream.get_by_itag(699).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(699).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + else: + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + elif res == '720p': + if stream.get_by_itag(698): + type = stream.get_by_itag(698).mime_type + filesize = f"{(stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{stream.get_by_itag(698).fps}fps" + vdo_codec = stream.get_by_itag(698).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(698).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + else: + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + elif res == '480p': + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(140).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(140).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + elif res == '360p': + type = matching_stream.mime_type + filesize = f"{matching_stream.filesize / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize >= 1073741824 else f"{matching_stream.filesize / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = matching_stream.audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = matching_stream.abr + elif res in ['240p', '144p']: + type = matching_stream.mime_type + filesize = f"{(matching_stream.filesize + stream.get_by_itag(139).filesize) / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize + stream.get_by_itag(139).filesize >= 1073741824 else f"{(matching_stream.filesize + stream.get_by_itag(139).filesize) / (1024 * 1024):.2f} MB" + fps = f"{matching_stream.fps}fps" + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(139).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(139).abr + elif res == 'mp3': + type = "audio/mp3" + filesize = f"{matching_stream.filesize / (1024 * 1024 * 1024):.2f} GB" if matching_stream.filesize >= 1073741824 else f"{matching_stream.filesize / (1024 * 1024):.2f} MB" + fps = "none" + vdo_codec = "none" + ado_codec = matching_stream.audio_codec + vdo_bitrate = "none" + ado_bitrate = matching_stream.abr + + else: + filesize = "N/A" + message = stream_resolutions[res]['message'] + [type] + [filesize] + [fps] + [vdo_codec] + [ado_codec] + [vdo_bitrate] + [ado_bitrate] + table.append(message) + + if not found: + print('Sorry, No video streams found....!!!') + sys.exit() + + print(f'\nTitle: {video.title}\nAuthor: {author}\nPublished On: {video.publish_date.strftime("%d/%m/%Y")}\nDuration: {video.length}\nViews: {views}\n') + print(tabulate(table, headers=['Stream', 'Alias (for -s flag)', 'Format', 'Size', 'FrameRate', 'V-Codec', 'A-Codec', 'V-BitRate', 'A-BitRate'])) + print('\n') + else: + print('\nInvalid video link! Please enter a valid video url...!!') + +def show_raw_info(link, prettify=False): + if set_global_video_info(link): + streams_list = [] + found = False + + for res in stream_resolutions.keys(): + if found or (res not in ['mp3'] and stream.filter(res=res)) or (res == 'mp3' and stream.get_by_itag(140)): + found = True + if res == 'mp3': + matching_stream = stream.get_by_itag(140) + else: + matching_stream = next((s for s in stream if s.resolution == res), None) + if matching_stream is not None: + if res == '4320p': + itag = matching_stream.itag + resolution = '4320p' + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(140).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = True if matching_stream.itag == 702 else False + if res == '2160p': + resolution = '2160p' + if stream.get_by_itag(701): + itag = 701 + type = stream.get_by_itag(701).mime_type + filesize = stream.get_by_itag(701).filesize + stream.get_by_itag(140).filesize + fps = stream.get_by_itag(701).fps + vdo_codec = stream.get_by_itag(701).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(701).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = True + else: + itag = matching_stream.itag + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(251).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(251).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(251).abr + is_hdr = False + elif res == '1440p': + resolution = '1440p' + if stream.get_by_itag(700): + itag = 700 + type = stream.get_by_itag(700).mime_type + filesize = stream.get_by_itag(700).filesize + stream.get_by_itag(140).filesize + fps = stream.get_by_itag(700).fps + vdo_codec = stream.get_by_itag(700).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(700).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = True + else: + itag = matching_stream.itag + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(251).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(251).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(251).abr + is_hdr = False + elif res == '1080p': + resolution = '1080p' + if stream.get_by_itag(699): + itag = 699 + type = stream.get_by_itag(699).mime_type + filesize = stream.get_by_itag(699).filesize + stream.get_by_itag(140).filesize + fps = stream.get_by_itag(699).fps + vdo_codec = stream.get_by_itag(699).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(699).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = True + else: + itag = matching_stream.itag + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(140).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = False + elif res == '720p': + resolution = '720p' + if stream.get_by_itag(698): + itag = 698 + type = stream.get_by_itag(698).mime_type + filesize = stream.get_by_itag(698).filesize + stream.get_by_itag(140).filesize + fps = stream.get_by_itag(698).fps + vdo_codec = stream.get_by_itag(698).video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{stream.get_by_itag(698).bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = True + else: + itag = matching_stream.itag + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(140).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = False + elif res == '480p': + itag = matching_stream.itag + resolution = '480p' + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(140).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(140).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(140).abr + is_hdr = False + elif res == '360p': + itag = matching_stream.itag + resolution = '360p' + type = matching_stream.mime_type + filesize = matching_stream.filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = matching_stream.audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = matching_stream.abr + is_hdr = False + elif res in ['240p', '144p']: + itag = matching_stream.itag + resolution = res + type = matching_stream.mime_type + filesize = matching_stream.filesize + stream.get_by_itag(139).filesize + fps = matching_stream.fps + vdo_codec = matching_stream.video_codec + ado_codec = stream.get_by_itag(139).audio_codec + vdo_bitrate = f"{matching_stream.bitrate / 1024:.0f}kbps" + ado_bitrate = stream.get_by_itag(139).abr + is_hdr = False + elif res == 'mp3': + itag = matching_stream.itag + resolution = 'mp3' + type = "audio/mp3" + filesize = matching_stream.filesize + fps = None + vdo_codec = None + ado_codec = matching_stream.audio_codec + vdo_bitrate = None + ado_bitrate = matching_stream.abr + is_hdr = False + + else: + filesize = "N/A" + current_stream = { + 'itag': itag, + 'res': resolution, + 'mime_type': type, + 'file_size': filesize, + 'fps': fps, + 'vcodec': vdo_codec, + 'acodec': ado_codec, + 'vbitrate': vdo_bitrate, + 'abitrate': ado_bitrate, + 'is_hdr': is_hdr + } + streams_list.append(current_stream) + + if not found: + print('Sorry, No video streams found....!!!') + sys.exit() + + if prettify: + print(json.dumps({ + 'id': video.video_id, + 'title': video.title, + 'author': author, + 'thumbnail_url': thumbnail, + 'views': video.views, + 'published_on': video.publish_date.strftime('%d/%m/%Y'), + 'duration': video.length, + 'streams': streams_list, + }, indent=4)) + else: + print(json.dumps({ + 'id': video.video_id, + 'title': video.title, + 'author': author, + 'thumbnail_url': thumbnail, + 'views': video.views, + 'published_on': video.publish_date.strftime('%d/%m/%Y'), + 'duration': video.length, + 'streams': streams_list, + })) + else: + print('\nInvalid video link! Please enter a valid video url...!!') + +def get_allowed_streams(link): + if set_global_video_info(link): + allowed_streams = [] + found = False + for res in stream_resolutions.keys(): + if found or (res not in ['mp3'] and stream.filter(res=res)) or (res == 'mp3' and stream.get_by_itag(140)): + found = True + allowed_streams.extend(stream_resolutions[res]['allowed_streams']) + return allowed_streams + else: + print('\nInvalid video link! Please enter a valid video url...!!') + return [] + +def print_short_info(chosen_stream): + if chosen_stream in ['720', '720p', 'hd']: + print(f'\nVideo: {title}\nSelected Stream: 720p (HD)\n') + elif chosen_stream in ['360', '360p']: + print(f'\nVideo: {title}\nSelected Stream: 360p (SD)\n') + elif chosen_stream in ['1080', '1080p', 'fhd']: + print(f'\nVideo: {title}\nSelected Stream: 1080p (FHD)\n') + elif chosen_stream in ['480', '480p']: + print(f'\nVideo: {title}\nSelected Stream: 480p (SD)\n') + elif chosen_stream in ['240', '240p']: + print(f'\nVideo: {title}\nSelected Stream: 240p (LD)\n') + elif chosen_stream in ['144', '144p']: + print(f'\nVideo: {title}\nSelected Stream: 144p (LD)\n') + elif chosen_stream in ['4320', '4320p', '8k']: + print(f'\nVideo: {title}\nSelected Stream: 4320p (8K)\n') + elif chosen_stream in ['2160', '2160p', '4k']: + print(f'\nVideo: {title}\nSelected Stream: 2160p (4K)\n') + elif chosen_stream in ['1440', '1440p', '2k']: + print(f'\nVideo: {title}\nSelected Stream: 1440p (2K)\n') + elif chosen_stream == 'mp3': + print(f'\nVideo: {title}\nSelected Stream: mp3 (Audio)\n') + +def download_stream(link, chosen_stream): + if set_global_video_info(link): + print_short_info(chosen_stream) + allowed_streams = get_allowed_streams(link) + if chosen_stream in allowed_streams: + if chosen_stream in ['360', '360p']: + download_progressive(stream, 18, title, '360p', 'mp4') + + elif chosen_stream in ['1080', '1080p', 'fhd']: + if stream.get_by_itag(699): + merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 699, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(299): + merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 299, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(137): + merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 137, 140, 'mp4', tempDIR)) + + elif chosen_stream in ['720', '720p', 'hd']: + if stream.get_by_itag(698): + merge_audio_video(title, '720p', 'mp4', download_nonprogressive(stream, 698, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(298): + merge_audio_video(title, '720p', 'mp4', download_nonprogressive(stream, 298, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(136): + merge_audio_video(title, '720p', 'mp4', download_nonprogressive(stream, 136, 140, 'mp4', tempDIR)) + + elif chosen_stream in ['480', '480p']: + merge_audio_video(title, '480p', 'mp4', download_nonprogressive(stream, 135, 140, 'mp4', tempDIR)) + + elif chosen_stream in ['240', '240p']: + merge_audio_video(title, '240p', 'mp4', download_nonprogressive(stream, 133, 139, 'mp4', tempDIR)) + + elif chosen_stream in ['144', '144p']: + merge_audio_video(title, '144p', 'mp4', download_nonprogressive(stream, 160, 139, 'mp4', tempDIR)) + + elif chosen_stream in ['4320', '4320p', '8k']: + if stream.get_by_itag(702): + merge_audio_video(title, '8k', 'mp4', download_nonprogressive(stream, 702, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(571): + merge_audio_video(title, '8k', 'mp4', download_nonprogressive(stream, 571, 140, 'mp4', tempDIR)) + + elif chosen_stream in ['2160', '2160p', '4k']: + if stream.get_by_itag(701): + merge_audio_video(title, '4k', 'mp4', download_nonprogressive(stream, 701, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(315): + merge_audio_video(title, '4k', 'webm', download_nonprogressive(stream, 315, 251, 'webm', tempDIR)) + elif stream.get_by_itag(313): + merge_audio_video(title, '4k', 'webm', download_nonprogressive(stream, 313, 251, 'webm', tempDIR)) + + elif chosen_stream in ['1440', '1440p', '2k']: + if stream.get_by_itag(700): + merge_audio_video(title, '2k', 'mp4', download_nonprogressive(stream, 700, 140, 'mp4', tempDIR)) + elif stream.get_by_itag(308): + merge_audio_video(title, '2k', 'webm', download_nonprogressive(stream, 308, 251, 'webm', tempDIR)) + elif stream.get_by_itag(271): + merge_audio_video(title, '2k', 'webm', download_nonprogressive(stream, 271, 251, 'webm', tempDIR)) + + elif chosen_stream == 'mp3': + convert_to_mp3(title, thumbnail, download_audio(stream, 140, tempDIR), author, video.title, author) + else: + print('\nInvalid download stream or stream not available! Please choose a different stream...!! (use -i to see available streams)') + else: + print('\nInvalid video link! Please enter a valid video url...!!') \ No newline at end of file