1
1
mirror of https://github.com/neosubhamoy/pytubepp.git synced 2026-02-04 18:22:23 +05:30

20 Commits

5 changed files with 240 additions and 69 deletions

View File

@@ -23,7 +23,6 @@ jobs:
- name: 📦 Install dependencies - name: 📦 Install dependencies
run: | run: |
pip install -r requirements.txt pip install -r requirements.txt
pip install wheel twine
- name: 🛠️ Build package - name: 🛠️ Build package
run: python3 setup.py sdist bdist_wheel run: python3 setup.py sdist bdist_wheel

View File

@@ -1,22 +1,24 @@
# pytubePP - (Pytube Post Processor) # PytubePP - (Pytube Post Processor)
### A Simple CLI Tool to Download Your Favourite YouTube Videos Effortlessly! ### 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/) [![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/) [![verion](https://img.shields.io/badge/version-v1.0.6_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/) [![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/) [![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/) [![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/neosubhamoy/pytubepp/)
😀 GOOD NEWS: If you are Windows(10/11) user and don't want to bother remembering PytubePP Commands! (You are not familier with Command Line Tools). We recently released a Browser Extension that can auto detect YouTube Videos and You can download the Video in one click directly from the browser using PytubePP CLI. Install [PytubePP Helper](https://github.com/neosubhamoy/pytubepp-helper) app in your System and add [PytubePP Extension](https://github.com/neosubhamoy/pytubepp-extension) in your Browser to get started.
### **🏷️ Features** ### **🏷️ Features**
* Auto Post-Process & Merge YouTube DASH Streams * Auto Post-Process & Merge YouTube DASH Streams
* Supports upto 8K 60fps HDR Stream Download
* Supports MP3 Download (with Embeded Thumbnail and Tags) * Supports MP3 Download (with Embeded Thumbnail and Tags)
* Smart Stream Selection * Smart Stream Selection
* Highly Configurable and Many More 😉 * Highly Configurable and Many More 😉
### **🧩 Dependencies** ### **🧩 Dependencies**
* [pytube](https://pypi.org/project/pytube/) * [pytubefix](https://pypi.org/project/pytubefix/)
* [FFmpeg (Not Pre-Included)](https://ffmpeg.org/) * [FFmpeg (Not Pre-Included)](https://ffmpeg.org/)
* [ffmpy](https://pypi.org/project/ffmpy/) * [ffmpy](https://pypi.org/project/ffmpy/)
* [mutagen](https://pypi.org/project/mutagen/) * [mutagen](https://pypi.org/project/mutagen/)
@@ -27,23 +29,29 @@
* [setuptools](https://pypi.org/project/setuptools/) * [setuptools](https://pypi.org/project/setuptools/)
### **🛠️ Installation** ### **🛠️ Installation**
You can install pytubePP in your system via PIP by simply running the below command You can install PytubePP in your system via PIP by simply running the below command
```terminal ```terminal
pip install pytubepp 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)** **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!)** **>> Install FFmpeg (If you haven't already!)**
Linux (Ubuntu): `apt install ffmpeg`<br> Linux (Ubuntu): `sudo apt install ffmpeg`<br>
Windows (using Chocolatey): `choco install ffmpeg`<br> Linux (Fedora): `sudo dnf install ffmpeg-free`<br>
Windows (10/11): `winget install ffmpeg`<br>
MacOS (using Homebrew): `brew install ffmpeg`<br> MacOS (using Homebrew): `brew install ffmpeg`<br>
Android (using Termux): `pkg install ffmpeg` Android (using Termux): `pkg install ffmpeg`
**NOTE: Always make sure 'PytubePP' and 'Pytubefix' is on the latest version to avoid issues (update them at least once a week) (Use the command below to update)**
```
pip install pytubefix pytubepp --upgrade
```
### **📌 Commands and Flags** ### **📌 Commands and Flags**
Using pytubePP is as simple as just supplying it only the YouTube video url as argument! 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" ** 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: * To download a video in maximum available resolution the command will look like:
```terminal ```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo"
@@ -56,13 +64,18 @@ pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s 480p
```terminal ```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3 pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3
``` ```
* To fetch the video information the command will be:
```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -i
```
* To cancel/stop an ongoing download press `CTRL` + `C` on keyboard (it is recommended to run the `-ct` flag once after canceling an ongoing download).
* List of all available flags are given below: * List of all available flags are given below:
| Flag | Usage | Requires Parameter | Requires URL | Parameters | Default | | 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 | | -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` `4320` `4320p` `8k` `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 | | -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` | | -ds | Set default download stream | YES | NO | `144p` `240p` `360p` `480p` `720p` `1080p` `1440p` `2160p` `4320p` `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): `"/path/to/folder"` (Make sure the folder path you enterted is already created and accessable) | Within `Pytube Downloads` folder in your System's `Downloads` folder | | -df | Set custom download folder path | YES | NO | Use the full path excluding the last trailing slash within double quotes eg(in Linux): `"/path/to/folder"` (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 | | -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 | | -sc | Show all current user configurations | NO | NO | No parameters | No default |
@@ -73,7 +86,7 @@ pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3
### 📝 License & Usage ### 📝 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. 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** **🌟 Liked this project? Please consider giving it a star to show me your appreciation**
<br></br> <br></br>

View File

@@ -1,13 +1,23 @@
from pytube import YouTube from pytubefix import YouTube
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB
from tabulate import tabulate from tabulate import tabulate
from tqdm import tqdm from tqdm import tqdm
import appdirs, ffmpy, requests, re, os, sys, random, shutil, platform, json, argparse, tempfile, pkg_resources 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(): def get_version():
try: try:
return pkg_resources.get_distribution("pytubepp").version return version('pytubepp')
except pkg_resources.DistributionNotFound: except Exception as e:
return "Unknown" return "Unknown"
def get_download_folder(): def get_download_folder():
@@ -84,7 +94,7 @@ def merge_audio_video(title, resolution, file_extention, random_filename, tempDI
ff.run(stdout=devnull, stderr=devnull) ff.run(stdout=devnull, stderr=devnull)
devnull.close() devnull.close()
os.rename(output_temp_file, output_file) shutil.move(output_temp_file, output_file)
postprocess_cleanup(tempDIR, ['_vdo.' + file_extention, '_ado.' + file_extention, '_merged.' + file_extention], random_filename) postprocess_cleanup(tempDIR, ['_vdo.' + file_extention, '_ado.' + file_extention, '_merged.' + file_extention], random_filename)
print('Done! 🎉') print('Done! 🎉')
@@ -132,40 +142,42 @@ def convert_to_mp3(title, thumbnail_url, random_filename, mp3_artist='Unknown',
)) ))
audio.save() audio.save()
os.rename(output_temp_file, output_file) shutil.move(output_temp_file, output_file)
postprocess_cleanup(tempDIR, ['_thumbnail.jpg', '_thumbnail.mp4', '_ado.mp4', '_merged.mp4'], random_filename) postprocess_cleanup(tempDIR, ['_thumbnail.jpg', '_thumbnail.mp4', '_ado.mp4', '_merged.mp4'], random_filename)
print('Done! 🎉') print('Done! 🎉')
def download_progressive(stream, itag, title, resolution, file_extention, tempDIR=tempDIR, downloadDIR=downloadDIR): def download_progressive(stream, itag, title, resolution, file_extention, tempDIR=tempDIR, downloadDIR=downloadDIR):
global vdo_filesize, progress_bar global total_filesize, progress_bar
selected_vdo = stream.get_by_itag(itag) selected_vdo = stream.get_by_itag(itag)
vdo_filesize = selected_vdo.filesize total_filesize = selected_vdo.filesize
progress_bar = tqdm(total=vdo_filesize, unit='B', unit_scale=True, desc="Downloading") progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Video+Audio")
random_filename = str(random.randint(1000000000, 9999999999)) random_filename = str(random.randint(1000000000, 9999999999))
filename = random_filename + '_vdo.' + file_extention filename = random_filename + '_vdo.' + file_extention
output_temp_file = os.path.join(tempDIR, filename) output_temp_file = os.path.join(tempDIR, filename)
output_file = os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '.' + file_extention)) output_file = os.path.join(downloadDIR, get_unique_filename(title + '_' + resolution + '.' + file_extention))
selected_vdo.download(output_path=tempDIR, filename=filename) selected_vdo.download(output_path=tempDIR, filename=filename)
print('Processing...') print('Processing...')
os.rename(output_temp_file, output_file) shutil.move(output_temp_file, output_file)
print('Done! 🎉') print('Done! 🎉')
def download_nonprogressive(stream, itag_vdo, itag_ado, file_extention, output_path): def download_nonprogressive(stream, itag_vdo, itag_ado, file_extention, output_path):
global vdo_filesize, progress_bar global total_filesize, progress_bar
selected_vdo = stream.get_by_itag(itag_vdo) selected_vdo = stream.get_by_itag(itag_vdo)
selected_ado = stream.get_by_itag(itag_ado) 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)) 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) 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) selected_ado.download(output_path=output_path, filename=random_filename + '_ado.' + file_extention)
return random_filename return random_filename
def download_audio(stream, itag, output_path): def download_audio(stream, itag, output_path):
global vdo_filesize, progress_bar global total_filesize, progress_bar
selected_ado = stream.get_by_itag(itag) selected_ado = stream.get_by_itag(itag)
vdo_filesize = selected_ado.filesize total_filesize = selected_ado.filesize
progress_bar = tqdm(total=vdo_filesize, unit='B', unit_scale=True, desc="Downloading") progress_bar = tqdm(total=total_filesize, unit='B', unit_scale=True, desc="Downloading Audio")
random_filename = str(random.randint(1000000000, 9999999999)) random_filename = str(random.randint(1000000000, 9999999999))
selected_ado.download(output_path=output_path, filename=random_filename + '_ado.mp4') selected_ado.download(output_path=output_path, filename=random_filename + '_ado.mp4')
return random_filename return random_filename
@@ -188,7 +200,7 @@ def download_thumbnail(url, file_path):
sys.exit() sys.exit()
def progress(chunk, file_handle, bytes_remaining): def progress(chunk, file_handle, bytes_remaining):
chunk_size = vdo_filesize - bytes_remaining chunk_size = total_filesize - bytes_remaining
progress_bar.update(chunk_size - progress_bar.n) progress_bar.update(chunk_size - progress_bar.n)
if bytes_remaining == 0: if bytes_remaining == 0:
@@ -229,6 +241,10 @@ def is_valid_url(url):
return match return match
def set_global_video_info(link): 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): if is_valid_url(link):
global video, author, title, thumbnail, views, stream, stream_resolutions, maxres global video, author, title, thumbnail, views, stream, stream_resolutions, maxres
link = is_valid_url(link).group(1) link = is_valid_url(link).group(1)
@@ -239,41 +255,45 @@ def set_global_video_info(link):
views = str(video.views) views = str(video.views)
stream = video.streams stream = video.streams
stream_resolutions = { stream_resolutions = {
'4320p': {
'allowed_streams': ['8k', '4320', '4320p'],
'message': ['4320p', '[8k, 4320, 4320p]']
},
'2160p': { '2160p': {
'allowed_streams': ['4k', '2160', '2160p'], 'allowed_streams': ['4k', '2160', '2160p'],
'message': ['2160p', 'webm', 'vp90', 'opus', '[4k, 2160, 2160p]'] 'message': ['2160p', '[4k, 2160, 2160p]']
}, },
'1440p': { '1440p': {
'allowed_streams': ['2k', '1440', '1440p'], 'allowed_streams': ['2k', '1440', '1440p'],
'message': ['1440p', 'webm', 'vp90', 'opus', '[2k, 1440, 1440p]'] 'message': ['1440p', '[2k, 1440, 1440p]']
}, },
'1080p': { '1080p': {
'allowed_streams': ['fhd', '1080', '1080p'], 'allowed_streams': ['fhd', '1080', '1080p'],
'message': ['1080p', 'mp4', 'avc1', 'aac', '[fhd, 1080, 1080p]'] 'message': ['1080p', '[fhd, 1080, 1080p]']
}, },
'720p': { '720p': {
'allowed_streams': ['hd', '720', '720p'], 'allowed_streams': ['hd', '720', '720p'],
'message': ['720p', 'mp4', 'avc1', 'aac', '[hd, 720, 720p]'] 'message': ['720p', '[hd, 720, 720p]']
}, },
'480p': { '480p': {
'allowed_streams': ['480', '480p'], 'allowed_streams': ['480', '480p'],
'message': ['480p', 'mp4', 'avc1', 'aac', '[480, 480p]'] 'message': ['480p', '[480, 480p]']
}, },
'360p': { '360p': {
'allowed_streams': ['360', '360p'], 'allowed_streams': ['360', '360p'],
'message': ['360p', 'mp4', 'avc1', 'aac', '[360, 360p]'] 'message': ['360p', '[360, 360p]']
}, },
'240p': { '240p': {
'allowed_streams': ['240', '240p'], 'allowed_streams': ['240', '240p'],
'message': ['240p', 'mp4', 'avc1', 'aac', '[240, 240p]'] 'message': ['240p', '[240, 240p]']
}, },
'144p': { '144p': {
'allowed_streams': ['144', '144p'], 'allowed_streams': ['144', '144p'],
'message': ['144p', 'mp4', 'avc1', 'aac', '[144, 144p]'] 'message': ['144p', '[144, 144p]']
}, },
'mp3': { 'mp3': {
'allowed_streams': ['mp3'], 'allowed_streams': ['mp3'],
'message': ['mp3', 'mp3', 'none', 'mp3', '[mp3]'] 'message': ['mp3', '[mp3]']
} }
} }
for res in stream_resolutions.keys(): for res in stream_resolutions.keys():
@@ -297,10 +317,118 @@ def show_video_info(link):
else: else:
matching_stream = next((s for s in stream if s.resolution == res), None) matching_stream = next((s for s in stream if s.resolution == res), None)
if matching_stream is not 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):.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):.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):.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):.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):.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):.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):.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):.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):.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):.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):.2f} MB" filesize = 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):.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):.2f} MB"
fps = "none"
vdo_codec = "none"
ado_codec = matching_stream.audio_codec
vdo_bitrate = "none"
ado_bitrate = matching_stream.abr
else: else:
filesize = "N/A" filesize = "N/A"
message = stream_resolutions[res]['message'] + [filesize] message = stream_resolutions[res]['message'] + [type] + [filesize] + [fps] + [vdo_codec] + [ado_codec] + [vdo_bitrate] + [ado_bitrate]
table.append(message) table.append(message)
if not found: if not found:
@@ -308,7 +436,7 @@ def show_video_info(link):
sys.exit() sys.exit()
print(f'\nTitle: {video.title}\nAuthor: {author}\nViews: {views}\n') print(f'\nTitle: {video.title}\nAuthor: {author}\nViews: {views}\n')
print(tabulate(table, headers=['Streams', 'Format', 'Video Codec', 'Audio Codec', 'Aliases', 'Size'])) print(tabulate(table, headers=['Stream', 'Alias (for -s flag)', 'Format', 'Size', 'FrameRate', 'V-Codec', 'A-Codec', 'V-BitRate', 'A-BitRate']))
print('\n') print('\n')
else: else:
print('\nInvalid video link! Please enter a valid video url...!!') print('\nInvalid video link! Please enter a valid video url...!!')
@@ -328,51 +456,79 @@ def get_allowed_streams(link):
def print_short_info(chosen_stream): def print_short_info(chosen_stream):
if chosen_stream in ['720', '720p', 'hd']: if chosen_stream in ['720', '720p', 'hd']:
print(f'\nVideo: {title}\nSelected Stream: 720p (HD) [mp4 - avc1 - aac]\n') print(f'\nVideo: {title}\nSelected Stream: 720p (HD)\n')
elif chosen_stream in ['360', '360p']: elif chosen_stream in ['360', '360p']:
print(f'\nVideo: {title}\nSelected Stream: 360p (SD) [mp4 - avc1 - aac]\n') print(f'\nVideo: {title}\nSelected Stream: 360p (SD)\n')
elif chosen_stream in ['1080', '1080p', 'fhd']: elif chosen_stream in ['1080', '1080p', 'fhd']:
print(f'\nVideo: {title}\nSelected Stream: 1080p (FHD) [mp4 - avc1 - aac]\n') print(f'\nVideo: {title}\nSelected Stream: 1080p (FHD)\n')
elif chosen_stream in ['480', '480p']: elif chosen_stream in ['480', '480p']:
print(f'\nVideo: {title}\nSelected Stream: 480p (SD) [mp4 - avc1 - aac]\n') print(f'\nVideo: {title}\nSelected Stream: 480p (SD)\n')
elif chosen_stream in ['240', '240p']: elif chosen_stream in ['240', '240p']:
print(f'\nVideo: {title}\nSelected Stream: 240p (LD) [mp4 - avc1 - aac]\n') print(f'\nVideo: {title}\nSelected Stream: 240p (LD)\n')
elif chosen_stream in ['144', '144p']: elif chosen_stream in ['144', '144p']:
print(f'\nVideo: {title}\nSelected Stream: 144p (LD) [mp4 - avc1 - aac]\n') 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']: elif chosen_stream in ['2160', '2160p', '4k']:
print(f'\nVideo: {title}\nSelected Stream: 2160p (4K) [webm - vp90 - opus]\n') print(f'\nVideo: {title}\nSelected Stream: 2160p (4K)\n')
elif chosen_stream in ['1440', '1440p', '2k']: elif chosen_stream in ['1440', '1440p', '2k']:
print(f'\nVideo: {title}\nSelected Stream: 1440p (2K) [webm - vp90 - opus]\n') print(f'\nVideo: {title}\nSelected Stream: 1440p (2K)\n')
elif chosen_stream == 'mp3': elif chosen_stream == 'mp3':
print(f'\nVideo: {title}\nSelected Stream: mp3 (Audio) [mp3 - dynamic - 44.1khz]\n') print(f'\nVideo: {title}\nSelected Stream: mp3 (Audio)\n')
def download_stream(link, chosen_stream): def download_stream(link, chosen_stream):
if set_global_video_info(link): if set_global_video_info(link):
print_short_info(chosen_stream) print_short_info(chosen_stream)
allowed_streams = get_allowed_streams(link) allowed_streams = get_allowed_streams(link)
if chosen_stream in allowed_streams: if chosen_stream in allowed_streams:
if chosen_stream in ['720', '720p', 'hd']: if chosen_stream in ['360', '360p']:
download_progressive(stream, 22, title, '720p', 'mp4')
elif chosen_stream in ['360', '360p']:
download_progressive(stream, 18, title, '360p', 'mp4') download_progressive(stream, 18, title, '360p', 'mp4')
elif chosen_stream in ['1080', '1080p', 'fhd']: 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)) 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']: elif chosen_stream in ['480', '480p']:
merge_audio_video(title, '480p', 'mp4', download_nonprogressive(stream, 135, 140, 'mp4', tempDIR)) merge_audio_video(title, '480p', 'mp4', download_nonprogressive(stream, 135, 140, 'mp4', tempDIR))
elif chosen_stream in ['240', '240p']: elif chosen_stream in ['240', '240p']:
merge_audio_video(title, '240p', 'mp4', download_nonprogressive(stream, 133, 140, 'mp4', tempDIR)) merge_audio_video(title, '240p', 'mp4', download_nonprogressive(stream, 133, 139, 'mp4', tempDIR))
elif chosen_stream in ['144', '144p']: elif chosen_stream in ['144', '144p']:
merge_audio_video(title, '144p', 'mp4', download_nonprogressive(stream, 160, 140, 'mp4', tempDIR)) 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']: 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)) merge_audio_video(title, '4k', 'webm', download_nonprogressive(stream, 313, 251, 'webm', tempDIR))
elif chosen_stream in ['1440', '1440p', '2k']: 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)) merge_audio_video(title, '2k', 'webm', download_nonprogressive(stream, 271, 251, 'webm', tempDIR))
elif chosen_stream == 'mp3': elif chosen_stream == 'mp3':
@@ -387,8 +543,8 @@ 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 = 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('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('-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('-ds', '--default-stream', default=argparse.SUPPRESS, help='set default download stream (default: max) [available arguments: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p, 4320p, 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('-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, 4320p, 144, 240, 360, 480, 720, 1080, 1440, 2160, 4320, mp3, hd, fhd, 2k, 4k, 8k]')
parser.add_argument('-i', '--show-info', action='store_true', help='show video info (title, author, views and available_streams)') 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('-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('-r', '--reset-default', action='store_true', help='reset to default settings (download_folder and default_stream)')
@@ -460,7 +616,7 @@ def main():
if 'default_stream' in args: if 'default_stream' in args:
if args.default_stream != defaultStream: if args.default_stream != defaultStream:
if args.default_stream in ['144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '2160p', 'mp3', 'max']: if args.default_stream in ['144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '2160p', '4320p', 'mp3', 'max']:
update_config('defaultStream', args.default_stream) update_config('defaultStream', args.default_stream)
print(f'\nDefault stream updated to: {args.default_stream}') print(f'\nDefault stream updated to: {args.default_stream}')
else: else:
@@ -478,7 +634,7 @@ def main():
print(f'\ndownloadDIR: {downloadDIR}\ntempDIR: {tempDIR}\nconfigDIR: {configDIR}\ndefaultStream: {defaultStream}\n') print(f'\ndownloadDIR: {downloadDIR}\ntempDIR: {tempDIR}\nconfigDIR: {configDIR}\ndefaultStream: {defaultStream}\n')
if args.version: if args.version:
print(f'\npytubePP (Pytube Post Processor) - version: {version}\n') print(f'pytubepp {version}')
if args.show_info: if args.show_info:
print('\nNo video url supplied! exiting...!!') print('\nNo video url supplied! exiting...!!')

View File

@@ -1,4 +1,4 @@
pytube pytubefix
requests requests
ffmpy ffmpy
mutagen mutagen
@@ -6,3 +6,5 @@ tabulate
tqdm tqdm
appdirs appdirs
setuptools setuptools
wheel
twine

View File

@@ -6,7 +6,7 @@ with open('README.md', 'r', encoding='utf8') as file:
setup( setup(
name='pytubepp', name='pytubepp',
version='1.0.1', version='1.0.6',
description='A Simple CLI Tool to Download Your Favorite YouTube Videos Effortlessly!', description='A Simple CLI Tool to Download Your Favorite YouTube Videos Effortlessly!',
long_description=readme, long_description=readme,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
@@ -22,7 +22,7 @@ setup(
], ],
}, },
install_requires=[ install_requires=[
'pytube', 'pytubefix',
'requests', 'requests',
'ffmpy', 'ffmpy',
'mutagen', 'mutagen',
@@ -42,6 +42,7 @@ setup(
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python", "Programming Language :: Python",
"Topic :: Internet", "Topic :: Internet",
"Topic :: Multimedia :: Video", "Topic :: Multimedia :: Video",