1
1
mirror of https://github.com/neosubhamoy/pytubepp.git synced 2025-12-19 12:23:05 +05:30

(first): initial commit

This commit is contained in:
2024-02-19 20:52:37 +05:30
commit 740fc83265
7 changed files with 674 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
# Compiled python modules.
*.pyc
# Setuptools distribution folder.
/dist/
# Setuptools build folder.
/build/
# Python egg metadata, regenerated from source files by setuptools.
/*.egg-info

21
LICENCE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Subhamoy Biswas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

84
README.md Normal file
View File

@@ -0,0 +1,84 @@
# pytubePP - (Pytube Post Processor)
### A Simple CLI Tool to Download Your Favourite YouTube Videos Effortlessly!
[![status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/neosubhamoy/pytubepp/)
[![verion](https://img.shields.io/badge/version-v1.0.1_stable-yellow.svg?style=flat)](https://github.com/neosubhamoy/pytubepp/)
[![python](https://img.shields.io/badge/python-v3.12.x-blue?logo=python&style=flat)](https://www.python.org/downloads/)
[![builds](https://img.shields.io/badge/builds-passing-brightgreen.svg?style=flat)](https://github.com/neosubhamoy/pytubepp/)
[![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/neosubhamoy/pytubepp/)
### **🏷️ Features**
* Auto Post-Process & Merge YouTube DASH Streams
* Supports MP3 Download (with Embeded Thumbnail and Tags)
* Smart Stream Selection
* Highly Configurable and Many More 😉
### **🧩 Dependencies**
* [pytube](https://pypi.org/project/pytube/)
* [FFmpeg (Not Pre-Included)](https://ffmpeg.org/)
* [ffmpy](https://pypi.org/project/ffmpy/)
* [mutagen](https://pypi.org/project/mutagen/)
* [tabulate](https://pypi.org/project/tabulate/)
* [tqdm](https://pypi.org/project/tqdm/)
* [appdirs](https://pypi.org/project/appdirs/)
* [requests](https://pypi.org/project/requests/)
* [setuptools](https://pypi.org/project/setuptools/)
### **🛠️ Installation**
You can install pytubePP in your system via PIP by simply running the below command
```terminal
pip install pytubepp
```
**IMPORTANT: Before installing pytubePP make sure FFmpeg is installed in your system and accesable via your cli interface (FFmpeg is Must Required as some of the core features of pytubePP relies on FFmpeg, but due to security reasons we can not ship it with the module)**
**>> Install FFmpeg (If you haven't already!)**
Linux (Ubuntu): `apt install ffmpeg`<br>
Windows (using Chocolatey): `choco install ffmpeg`<br>
MacOS (using Homebrew): `brew install ffmpeg`<br>
Android (using Termux): `pkg install ffmpeg`
### **📌 Commands and Flags**
Using pytubePP is as simple as just supplying it only the YouTube video url as argument!
** Before Starting Please NOTE: pytubePP follows a simple rule - "Use the Default Download Configuration if No Flags are Passed"
* To download a video in maximum available resolution the command will look like:
```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo"
```
* To download the video in a specific resolution (suppose 480p) the command will be:
```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s 480p
```
* To download the video in audio-only MP3 format the command will be:
```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3
```
* List of all available flags are given below:
| Flag | Usage | Requires Parameter | Requires URL | Parameters | Default |
| :--- | :--- | :--- | :--- | :--- | :--- |
| -s | Choose preferred download stream | YES | YES | `144` `144p` `240` `240p` `360` `360p` `480` `480p` `720` `720p` `hd` `1080` `1080p` `fhd` `1440` `1440p` `2k` `2160` `2160p` `4k` `mp3` (Pass any one of them) | Your chosen Default Stream via `-ds` flag |
| -i | Shows the video information like: Title, Author, Views, Available Download Streams | NO | YES | No parameters | No default |
| -ds | Set default download stream | YES | NO | `144p` `240p` `360p` `480p` `720p` `1080p` `1440p` `2160p` `mp3` `max` (Pass any one of them) | `max` |
| -df | Set custom download folder path | YES | NO | Use the full path excluding the last trailing slash within double quotes eg(in Linux):`"/home/YourUsername/Downloads/CoolFolderName"` (Make sure the folder path you enterted is already created and accessable) | Within `Pytube Downloads` folder in your System's `Downloads` folder |
| -r | Reset to default configuration (Download Folder, Default Stream) | NO | NO | No parameters | No default |
| -sc | Show all current user configurations | NO | NO | No parameters | No default |
| -ct | Clear temporary files (audio, video, thumbnail) of the failed, incomplete downloads | NO | NO | No parameters | No default |
⭕ Noticed any Bugs? or Want to give me some suggetions? always feel free to open an issue...!!
### 📝 License & Usage
pytubePP - (Pytube Post Processor) is a Fully Open Sourced Project licensed under MIT License. Anyone can view, modify, use (personal and commercial) or distribute it's sources without any attribution and extra permissions.
**🌟 Liked this project? Please consider giving it a star to show me your appreciation**
<br></br>
<br></br>
****
An Open Sourced Project - Developed with ❤️ by **Subhamoy**

1
pytubepp/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .main import main

491
pytubepp/main.py Normal file
View File

@@ -0,0 +1,491 @@
from pytube import YouTube
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB
from tabulate import tabulate
from tqdm import tqdm
import appdirs, ffmpy, requests, re, os, sys, random, shutil, platform, json, argparse, tempfile, pkg_resources
def get_version():
try:
return pkg_resources.get_distribution("pytubepp").version
except pkg_resources.DistributionNotFound:
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", "Pytube Downloads")
os.makedirs(cli_download_dir, exist_ok=True)
return cli_download_dir
else:
cli_download_dir = os.path.join(appdirs.user_download_dir(), "Pytube 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...!')
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()
os.rename(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()
os.rename(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 vdo_filesize, progress_bar
selected_vdo = stream.get_by_itag(itag)
vdo_filesize = selected_vdo.filesize
progress_bar = tqdm(total=vdo_filesize, unit='B', unit_scale=True, desc="Downloading")
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...')
os.rename(output_temp_file, output_file)
print('Done! 🎉')
def download_nonprogressive(stream, itag_vdo, itag_ado, file_extention, output_path):
global vdo_filesize, progress_bar
selected_vdo = stream.get_by_itag(itag_vdo)
selected_ado = stream.get_by_itag(itag_ado)
vdo_filesize = selected_vdo.filesize
progress_bar = tqdm(total=vdo_filesize, unit='B', unit_scale=True, desc="Downloading")
random_filename = str(random.randint(1000000000, 9999999999))
selected_vdo.download(output_path=output_path, filename=random_filename + '_vdo.' + file_extention)
selected_ado.download(output_path=output_path, filename=random_filename + '_ado.' + file_extention)
return random_filename
def download_audio(stream, itag, output_path):
global vdo_filesize, progress_bar
selected_ado = stream.get_by_itag(itag)
vdo_filesize = selected_ado.filesize
progress_bar = tqdm(total=vdo_filesize, unit='B', unit_scale=True, desc="Downloading")
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 = vdo_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 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 = {
'2160p': {
'allowed_streams': ['4k', '2160', '2160p'],
'message': ['2160p', 'webm', 'vp90', 'opus', '[4k, 2160, 2160p]']
},
'1440p': {
'allowed_streams': ['2k', '1440', '1440p'],
'message': ['1440p', 'webm', 'vp90', 'opus', '[2k, 1440, 1440p]']
},
'1080p': {
'allowed_streams': ['fhd', '1080', '1080p'],
'message': ['1080p', 'mp4', 'avc1', 'aac', '[fhd, 1080, 1080p]']
},
'720p': {
'allowed_streams': ['hd', '720', '720p'],
'message': ['720p', 'mp4', 'avc1', 'aac', '[hd, 720, 720p]']
},
'480p': {
'allowed_streams': ['480', '480p'],
'message': ['480p', 'mp4', 'avc1', 'aac', '[480, 480p]']
},
'360p': {
'allowed_streams': ['360', '360p'],
'message': ['360p', 'mp4', 'avc1', 'aac', '[360, 360p]']
},
'240p': {
'allowed_streams': ['240', '240p'],
'message': ['240p', 'mp4', 'avc1', 'aac', '[240, 240p]']
},
'144p': {
'allowed_streams': ['144', '144p'],
'message': ['144p', 'mp4', 'avc1', 'aac', '[144, 144p]']
},
'mp3': {
'allowed_streams': ['mp3'],
'message': ['mp3', 'mp3', 'none', '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:
filesize = f"{matching_stream.filesize / (1024 * 1024):.2f} MB"
else:
filesize = "N/A"
message = stream_resolutions[res]['message'] + [filesize]
table.append(message)
if not found:
print('Sorry, No video streams found....!!!')
sys.exit()
print(f'\nTitle: {video.title}\nAuthor: {author}\nViews: {views}\n')
print(tabulate(table, headers=['Streams', 'Format', 'Video Codec', 'Audio Codec', 'Aliases', 'Size']))
print('\n')
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) [mp4 - avc1 - aac]\n')
elif chosen_stream in ['360', '360p']:
print(f'\nVideo: {title}\nSelected Stream: 360p (SD) [mp4 - avc1 - aac]\n')
elif chosen_stream in ['1080', '1080p', 'fhd']:
print(f'\nVideo: {title}\nSelected Stream: 1080p (FHD) [mp4 - avc1 - aac]\n')
elif chosen_stream in ['480', '480p']:
print(f'\nVideo: {title}\nSelected Stream: 480p (SD) [mp4 - avc1 - aac]\n')
elif chosen_stream in ['240', '240p']:
print(f'\nVideo: {title}\nSelected Stream: 240p (LD) [mp4 - avc1 - aac]\n')
elif chosen_stream in ['144', '144p']:
print(f'\nVideo: {title}\nSelected Stream: 144p (LD) [mp4 - avc1 - aac]\n')
elif chosen_stream in ['2160', '2160p', '4k']:
print(f'\nVideo: {title}\nSelected Stream: 2160p (4K) [webm - vp90 - opus]\n')
elif chosen_stream in ['1440', '1440p', '2k']:
print(f'\nVideo: {title}\nSelected Stream: 1440p (2K) [webm - vp90 - opus]\n')
elif chosen_stream == 'mp3':
print(f'\nVideo: {title}\nSelected Stream: mp3 (Audio) [mp3 - dynamic - 44.1khz]\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 ['720', '720p', 'hd']:
download_progressive(stream, 22, title, '720p', 'mp4')
elif chosen_stream in ['360', '360p']:
download_progressive(stream, 18, title, '360p', 'mp4')
elif chosen_stream in ['1080', '1080p', 'fhd']:
merge_audio_video(title, '1080p', 'mp4', download_nonprogressive(stream, 137, 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, 140, 'mp4', tempDIR))
elif chosen_stream in ['144', '144p']:
merge_audio_video(title, '144p', 'mp4', download_nonprogressive(stream, 160, 140, 'mp4', tempDIR))
elif chosen_stream in ['2160', '2160p', '4k']:
merge_audio_video(title, '4k', 'webm', download_nonprogressive(stream, 313, 251, 'webm', tempDIR))
elif chosen_stream in ['1440', '1440p', '2k']:
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')
parser.add_argument('-df', '--download-folder', default=argparse.SUPPRESS, help='set custom download folder path (default: ~/Downloads/Pytube Downloads) [arg eg: "/path/to/folder"]')
parser.add_argument('-ds', '--default-stream', default=argparse.SUPPRESS, help='set default download stream (default: max) [available arguments: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p, mp3, max]')
parser.add_argument('-s', '--stream', default=argparse.SUPPRESS, help='choose download stream for the current video (default: your chosen --default-stream) [available arguments: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p, 144, 240, 360, 480, 720, 1080, 1440, 2160, mp3, hd, fhd, 2k, 4k]')
parser.add_argument('-i', '--show-info', action='store_true', help='show video info (title, author, views and available_streams)')
parser.add_argument('-sc', '--show-config', action='store_true', help='show all current user config settings')
parser.add_argument('-r', '--reset-default', action='store_true', help='reset to default settings (download_folder and default_stream)')
parser.add_argument('-ct', '--clear-temp', action='store_true', help='clear temporary files (audio, video, thumbnail files of the failed, incomplete downloads)')
parser.add_argument('-v', '--version', action='store_true', help='show version number')
args = parser.parse_args()
if len(sys.argv) == 1:
print('\nNo arguments supplied! exiting...!!')
parser.print_help()
sys.exit(1)
if args.url:
if 'download_folder' in args:
print('\nVideo url supplied! igonering -df flag...!!')
if 'default_stream' in args:
print('\nVideo url supplied! ignoreing -ds flag...!!')
if args.reset_default:
print('\nVideo url supplied! ignoreing -r flag...!!')
if args.clear_temp:
print('\nVideo url supplied! ignoreing -ct flag...!!')
if args.show_config:
print('\nVideo url supplied! ignoreing -sc flag...!!')
if args.show_info:
show_video_info(args.url)
if 'stream' in args:
download_stream(args.url, args.stream)
if 'stream' not in args and not args.show_info:
if set_global_video_info(args.url):
if defaultStream == 'max' and maxres != None:
download_stream(args.url, maxres)
return
if (defaultStream == 'mp3' and stream.get_by_itag(140)) or (defaultStream != 'max' and stream.filter(res=defaultStream)):
download_stream(args.url, defaultStream)
else:
if maxres != None:
print(f'\nDefault stream not available! ( Default: {defaultStream} | Available: {maxres} )')
answer = input('Do you want to download the maximum available stream ? [yes/no]\n')
while answer not in ['yes', 'y', 'no', 'n']:
print('Invalid answer! try again...!! answer with: [yes/y/no/n]')
answer = input('Do you want to download the maximum available stream ? [yes/no]\n')
if answer in ['yes', 'y']:
download_stream(args.url, maxres)
else:
print('Download cancelled! exiting...!!')
else:
print('Sorry, No downloadable video stream found....!!!')
else:
print('\nInvalid video link! Please enter a valid video url...!!')
else:
if 'download_folder' in args:
if args.download_folder != downloadDIR:
if os.path.isdir(args.download_folder):
update_config('downloadDIR', args.download_folder)
os.makedirs(args.download_folder, exist_ok=True)
print(f'\nDownload folder updated to: {args.download_folder}')
else:
print('\nInvalid download folder path! Please enter a valid path...!!')
else:
print('\nDownload folder path is the same! Not updating...!!')
if 'default_stream' in args:
if args.default_stream != defaultStream:
if args.default_stream in ['144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '2160p', 'mp3', 'max']:
update_config('defaultStream', args.default_stream)
print(f'\nDefault stream updated to: {args.default_stream}')
else:
print('\nInvalid default stream! Please enter a valid stream...!! (use -h to see available default_stream arguments)')
else:
print('\nDefault stream is the same! Not updating...!!')
if args.reset_default:
reset_config()
if args.clear_temp:
clear_temp_files()
if args.show_config:
print(f'\ndownloadDIR: {downloadDIR}\ntempDIR: {tempDIR}\nconfigDIR: {configDIR}\ndefaultStream: {defaultStream}\n')
if args.version:
print(f'\npytubePP (Pytube Post Processor) - version: {version}\n')
if args.show_info:
print('\nNo video url supplied! exiting...!!')
if 'stream' in args:
print('\nNo video url supplied! exiting...!!')
if __name__ == "__main__":
main()

8
requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
pytube
requests
ffmpy
mutagen
tabulate
tqdm
appdirs
setuptools

58
setup.py Normal file
View File

@@ -0,0 +1,58 @@
from setuptools import setup, find_packages
with open('README.md', 'r', encoding='utf8') as file:
readme = file.read()
setup(
name='pytubepp',
version='1.0.1',
description='A Simple CLI Tool to Download Your Favorite YouTube Videos Effortlessly!',
long_description=readme,
long_description_content_type='text/markdown',
author='Subhamoy Biswas',
author_email='hey@neosubhamoy.com',
license='MIT',
packages=find_packages(),
python_requires=">=3.8",
url="https://github.com/neosubhamoy/pytubepp",
entry_points={
'console_scripts': [
'pytubepp=pytubepp.main:main',
],
},
install_requires=[
'pytube',
'requests',
'ffmpy',
'mutagen',
'tabulate',
'tqdm',
'appdirs',
'setuptools',
],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python",
"Topic :: Internet",
"Topic :: Multimedia :: Video",
"Topic :: Multimedia :: Sound/Audio :: Conversion",
"Topic :: Multimedia :: Video :: Conversion",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Terminals",
"Topic :: Utilities",
],
keywords=["youtube", "download", "video", "pytube", "cli"],
project_urls={
"Bug Reports": "https://github.com/neosubhamoy/pytubepp/issues",
},
)