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

5 Commits

4 changed files with 88 additions and 21 deletions

View File

@@ -62,6 +62,8 @@
> Use `pip3` command instead of `pip` if you are on Linux or MacOS. > Use `pip3` command instead of `pip` if you are on Linux or MacOS.
> Use `--break-system-packages` flag to install 'PytubePP' in global environment if you get `error: externally-managed-environment` while installing in Linux or MacOS (Don't worry it will not break your system packages, it's just a security mesure)
```terminal ```terminal
pip install pytubepp pip install pytubepp
``` ```
@@ -72,21 +74,29 @@ pip install pytubepp
pip install pytubefix pytubepp --upgrade pip install pytubefix pytubepp --upgrade
``` ```
**UNINSTALL: If you want to uninstall PytubePP (Use the command below to uninstall) NOTE: it will only remove the 'PytubePP' python package**
```
pip uninstall pytubepp -y
```
### **📌 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 principle -> `Use Default Configuration if No Flags are Passed`
* To download a video in default configuration (maximum resolution and without any caption by default) the command will look like: * To download a video in default configuration (maximum resolution and without any caption by default) the command will look like:
```terminal ```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo"
``` ```
> NOTE: This command will behave differently if you have changed default configurations
* To download the video in a specific resolution (suppose 480p) the command will be: * To download the video in a specific resolution (suppose 480p) the command will be:
```terminal ```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s 480p pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s 480p
``` ```
> NOTE: PytubePP always uses default configuration of flags if they are not passed for example if you only pass `-s` flag then it will use the default caption along with it, if you only pass `-c` then it will use default stream and vice versa
* To download the video with embeded caption (suppose en - English) the command will be: * To download the video with embeded caption (suppose en - English) the command will be:
```terminal ```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -c en pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -c en
``` ```
> NOTE: You can override and disable default caption for the current video if you pass `-c none`
* To download the video in audio-only MP3 format the command will be: * To download the video in audio-only MP3 format the command will be:
```terminal ```terminal
pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3 pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -s mp3
@@ -101,7 +111,7 @@ pytubepp "https://youtube.com/watch?v=2lAe1cqCOXo" -i
| 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` `4320` `4320p` `8k` `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 |
| -c | Choose preferred caption | YES | YES | All [ISO 639-1 Language Codes](https://www.w3schools.com/tags/ref_language_codes.asp) + some others (Pass any one of them) eg: `en` for English | Your chosen Default Caption via `-dc` flag | | -c | Choose preferred caption | YES | YES | All [ISO 639-1 Language Codes](https://www.w3schools.com/tags/ref_language_codes.asp) + some others (Pass any one of them) + `none` for No Caption eg: `en` for English | Your chosen Default Caption via `-dc` flag |
| -i | Shows the video information like: Title, Author, Views, Publication Date, Duration, Available Download Streams | NO | YES | No parameters | No default | | -i | Shows the video information like: Title, Author, Views, Publication Date, Duration, Available Download Streams | NO | YES | No parameters | No default |
| -ls | Lists all available streams (video, audio, caption) (only for debuging purposes) | NO | YES | No parameters | No default | | -ls | Lists all available streams (video, audio, caption) (only for debuging purposes) | NO | YES | No parameters | No default |
| -ri | Shows the video information in raw json format | NO | YES | No parameters | No default | | -ri | Shows the video information in raw json format | NO | YES | No parameters | No default |

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pytubepp" name = "pytubepp"
version = "1.1.4" version = "1.1.5"
authors = [ authors = [
{ name="Subhamoy Biswas", email="hey@neosubhamoy.com" }, { name="Subhamoy Biswas", email="hey@neosubhamoy.com" },
] ]

View File

@@ -3,7 +3,7 @@ from tabulate import tabulate
from .config import get_temporary_directory, load_config, update_config, reset_config from .config import get_temporary_directory, load_config, update_config, reset_config
from .download import download_progressive, download_nonprogressive, download_audio, progress from .download import download_progressive, download_nonprogressive, download_audio, progress
from .postprocess import merge_audio_video, convert_to_mp3 from .postprocess import merge_audio_video, convert_to_mp3
from .utils import get_version, clear_temp_files, is_valid_url, network_available from .utils import get_version, clear_temp_files, is_valid_url, network_available, ffmpeg_installed, nodejs_installed
import appdirs, os, re, sys, argparse, json import appdirs, os, re, sys, argparse, json
class YouTubeDownloader: class YouTubeDownloader:
@@ -43,6 +43,11 @@ class YouTubeDownloader:
print('\nRequest timeout! Please check your network and try again...!!') print('\nRequest timeout! Please check your network and try again...!!')
sys.exit() sys.exit()
if not nodejs_installed():
print("\nWarning: Node.js is not installed or not found in PATH!")
print("BotGuard poToken generation will not work properly without Node.js environment")
print("Please install Node.js from https://nodejs.org/en/download\n")
if is_valid_url(link): if is_valid_url(link):
link = is_valid_url(link).group(1) link = is_valid_url(link).group(1)
self.video = YouTube(link, 'WEB', on_progress_callback=progress) self.video = YouTube(link, 'WEB', on_progress_callback=progress)
@@ -235,22 +240,38 @@ class YouTubeDownloader:
print('\nInvalid video link! Please enter a valid video url...!!') print('\nInvalid video link! Please enter a valid video url...!!')
return [] return []
def print_short_info(self, chosen_stream): def print_short_info(self, chosen_stream, chosen_caption=None):
resolution_map = { print(f'\nTitle: {self.title}')
'4320': '4320p (8K)', '4320p': '4320p (8K)', '8k': '4320p (8K)',
'2160': '2160p (4K)', '2160p': '2160p (4K)', '4k': '2160p (4K)', if chosen_stream == 'mp3':
'1440': '1440p (2K)', '1440p': '1440p (2K)', '2k': '1440p (2K)', print(f'Selected: Audio [128kbps (140)]')
'1080': '1080p (FHD)', '1080p': '1080p (FHD)', 'fhd': '1080p (FHD)', return
'720': '720p (HD)', '720p': '720p (HD)', 'hd': '720p (HD)',
'480': '480p (SD)', '480p': '480p (SD)', if chosen_stream in ['360', '360p']:
'360': '360p (SD)', '360p': '360p (SD)', print(f"Selected: Video [360p (18)] + Audio [96kbps (18)]{f' + Caption [{chosen_caption}]' if chosen_caption else ''}")
'240': '240p (LD)', '240p': '240p (LD)', return
'144': '144p (LD)', '144p': '144p (LD)',
'mp3': 'mp3 (Audio)' _select_suitable_audio_stream = lambda stream: 251 if stream.mime_type == 'video/webm' else 140
} res = next((k for k, v in self.stream_resolutions.items() if chosen_stream in v['allowed_streams']), None)
print(f'\nTitle: {self.title}\nSelected Stream: {resolution_map.get(chosen_stream, "Unknown")}\n')
if res:
hdr_stream = None
if res in ['4320p', '2160p', '1440p', '1080p', '720p']:
hdr_itags = {'4320p': 702, '2160p': 701, '1440p': 700, '1080p': 699, '720p': 698}
hdr_stream = self.stream.get_by_itag(hdr_itags.get(res))
matching_stream = hdr_stream if hdr_stream else self.stream.filter(res=res).first()
audio_stream = self.stream.get_by_itag(_select_suitable_audio_stream(matching_stream))
print(f"Selected: Video [{res} ({matching_stream.itag})] + Audio [{audio_stream.abr} ({audio_stream.itag})]{f' + Caption [{chosen_caption}]' if chosen_caption else ''}")
def download_stream(self, link, chosen_stream, chosen_caption=None): def download_stream(self, link, chosen_stream, chosen_caption=None):
if not ffmpeg_installed():
print("\nWarning: FFmpeg is not installed or not found in PATH!")
print("Some core functionalities like video processing will not work properly without FFmpeg")
print("Please install FFmpeg, read https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation for instructions\n")
sys.exit()
if self.set_video_info(link): if self.set_video_info(link):
allowed_streams = self.get_allowed_streams(link) allowed_streams = self.get_allowed_streams(link)
allowed_captions = self.get_allowed_captions(link) allowed_captions = self.get_allowed_captions(link)
@@ -260,7 +281,7 @@ class YouTubeDownloader:
sys.exit() sys.exit()
if chosen_stream in allowed_streams: if chosen_stream in allowed_streams:
self.print_short_info(chosen_stream) self.print_short_info(chosen_stream, chosen_caption)
if chosen_stream in ['360', '360p']: if chosen_stream in ['360', '360p']:
download_progressive(self.stream, 18, self.title, '360p', 'mp4', self.captions, chosen_caption) download_progressive(self.stream, 18, self.title, '360p', 'mp4', self.captions, chosen_caption)
elif chosen_stream in ['1080', '1080p', 'fhd']: elif chosen_stream in ['1080', '1080p', 'fhd']:
@@ -381,9 +402,11 @@ def main():
# Handle download cases # Handle download cases
if hasattr(args, 'stream') and hasattr(args, 'caption'): if hasattr(args, 'stream') and hasattr(args, 'caption'):
if downloader.set_video_info(args.url): if downloader.set_video_info(args.url):
if args.caption not in downloader.captions.keys(): if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'):
print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)') print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)')
sys.exit() sys.exit()
elif args.caption == 'none':
downloader.download_stream(args.url, args.stream)
elif args.stream == 'mp3' and downloader.stream.get_by_itag(140): elif args.stream == 'mp3' and downloader.stream.get_by_itag(140):
print(f'\nYou have chosen to download mp3 stream! ( Captioning audio files is not supported )') print(f'\nYou have chosen to download mp3 stream! ( Captioning audio files is not supported )')
answer = input('Do you still want to continue downloading ? [yes/no]\n') answer = input('Do you still want to continue downloading ? [yes/no]\n')
@@ -424,9 +447,29 @@ def main():
print('Download cancelled! exiting...!!') print('Download cancelled! exiting...!!')
elif hasattr(args, 'caption'): elif hasattr(args, 'caption'):
if downloader.set_video_info(args.url): if downloader.set_video_info(args.url):
if args.caption not in downloader.captions.keys(): if (args.caption not in downloader.captions.keys()) and (args.caption != 'none'):
print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)') print('\nInvalid caption code or caption not available! Please choose a different caption...!! (use -i to see available captions)')
sys.exit() sys.exit()
elif args.caption == 'none':
if downloader.default_stream == 'max' and downloader.maxres:
downloader.download_stream(args.url, downloader.maxres)
elif downloader.default_stream == 'mp3' and downloader.stream.get_by_itag(140):
downloader.download_stream(args.url, downloader.default_stream)
elif downloader.default_stream != 'max' and downloader.stream.filter(res=downloader.default_stream):
downloader.download_stream(args.url, downloader.default_stream)
else:
if downloader.maxres:
print(f'\nDefault stream not available! ( Default: {downloader.default_stream} | Available: {downloader.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']:
downloader.download_stream(args.url, downloader.maxres)
else:
print('Download cancelled! exiting...!!')
else:
print('Sorry, No downloadable video stream found....!!!')
elif downloader.default_stream == 'max' and downloader.maxres: elif downloader.default_stream == 'max' and downloader.maxres:
downloader.download_stream(args.url, downloader.maxres, args.caption) downloader.download_stream(args.url, downloader.maxres, args.caption)
elif downloader.default_stream == 'mp3' and downloader.stream.get_by_itag(140): elif downloader.default_stream == 'mp3' and downloader.stream.get_by_itag(140):

View File

@@ -15,6 +15,20 @@ def network_available():
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return False return False
def nodejs_installed():
try:
subprocess.run(['node', '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def ffmpeg_installed():
try:
subprocess.run(['ffmpeg', '-version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def get_version(): def get_version():
try: try:
return version('pytubepp') return version('pytubepp')